cleanup sw req
This commit is contained in:
548
draft- to be removed SW/components/ARCHITECTURE.md
Normal file
548
draft- to be removed SW/components/ARCHITECTURE.md
Normal file
@@ -0,0 +1,548 @@
|
||||
# Sensor Hub Static Architecture
|
||||
|
||||
**Document Type:** Architecture Specification
|
||||
**Version:** 1.0
|
||||
**Date:** 2025-01-19
|
||||
**Traceability:** SRS Section 3.2, Annex B
|
||||
|
||||
## 1. Purpose and Scope
|
||||
|
||||
This document defines the static architecture of the Sensor Hub firmware, including component structure, interfaces, data flows, and concurrency model. This architecture enforces separation of concerns, hardware abstraction, and state-aware operation as defined in the SRS and cross-feature constraints.
|
||||
|
||||
**Audience:** Software architects, developers, reviewers, and test engineers.
|
||||
|
||||
## 2. Architectural Principles
|
||||
|
||||
### 2.1 Layered Architecture
|
||||
|
||||
The Sensor Hub follows a **strict layered architecture** with the following principles:
|
||||
|
||||
1. **Dependency Direction:** Dependencies flow downward only (Application → Drivers → OSAL → HAL)
|
||||
2. **Hardware Abstraction:** Application layer SHALL NOT access hardware directly (CFC-ARCH-01)
|
||||
3. **Persistence Abstraction:** All persistence access SHALL go through DP component (CFC-ARCH-01, CFC-DATA-01)
|
||||
4. **State Awareness:** All features SHALL respect system state restrictions (CFC-ARCH-02)
|
||||
|
||||
### 2.2 Component Responsibilities
|
||||
|
||||
- **Single Responsibility:** Each component has one well-defined purpose
|
||||
- **Clear Interfaces:** Public APIs are minimal and well-documented
|
||||
- **Event-Driven Communication:** Cross-component communication via Event System
|
||||
- **State-Dependent Behavior:** Components adapt behavior based on system state
|
||||
|
||||
## 3. Architecture Views
|
||||
|
||||
### 3.1 Context View
|
||||
|
||||
The Context View shows the Sensor Hub and its external actors.
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph External["External Actors"]
|
||||
MainHub[Main Hub]
|
||||
PeerHub[Peer Sensor Hub]
|
||||
Sensors[Environmental Sensors]
|
||||
SDCard[SD Card]
|
||||
OLED[OLED Display]
|
||||
Buttons[Buttons]
|
||||
end
|
||||
|
||||
subgraph SensorHub["Sensor Hub System"]
|
||||
AppLayer[Application Layer]
|
||||
Drivers[Drivers Layer]
|
||||
OSAL[OSAL Layer]
|
||||
HAL[HAL/ESP-IDF]
|
||||
end
|
||||
|
||||
MainHub <-->|"Encrypted Communication"| AppLayer
|
||||
PeerHub <-->|"Peer Communication"| AppLayer
|
||||
Sensors -->|"I2C/SPI/UART/Analog"| Drivers
|
||||
SDCard <-->|"SPI/SD Protocol"| Drivers
|
||||
OLED <-->|"I2C"| Drivers
|
||||
Buttons -->|"GPIO"| Drivers
|
||||
|
||||
AppLayer --> Drivers
|
||||
Drivers --> OSAL
|
||||
OSAL --> HAL
|
||||
```
|
||||
|
||||
**External Interfaces:**
|
||||
- **Main Hub:** Bidirectional encrypted communication (Wi-Fi/Zigbee/LoRa)
|
||||
- **Peer Sensor Hub:** Limited peer-to-peer communication
|
||||
- **Sensors:** I2C, SPI, UART, Analog interfaces
|
||||
- **SD Card:** SPI/SD protocol for persistent storage
|
||||
- **OLED Display:** I2C for local HMI
|
||||
- **Buttons:** GPIO inputs for user interaction
|
||||
|
||||
### 3.2 Component View
|
||||
|
||||
The Component View shows the major software components and their relationships.
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph AppLayer["Application Layer"]
|
||||
subgraph BusinessStack["Business Stack"]
|
||||
STM[State Manager<br/>STM]
|
||||
EventSys[Event System]
|
||||
SensorMgr[Sensor Manager]
|
||||
MCMgr[Machine Constant<br/>Manager]
|
||||
OTAMgr[OTA Manager]
|
||||
MainHubAPI[Main Hub APIs]
|
||||
end
|
||||
|
||||
subgraph DPStack["DP Stack"]
|
||||
DataPool[Data Pool]
|
||||
Persistence[Persistence]
|
||||
end
|
||||
|
||||
DiagTask[Diagnostics Task]
|
||||
ErrorHandler[Error Handler]
|
||||
end
|
||||
|
||||
subgraph Drivers["Drivers Layer"]
|
||||
SensorDrivers[Sensor Drivers<br/>I2C/SPI/UART/ADC]
|
||||
NetworkStack[Network Stack<br/>Wi-Fi/Zigbee/LoRa]
|
||||
DiagProtocol[Diagnostic Protocol<br/>Stack]
|
||||
SDDriver[SD Card Driver]
|
||||
NVMDriver[NVM Driver]
|
||||
OLEDDriver[OLED Driver]
|
||||
ButtonDriver[Button Driver]
|
||||
end
|
||||
|
||||
subgraph OSAL["OSAL Layer"]
|
||||
TaskOS[Task Abstraction]
|
||||
TimerOS[Software Timer]
|
||||
SocketOS[Socket Abstraction]
|
||||
end
|
||||
|
||||
subgraph Utils["Utilities"]
|
||||
Logger[Logger]
|
||||
TimeUtils[Time Utils]
|
||||
end
|
||||
|
||||
STM --> EventSys
|
||||
SensorMgr --> EventSys
|
||||
SensorMgr --> SensorDrivers
|
||||
MCMgr --> Persistence
|
||||
OTAMgr --> NetworkStack
|
||||
OTAMgr --> Persistence
|
||||
MainHubAPI --> NetworkStack
|
||||
MainHubAPI --> DataPool
|
||||
EventSys --> DataPool
|
||||
DataPool --> Persistence
|
||||
Persistence --> SDDriver
|
||||
Persistence --> NVMDriver
|
||||
DiagTask --> Persistence
|
||||
DiagTask --> EventSys
|
||||
ErrorHandler --> STM
|
||||
ErrorHandler --> DiagTask
|
||||
|
||||
SensorMgr --> Logger
|
||||
OTAMgr --> Logger
|
||||
MainHubAPI --> Logger
|
||||
DiagTask --> Logger
|
||||
|
||||
SensorMgr --> TimeUtils
|
||||
|
||||
SensorDrivers --> TaskOS
|
||||
NetworkStack --> SocketOS
|
||||
NetworkStack --> TaskOS
|
||||
Persistence --> TaskOS
|
||||
|
||||
SensorDrivers --> OSAL
|
||||
NetworkStack --> OSAL
|
||||
SDDriver --> OSAL
|
||||
OLEDDriver --> OSAL
|
||||
ButtonDriver --> OSAL
|
||||
```
|
||||
|
||||
**Component Responsibilities:**
|
||||
|
||||
| Component | Responsibility | Non-Responsibility |
|
||||
|-----------|----------------|-------------------|
|
||||
| **State Manager (STM)** | System state machine, state transitions, teardown coordination | Feature logic, hardware access |
|
||||
| **Event System** | Publish/subscribe event bus, cross-component communication | Business logic, state management |
|
||||
| **Sensor Manager** | Sensor lifecycle, acquisition scheduling, data filtering | Hardware access, persistence, communication |
|
||||
| **Machine Constant Manager** | MC loading, validation, update coordination | Sensor initialization, hardware access |
|
||||
| **OTA Manager** | OTA negotiation, firmware reception, validation, activation | Network stack implementation, secure boot |
|
||||
| **Main Hub APIs** | Main Hub communication protocol, message handling | Network stack implementation, sensor data generation |
|
||||
| **Data Pool** | Runtime data storage, latest sensor values, system state | Persistence, communication |
|
||||
| **Persistence** | Persistent storage abstraction, serialization, wear management | Business logic, hardware access |
|
||||
| **Diagnostics Task** | Diagnostic event collection, storage, query interface | Fault detection, state management |
|
||||
| **Error Handler** | Fault classification, escalation, state transition triggers | Diagnostic storage, feature logic |
|
||||
|
||||
### 3.3 Data Flow View
|
||||
|
||||
The Data Flow View shows how data flows through the system.
|
||||
|
||||
#### 3.3.1 Sensor Data Acquisition Flow
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Sensor as Sensor Hardware
|
||||
participant Driver as Sensor Driver
|
||||
participant SMgr as Sensor Manager
|
||||
participant EventSys as Event System
|
||||
participant DP as Data Pool
|
||||
participant Persist as Persistence
|
||||
participant MainHub as Main Hub APIs
|
||||
|
||||
Note over Sensor,MainHub: Normal Acquisition Cycle
|
||||
|
||||
SMgr->>Driver: readSensor(sensor_id)
|
||||
Driver->>Sensor: I2C/SPI/UART read
|
||||
Sensor-->>Driver: raw_sample
|
||||
Driver-->>SMgr: raw_sample
|
||||
|
||||
loop 10 samples
|
||||
SMgr->>Driver: readSensor(sensor_id)
|
||||
Driver-->>SMgr: raw_sample
|
||||
end
|
||||
|
||||
SMgr->>SMgr: filter(raw_samples)
|
||||
SMgr->>SMgr: generateTimestamp()
|
||||
SMgr->>EventSys: publish(SENSOR_DATA_UPDATE, record)
|
||||
EventSys->>DP: update(sensor_id, record)
|
||||
EventSys->>Persist: async_persist(record)
|
||||
EventSys->>MainHub: notify(SENSOR_DATA_UPDATE)
|
||||
|
||||
MainHub->>MainHub: queueForTransmission(record)
|
||||
```
|
||||
|
||||
#### 3.3.2 Diagnostic Event Flow
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Component as Any Component
|
||||
participant ErrorHandler as Error Handler
|
||||
participant DiagTask as Diagnostics Task
|
||||
participant EventSys as Event System
|
||||
participant STM as State Manager
|
||||
participant Persist as Persistence
|
||||
|
||||
Component->>ErrorHandler: reportFault(diagnostic_code, severity)
|
||||
ErrorHandler->>ErrorHandler: classifyFault()
|
||||
ErrorHandler->>ErrorHandler: checkEscalation()
|
||||
|
||||
alt Severity == FATAL
|
||||
ErrorHandler->>STM: triggerStateTransition(FAULT)
|
||||
STM->>EventSys: publish(STATE_CHANGED, FAULT)
|
||||
else Severity == WARNING
|
||||
ErrorHandler->>STM: triggerStateTransition(WARNING)
|
||||
end
|
||||
|
||||
ErrorHandler->>DiagTask: logDiagnostic(event)
|
||||
DiagTask->>Persist: persistDiagnostic(event)
|
||||
DiagTask->>EventSys: publish(DIAGNOSTIC_EVENT, event)
|
||||
```
|
||||
|
||||
#### 3.3.3 OTA Update Flow
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant MainHub as Main Hub
|
||||
participant MainHubAPI as Main Hub APIs
|
||||
participant OTAMgr as OTA Manager
|
||||
participant STM as State Manager
|
||||
participant Persist as Persistence
|
||||
participant NetworkStack as Network Stack
|
||||
participant Security as Security
|
||||
|
||||
MainHub->>MainHubAPI: OTA_REQUEST(message)
|
||||
MainHubAPI->>OTAMgr: otaRequestReceived()
|
||||
OTAMgr->>OTAMgr: validateReadiness()
|
||||
OTAMgr->>STM: requestStateTransition(OTA_PREP)
|
||||
STM->>STM: validateTransition()
|
||||
STM->>EventSys: publish(STATE_CHANGED, OTA_PREP)
|
||||
|
||||
OTAMgr->>MainHubAPI: otaAcknowledge(ACCEPT)
|
||||
MainHubAPI->>MainHub: OTA_ACK(ACCEPT)
|
||||
|
||||
OTAMgr->>STM: requestStateTransition(TEARDOWN)
|
||||
STM->>Persist: flushCriticalData()
|
||||
Persist-->>STM: flushComplete()
|
||||
STM->>EventSys: publish(STATE_CHANGED, TEARDOWN)
|
||||
STM->>STM: transitionTo(OTA_UPDATE)
|
||||
|
||||
loop Firmware Chunks
|
||||
MainHub->>NetworkStack: firmwareChunk(data)
|
||||
NetworkStack->>OTAMgr: firmwareChunkReceived(data)
|
||||
OTAMgr->>Persist: storeFirmwareChunk(data)
|
||||
end
|
||||
|
||||
OTAMgr->>Security: validateFirmwareIntegrity()
|
||||
Security-->>OTAMgr: validationResult
|
||||
|
||||
alt Validation Success
|
||||
OTAMgr->>OTAMgr: flashFirmware()
|
||||
OTAMgr->>STM: reboot()
|
||||
else Validation Failure
|
||||
OTAMgr->>STM: requestStateTransition(FAULT)
|
||||
OTAMgr->>MainHubAPI: otaStatus(FAILED)
|
||||
end
|
||||
```
|
||||
|
||||
### 3.4 Concurrency View
|
||||
|
||||
The Concurrency View shows the task model, priorities, and resource ownership.
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph Tasks["RTOS Tasks"]
|
||||
SensorTask["Sensor Acquisition Task<br/>Priority: HIGH<br/>Stack: 8KB<br/>Period: 1s"]
|
||||
CommTask["Communication Task<br/>Priority: MEDIUM<br/>Stack: 12KB<br/>Event-driven"]
|
||||
PersistTask["Persistence Task<br/>Priority: MEDIUM<br/>Stack: 6KB<br/>Event-driven"]
|
||||
DiagTask["Diagnostics Task<br/>Priority: LOW<br/>Stack: 4KB<br/>Period: 10s"]
|
||||
HMITask["HMI Task<br/>Priority: LOW<br/>Stack: 4KB<br/>Event-driven"]
|
||||
OTATask["OTA Task<br/>Priority: HIGH<br/>Stack: 16KB<br/>Event-driven"]
|
||||
SystemTask["System Management Task<br/>Priority: HIGH<br/>Stack: 6KB<br/>Event-driven"]
|
||||
end
|
||||
|
||||
subgraph Components["Components"]
|
||||
SensorMgr[Sensor Manager]
|
||||
MainHubAPI[Main Hub APIs]
|
||||
Persistence[Persistence]
|
||||
DiagTaskComp[Diagnostics Task]
|
||||
HMI[HMI]
|
||||
OTAMgr[OTA Manager]
|
||||
STM[State Manager]
|
||||
end
|
||||
|
||||
SensorTask --> SensorMgr
|
||||
CommTask --> MainHubAPI
|
||||
PersistTask --> Persistence
|
||||
DiagTask --> DiagTaskComp
|
||||
HMITask --> HMI
|
||||
OTATask --> OTAMgr
|
||||
SystemTask --> STM
|
||||
```
|
||||
|
||||
**Task Priorities and Responsibilities:**
|
||||
|
||||
| Task | Priority | Stack Size | Responsibility | Blocking Operations |
|
||||
|------|----------|------------|---------------|---------------------|
|
||||
| **Sensor Acquisition** | HIGH | 8KB | Sensor sampling, filtering | Sensor I/O (bounded) |
|
||||
| **Communication** | MEDIUM | 12KB | Main Hub communication | Network I/O (bounded) |
|
||||
| **Persistence** | MEDIUM | 6KB | Data persistence | Storage I/O (bounded) |
|
||||
| **Diagnostics** | LOW | 4KB | Diagnostic collection | None (non-blocking) |
|
||||
| **HMI** | LOW | 4KB | Display updates, button handling | Display I/O (bounded) |
|
||||
| **OTA** | HIGH | 16KB | Firmware update operations | Network I/O, flash I/O |
|
||||
| **System Management** | HIGH | 6KB | State machine, teardown | None (coordination only) |
|
||||
|
||||
**Resource Ownership:**
|
||||
|
||||
| Resource | Owner | Access Method | Concurrency Control |
|
||||
|----------|-------|--------------|---------------------|
|
||||
| **Sensor Drivers** | Sensor Acquisition Task | Direct (exclusive) | Task-level ownership |
|
||||
| **Network Stack** | Communication Task | Direct (exclusive) | Task-level ownership |
|
||||
| **SD Card** | Persistence Task | Direct (exclusive) | Mutex (if shared) |
|
||||
| **NVM** | Persistence Task | Direct (exclusive) | Mutex (if shared) |
|
||||
| **Data Pool** | All Tasks | Event System | Lock-free (atomic operations) |
|
||||
| **Event System** | All Tasks | Publish/Subscribe | Lock-free queue |
|
||||
| **State Machine** | System Management Task | Direct (exclusive) | Task-level ownership |
|
||||
|
||||
## 4. Component Specifications
|
||||
|
||||
### 4.1 State Manager (STM)
|
||||
|
||||
**Location:** `application_layer/business_stack/STM/`
|
||||
|
||||
**Responsibilities:**
|
||||
- Implement system FSM as defined in System State Machine Specification
|
||||
- Enforce valid state transitions
|
||||
- Coordinate teardown sequences
|
||||
- Notify components of state changes
|
||||
|
||||
**Public API:**
|
||||
```c
|
||||
// State query
|
||||
system_state_t stm_getCurrentState(void);
|
||||
bool stm_isStateValid(system_state_t state);
|
||||
|
||||
// State transition
|
||||
bool stm_requestTransition(system_state_t target_state, transition_reason_t reason);
|
||||
bool stm_validateTransition(system_state_t from, system_state_t to);
|
||||
|
||||
// Teardown coordination
|
||||
bool stm_initiateTeardown(teardown_reason_t reason);
|
||||
bool stm_isTeardownComplete(void);
|
||||
|
||||
// Component registration
|
||||
bool stm_registerStateListener(state_listener_t listener);
|
||||
```
|
||||
|
||||
**State Dependencies:**
|
||||
- SHALL be initialized first (during INIT state)
|
||||
- SHALL coordinate with Error Handler for fault-triggered transitions
|
||||
- SHALL coordinate with OTA Manager for OTA-triggered transitions
|
||||
|
||||
### 4.2 Event System
|
||||
|
||||
**Location:** `application_layer/business_stack/event_system/`
|
||||
|
||||
**Responsibilities:**
|
||||
- Provide publish/subscribe event bus
|
||||
- Decouple components via events
|
||||
- Ensure non-blocking event delivery
|
||||
|
||||
**Public API:**
|
||||
```c
|
||||
// Event types
|
||||
typedef enum {
|
||||
EVENT_SENSOR_DATA_UPDATE,
|
||||
EVENT_DIAGNOSTIC_EVENT,
|
||||
EVENT_STATE_CHANGED,
|
||||
EVENT_OTA_REQUEST,
|
||||
// ... other event types
|
||||
} event_type_t;
|
||||
|
||||
// Publish event
|
||||
bool event_publish(event_type_t type, void* payload, size_t payload_size);
|
||||
|
||||
// Subscribe to events
|
||||
bool event_subscribe(event_type_t type, event_handler_t handler);
|
||||
bool event_unsubscribe(event_type_t type, event_handler_t handler);
|
||||
```
|
||||
|
||||
**Constraints:**
|
||||
- SHALL be non-blocking (lock-free queue)
|
||||
- SHALL support multiple subscribers per event type
|
||||
- SHALL NOT perform blocking operations in event handlers
|
||||
|
||||
### 4.3 Sensor Manager
|
||||
|
||||
**Location:** `application_layer/business_stack/sensor_manager/`
|
||||
|
||||
**Responsibilities:**
|
||||
- Sensor lifecycle management (detection, initialization, sampling)
|
||||
- High-frequency sampling and filtering
|
||||
- Timestamp generation
|
||||
- Sensor failure detection
|
||||
|
||||
**Public API:**
|
||||
```c
|
||||
// Sensor lifecycle
|
||||
bool sensorMgr_initialize(void);
|
||||
bool sensorMgr_detectSensors(void);
|
||||
bool sensorMgr_startAcquisition(void);
|
||||
bool sensorMgr_stopAcquisition(void);
|
||||
|
||||
// Sensor data access
|
||||
bool sensorMgr_getLatestData(uint8_t sensor_id, sensor_data_record_t* record);
|
||||
bool sensorMgr_getAllSensorData(sensor_data_record_t* records, size_t* count);
|
||||
|
||||
// Sensor status
|
||||
bool sensorMgr_isSensorPresent(uint8_t sensor_id);
|
||||
bool sensorMgr_isSensorEnabled(uint8_t sensor_id);
|
||||
```
|
||||
|
||||
**State Dependencies:**
|
||||
- SHALL NOT perform acquisition during OTA_UPDATE, MC_UPDATE, TEARDOWN states
|
||||
- SHALL pause acquisition during SERVICE state (optional)
|
||||
- SHALL continue acquisition during SD_DEGRADED state (without persistence)
|
||||
|
||||
### 4.4 Data Persistence (DP Component)
|
||||
|
||||
**Location:** `application_layer/DP_stack/persistence/`
|
||||
|
||||
**Responsibilities:**
|
||||
- Abstract storage media (SD card, NVM)
|
||||
- Serialize/deserialize structured data
|
||||
- Manage wear-aware storage
|
||||
- Ensure data integrity
|
||||
|
||||
**Public API:**
|
||||
```c
|
||||
// Data persistence
|
||||
bool persistence_writeSensorData(const sensor_data_record_t* record);
|
||||
bool persistence_writeDiagnostic(const diagnostic_event_t* event);
|
||||
bool persistence_writeMachineConstants(const machine_constants_t* mc);
|
||||
|
||||
// Data retrieval
|
||||
bool persistence_readSensorData(sensor_data_record_t* records, size_t* count);
|
||||
bool persistence_readDiagnostics(diagnostic_event_t* events, size_t* count);
|
||||
bool persistence_readMachineConstants(machine_constants_t* mc);
|
||||
|
||||
// Flush operations
|
||||
bool persistence_flushCriticalData(void);
|
||||
bool persistence_isFlushComplete(void);
|
||||
```
|
||||
|
||||
**Constraints:**
|
||||
- SHALL be the sole interface for persistent storage access (CFC-ARCH-01)
|
||||
- SHALL NOT allow direct hardware access from application layer
|
||||
- SHALL verify persistence completion before state transitions (CFC-DATA-02)
|
||||
|
||||
### 4.5 OTA Manager
|
||||
|
||||
**Location:** `application_layer/business_stack/fw_upgrader/`
|
||||
|
||||
**Responsibilities:**
|
||||
- OTA negotiation with Main Hub
|
||||
- Firmware reception and storage
|
||||
- Firmware integrity validation
|
||||
- Controlled firmware activation
|
||||
|
||||
**Public API:**
|
||||
```c
|
||||
// OTA operations
|
||||
bool ota_handleRequest(const ota_request_t* request);
|
||||
bool ota_receiveFirmwareChunk(const uint8_t* chunk, size_t chunk_size);
|
||||
bool ota_validateFirmware(void);
|
||||
bool ota_activateFirmware(void);
|
||||
|
||||
// OTA status
|
||||
ota_status_t ota_getStatus(void);
|
||||
bool ota_isReady(void);
|
||||
```
|
||||
|
||||
**State Dependencies:**
|
||||
- SHALL NOT operate during WARNING, FAULT, SERVICE, SD_DEGRADED states
|
||||
- SHALL coordinate with STM for state transitions
|
||||
- SHALL coordinate with Persistence for data flush before activation
|
||||
|
||||
## 5. Architectural Constraints Mapping
|
||||
|
||||
| Constraint | Architectural Mechanism | Enforcement |
|
||||
|------------|-------------------------|-------------|
|
||||
| **CFC-ARCH-01** (Hardware Abstraction) | Driver layer abstraction, OSAL layer | Compile-time (no direct HAL includes in application) |
|
||||
| **CFC-ARCH-02** (State-Aware Execution) | STM state queries, per-state execution rules | Runtime (state checks in components) |
|
||||
| **CFC-TIME-01** (Non-Blocking) | Event System, async operations | Design-time (no blocking calls in critical paths) |
|
||||
| **CFC-TIME-02** (Deterministic) | Static memory allocation, bounded operations | Design-time (no dynamic allocation in acquisition) |
|
||||
| **CFC-DATA-01** (Single Source of Truth) | DP component as sole persistence interface | Compile-time (no direct storage access) |
|
||||
| **CFC-DATA-02** (Data Consistency) | Persistence flush before state transitions | Runtime (STM coordination) |
|
||||
| **CFC-SEC-01** (Security First) | Secure boot before application start | Boot-time (hardware-enforced) |
|
||||
| **CFC-SEC-02** (Encrypted Channels) | TLS/DTLS in Network Stack | Runtime (mandatory encryption) |
|
||||
| **CFC-DBG-01** (Debug Isolation) | Debug session authentication, state restrictions | Runtime (authentication checks) |
|
||||
|
||||
## 6. Repository Structure Mapping
|
||||
|
||||
| Architecture Layer | Repository Path | Components |
|
||||
|-------------------|-----------------|------------|
|
||||
| **Application Layer** | `application_layer/business_stack/` | STM, Event System, Sensor Manager, MC Manager, OTA Manager, Main Hub APIs |
|
||||
| **DP Stack** | `application_layer/DP_stack/` | Data Pool, Persistence |
|
||||
| **Diagnostics** | `application_layer/diag_task/`, `application_layer/error_handler/` | Diagnostics Task, Error Handler |
|
||||
| **Drivers** | `drivers/` | Network Stack, Diagnostic Protocol, SD Card, NVM, Sensors |
|
||||
| **ESP-IDF Wrappers** | `ESP_IDF_FW_wrappers/` | GPIO, I2C, SPI, UART, ADC, DMA, Wi-Fi |
|
||||
| **OSAL** | `os/` | Task, Software Timer |
|
||||
| **Utilities** | `utils/` | Logger, Time Utils |
|
||||
|
||||
## 7. Traceability
|
||||
|
||||
- **SRS Section 3.2:** Interface Requirements
|
||||
- **SRS Section 3.4:** Design Constraints
|
||||
- **Cross-Feature Constraints:** All architectural constraints mapped
|
||||
- **System State Machine Specification:** State-aware component behavior
|
||||
|
||||
## 8. Diagrams Summary
|
||||
|
||||
- **Context View:** External interfaces and actors
|
||||
- **Component View:** Major components and relationships
|
||||
- **Data Flow View:** Sensor data, diagnostic, and OTA flows
|
||||
- **Concurrency View:** Task model, priorities, resource ownership
|
||||
|
||||
---
|
||||
|
||||
**Next Steps:**
|
||||
- Component-level specifications (detailed APIs per component)
|
||||
- Sequence diagrams for additional flows (MC update, diagnostic session)
|
||||
- Interface Control Documents (ICD) for external interfaces
|
||||
346
draft- to be removed SW/components/ESP_IDF_FW_wrappers/README.md
Normal file
346
draft- to be removed SW/components/ESP_IDF_FW_wrappers/README.md
Normal file
@@ -0,0 +1,346 @@
|
||||
# ESP-IDF Firmware Wrappers
|
||||
|
||||
## Overview
|
||||
|
||||
This directory contains C++ object-oriented wrappers for ESP-IDF peripheral drivers. Each wrapper provides a clean, easy-to-use interface that encapsulates the ESP-IDF C APIs while maintaining full functionality and performance.
|
||||
|
||||
## Architecture
|
||||
|
||||
The wrapper architecture follows these principles:
|
||||
|
||||
- **Object-Oriented Design**: Each peripheral is wrapped in a C++ class
|
||||
- **RAII Pattern**: Resources are automatically managed through constructors/destructors
|
||||
- **Error Handling**: Comprehensive error checking with ESP-IDF error code logging
|
||||
- **Type Safety**: Strong typing with enumerations instead of magic numbers
|
||||
- **Documentation**: Full Doxygen documentation for all public APIs
|
||||
- **Naming Convention**: Follows project coding guidelines (snake_case files, PascalCase classes)
|
||||
|
||||
## Implemented Modules
|
||||
|
||||
### ✅ GPIO Wrapper (`gpio/`)
|
||||
- **Status**: Complete
|
||||
- **Features**: Pin configuration, digital I/O, interrupt handling, pin validation
|
||||
- **Key Classes**: `Gpio`
|
||||
- **Dependencies**: `driver/gpio.h`, `esp_err.h`, `esp_log.h`
|
||||
|
||||
### ✅ UART Wrapper (`uart/`)
|
||||
- **Status**: Complete
|
||||
- **Features**: Multi-port support, configurable parameters, timeout support, flow control
|
||||
- **Key Classes**: `Uart`
|
||||
- **Dependencies**: `driver/uart.h`, `esp_err.h`, `esp_log.h`, `freertos/FreeRTOS.h`
|
||||
|
||||
### ✅ I2C Wrapper (`i2c/`)
|
||||
- **Status**: Complete
|
||||
- **Features**: Master/slave modes, register operations, bus scanning, timeout support
|
||||
- **Key Classes**: `I2c`
|
||||
- **Dependencies**: `driver/i2c.h`, `esp_err.h`, `esp_log.h`, `freertos/FreeRTOS.h`
|
||||
|
||||
### ✅ SPI Wrapper (`spi/`)
|
||||
- **Status**: Complete
|
||||
- **Features**: Multi-host support, device management, DMA support, command/address phases
|
||||
- **Key Classes**: `Spi`
|
||||
- **Dependencies**: `driver/spi_master.h`, `esp_err.h`, `esp_log.h`
|
||||
|
||||
### ✅ ADC Wrapper (`adc/`)
|
||||
- **Status**: Complete
|
||||
- **Features**: Multi-unit/channel support, automatic calibration, averaging, multiple resolutions
|
||||
- **Key Classes**: `Adc`
|
||||
- **Dependencies**: `esp_adc/adc_oneshot.h`, `esp_adc/adc_cali.h`, `esp_err.h`, `esp_log.h`
|
||||
|
||||
### ✅ WiFi Wrapper (`wifi/`)
|
||||
- **Status**: Complete
|
||||
- **Features**: STA/AP/APSTA modes, network scanning, event handling, security support
|
||||
- **Key Classes**: `Wifi`
|
||||
- **Dependencies**: `esp_wifi.h`, `esp_netif.h`, `esp_event.h`, `nvs_flash.h`
|
||||
|
||||
### ✅ DMA Wrapper (`dma/`)
|
||||
- **Status**: Complete
|
||||
- **Features**: GDMA channel management, memory transfers, descriptor management, DMA-capable memory allocation
|
||||
- **Key Classes**: `Dma`
|
||||
- **Dependencies**: `driver/gdma.h`, `esp_dma_utils.h`, `esp_hw_support`, `esp_heap_caps.h`
|
||||
|
||||
### ✅ Bluetooth Wrapper (`bt/`)
|
||||
- **Status**: Complete
|
||||
- **Features**: BLE/Classic/Dual modes, GATT server/client, advertising, scanning, pairing
|
||||
- **Key Classes**: `Bluetooth`
|
||||
- **Dependencies**: `esp_bt.h`, `esp_gap_ble_api.h`, `esp_gatts_api.h`, `esp_gattc_api.h`, `nvs_flash.h`
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
ESP_IDF_FW_wrappers/
|
||||
├── README.md # This overview document
|
||||
├── gpio/
|
||||
│ ├── com/
|
||||
│ │ ├── gpio.hpp # GPIO wrapper header
|
||||
│ │ └── gpio.cpp # GPIO wrapper implementation
|
||||
│ ├── test/ # Unit tests (empty)
|
||||
│ ├── CMakeLists.txt # Build configuration
|
||||
│ └── README.md # GPIO module documentation
|
||||
├── uart/
|
||||
│ ├── com/
|
||||
│ │ ├── uart.hpp # UART wrapper header
|
||||
│ │ └── uart.cpp # UART wrapper implementation
|
||||
│ ├── test/ # Unit tests (empty)
|
||||
│ ├── CMakeLists.txt # Build configuration
|
||||
│ └── README.md # UART module documentation
|
||||
├── i2c/
|
||||
│ ├── com/
|
||||
│ │ ├── i2c.hpp # I2C wrapper header
|
||||
│ │ └── i2c.cpp # I2C wrapper implementation
|
||||
│ ├── test/ # Unit tests (empty)
|
||||
│ ├── CMakeLists.txt # Build configuration
|
||||
│ └── README.md # I2C module documentation
|
||||
├── spi/
|
||||
│ ├── com/
|
||||
│ │ ├── spi.hpp # SPI wrapper header
|
||||
│ │ └── spi.cpp # SPI wrapper implementation
|
||||
│ ├── test/ # Unit tests (empty)
|
||||
│ ├── CMakeLists.txt # Build configuration
|
||||
│ └── README.md # SPI module documentation
|
||||
├── adc/
|
||||
│ ├── com/
|
||||
│ │ ├── adc.hpp # ADC wrapper header
|
||||
│ │ └── adc.cpp # ADC wrapper implementation
|
||||
│ ├── test/ # Unit tests (empty)
|
||||
│ ├── CMakeLists.txt # Build configuration
|
||||
│ └── README.md # ADC module documentation
|
||||
├── wifi/
|
||||
│ ├── com/
|
||||
│ │ ├── wifi.hpp # WiFi wrapper header
|
||||
│ │ └── wifi.cpp # WiFi wrapper implementation
|
||||
│ ├── test/ # Unit tests (empty)
|
||||
│ ├── CMakeLists.txt # Build configuration
|
||||
│ └── README.md # WiFi module documentation
|
||||
├── bt/ # Bluetooth wrapper (✅ Complete)
|
||||
│ ├── com/
|
||||
│ │ ├── bt.hpp # Bluetooth wrapper header
|
||||
│ │ └── bt.cpp # Bluetooth wrapper implementation
|
||||
│ ├── test/ # Unit tests (empty)
|
||||
│ ├── CMakeLists.txt # Build configuration
|
||||
│ └── README.md # Bluetooth module documentation
|
||||
└── dma/ # DMA wrapper (✅ Complete)
|
||||
├── com/
|
||||
│ ├── dma.hpp # DMA wrapper header
|
||||
│ └── dma.cpp # DMA wrapper implementation
|
||||
├── test/ # Unit tests (empty)
|
||||
├── CMakeLists.txt # Build configuration
|
||||
└── README.md # DMA module documentation
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic GPIO Usage
|
||||
```cpp
|
||||
#include "gpio.hpp"
|
||||
|
||||
Gpio gpio;
|
||||
gpio.configure(2, GpioMode::OUTPUT);
|
||||
gpio.setLevel(2, 1); // Set pin high
|
||||
```
|
||||
|
||||
### UART Communication
|
||||
```cpp
|
||||
#include "uart.hpp"
|
||||
|
||||
Uart uart;
|
||||
UartConfig config = Uart::getDefaultConfig();
|
||||
config.baudrate = UartBaudrate::BAUD_115200;
|
||||
config.txPin = 1;
|
||||
config.rxPin = 3;
|
||||
|
||||
uart.initialize(UartPort::PORT_0, config);
|
||||
uart.transmit(UartPort::PORT_0, (uint8_t*)"Hello", 5);
|
||||
```
|
||||
|
||||
### I2C Device Communication
|
||||
```cpp
|
||||
#include "i2c.hpp"
|
||||
|
||||
I2c i2c;
|
||||
I2cConfig config = I2c::getDefaultConfig();
|
||||
i2c.initialize(I2cPort::PORT_0, config);
|
||||
|
||||
uint8_t data[] = {0x01, 0x02};
|
||||
i2c.write(I2cPort::PORT_0, 0x48, data, sizeof(data));
|
||||
```
|
||||
|
||||
### SPI Device Communication
|
||||
```cpp
|
||||
#include "spi.hpp"
|
||||
|
||||
Spi spi;
|
||||
SpiBusConfig busConfig = Spi::getDefaultBusConfig();
|
||||
spi.initializeBus(SpiHost::SPI2_HOST, busConfig);
|
||||
|
||||
SpiDeviceConfig deviceConfig = Spi::getDefaultDeviceConfig();
|
||||
spi_device_handle_t device;
|
||||
spi.addDevice(SpiHost::SPI2_HOST, deviceConfig, &device);
|
||||
|
||||
uint8_t txData[] = {0xAA, 0x55};
|
||||
uint8_t rxData[2];
|
||||
spi.transmit(device, txData, rxData, 2);
|
||||
```
|
||||
|
||||
### ADC Reading
|
||||
```cpp
|
||||
#include "adc.hpp"
|
||||
|
||||
Adc adc;
|
||||
adc.initializeUnit(AdcUnit::UNIT_1, AdcBitwidth::WIDTH_12BIT);
|
||||
|
||||
AdcChannelConfig config = Adc::getDefaultChannelConfig();
|
||||
adc.configureChannel(config);
|
||||
|
||||
int32_t voltage = adc.readVoltage(AdcUnit::UNIT_1, AdcChannel::CHANNEL_0);
|
||||
```
|
||||
|
||||
### WiFi Connection
|
||||
```cpp
|
||||
#include "wifi.hpp"
|
||||
|
||||
Wifi wifi;
|
||||
WifiConfig config = {};
|
||||
config.mode = WifiMode::STA;
|
||||
config.staConfig = Wifi::getDefaultStaConfig();
|
||||
strcpy(config.staConfig.ssid, "MyNetwork");
|
||||
strcpy(config.staConfig.password, "MyPassword");
|
||||
|
||||
wifi.initialize(config);
|
||||
wifi.start();
|
||||
wifi.connect();
|
||||
```
|
||||
|
||||
## Build Integration
|
||||
|
||||
Each wrapper is a separate ESP-IDF component with its own `CMakeLists.txt`. To use a wrapper in your application:
|
||||
|
||||
1. Add the wrapper component to your project's `CMakeLists.txt`:
|
||||
```cmake
|
||||
set(EXTRA_COMPONENT_DIRS "components/ESP_IDF_FW_wrappers/gpio")
|
||||
```
|
||||
|
||||
2. Include the wrapper in your component's dependencies:
|
||||
```cmake
|
||||
idf_component_register(
|
||||
SRCS "main.cpp"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES gpio_wrapper
|
||||
)
|
||||
```
|
||||
|
||||
3. Include the header in your code:
|
||||
```cpp
|
||||
#include "gpio.hpp"
|
||||
```
|
||||
|
||||
## Design Patterns
|
||||
|
||||
### RAII (Resource Acquisition Is Initialization)
|
||||
All wrappers follow RAII principles:
|
||||
- Resources are acquired in constructors
|
||||
- Resources are released in destructors
|
||||
- Exception safety through automatic cleanup
|
||||
|
||||
### Error Handling
|
||||
Consistent error handling across all wrappers:
|
||||
- Boolean return values for success/failure
|
||||
- Negative return values for error conditions
|
||||
- ESP-IDF error codes logged with descriptive messages
|
||||
- Input parameter validation
|
||||
|
||||
### Configuration Structures
|
||||
Each wrapper provides configuration structures:
|
||||
- Default configuration functions
|
||||
- Type-safe enumerations
|
||||
- Clear parameter documentation
|
||||
|
||||
## Testing
|
||||
|
||||
Each wrapper includes a `test/` directory for unit tests (currently empty). Future development should include:
|
||||
- Unit tests for all public APIs
|
||||
- Integration tests with real hardware
|
||||
- Performance benchmarks
|
||||
- Memory usage tests
|
||||
|
||||
## Future Development
|
||||
|
||||
### All Core Modules Implemented
|
||||
|
||||
All 8 core ESP-IDF peripheral wrapper modules have been successfully implemented:
|
||||
- GPIO, UART, I2C, SPI, ADC, WiFi, DMA, and Bluetooth wrappers are complete
|
||||
- Each module includes full functionality, documentation, and usage examples
|
||||
|
||||
### Enhancements for Existing Modules
|
||||
|
||||
1. **GPIO**
|
||||
- Matrix keyboard support
|
||||
- PWM integration
|
||||
- Capacitive touch support
|
||||
|
||||
2. **UART**
|
||||
- RS485 support
|
||||
- Pattern detection
|
||||
- DMA integration
|
||||
|
||||
3. **I2C**
|
||||
- Multi-master support
|
||||
- Clock stretching
|
||||
- 10-bit addressing
|
||||
|
||||
4. **SPI**
|
||||
- Slave mode support
|
||||
- Quad SPI support
|
||||
- LCD interface support
|
||||
|
||||
5. **ADC**
|
||||
- Continuous mode
|
||||
- DMA integration
|
||||
- Multi-channel simultaneous sampling
|
||||
|
||||
6. **WiFi**
|
||||
- WPS support
|
||||
- Mesh networking
|
||||
- Enterprise security
|
||||
|
||||
## Contributing
|
||||
|
||||
When contributing to the wrapper modules:
|
||||
|
||||
1. Follow the established coding guidelines in `6 Guidelines.md`
|
||||
2. Maintain consistent API design across modules
|
||||
3. Include comprehensive documentation
|
||||
4. Add unit tests for new functionality
|
||||
5. Update the module's README.md file
|
||||
6. Ensure thread safety where applicable
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
- All wrappers provide minimal overhead over direct ESP-IDF calls
|
||||
- RAII pattern ensures efficient resource management
|
||||
- Inline functions used where appropriate
|
||||
- No dynamic memory allocation in critical paths
|
||||
- Direct ESP-IDF function calls for optimal performance
|
||||
|
||||
## Memory Usage
|
||||
|
||||
- Fixed memory footprint per wrapper instance
|
||||
- No hidden dynamic allocations
|
||||
- Resource handles managed efficiently
|
||||
- Stack-based configuration structures
|
||||
|
||||
## Thread Safety
|
||||
|
||||
- GPIO: Not thread-safe, requires external synchronization
|
||||
- UART: Thread-safe (ESP-IDF driver is thread-safe)
|
||||
- I2C: Thread-safe for different ports, synchronize access to same port
|
||||
- SPI: Thread-safe (ESP-IDF driver is thread-safe)
|
||||
- ADC: Thread-safe (ESP-IDF driver is thread-safe)
|
||||
- WiFi: Thread-safe (ESP-IDF driver is thread-safe)
|
||||
|
||||
## Compatibility
|
||||
|
||||
- **ESP-IDF Version**: v5.x
|
||||
- **ESP32 Variants**: ESP32, ESP32-S2, ESP32-S3, ESP32-C3, ESP32-C6
|
||||
- **Compiler**: GCC with C++11 or later
|
||||
- **Build System**: CMake-based ESP-IDF build system
|
||||
@@ -0,0 +1,5 @@
|
||||
idf_component_register(
|
||||
SRCS "com/adc.cpp"
|
||||
INCLUDE_DIRS "com"
|
||||
REQUIRES driver esp_adc logger
|
||||
)
|
||||
@@ -0,0 +1,276 @@
|
||||
# ADC Wrapper Module
|
||||
|
||||
## Overview
|
||||
|
||||
The ADC wrapper module provides a C++ object-oriented interface for ESP-IDF ADC functionality. This module encapsulates the ESP-IDF ADC oneshot driver functions and provides a clean, easy-to-use API for analog-to-digital conversion with automatic calibration support.
|
||||
|
||||
## Features
|
||||
|
||||
- **Multi-Unit Support**: Support for ADC1 and ADC2 units
|
||||
- **Multi-Channel Support**: Support for up to 10 channels per unit
|
||||
- **Automatic Calibration**: Built-in calibration for accurate voltage readings
|
||||
- **Multiple Resolutions**: Support for 9-13 bit ADC resolution
|
||||
- **Attenuation Control**: Configurable input voltage ranges
|
||||
- **Averaging**: Built-in sample averaging for noise reduction
|
||||
- **Error Handling**: Comprehensive error checking and logging
|
||||
|
||||
## Architecture
|
||||
|
||||
### Class Diagram
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ Adc │
|
||||
├─────────────────────────────────────┤
|
||||
│ - m_adcHandle_[2]: handle_t │
|
||||
│ - m_caliHandle_[2][10]: handle_t │
|
||||
│ - m_unitInitialized_[2]: bool │
|
||||
│ - m_channelConfigured_[2][10]: bool │
|
||||
│ - m_unitBitwidth_[2]: AdcBitwidth │
|
||||
├─────────────────────────────────────┤
|
||||
│ + Adc() │
|
||||
│ + ~Adc() │
|
||||
│ + initializeUnit(unit, width): bool │
|
||||
│ + deinitializeUnit(unit): bool │
|
||||
│ + configureChannel(config): bool │
|
||||
│ + readVoltage(unit, ch): int32_t │
|
||||
│ + readRaw(unit, ch): int32_t │
|
||||
│ + readVoltageAverage(...): int32_t │
|
||||
│ + readRawAverage(...): int32_t │
|
||||
│ + isUnitInitialized(unit): bool │
|
||||
│ + isChannelConfigured(...): bool │
|
||||
│ + getMaxRawValue(width): uint32_t │
|
||||
│ + getDefaultChannelConfig(): Config │
|
||||
│ - convertUnit(unit): adc_unit_t │
|
||||
│ - convertChannel(ch): adc_channel_t │
|
||||
│ - convertAttenuation(...): adc_att │
|
||||
│ - convertBitwidth(...): adc_bit_t │
|
||||
│ - initializeCalibration(...): bool │
|
||||
│ - deinitializeCalibration(...): bool│
|
||||
│ - getUnitIndex(unit): uint8_t │
|
||||
│ - getChannelIndex(ch): uint8_t │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Enumerations
|
||||
|
||||
#### AdcUnit
|
||||
- `UNIT_1`: ADC unit 1
|
||||
- `UNIT_2`: ADC unit 2
|
||||
|
||||
#### AdcChannel
|
||||
- `CHANNEL_0` to `CHANNEL_9`: ADC channels 0-9
|
||||
|
||||
#### AdcAttenuation
|
||||
- `ATTEN_0dB`: 0dB attenuation (0-950mV range)
|
||||
- `ATTEN_2_5dB`: 2.5dB attenuation (0-1250mV range)
|
||||
- `ATTEN_6dB`: 6dB attenuation (0-1750mV range)
|
||||
- `ATTEN_11dB`: 11dB attenuation (0-3100mV range)
|
||||
|
||||
#### AdcBitwidth
|
||||
- `WIDTH_9BIT`: 9-bit resolution (0-511)
|
||||
- `WIDTH_10BIT`: 10-bit resolution (0-1023)
|
||||
- `WIDTH_11BIT`: 11-bit resolution (0-2047)
|
||||
- `WIDTH_12BIT`: 12-bit resolution (0-4095)
|
||||
- `WIDTH_13BIT`: 13-bit resolution (0-8191)
|
||||
|
||||
### Configuration Structure
|
||||
|
||||
```cpp
|
||||
struct AdcChannelConfig {
|
||||
AdcUnit unit; // ADC unit
|
||||
AdcChannel channel; // ADC channel
|
||||
AdcAttenuation atten; // Attenuation level
|
||||
AdcBitwidth bitwidth; // ADC resolution
|
||||
};
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic ADC Reading
|
||||
|
||||
```cpp
|
||||
#include "adc.hpp"
|
||||
|
||||
// Create ADC instance
|
||||
Adc adc;
|
||||
|
||||
// Initialize ADC unit 1 with 12-bit resolution
|
||||
adc.initializeUnit(AdcUnit::UNIT_1, AdcBitwidth::WIDTH_12BIT);
|
||||
|
||||
// Configure channel 0
|
||||
AdcChannelConfig config = Adc::getDefaultChannelConfig();
|
||||
config.unit = AdcUnit::UNIT_1;
|
||||
config.channel = AdcChannel::CHANNEL_0;
|
||||
config.atten = AdcAttenuation::ATTEN_11dB; // 0-3.1V range
|
||||
config.bitwidth = AdcBitwidth::WIDTH_12BIT;
|
||||
|
||||
adc.configureChannel(config);
|
||||
|
||||
// Read voltage in millivolts
|
||||
int32_t voltage = adc.readVoltage(AdcUnit::UNIT_1, AdcChannel::CHANNEL_0);
|
||||
printf("Voltage: %ld mV\n", voltage);
|
||||
|
||||
// Read raw ADC value
|
||||
int32_t raw = adc.readRaw(AdcUnit::UNIT_1, AdcChannel::CHANNEL_0);
|
||||
printf("Raw value: %ld\n", raw);
|
||||
```
|
||||
|
||||
### Multiple Channels
|
||||
|
||||
```cpp
|
||||
// Configure multiple channels
|
||||
AdcChannelConfig configs[] = {
|
||||
{AdcUnit::UNIT_1, AdcChannel::CHANNEL_0, AdcAttenuation::ATTEN_11dB, AdcBitwidth::WIDTH_12BIT},
|
||||
{AdcUnit::UNIT_1, AdcChannel::CHANNEL_1, AdcAttenuation::ATTEN_6dB, AdcBitwidth::WIDTH_12BIT},
|
||||
{AdcUnit::UNIT_2, AdcChannel::CHANNEL_0, AdcAttenuation::ATTEN_11dB, AdcBitwidth::WIDTH_12BIT}
|
||||
};
|
||||
|
||||
// Initialize units
|
||||
adc.initializeUnit(AdcUnit::UNIT_1, AdcBitwidth::WIDTH_12BIT);
|
||||
adc.initializeUnit(AdcUnit::UNIT_2, AdcBitwidth::WIDTH_12BIT);
|
||||
|
||||
// Configure all channels
|
||||
for (const auto& config : configs) {
|
||||
adc.configureChannel(config);
|
||||
}
|
||||
|
||||
// Read from all channels
|
||||
for (const auto& config : configs) {
|
||||
int32_t voltage = adc.readVoltage(config.unit, config.channel);
|
||||
printf("Unit %d Channel %d: %ld mV\n",
|
||||
static_cast<int>(config.unit),
|
||||
static_cast<int>(config.channel),
|
||||
voltage);
|
||||
}
|
||||
```
|
||||
|
||||
### Noise Reduction with Averaging
|
||||
|
||||
```cpp
|
||||
// Read 10 samples and return average
|
||||
int32_t avgVoltage = adc.readVoltageAverage(AdcUnit::UNIT_1, AdcChannel::CHANNEL_0, 10);
|
||||
printf("Average voltage: %ld mV\n", avgVoltage);
|
||||
|
||||
// Read 50 raw samples and return average
|
||||
int32_t avgRaw = adc.readRawAverage(AdcUnit::UNIT_1, AdcChannel::CHANNEL_0, 50);
|
||||
printf("Average raw: %ld\n", avgRaw);
|
||||
```
|
||||
|
||||
### Different Voltage Ranges
|
||||
|
||||
```cpp
|
||||
// Low voltage sensor (0-950mV)
|
||||
AdcChannelConfig lowVoltageConfig = {};
|
||||
lowVoltageConfig.unit = AdcUnit::UNIT_1;
|
||||
lowVoltageConfig.channel = AdcChannel::CHANNEL_0;
|
||||
lowVoltageConfig.atten = AdcAttenuation::ATTEN_0dB; // 0-950mV
|
||||
lowVoltageConfig.bitwidth = AdcBitwidth::WIDTH_12BIT;
|
||||
|
||||
// Battery voltage (0-3.1V)
|
||||
AdcChannelConfig batteryConfig = {};
|
||||
batteryConfig.unit = AdcUnit::UNIT_1;
|
||||
batteryConfig.channel = AdcChannel::CHANNEL_1;
|
||||
batteryConfig.atten = AdcAttenuation::ATTEN_11dB; // 0-3100mV
|
||||
batteryConfig.bitwidth = AdcBitwidth::WIDTH_12BIT;
|
||||
|
||||
adc.configureChannel(lowVoltageConfig);
|
||||
adc.configureChannel(batteryConfig);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### Constructor/Destructor
|
||||
|
||||
- **Adc()**: Initialize ADC wrapper instance
|
||||
- **~Adc()**: Clean up resources and deinitialize all units
|
||||
|
||||
### Unit Management
|
||||
|
||||
- **initializeUnit(unit, bitwidth)**: Initialize ADC unit with resolution
|
||||
- **deinitializeUnit(unit)**: Deinitialize ADC unit
|
||||
- **isUnitInitialized(unit)**: Check if unit is initialized
|
||||
|
||||
### Channel Configuration
|
||||
|
||||
- **configureChannel(config)**: Configure ADC channel
|
||||
- **isChannelConfigured(unit, channel)**: Check if channel is configured
|
||||
|
||||
### Reading Methods
|
||||
|
||||
- **readVoltage(unit, channel)**: Read calibrated voltage in mV
|
||||
- **readRaw(unit, channel)**: Read raw ADC value
|
||||
- **readVoltageAverage(unit, channel, samples)**: Read averaged voltage
|
||||
- **readRawAverage(unit, channel, samples)**: Read averaged raw value
|
||||
|
||||
### Utility Methods
|
||||
|
||||
- **getMaxRawValue(bitwidth)**: Get maximum raw value for resolution
|
||||
- **getDefaultChannelConfig()**: Get default configuration
|
||||
|
||||
## Voltage Ranges by Attenuation
|
||||
|
||||
| Attenuation | Voltage Range | Typical Use Case |
|
||||
|-------------|---------------|------------------|
|
||||
| 0dB | 0 - 950mV | Low voltage sensors |
|
||||
| 2.5dB | 0 - 1250mV | 1.2V references |
|
||||
| 6dB | 0 - 1750mV | 1.8V logic levels |
|
||||
| 11dB | 0 - 3100mV | 3.3V logic, battery voltage |
|
||||
|
||||
## ADC Resolution and Range
|
||||
|
||||
| Bitwidth | Resolution | Max Raw Value | LSB (at 3.1V) |
|
||||
|----------|------------|---------------|----------------|
|
||||
| 9-bit | 512 steps | 511 | ~6.1 mV |
|
||||
| 10-bit | 1024 steps | 1023 | ~3.0 mV |
|
||||
| 11-bit | 2048 steps | 2047 | ~1.5 mV |
|
||||
| 12-bit | 4096 steps | 4095 | ~0.76 mV |
|
||||
| 13-bit | 8192 steps | 8191 | ~0.38 mV |
|
||||
|
||||
## Error Handling
|
||||
|
||||
The module provides comprehensive error handling:
|
||||
- Unit and channel validation
|
||||
- Initialization status checks
|
||||
- ESP-IDF error codes are caught and logged
|
||||
- Return values indicate success/failure for all operations
|
||||
- Graceful fallback when calibration is unavailable
|
||||
|
||||
## Dependencies
|
||||
|
||||
- ESP-IDF ADC driver (`esp_adc/adc_oneshot.h`)
|
||||
- ESP-IDF ADC calibration (`esp_adc/adc_cali.h`)
|
||||
- ESP-IDF error handling (`esp_err.h`)
|
||||
- ESP-IDF logging (`esp_log.h`)
|
||||
|
||||
## Thread Safety
|
||||
|
||||
The ADC wrapper uses ESP-IDF's thread-safe ADC driver. Multiple tasks can safely read from different channels simultaneously.
|
||||
|
||||
## Memory Usage
|
||||
|
||||
- Fixed memory footprint per instance
|
||||
- Calibration handles stored per channel
|
||||
- No dynamic memory allocation in wrapper layer
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
- Direct ESP-IDF function calls for optimal performance
|
||||
- Calibration lookup tables for fast voltage conversion
|
||||
- Averaging reduces noise but increases read time
|
||||
- Higher resolution increases conversion time
|
||||
|
||||
## Calibration
|
||||
|
||||
The module automatically initializes calibration for each configured channel:
|
||||
- Uses ESP-IDF's curve fitting calibration scheme
|
||||
- Provides accurate voltage readings across temperature range
|
||||
- Falls back to linear estimation if calibration fails
|
||||
- Calibration data stored in eFuse (factory calibrated)
|
||||
|
||||
## Limitations
|
||||
|
||||
- ADC2 cannot be used when WiFi is active
|
||||
- Some channels may not be available on all ESP32 variants
|
||||
- Maximum sampling rate depends on resolution and calibration
|
||||
- Input impedance affects accuracy for high-impedance sources
|
||||
@@ -0,0 +1,363 @@
|
||||
/**
|
||||
* @file adc.cpp
|
||||
* @brief ADC wrapper component implementation
|
||||
* @author Mahmoud Elmohtady
|
||||
* @company Nabd solutions - ASF
|
||||
* @copyright Copyright (c) 2025
|
||||
*/
|
||||
|
||||
#include "adc.hpp"
|
||||
#include "logger.hpp"
|
||||
#include <cstring>
|
||||
|
||||
static const char* TAG = "ADC_WRAPPER";
|
||||
|
||||
Adc::Adc()
|
||||
{
|
||||
// Initialize handles to NULL
|
||||
memset(m_adcHandle_, 0, sizeof(m_adcHandle_));
|
||||
memset(m_caliHandle_, 0, sizeof(m_caliHandle_));
|
||||
memset(m_unitInitialized_, false, sizeof(m_unitInitialized_));
|
||||
memset(m_channelConfigured_, false, sizeof(m_channelConfigured_));
|
||||
|
||||
m_unitBitwidth_[0] = AdcBitwidth::WIDTH_12BIT;
|
||||
m_unitBitwidth_[1] = AdcBitwidth::WIDTH_12BIT;
|
||||
|
||||
ASF_LOGI(TAG, 2000, asf::logger::Criticality::LOW, "ADC wrapper initialized");
|
||||
}
|
||||
|
||||
Adc::~Adc()
|
||||
{
|
||||
// Deinitialize all units
|
||||
for (int i = 0; i < 2; i++) {
|
||||
if (m_unitInitialized_[i]) {
|
||||
deinitializeUnit(static_cast<AdcUnit>(i + 1));
|
||||
}
|
||||
}
|
||||
ASF_LOGI(TAG, 2001, asf::logger::Criticality::LOW, "ADC wrapper destroyed");
|
||||
}
|
||||
|
||||
bool Adc::initialize()
|
||||
{
|
||||
ASF_LOGI(TAG, 2002, asf::logger::Criticality::LOW, "ADC initialized successfully");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Adc::initializeUnit(AdcUnit unit, AdcBitwidth bitwidth)
|
||||
{
|
||||
uint8_t unitIdx = getUnitIndex(unit);
|
||||
|
||||
if (m_unitInitialized_[unitIdx]) {
|
||||
ASF_LOGW(TAG, 2003, asf::logger::Criticality::MEDIUM, "ADC unit %d already initialized", unitIdx + 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Configure ADC unit
|
||||
adc_oneshot_unit_init_cfg_t initConfig = {};
|
||||
initConfig.unit_id = convertUnit(unit);
|
||||
|
||||
esp_err_t ret = adc_oneshot_new_unit(&initConfig, &m_adcHandle_[unitIdx]);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2004, asf::logger::Criticality::HIGH, "Failed to initialize ADC unit %d: %s", unitIdx + 1, esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
m_unitInitialized_[unitIdx] = true;
|
||||
m_unitBitwidth_[unitIdx] = bitwidth;
|
||||
ASF_LOGI(TAG, 2005, asf::logger::Criticality::LOW, "ADC unit %d initialized successfully", unitIdx + 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Adc::deinitializeUnit(AdcUnit unit)
|
||||
{
|
||||
uint8_t unitIdx = getUnitIndex(unit);
|
||||
|
||||
if (!m_unitInitialized_[unitIdx]) {
|
||||
ASF_LOGW(TAG, 2006, asf::logger::Criticality::MEDIUM, "ADC unit %d not initialized", unitIdx + 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Deinitialize all calibrations for this unit
|
||||
for (int ch = 0; ch < 10; ch++) {
|
||||
if (m_channelConfigured_[unitIdx][ch]) {
|
||||
deinitializeCalibration(unit, static_cast<AdcChannel>(ch));
|
||||
m_channelConfigured_[unitIdx][ch] = false;
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t ret = adc_oneshot_del_unit(m_adcHandle_[unitIdx]);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2007, asf::logger::Criticality::HIGH, "Failed to deinitialize ADC unit %d: %s", unitIdx + 1, esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
m_adcHandle_[unitIdx] = nullptr;
|
||||
m_unitInitialized_[unitIdx] = false;
|
||||
ASF_LOGI(TAG, 2008, asf::logger::Criticality::LOW, "ADC unit %d deinitialized", unitIdx + 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Adc::configureChannel(const AdcChannelConfig& config)
|
||||
{
|
||||
uint8_t unitIdx = getUnitIndex(config.unit);
|
||||
uint8_t channelIdx = getChannelIndex(config.channel);
|
||||
|
||||
if (!m_unitInitialized_[unitIdx]) {
|
||||
ASF_LOGE(TAG, 2009, asf::logger::Criticality::HIGH, "ADC unit %d not initialized", unitIdx + 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Configure channel
|
||||
adc_oneshot_chan_cfg_t chanConfig = {};
|
||||
chanConfig.atten = convertAttenuation(config.atten);
|
||||
chanConfig.bitwidth = convertBitwidth(config.bitwidth);
|
||||
|
||||
esp_err_t ret = adc_oneshot_config_channel(m_adcHandle_[unitIdx],
|
||||
convertChannel(config.channel), &chanConfig);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2010, asf::logger::Criticality::HIGH, "Failed to configure ADC channel %d on unit %d: %s",
|
||||
channelIdx, unitIdx + 1, esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Initialize calibration
|
||||
if (!initializeCalibration(config.unit, config.channel, config.atten)) {
|
||||
ASF_LOGW(TAG, 2011, asf::logger::Criticality::MEDIUM, "Failed to initialize calibration for channel %d on unit %d",
|
||||
channelIdx, unitIdx + 1);
|
||||
}
|
||||
|
||||
m_channelConfigured_[unitIdx][channelIdx] = true;
|
||||
ASF_LOGI(TAG, 2012, asf::logger::Criticality::LOW, "ADC channel %d configured on unit %d", channelIdx, unitIdx + 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
int32_t Adc::readVoltage(AdcUnit unit, AdcChannel channel)
|
||||
{
|
||||
uint8_t unitIdx = getUnitIndex(unit);
|
||||
uint8_t channelIdx = getChannelIndex(channel);
|
||||
|
||||
if (!m_unitInitialized_[unitIdx]) {
|
||||
ASF_LOGE(TAG, 2013, asf::logger::Criticality::HIGH, "ADC unit %d not initialized", unitIdx + 1);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!m_channelConfigured_[unitIdx][channelIdx]) {
|
||||
ASF_LOGE(TAG, 2014, asf::logger::Criticality::HIGH, "ADC channel %d not configured on unit %d", channelIdx, unitIdx + 1);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Read raw value
|
||||
int rawValue = 0;
|
||||
esp_err_t ret = adc_oneshot_read(m_adcHandle_[unitIdx], convertChannel(channel), &rawValue);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2015, asf::logger::Criticality::HIGH, "Failed to read ADC channel %d on unit %d: %s",
|
||||
channelIdx, unitIdx + 1, esp_err_to_name(ret));
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Convert to voltage if calibration is available
|
||||
if (m_caliHandle_[unitIdx][channelIdx] != nullptr) {
|
||||
int voltage = 0;
|
||||
ret = adc_cali_raw_to_voltage(m_caliHandle_[unitIdx][channelIdx], rawValue, &voltage);
|
||||
if (ret == ESP_OK) {
|
||||
return voltage;
|
||||
} else {
|
||||
ASF_LOGW(TAG, 2016, asf::logger::Criticality::MEDIUM, "Failed to convert raw to voltage: %s", esp_err_to_name(ret));
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: estimate voltage based on attenuation and raw value
|
||||
uint32_t maxRaw = getMaxRawValue(m_unitBitwidth_[unitIdx]);
|
||||
return (rawValue * 3300) / maxRaw; // Rough estimation
|
||||
}
|
||||
|
||||
int32_t Adc::readRaw(AdcUnit unit, AdcChannel channel)
|
||||
{
|
||||
uint8_t unitIdx = getUnitIndex(unit);
|
||||
uint8_t channelIdx = getChannelIndex(channel);
|
||||
|
||||
if (!m_unitInitialized_[unitIdx]) {
|
||||
ASF_LOGE(TAG, 2017, asf::logger::Criticality::HIGH, "ADC unit %d not initialized", unitIdx + 1);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!m_channelConfigured_[unitIdx][channelIdx]) {
|
||||
ASF_LOGE(TAG, 2018, asf::logger::Criticality::HIGH, "ADC channel %d not configured on unit %d", channelIdx, unitIdx + 1);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int rawValue = 0;
|
||||
esp_err_t ret = adc_oneshot_read(m_adcHandle_[unitIdx], convertChannel(channel), &rawValue);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2019, asf::logger::Criticality::HIGH, "Failed to read ADC channel %d on unit %d: %s",
|
||||
channelIdx, unitIdx + 1, esp_err_to_name(ret));
|
||||
return -1;
|
||||
}
|
||||
|
||||
return rawValue;
|
||||
}
|
||||
|
||||
int32_t Adc::readVoltageAverage(AdcUnit unit, AdcChannel channel, uint32_t samples)
|
||||
{
|
||||
if (samples == 0) {
|
||||
ASF_LOGE(TAG, 2020, asf::logger::Criticality::HIGH, "Invalid sample count");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int64_t sum = 0;
|
||||
uint32_t validSamples = 0;
|
||||
|
||||
for (uint32_t i = 0; i < samples; i++) {
|
||||
int32_t voltage = readVoltage(unit, channel);
|
||||
if (voltage >= 0) {
|
||||
sum += voltage;
|
||||
validSamples++;
|
||||
}
|
||||
}
|
||||
|
||||
if (validSamples == 0) {
|
||||
ASF_LOGE(TAG, 2021, asf::logger::Criticality::HIGH, "No valid samples obtained");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return static_cast<int32_t>(sum / validSamples);
|
||||
}
|
||||
|
||||
int32_t Adc::readRawAverage(AdcUnit unit, AdcChannel channel, uint32_t samples)
|
||||
{
|
||||
if (samples == 0) {
|
||||
ASF_LOGE(TAG, 2022, asf::logger::Criticality::HIGH, "Invalid sample count");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int64_t sum = 0;
|
||||
uint32_t validSamples = 0;
|
||||
|
||||
for (uint32_t i = 0; i < samples; i++) {
|
||||
int32_t raw = readRaw(unit, channel);
|
||||
if (raw >= 0) {
|
||||
sum += raw;
|
||||
validSamples++;
|
||||
}
|
||||
}
|
||||
|
||||
if (validSamples == 0) {
|
||||
ASF_LOGE(TAG, 2023, asf::logger::Criticality::HIGH, "No valid samples obtained");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return static_cast<int32_t>(sum / validSamples);
|
||||
}
|
||||
|
||||
bool Adc::isUnitInitialized(AdcUnit unit) const
|
||||
{
|
||||
uint8_t unitIdx = getUnitIndex(unit);
|
||||
return m_unitInitialized_[unitIdx];
|
||||
}
|
||||
|
||||
bool Adc::isChannelConfigured(AdcUnit unit, AdcChannel channel) const
|
||||
{
|
||||
uint8_t unitIdx = getUnitIndex(unit);
|
||||
uint8_t channelIdx = getChannelIndex(channel);
|
||||
return m_channelConfigured_[unitIdx][channelIdx];
|
||||
}
|
||||
|
||||
uint32_t Adc::getMaxRawValue(AdcBitwidth bitwidth)
|
||||
{
|
||||
switch (bitwidth) {
|
||||
case AdcBitwidth::WIDTH_9BIT:
|
||||
return 511;
|
||||
case AdcBitwidth::WIDTH_10BIT:
|
||||
return 1023;
|
||||
case AdcBitwidth::WIDTH_11BIT:
|
||||
return 2047;
|
||||
case AdcBitwidth::WIDTH_12BIT:
|
||||
return 4095;
|
||||
case AdcBitwidth::WIDTH_13BIT:
|
||||
return 8191;
|
||||
default:
|
||||
return 4095;
|
||||
}
|
||||
}
|
||||
|
||||
AdcChannelConfig Adc::getDefaultChannelConfig()
|
||||
{
|
||||
AdcChannelConfig config = {};
|
||||
config.unit = AdcUnit::UNIT_1;
|
||||
config.channel = AdcChannel::CHANNEL_0;
|
||||
config.atten = AdcAttenuation::ATTEN_11dB;
|
||||
config.bitwidth = AdcBitwidth::WIDTH_12BIT;
|
||||
return config;
|
||||
}
|
||||
|
||||
adc_unit_t Adc::convertUnit(AdcUnit unit)
|
||||
{
|
||||
return static_cast<adc_unit_t>(unit);
|
||||
}
|
||||
|
||||
adc_channel_t Adc::convertChannel(AdcChannel channel)
|
||||
{
|
||||
return static_cast<adc_channel_t>(channel);
|
||||
}
|
||||
|
||||
adc_atten_t Adc::convertAttenuation(AdcAttenuation atten)
|
||||
{
|
||||
return static_cast<adc_atten_t>(atten);
|
||||
}
|
||||
|
||||
adc_bitwidth_t Adc::convertBitwidth(AdcBitwidth bitwidth)
|
||||
{
|
||||
return static_cast<adc_bitwidth_t>(bitwidth);
|
||||
}
|
||||
|
||||
bool Adc::initializeCalibration(AdcUnit unit, AdcChannel channel, AdcAttenuation atten)
|
||||
{
|
||||
uint8_t unitIdx = getUnitIndex(unit);
|
||||
uint8_t channelIdx = getChannelIndex(channel);
|
||||
|
||||
adc_cali_line_fitting_config_t caliConfig = {};
|
||||
caliConfig.unit_id = convertUnit(unit);
|
||||
caliConfig.atten = convertAttenuation(atten);
|
||||
caliConfig.bitwidth = convertBitwidth(m_unitBitwidth_[unitIdx]);
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
caliConfig.default_vref = 1100; // Default Vref for ESP32
|
||||
#endif
|
||||
|
||||
esp_err_t ret = adc_cali_create_scheme_line_fitting(&caliConfig, &m_caliHandle_[unitIdx][channelIdx]);
|
||||
if (ret == ESP_OK) {
|
||||
ASF_LOGI(TAG, 2024, asf::logger::Criticality::LOW, "Calibration initialized for unit %d channel %d", unitIdx + 1, channelIdx);
|
||||
return true;
|
||||
} else {
|
||||
ASF_LOGW(TAG, 2025, asf::logger::Criticality::MEDIUM, "Failed to initialize calibration for unit %d channel %d: %s",
|
||||
unitIdx + 1, channelIdx, esp_err_to_name(ret));
|
||||
m_caliHandle_[unitIdx][channelIdx] = nullptr;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Adc::deinitializeCalibration(AdcUnit unit, AdcChannel channel)
|
||||
{
|
||||
uint8_t unitIdx = getUnitIndex(unit);
|
||||
uint8_t channelIdx = getChannelIndex(channel);
|
||||
|
||||
if (m_caliHandle_[unitIdx][channelIdx] != nullptr) {
|
||||
esp_err_t ret = adc_cali_delete_scheme_line_fitting(m_caliHandle_[unitIdx][channelIdx]);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGW(TAG, 2026, asf::logger::Criticality::MEDIUM, "Failed to deinitialize calibration: %s", esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
m_caliHandle_[unitIdx][channelIdx] = nullptr;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t Adc::getUnitIndex(AdcUnit unit) const
|
||||
{
|
||||
return static_cast<uint8_t>(unit) - 1;
|
||||
}
|
||||
|
||||
uint8_t Adc::getChannelIndex(AdcChannel channel) const
|
||||
{
|
||||
return static_cast<uint8_t>(channel);
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
/**
|
||||
* @file adc.hpp
|
||||
* @brief ADC wrapper component header - Wrapper for ESP-IDF ADC functionality
|
||||
* @author Mahmoud Elmohtady
|
||||
* @company Nabd solutions - ASF
|
||||
* @copyright Copyright (c) 2025
|
||||
*/
|
||||
|
||||
#ifndef ADC_HPP
|
||||
#define ADC_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include "esp_adc/adc_oneshot.h"
|
||||
#include "esp_adc/adc_cali.h"
|
||||
#include "esp_adc/adc_cali_scheme.h"
|
||||
#include "esp_err.h"
|
||||
|
||||
/**
|
||||
* @brief ADC unit enumeration
|
||||
*/
|
||||
enum class AdcUnit
|
||||
{
|
||||
UNIT_1 = ADC_UNIT_1,
|
||||
UNIT_2 = ADC_UNIT_2
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief ADC channel enumeration
|
||||
*/
|
||||
enum class AdcChannel
|
||||
{
|
||||
CHANNEL_0 = ADC_CHANNEL_0,
|
||||
CHANNEL_1 = ADC_CHANNEL_1,
|
||||
CHANNEL_2 = ADC_CHANNEL_2,
|
||||
CHANNEL_3 = ADC_CHANNEL_3,
|
||||
CHANNEL_4 = ADC_CHANNEL_4,
|
||||
CHANNEL_5 = ADC_CHANNEL_5,
|
||||
CHANNEL_6 = ADC_CHANNEL_6,
|
||||
CHANNEL_7 = ADC_CHANNEL_7,
|
||||
CHANNEL_8 = ADC_CHANNEL_8,
|
||||
CHANNEL_9 = ADC_CHANNEL_9
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief ADC attenuation enumeration
|
||||
*/
|
||||
enum class AdcAttenuation
|
||||
{
|
||||
ATTEN_0dB = ADC_ATTEN_DB_0, ///< 0dB attenuation (0-950mV)
|
||||
ATTEN_2_5dB = ADC_ATTEN_DB_2_5, ///< 2.5dB attenuation (0-1250mV)
|
||||
ATTEN_6dB = ADC_ATTEN_DB_6, ///< 6dB attenuation (0-1750mV)
|
||||
ATTEN_11dB = ADC_ATTEN_DB_11 ///< 11dB attenuation (0-3100mV)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief ADC bitwidth enumeration
|
||||
*/
|
||||
enum class AdcBitwidth
|
||||
{
|
||||
WIDTH_9BIT = ADC_BITWIDTH_9,
|
||||
WIDTH_10BIT = ADC_BITWIDTH_10,
|
||||
WIDTH_11BIT = ADC_BITWIDTH_11,
|
||||
WIDTH_12BIT = ADC_BITWIDTH_12,
|
||||
WIDTH_13BIT = ADC_BITWIDTH_13
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief ADC channel configuration structure
|
||||
*/
|
||||
struct AdcChannelConfig
|
||||
{
|
||||
AdcUnit unit; ///< ADC unit
|
||||
AdcChannel channel; ///< ADC channel
|
||||
AdcAttenuation atten; ///< Attenuation level
|
||||
AdcBitwidth bitwidth; ///< ADC resolution
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief ADC wrapper class
|
||||
*
|
||||
* Provides a C++ wrapper for ESP-IDF ADC functionality
|
||||
* with simplified interface and error handling.
|
||||
* This class encapsulates ESP-IDF ADC oneshot driver functions in an object-oriented interface.
|
||||
*/
|
||||
class Adc
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @details Initializes the ADC wrapper instance
|
||||
*/
|
||||
Adc();
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
* @details Cleans up resources and deinitializes ADC units
|
||||
*/
|
||||
~Adc();
|
||||
|
||||
/**
|
||||
* @brief Initialize ADC component
|
||||
* @return true if initialized successfully, false otherwise
|
||||
*/
|
||||
bool initialize();
|
||||
|
||||
/**
|
||||
* @brief Initialize ADC unit
|
||||
* @param unit ADC unit to initialize
|
||||
* @param bitwidth ADC resolution
|
||||
* @return true if initialized successfully, false otherwise
|
||||
*/
|
||||
bool initializeUnit(AdcUnit unit, AdcBitwidth bitwidth);
|
||||
|
||||
/**
|
||||
* @brief Deinitialize ADC unit
|
||||
* @param unit ADC unit to deinitialize
|
||||
* @return true if deinitialized successfully, false otherwise
|
||||
*/
|
||||
bool deinitializeUnit(AdcUnit unit);
|
||||
|
||||
/**
|
||||
* @brief Configure ADC channel
|
||||
* @param config Channel configuration
|
||||
* @return true if configured successfully, false otherwise
|
||||
* @note Unit must be initialized before configuring channels
|
||||
*/
|
||||
bool configureChannel(const AdcChannelConfig& config);
|
||||
|
||||
/**
|
||||
* @brief Read voltage from a channel
|
||||
* @param unit ADC unit
|
||||
* @param channel ADC channel to read
|
||||
* @return Voltage in millivolts, or -1 on error
|
||||
* @note Channel must be configured before reading
|
||||
*/
|
||||
int32_t readVoltage(AdcUnit unit, AdcChannel channel);
|
||||
|
||||
/**
|
||||
* @brief Read raw value from a channel
|
||||
* @param unit ADC unit
|
||||
* @param channel ADC channel to read
|
||||
* @return Raw ADC value, or -1 on error
|
||||
* @note Channel must be configured before reading
|
||||
*/
|
||||
int32_t readRaw(AdcUnit unit, AdcChannel channel);
|
||||
|
||||
/**
|
||||
* @brief Read multiple samples and return average
|
||||
* @param unit ADC unit
|
||||
* @param channel ADC channel to read
|
||||
* @param samples Number of samples to average
|
||||
* @return Average voltage in millivolts, or -1 on error
|
||||
*/
|
||||
int32_t readVoltageAverage(AdcUnit unit, AdcChannel channel, uint32_t samples);
|
||||
|
||||
/**
|
||||
* @brief Read multiple raw samples and return average
|
||||
* @param unit ADC unit
|
||||
* @param channel ADC channel to read
|
||||
* @param samples Number of samples to average
|
||||
* @return Average raw ADC value, or -1 on error
|
||||
*/
|
||||
int32_t readRawAverage(AdcUnit unit, AdcChannel channel, uint32_t samples);
|
||||
|
||||
/**
|
||||
* @brief Check if ADC unit is initialized
|
||||
* @param unit ADC unit to check
|
||||
* @return true if initialized, false otherwise
|
||||
*/
|
||||
bool isUnitInitialized(AdcUnit unit) const;
|
||||
|
||||
/**
|
||||
* @brief Check if channel is configured
|
||||
* @param unit ADC unit
|
||||
* @param channel ADC channel
|
||||
* @return true if configured, false otherwise
|
||||
*/
|
||||
bool isChannelConfigured(AdcUnit unit, AdcChannel channel) const;
|
||||
|
||||
/**
|
||||
* @brief Get maximum raw value for given bitwidth
|
||||
* @param bitwidth ADC bitwidth
|
||||
* @return Maximum raw value
|
||||
*/
|
||||
static uint32_t getMaxRawValue(AdcBitwidth bitwidth);
|
||||
|
||||
/**
|
||||
* @brief Get default channel configuration
|
||||
* @return Default ADC channel configuration
|
||||
*/
|
||||
static AdcChannelConfig getDefaultChannelConfig();
|
||||
|
||||
private:
|
||||
adc_oneshot_unit_handle_t m_adcHandle_[2]; ///< ADC unit handles
|
||||
adc_cali_handle_t m_caliHandle_[2][10]; ///< Calibration handles [unit][channel]
|
||||
bool m_unitInitialized_[2]; ///< Unit initialization status
|
||||
bool m_channelConfigured_[2][10]; ///< Channel configuration status [unit][channel]
|
||||
AdcBitwidth m_unitBitwidth_[2]; ///< Bitwidth for each unit
|
||||
|
||||
/**
|
||||
* @brief Convert AdcUnit to ESP-IDF adc_unit_t
|
||||
* @param unit ADC unit
|
||||
* @return ESP-IDF adc_unit_t
|
||||
*/
|
||||
adc_unit_t convertUnit(AdcUnit unit);
|
||||
|
||||
/**
|
||||
* @brief Convert AdcChannel to ESP-IDF adc_channel_t
|
||||
* @param channel ADC channel
|
||||
* @return ESP-IDF adc_channel_t
|
||||
*/
|
||||
adc_channel_t convertChannel(AdcChannel channel);
|
||||
|
||||
/**
|
||||
* @brief Convert AdcAttenuation to ESP-IDF adc_atten_t
|
||||
* @param atten ADC attenuation
|
||||
* @return ESP-IDF adc_atten_t
|
||||
*/
|
||||
adc_atten_t convertAttenuation(AdcAttenuation atten);
|
||||
|
||||
/**
|
||||
* @brief Convert AdcBitwidth to ESP-IDF adc_bitwidth_t
|
||||
* @param bitwidth ADC bitwidth
|
||||
* @return ESP-IDF adc_bitwidth_t
|
||||
*/
|
||||
adc_bitwidth_t convertBitwidth(AdcBitwidth bitwidth);
|
||||
|
||||
/**
|
||||
* @brief Initialize calibration for channel
|
||||
* @param unit ADC unit
|
||||
* @param channel ADC channel
|
||||
* @param atten Attenuation level
|
||||
* @return true if calibration initialized, false otherwise
|
||||
*/
|
||||
bool initializeCalibration(AdcUnit unit, AdcChannel channel, AdcAttenuation atten);
|
||||
|
||||
/**
|
||||
* @brief Deinitialize calibration for channel
|
||||
* @param unit ADC unit
|
||||
* @param channel ADC channel
|
||||
* @return true if calibration deinitialized, false otherwise
|
||||
*/
|
||||
bool deinitializeCalibration(AdcUnit unit, AdcChannel channel);
|
||||
|
||||
/**
|
||||
* @brief Get unit index from AdcUnit
|
||||
* @param unit ADC unit
|
||||
* @return Unit index (0 or 1)
|
||||
*/
|
||||
uint8_t getUnitIndex(AdcUnit unit) const;
|
||||
|
||||
/**
|
||||
* @brief Get channel index from AdcChannel
|
||||
* @param channel ADC channel
|
||||
* @return Channel index (0-9)
|
||||
*/
|
||||
uint8_t getChannelIndex(AdcChannel channel) const;
|
||||
};
|
||||
|
||||
#endif // ADC_HPP
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
ID,Component,Level,Criticality,Message
|
||||
2000,ADC,INFO,Low,ADC wrapper initialized
|
||||
2001,ADC,INFO,Low,ADC wrapper destroyed
|
||||
2002,ADC,INFO,Low,ADC initialized successfully
|
||||
2003,ADC,WARNING,Medium,ADC unit %d already initialized
|
||||
2004,ADC,ERROR,High,Failed to initialize ADC unit %d: %s
|
||||
2005,ADC,INFO,Low,ADC unit %d initialized successfully
|
||||
2006,ADC,WARNING,Medium,ADC unit %d not initialized
|
||||
2007,ADC,ERROR,High,Failed to deinitialize ADC unit %d: %s
|
||||
2008,ADC,INFO,Low,ADC unit %d deinitialized
|
||||
2009,ADC,ERROR,High,ADC unit %d not initialized
|
||||
2010,ADC,ERROR,High,Failed to configure ADC channel %d on unit %d: %s
|
||||
2011,ADC,WARNING,Medium,Failed to initialize calibration for channel %d on unit %d
|
||||
2012,ADC,INFO,Low,ADC channel %d configured on unit %d
|
||||
2013,ADC,ERROR,High,ADC unit %d not initialized
|
||||
2014,ADC,ERROR,High,ADC channel %d not configured on unit %d
|
||||
2015,ADC,ERROR,High,Failed to read ADC channel %d on unit %d: %s
|
||||
2016,ADC,WARNING,Medium,Failed to convert raw to voltage: %s
|
||||
2017,ADC,ERROR,High,ADC unit %d not initialized
|
||||
2018,ADC,ERROR,High,ADC channel %d not configured on unit %d
|
||||
2019,ADC,ERROR,High,Failed to read ADC channel %d on unit %d: %s
|
||||
2020,ADC,ERROR,High,Invalid sample count
|
||||
2021,ADC,ERROR,High,No valid samples obtained
|
||||
2022,ADC,ERROR,High,Invalid sample count
|
||||
2023,ADC,ERROR,High,No valid samples obtained
|
||||
2024,ADC,INFO,Low,Calibration initialized for unit %d channel %d
|
||||
2025,ADC,WARNING,Medium,Failed to initialize calibration for unit %d channel %d: %s
|
||||
2026,ADC,WARNING,Medium,Failed to deinitialize calibration: %s
|
||||
|
@@ -0,0 +1,34 @@
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
|
||||
folder_path = os.path.abspath(os.path.join("components", "system_tests"))
|
||||
if folder_path not in sys.path:
|
||||
sys.path.append(folder_path)
|
||||
|
||||
from scan_serial import ESP32Runner
|
||||
|
||||
def test_adc_initialize():
|
||||
runner = ESP32Runner(mode="SIM", port="COM9")
|
||||
runner.start()
|
||||
print("--- QEMU Runner Started ---", flush=True)
|
||||
try:
|
||||
start_time = time.time()
|
||||
while time.time() - start_time < 30:
|
||||
line = runner.get_line(timeout=1.0)
|
||||
if line:
|
||||
print(line, flush=True)
|
||||
if "ADC wrapper initialized" in line or "ADC initialized successfully" in line:
|
||||
print("SUCCESS CRITERIA MET!", flush=True)
|
||||
return 0
|
||||
if runner.process.poll() is not None:
|
||||
print(f"Process exited with code: {runner.process.returncode}", flush=True)
|
||||
return 1
|
||||
finally:
|
||||
runner.stop()
|
||||
print("Done.", flush=True)
|
||||
return 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
exit_code = test_adc_initialize()
|
||||
sys.exit(exit_code)
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<test_scenario>
|
||||
<!-- The configuration for the test environment. -->
|
||||
<!-- Available configurations: SIMULATE, HIL -->
|
||||
<config>SIMULATE</config>
|
||||
|
||||
<test_case>
|
||||
<test_case_id>ADC_INIT_TEST</test_case_id>
|
||||
<!-- The main command that executes the test itself. -->
|
||||
<test_exec>python components/ESP_IDF_FW_wrappers/adc/test/adc_init_test.py</test_exec>
|
||||
</test_case>
|
||||
|
||||
|
||||
</test_scenario>
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* @file test_adc.cpp
|
||||
* @brief Unit tests for ADC wrapper component
|
||||
* @author Mahmoud Elmohtady
|
||||
* @company Nabd solutions - ASF
|
||||
* @copyright Copyright (c) 2025
|
||||
*/
|
||||
|
||||
#include "unity.h"
|
||||
#include "adc.hpp"
|
||||
|
||||
extern "C" {
|
||||
|
||||
void setUp(void)
|
||||
{
|
||||
// Set up test fixtures before each test
|
||||
}
|
||||
|
||||
void tearDown(void)
|
||||
{
|
||||
// Clean up test fixtures after each test
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Test ADC initialization
|
||||
*/
|
||||
void test_adc_initialize(void)
|
||||
{
|
||||
Adc adc;
|
||||
bool result = adc.initialize();
|
||||
TEST_ASSERT_TRUE(result);
|
||||
TEST_ASSERT_TRUE(adc.isInitialized());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Test ADC deinitialize
|
||||
*/
|
||||
void test_adc_deinitialize(void)
|
||||
{
|
||||
Adc adc;
|
||||
adc.initialize();
|
||||
|
||||
bool result = adc.deinitialize();
|
||||
TEST_ASSERT_TRUE(result);
|
||||
TEST_ASSERT_FALSE(adc.isInitialized());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Test ADC read voltage without initialization
|
||||
*/
|
||||
void test_adc_read_voltage_not_initialized(void)
|
||||
{
|
||||
Adc adc;
|
||||
int32_t result = adc.readVoltage(AdcChannel::CHANNEL_0, AdcAttenuation::ATTEN_0dB);
|
||||
TEST_ASSERT_EQUAL(-1, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Test ADC read raw without initialization
|
||||
*/
|
||||
void test_adc_read_raw_not_initialized(void)
|
||||
{
|
||||
Adc adc;
|
||||
int32_t result = adc.readRaw(AdcChannel::CHANNEL_0, AdcAttenuation::ATTEN_0dB);
|
||||
TEST_ASSERT_EQUAL(-1, result);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
idf_component_register(
|
||||
SRCS "com/bt.cpp"
|
||||
INCLUDE_DIRS "com"
|
||||
REQUIRES bt nvs_flash logger
|
||||
)
|
||||
@@ -0,0 +1,502 @@
|
||||
# Bluetooth Wrapper Module
|
||||
|
||||
## Overview
|
||||
|
||||
The Bluetooth wrapper module provides a C++ object-oriented interface for ESP-IDF Bluetooth functionality. This module encapsulates the ESP-IDF Bluetooth and BLE (Bluetooth Low Energy) driver functions and provides a clean, easy-to-use API for Bluetooth Classic, BLE, and dual-mode operations.
|
||||
|
||||
## Features
|
||||
|
||||
- **Multiple Modes**: Bluetooth Classic, BLE, and dual-mode support
|
||||
- **BLE Advertising**: Configurable advertising parameters and data
|
||||
- **BLE Scanning**: Network discovery and device scanning
|
||||
- **GATT Server**: Create services and characteristics
|
||||
- **GATT Client**: Connect to and interact with remote devices
|
||||
- **Event Handling**: Comprehensive event callbacks for all operations
|
||||
- **Security Support**: Pairing, bonding, and encryption
|
||||
- **Power Management**: Configurable TX power levels
|
||||
|
||||
## Architecture
|
||||
|
||||
### Class Diagram
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ Bluetooth │
|
||||
├─────────────────────────────────────┤
|
||||
│ - m_isInitialized_: bool │
|
||||
│ - m_mode_: BtMode │
|
||||
│ - m_connectionState_: State │
|
||||
│ - m_connectedDeviceCount_: uint8_t │
|
||||
│ - m_gattsIf_: esp_gatt_if_t │
|
||||
│ - m_gattcIf_: esp_gatt_if_t │
|
||||
│ - m_appId_: uint16_t │
|
||||
│ - m_gapCallback_: Callback │
|
||||
│ - m_gattsCallback_: Callback │
|
||||
│ - m_gattcCallback_: Callback │
|
||||
├─────────────────────────────────────┤
|
||||
│ + Bluetooth() │
|
||||
│ + ~Bluetooth() │
|
||||
│ + initialize(mode): bool │
|
||||
│ + deinitialize(): bool │
|
||||
│ + setDeviceName(name): bool │
|
||||
│ + getDeviceName(name, len): bool │
|
||||
│ + setAdvParams(params): bool │
|
||||
│ + setAdvData(data): bool │
|
||||
│ + startAdvertising(): bool │
|
||||
│ + stopAdvertising(): bool │
|
||||
│ + startScanning(duration): bool │
|
||||
│ + stopScanning(): bool │
|
||||
│ + createGattService(svc): uint16_t │
|
||||
│ + addCharacteristic(...): uint16_t │
|
||||
│ + startGattService(handle): bool │
|
||||
│ + stopGattService(handle): bool │
|
||||
│ + sendNotification(...): bool │
|
||||
│ + sendIndication(...): bool │
|
||||
│ + connectToDevice(...): bool │
|
||||
│ + disconnectDevice(connId): bool │
|
||||
│ + getConnectionState(): State │
|
||||
│ + isConnected(): bool │
|
||||
│ + getConnectedDeviceCount(): uint8_t│
|
||||
│ + setGapEventCallback(...): void │
|
||||
│ + setGattsEventCallback(...): void │
|
||||
│ + setGattcEventCallback(...): void │
|
||||
│ + getMacAddress(mac): bool │
|
||||
│ + setTxPower(type, level): bool │
|
||||
│ + getTxPower(type): esp_power_level │
|
||||
│ + isInitialized(): bool │
|
||||
│ + getDefaultAdvParams(): Params │
|
||||
│ + getDefaultAdvData(): Data │
|
||||
│ + getDefaultGattService(): Service │
|
||||
│ + getDefaultGattChar(): Char │
|
||||
│ - gapEventHandler(...): void │
|
||||
│ - gattsEventHandler(...): void │
|
||||
│ - gattcEventHandler(...): void │
|
||||
│ - convertMode(mode): esp_bt_mode_t │
|
||||
│ - convertAdvType(type): esp_adv_t │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Enumerations
|
||||
|
||||
#### BtMode
|
||||
- `CLASSIC`: Bluetooth Classic mode
|
||||
- `BLE`: Bluetooth Low Energy mode
|
||||
- `DUAL`: Dual mode (Classic + BLE)
|
||||
|
||||
#### BleAdvType
|
||||
- `ADV_IND`: Connectable undirected advertising
|
||||
- `ADV_DIRECT_IND_HIGH`: Connectable directed advertising (high duty cycle)
|
||||
- `ADV_SCAN_IND`: Scannable undirected advertising
|
||||
- `ADV_NONCONN_IND`: Non-connectable undirected advertising
|
||||
- `ADV_DIRECT_IND_LOW`: Connectable directed advertising (low duty cycle)
|
||||
|
||||
#### BleConnectionState
|
||||
- `DISCONNECTED`: Not connected
|
||||
- `CONNECTING`: Connection in progress
|
||||
- `CONNECTED`: Successfully connected
|
||||
- `DISCONNECTING`: Disconnection in progress
|
||||
|
||||
### Configuration Structures
|
||||
|
||||
#### BleAdvParams
|
||||
```cpp
|
||||
struct BleAdvParams {
|
||||
uint16_t advIntMin; // Minimum advertising interval
|
||||
uint16_t advIntMax; // Maximum advertising interval
|
||||
BleAdvType advType; // Advertising type
|
||||
uint8_t ownAddrType; // Own address type
|
||||
uint8_t peerAddrType; // Peer address type
|
||||
uint8_t peerAddr[6]; // Peer address
|
||||
uint8_t channelMap; // Channel map
|
||||
uint8_t advFilterPolicy; // Advertising filter policy
|
||||
};
|
||||
```
|
||||
|
||||
#### BleAdvData
|
||||
```cpp
|
||||
struct BleAdvData {
|
||||
bool setName; // Set device name in advertising data
|
||||
bool setTxPower; // Set TX power in advertising data
|
||||
bool includeUuid; // Include service UUID
|
||||
bool setManufacturerData; // Set manufacturer data
|
||||
uint16_t appearance; // Device appearance
|
||||
uint16_t manufacturerId; // Manufacturer ID
|
||||
uint8_t manufacturerDataLen; // Manufacturer data length
|
||||
uint8_t* manufacturerData; // Manufacturer data
|
||||
uint8_t serviceUuidLen; // Service UUID length
|
||||
uint8_t* serviceUuid; // Service UUID
|
||||
char* deviceName; // Device name
|
||||
};
|
||||
```
|
||||
|
||||
#### GattService
|
||||
```cpp
|
||||
struct GattService {
|
||||
uint16_t serviceId; // Service ID
|
||||
uint16_t serviceUuid; // Service UUID
|
||||
uint16_t numHandles; // Number of handles
|
||||
bool isPrimary; // Primary service flag
|
||||
};
|
||||
```
|
||||
|
||||
#### GattCharacteristic
|
||||
```cpp
|
||||
struct GattCharacteristic {
|
||||
uint16_t charUuid; // Characteristic UUID
|
||||
uint8_t properties; // Characteristic properties
|
||||
uint8_t permissions; // Characteristic permissions
|
||||
uint16_t maxLen; // Maximum value length
|
||||
bool autoRsp; // Auto response flag
|
||||
};
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic BLE Advertising
|
||||
|
||||
```cpp
|
||||
#include "bt.hpp"
|
||||
|
||||
// BLE event callback
|
||||
void bleGapCallback(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t* param, void* userData) {
|
||||
switch (event) {
|
||||
case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
|
||||
printf("Advertising started\n");
|
||||
break;
|
||||
case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT:
|
||||
printf("Advertising stopped\n");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Create Bluetooth instance
|
||||
Bluetooth bt;
|
||||
|
||||
// Initialize BLE
|
||||
bt.initialize(BtMode::BLE);
|
||||
|
||||
// Set device name
|
||||
bt.setDeviceName("ESP32-BLE-Device");
|
||||
|
||||
// Set event callback
|
||||
bt.setGapEventCallback(bleGapCallback, nullptr);
|
||||
|
||||
// Configure advertising
|
||||
BleAdvParams advParams = Bluetooth::getDefaultAdvParams();
|
||||
advParams.advIntMin = 0x20; // 20ms
|
||||
advParams.advIntMax = 0x40; // 40ms
|
||||
|
||||
bt.setAdvParams(advParams);
|
||||
|
||||
// Set advertising data
|
||||
BleAdvData advData = Bluetooth::getDefaultAdvData();
|
||||
advData.setName = true;
|
||||
advData.setTxPower = true;
|
||||
|
||||
bt.setAdvData(advData);
|
||||
|
||||
// Start advertising
|
||||
bt.startAdvertising();
|
||||
```
|
||||
|
||||
### BLE GATT Server
|
||||
|
||||
```cpp
|
||||
// GATT server event callback
|
||||
void bleGattsCallback(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
|
||||
esp_ble_gatts_cb_param_t* param, void* userData) {
|
||||
switch (event) {
|
||||
case ESP_GATTS_CONNECT_EVT:
|
||||
printf("Client connected\n");
|
||||
break;
|
||||
case ESP_GATTS_DISCONNECT_EVT:
|
||||
printf("Client disconnected\n");
|
||||
break;
|
||||
case ESP_GATTS_READ_EVT:
|
||||
printf("Characteristic read request\n");
|
||||
break;
|
||||
case ESP_GATTS_WRITE_EVT:
|
||||
printf("Characteristic write request\n");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Set GATT server callback
|
||||
bt.setGattsEventCallback(bleGattsCallback, nullptr);
|
||||
|
||||
// Create a service
|
||||
GattService service = Bluetooth::getDefaultGattService();
|
||||
service.serviceUuid = 0x180F; // Battery Service
|
||||
service.numHandles = 4;
|
||||
|
||||
uint16_t serviceHandle = bt.createGattService(service);
|
||||
|
||||
// Add characteristic
|
||||
GattCharacteristic characteristic = Bluetooth::getDefaultGattCharacteristic();
|
||||
characteristic.charUuid = 0x2A19; // Battery Level
|
||||
characteristic.properties = ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_NOTIFY;
|
||||
|
||||
uint16_t charHandle = bt.addCharacteristic(serviceHandle, characteristic);
|
||||
|
||||
// Start the service
|
||||
bt.startGattService(serviceHandle);
|
||||
```
|
||||
|
||||
### BLE Scanning
|
||||
|
||||
```cpp
|
||||
// Scanning callback
|
||||
void bleScanCallback(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t* param, void* userData) {
|
||||
switch (event) {
|
||||
case ESP_GAP_BLE_SCAN_RESULT_EVT: {
|
||||
esp_ble_gap_cb_param_t* scanResult = param;
|
||||
if (scanResult->scan_rst.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) {
|
||||
printf("Found device: ");
|
||||
for (int i = 0; i < 6; i++) {
|
||||
printf("%02X", scanResult->scan_rst.bda[i]);
|
||||
if (i < 5) printf(":");
|
||||
}
|
||||
printf(" RSSI: %d\n", scanResult->scan_rst.rssi);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT:
|
||||
printf("Scan started\n");
|
||||
break;
|
||||
case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT:
|
||||
printf("Scan stopped\n");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Set scan callback
|
||||
bt.setGapEventCallback(bleScanCallback, nullptr);
|
||||
|
||||
// Start scanning for 10 seconds
|
||||
bt.startScanning(10);
|
||||
```
|
||||
|
||||
### BLE Client Connection
|
||||
|
||||
```cpp
|
||||
// GATT client callback
|
||||
void bleGattcCallback(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t* param, void* userData) {
|
||||
switch (event) {
|
||||
case ESP_GATTC_CONNECT_EVT:
|
||||
printf("Connected to server\n");
|
||||
break;
|
||||
case ESP_GATTC_DISCONNECT_EVT:
|
||||
printf("Disconnected from server\n");
|
||||
break;
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT:
|
||||
printf("Service discovery complete\n");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Set GATT client callback
|
||||
bt.setGattcEventCallback(bleGattcCallback, nullptr);
|
||||
|
||||
// Connect to remote device
|
||||
uint8_t remoteAddr[6] = {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC};
|
||||
bt.connectToDevice(remoteAddr, BLE_ADDR_TYPE_PUBLIC);
|
||||
```
|
||||
|
||||
### Dual Mode Operation
|
||||
|
||||
```cpp
|
||||
// Initialize in dual mode
|
||||
bt.initialize(BtMode::DUAL);
|
||||
|
||||
// Set up BLE advertising
|
||||
bt.setDeviceName("ESP32-Dual-Mode");
|
||||
bt.startAdvertising();
|
||||
|
||||
// Also available for Bluetooth Classic operations
|
||||
// (Classic Bluetooth APIs would be used here)
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### Constructor/Destructor
|
||||
|
||||
- **Bluetooth()**: Initialize Bluetooth wrapper instance
|
||||
- **~Bluetooth()**: Clean up resources and deinitialize Bluetooth
|
||||
|
||||
### Initialization Methods
|
||||
|
||||
- **initialize(mode)**: Initialize Bluetooth stack with specified mode
|
||||
- **deinitialize()**: Deinitialize Bluetooth stack
|
||||
- **isInitialized()**: Check if Bluetooth is initialized
|
||||
|
||||
### Device Configuration
|
||||
|
||||
- **setDeviceName(name)**: Set Bluetooth device name
|
||||
- **getDeviceName(name, maxLen)**: Get current device name
|
||||
- **getMacAddress(mac)**: Get Bluetooth MAC address
|
||||
|
||||
### BLE Advertising
|
||||
|
||||
- **setAdvParams(params)**: Set advertising parameters
|
||||
- **setAdvData(advData)**: Set advertising data
|
||||
- **startAdvertising()**: Start BLE advertising
|
||||
- **stopAdvertising()**: Stop BLE advertising
|
||||
|
||||
### BLE Scanning
|
||||
|
||||
- **startScanning(duration)**: Start BLE scanning
|
||||
- **stopScanning()**: Stop BLE scanning
|
||||
|
||||
### GATT Server
|
||||
|
||||
- **createGattService(service)**: Create GATT service
|
||||
- **addCharacteristic(serviceHandle, characteristic)**: Add characteristic to service
|
||||
- **startGattService(serviceHandle)**: Start GATT service
|
||||
- **stopGattService(serviceHandle)**: Stop GATT service
|
||||
- **sendNotification(connId, attrHandle, data, dataLen)**: Send notification
|
||||
- **sendIndication(connId, attrHandle, data, dataLen)**: Send indication
|
||||
|
||||
### GATT Client
|
||||
|
||||
- **connectToDevice(remoteAddr, addrType)**: Connect to remote device
|
||||
- **disconnectDevice(connId)**: Disconnect from device
|
||||
|
||||
### Connection Management
|
||||
|
||||
- **getConnectionState()**: Get current connection state
|
||||
- **isConnected()**: Check if any device is connected
|
||||
- **getConnectedDeviceCount()**: Get number of connected devices
|
||||
|
||||
### Event Handling
|
||||
|
||||
- **setGapEventCallback(callback, userData)**: Set GAP event callback
|
||||
- **setGattsEventCallback(callback, userData)**: Set GATT server callback
|
||||
- **setGattcEventCallback(callback, userData)**: Set GATT client callback
|
||||
|
||||
### Power Management
|
||||
|
||||
- **setTxPower(powerType, powerLevel)**: Set TX power level
|
||||
- **getTxPower(powerType)**: Get current TX power level
|
||||
|
||||
### Configuration Methods
|
||||
|
||||
- **getDefaultAdvParams()**: Get default advertising parameters
|
||||
- **getDefaultAdvData()**: Get default advertising data
|
||||
- **getDefaultGattService()**: Get default GATT service configuration
|
||||
- **getDefaultGattCharacteristic()**: Get default characteristic configuration
|
||||
|
||||
## Error Handling
|
||||
|
||||
The module provides comprehensive error handling:
|
||||
- Initialization status checks
|
||||
- ESP-IDF error codes are caught and logged
|
||||
- Return values indicate success/failure for all operations
|
||||
- Event callbacks for asynchronous status updates
|
||||
- Automatic cleanup of resources on errors
|
||||
|
||||
## Dependencies
|
||||
|
||||
- ESP-IDF Bluetooth stack (`esp_bt.h`, `esp_bt_main.h`)
|
||||
- ESP-IDF BLE GAP API (`esp_gap_ble_api.h`)
|
||||
- ESP-IDF GATT server API (`esp_gatts_api.h`)
|
||||
- ESP-IDF GATT client API (`esp_gattc_api.h`)
|
||||
- NVS flash storage (`nvs_flash.h`)
|
||||
- ESP-IDF error handling (`esp_err.h`)
|
||||
- ESP-IDF logging (`esp_log.h`)
|
||||
|
||||
## Thread Safety
|
||||
|
||||
The Bluetooth wrapper uses ESP-IDF's thread-safe Bluetooth stack. Event callbacks are called from the Bluetooth task context and should be kept short.
|
||||
|
||||
## Memory Usage
|
||||
|
||||
- Fixed memory footprint per instance
|
||||
- Bluetooth stack uses significant RAM (configurable)
|
||||
- GATT database stored in flash/RAM
|
||||
- Event callbacks use minimal stack space
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
- Bluetooth operations are asynchronous with event callbacks
|
||||
- GATT operations have latency depending on connection interval
|
||||
- Multiple simultaneous connections increase memory usage
|
||||
- Advertising and scanning affect power consumption
|
||||
|
||||
## Security Features
|
||||
|
||||
### Pairing and Bonding
|
||||
- Support for various pairing methods
|
||||
- Secure Simple Pairing (SSP)
|
||||
- Out-of-Band (OOB) authentication
|
||||
- Passkey entry and numeric comparison
|
||||
|
||||
### Encryption
|
||||
- AES-128 encryption for BLE
|
||||
- Link-level security
|
||||
- Application-level encryption support
|
||||
|
||||
## Power Management
|
||||
|
||||
### BLE Power Optimization
|
||||
- Configurable advertising intervals
|
||||
- Connection interval optimization
|
||||
- Sleep mode support
|
||||
- TX power control
|
||||
|
||||
### Connection Parameters
|
||||
- Minimum/maximum connection intervals
|
||||
- Slave latency configuration
|
||||
- Supervision timeout settings
|
||||
|
||||
## Common Use Cases
|
||||
|
||||
### IoT Sensor Node
|
||||
- BLE advertising with sensor data
|
||||
- GATT server for configuration
|
||||
- Low power operation
|
||||
- Mobile app connectivity
|
||||
|
||||
### BLE Gateway
|
||||
- Multiple device connections
|
||||
- Data aggregation and forwarding
|
||||
- WiFi + BLE dual connectivity
|
||||
- Cloud integration
|
||||
|
||||
### HID Device
|
||||
- Keyboard/mouse emulation
|
||||
- Custom HID reports
|
||||
- Pairing and bonding
|
||||
- Battery level reporting
|
||||
|
||||
### Beacon Applications
|
||||
- iBeacon/Eddystone protocols
|
||||
- Proximity detection
|
||||
- Asset tracking
|
||||
- Location services
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **Initialization Failures**: Check NVS partition and Bluetooth configuration
|
||||
2. **Connection Issues**: Verify advertising parameters and device compatibility
|
||||
3. **GATT Errors**: Check service/characteristic UUIDs and permissions
|
||||
4. **Memory Issues**: Monitor heap usage, especially with multiple connections
|
||||
5. **Range Problems**: Check TX power settings and antenna configuration
|
||||
|
||||
### Debug Tips
|
||||
|
||||
1. Enable Bluetooth logging in menuconfig
|
||||
2. Use ESP-IDF Bluetooth tools for analysis
|
||||
3. Monitor connection parameters
|
||||
4. Check for interference on 2.4GHz band
|
||||
5. Verify security requirements and capabilities
|
||||
@@ -0,0 +1,621 @@
|
||||
/**
|
||||
* @file bt.cpp
|
||||
* @brief Bluetooth wrapper component implementation
|
||||
* @author Mahmoud Elmohtady
|
||||
* @company Nabd solutions - ASF
|
||||
* @copyright Copyright (c) 2025
|
||||
*/
|
||||
|
||||
#include "bt.hpp"
|
||||
#include "logger.hpp"
|
||||
#include <cstring>
|
||||
|
||||
static const char* TAG = "BT_WRAPPER";
|
||||
|
||||
// Static instance for callbacks
|
||||
Bluetooth* Bluetooth::s_instance_ = nullptr;
|
||||
|
||||
Bluetooth::Bluetooth()
|
||||
: m_isInitialized_(false)
|
||||
, m_mode_(BtMode::BLE)
|
||||
, m_connectionState_(BleConnectionState::DISCONNECTED)
|
||||
, m_connectedDeviceCount_(0)
|
||||
#ifdef CONFIG_BT_ENABLED
|
||||
, m_gattsIf_(ESP_GATT_IF_NONE)
|
||||
, m_gattcIf_(ESP_GATT_IF_NONE)
|
||||
#else
|
||||
, m_gattsIf_(0)
|
||||
, m_gattcIf_(0)
|
||||
#endif
|
||||
, m_appId_(0)
|
||||
, m_gapCallback_(nullptr)
|
||||
, m_gapUserData_(nullptr)
|
||||
, m_gattsCallback_(nullptr)
|
||||
, m_gattsUserData_(nullptr)
|
||||
, m_gattcCallback_(nullptr)
|
||||
, m_gattcUserData_(nullptr)
|
||||
{
|
||||
s_instance_ = this;
|
||||
ASF_LOGI(TAG, 2100, asf::logger::Criticality::LOW, "Bluetooth wrapper initialized");
|
||||
}
|
||||
|
||||
Bluetooth::~Bluetooth()
|
||||
{
|
||||
deinitialize();
|
||||
s_instance_ = nullptr;
|
||||
ASF_LOGI(TAG, 2101, asf::logger::Criticality::LOW, "Bluetooth wrapper destroyed");
|
||||
}
|
||||
|
||||
bool Bluetooth::initialize(BtMode mode)
|
||||
{
|
||||
#ifdef CONFIG_BT_ENABLED
|
||||
if (m_isInitialized_) {
|
||||
ASF_LOGW(TAG, 2102, asf::logger::Criticality::MEDIUM, "Bluetooth already initialized");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Initialize NVS
|
||||
esp_err_t ret = nvs_flash_init();
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
ret = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK(ret);
|
||||
|
||||
// Release Bluetooth controller memory if not needed
|
||||
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
|
||||
|
||||
// Initialize Bluetooth controller
|
||||
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||
ret = esp_bt_controller_init(&bt_cfg);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2103, asf::logger::Criticality::HIGH, "Failed to initialize BT controller: %s", esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Enable Bluetooth controller
|
||||
ret = esp_bt_controller_enable(convertMode(mode));
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2104, asf::logger::Criticality::HIGH, "Failed to enable BT controller: %s", esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Initialize Bluedroid stack
|
||||
ret = esp_bluedroid_init();
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2105, asf::logger::Criticality::HIGH, "Failed to initialize Bluedroid: %s", esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Enable Bluedroid stack
|
||||
ret = esp_bluedroid_enable();
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2106, asf::logger::Criticality::HIGH, "Failed to enable Bluedroid: %s", esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Register GAP callback
|
||||
ret = esp_ble_gap_register_callback(gapEventHandler);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2107, asf::logger::Criticality::HIGH, "Failed to register GAP callback: %s", esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Register GATTS callback
|
||||
ret = esp_ble_gatts_register_callback(gattsEventHandler);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2108, asf::logger::Criticality::HIGH, "Failed to register GATTS callback: %s", esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Register GATTC callback
|
||||
ret = esp_ble_gattc_register_callback(gattcEventHandler);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2109, asf::logger::Criticality::HIGH, "Failed to register GATTC callback: %s", esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
m_mode_ = mode;
|
||||
m_isInitialized_ = true;
|
||||
ASF_LOGI(TAG, 2110, asf::logger::Criticality::LOW, "Bluetooth initialized successfully");
|
||||
return true;
|
||||
#else
|
||||
ASF_LOGW(TAG, 2111, asf::logger::Criticality::MEDIUM, "Bluetooth disabled in sdkconfig");
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Bluetooth::deinitialize()
|
||||
{
|
||||
#ifdef CONFIG_BT_ENABLED
|
||||
if (!m_isInitialized_) {
|
||||
ASF_LOGW(TAG, 2112, asf::logger::Criticality::MEDIUM, "Bluetooth not initialized");
|
||||
return true;
|
||||
}
|
||||
|
||||
stopAdvertising();
|
||||
stopScanning();
|
||||
|
||||
// Disable Bluedroid stack
|
||||
esp_err_t ret = esp_bluedroid_disable();
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2113, asf::logger::Criticality::HIGH, "Failed to disable Bluedroid: %s", esp_err_to_name(ret));
|
||||
}
|
||||
|
||||
// Deinitialize Bluedroid stack
|
||||
ret = esp_bluedroid_deinit();
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2114, asf::logger::Criticality::HIGH, "Failed to deinitialize Bluedroid: %s", esp_err_to_name(ret));
|
||||
}
|
||||
|
||||
// Disable Bluetooth controller
|
||||
ret = esp_bt_controller_disable();
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2115, asf::logger::Criticality::HIGH, "Failed to disable BT controller: %s", esp_err_to_name(ret));
|
||||
}
|
||||
|
||||
// Deinitialize Bluetooth controller
|
||||
ret = esp_bt_controller_deinit();
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2116, asf::logger::Criticality::HIGH, "Failed to deinitialize BT controller: %s", esp_err_to_name(ret));
|
||||
}
|
||||
|
||||
m_isInitialized_ = false;
|
||||
m_connectionState_ = BleConnectionState::DISCONNECTED;
|
||||
m_connectedDeviceCount_ = 0;
|
||||
ASF_LOGI(TAG, 2117, asf::logger::Criticality::LOW, "Bluetooth deinitialized");
|
||||
return true;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Bluetooth::setDeviceName(const char* name)
|
||||
{
|
||||
#ifdef CONFIG_BT_ENABLED
|
||||
if (!m_isInitialized_ || name == nullptr) {
|
||||
ASF_LOGE(TAG, 2118, asf::logger::Criticality::HIGH, "Bluetooth not initialized or invalid name");
|
||||
return false;
|
||||
}
|
||||
|
||||
esp_err_t ret = esp_ble_gap_set_device_name(name);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2119, asf::logger::Criticality::HIGH, "Failed to set device name: %s", esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
ASF_LOGI(TAG, 2120, asf::logger::Criticality::LOW, "Device name set to: %s", name);
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Bluetooth::getDeviceName(char* name, size_t maxLen)
|
||||
{
|
||||
if (name == nullptr || maxLen == 0) return false;
|
||||
strncpy(name, "ESP32-BT", maxLen - 1);
|
||||
name[maxLen - 1] = '\0';
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Bluetooth::setAdvParams(const BleAdvParams& params)
|
||||
{
|
||||
#ifdef CONFIG_BT_ENABLED
|
||||
if (!m_isInitialized_) {
|
||||
ASF_LOGE(TAG, 2121, asf::logger::Criticality::HIGH, "Bluetooth not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
esp_ble_adv_params_t advParams = {};
|
||||
advParams.adv_int_min = params.advIntMin;
|
||||
advParams.adv_int_max = params.advIntMax;
|
||||
advParams.adv_type = convertAdvType(params.advType);
|
||||
advParams.own_addr_type = static_cast<esp_ble_addr_type_t>(params.ownAddrType);
|
||||
advParams.peer_addr_type = static_cast<esp_ble_addr_type_t>(params.peerAddrType);
|
||||
memcpy(advParams.peer_addr, params.peerAddr, 6);
|
||||
advParams.channel_map = static_cast<esp_ble_adv_channel_t>(params.channelMap);
|
||||
advParams.adv_filter_policy = static_cast<esp_ble_adv_filter_t>(params.advFilterPolicy);
|
||||
|
||||
esp_err_t ret = esp_ble_gap_config_adv_data_raw(nullptr, 0);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2122, asf::logger::Criticality::HIGH, "Failed to set advertising parameters: %s", esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
ASF_LOGI(TAG, 2123, asf::logger::Criticality::LOW, "Advertising parameters set successfully");
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Bluetooth::setAdvData(const BleAdvData& advData)
|
||||
{
|
||||
#ifdef CONFIG_BT_ENABLED
|
||||
if (!m_isInitialized_) {
|
||||
ASF_LOGE(TAG, 2124, asf::logger::Criticality::HIGH, "Bluetooth not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
esp_ble_adv_data_t bleAdvData = {};
|
||||
bleAdvData.set_scan_rsp = false;
|
||||
bleAdvData.include_name = advData.setName;
|
||||
bleAdvData.include_txpower = advData.setTxPower;
|
||||
bleAdvData.min_interval = 0x0006;
|
||||
bleAdvData.max_interval = 0x0010;
|
||||
bleAdvData.appearance = advData.appearance;
|
||||
bleAdvData.manufacturer_len = advData.manufacturerDataLen;
|
||||
bleAdvData.p_manufacturer_data = advData.manufacturerData;
|
||||
bleAdvData.service_data_len = 0;
|
||||
bleAdvData.p_service_data = nullptr;
|
||||
bleAdvData.service_uuid_len = advData.serviceUuidLen;
|
||||
bleAdvData.p_service_uuid = advData.serviceUuid;
|
||||
bleAdvData.flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT);
|
||||
|
||||
esp_err_t ret = esp_ble_gap_config_adv_data(&bleAdvData);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2125, asf::logger::Criticality::HIGH, "Failed to set advertising data: %s", esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
ASF_LOGI(TAG, 2126, asf::logger::Criticality::LOW, "Advertising data set successfully");
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Bluetooth::startAdvertising()
|
||||
{
|
||||
#ifdef CONFIG_BT_ENABLED
|
||||
if (!m_isInitialized_) {
|
||||
ASF_LOGE(TAG, 2127, asf::logger::Criticality::HIGH, "Bluetooth not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
esp_ble_adv_params_t params = {};
|
||||
params.adv_int_min = 0x20;
|
||||
params.adv_int_max = 0x40;
|
||||
params.adv_type = ADV_TYPE_IND;
|
||||
params.own_addr_type = BLE_ADDR_TYPE_PUBLIC;
|
||||
params.channel_map = ADV_CHNL_ALL;
|
||||
params.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY;
|
||||
|
||||
esp_err_t ret = esp_ble_gap_start_advertising(¶ms);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2128, asf::logger::Criticality::HIGH, "Failed to start advertising: %s", esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
ASF_LOGI(TAG, 2129, asf::logger::Criticality::LOW, "BLE advertising started");
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Bluetooth::stopAdvertising()
|
||||
{
|
||||
#ifdef CONFIG_BT_ENABLED
|
||||
if (!m_isInitialized_) {
|
||||
ASF_LOGW(TAG, 2130, asf::logger::Criticality::MEDIUM, "Bluetooth not initialized");
|
||||
return true;
|
||||
}
|
||||
|
||||
esp_err_t ret = esp_ble_gap_stop_advertising();
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2131, asf::logger::Criticality::HIGH, "Failed to stop advertising: %s", esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
ASF_LOGI(TAG, 2132, asf::logger::Criticality::LOW, "BLE advertising stopped");
|
||||
return true;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Bluetooth::startScanning(uint32_t duration)
|
||||
{
|
||||
#ifdef CONFIG_BT_ENABLED
|
||||
if (!m_isInitialized_) {
|
||||
ASF_LOGE(TAG, 2133, asf::logger::Criticality::HIGH, "Bluetooth not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
esp_ble_scan_params_t scanParams = {};
|
||||
scanParams.scan_type = BLE_SCAN_TYPE_ACTIVE;
|
||||
scanParams.own_addr_type = BLE_ADDR_TYPE_PUBLIC;
|
||||
scanParams.scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL;
|
||||
scanParams.scan_interval = 0x50;
|
||||
scanParams.scan_window = 0x30;
|
||||
scanParams.scan_duplicate = BLE_SCAN_DUPLICATE_DISABLE;
|
||||
|
||||
esp_err_t ret = esp_ble_gap_set_scan_params(&scanParams);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2134, asf::logger::Criticality::HIGH, "Failed to set scan parameters: %s", esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
ret = esp_ble_gap_start_scanning(duration);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2135, asf::logger::Criticality::HIGH, "Failed to start scanning: %s", esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
ASF_LOGI(TAG, 2136, asf::logger::Criticality::LOW, "BLE scanning started (duration: %lu seconds)", duration);
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Bluetooth::stopScanning()
|
||||
{
|
||||
#ifdef CONFIG_BT_ENABLED
|
||||
if (!m_isInitialized_) {
|
||||
ASF_LOGW(TAG, 2137, asf::logger::Criticality::MEDIUM, "Bluetooth not initialized");
|
||||
return true;
|
||||
}
|
||||
|
||||
esp_err_t ret = esp_ble_gap_stop_scanning();
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2138, asf::logger::Criticality::HIGH, "Failed to stop scanning: %s", esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
ASF_LOGI(TAG, 2139, asf::logger::Criticality::LOW, "BLE scanning stopped");
|
||||
return true;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
uint16_t Bluetooth::createGattService(const GattService& service)
|
||||
{
|
||||
#ifdef CONFIG_BT_ENABLED
|
||||
if (!m_isInitialized_) {
|
||||
ASF_LOGE(TAG, 2140, asf::logger::Criticality::HIGH, "Bluetooth not initialized");
|
||||
return 0;
|
||||
}
|
||||
|
||||
esp_gatt_srvc_id_t serviceId = {};
|
||||
serviceId.is_primary = service.isPrimary;
|
||||
serviceId.id.inst_id = service.serviceId;
|
||||
serviceId.id.uuid.len = ESP_UUID_LEN_16;
|
||||
serviceId.id.uuid.uuid.uuid16 = service.serviceUuid;
|
||||
|
||||
esp_err_t ret = esp_ble_gatts_create_service(m_gattsIf_, &serviceId, service.numHandles);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2141, asf::logger::Criticality::HIGH, "Failed to create GATT service: %s", esp_err_to_name(ret));
|
||||
return 0;
|
||||
}
|
||||
|
||||
ASF_LOGI(TAG, 2142, asf::logger::Criticality::LOW, "GATT service created successfully");
|
||||
return service.serviceId;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Bluetooth::startGattService(uint16_t serviceHandle)
|
||||
{
|
||||
#ifdef CONFIG_BT_ENABLED
|
||||
if (!m_isInitialized_) {
|
||||
ASF_LOGE(TAG, 2143, asf::logger::Criticality::HIGH, "Bluetooth not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
esp_err_t ret = esp_ble_gatts_start_service(serviceHandle);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2144, asf::logger::Criticality::HIGH, "Failed to start GATT service: %s", esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
ASF_LOGI(TAG, 2145, asf::logger::Criticality::LOW, "GATT service started successfully");
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
BleConnectionState Bluetooth::getConnectionState() const
|
||||
{
|
||||
return m_connectionState_;
|
||||
}
|
||||
|
||||
bool Bluetooth::isConnected() const
|
||||
{
|
||||
return m_connectionState_ == BleConnectionState::CONNECTED;
|
||||
}
|
||||
|
||||
uint8_t Bluetooth::getConnectedDeviceCount() const
|
||||
{
|
||||
return m_connectedDeviceCount_;
|
||||
}
|
||||
|
||||
void Bluetooth::setGapEventCallback(BtGapEventCallback callback, void* userData)
|
||||
{
|
||||
m_gapCallback_ = callback;
|
||||
m_gapUserData_ = userData;
|
||||
}
|
||||
|
||||
void Bluetooth::setGattsEventCallback(BtGattsEventCallback callback, void* userData)
|
||||
{
|
||||
m_gattsCallback_ = callback;
|
||||
m_gattsUserData_ = userData;
|
||||
}
|
||||
|
||||
void Bluetooth::setGattcEventCallback(BtGattcEventCallback callback, void* userData)
|
||||
{
|
||||
m_gattcCallback_ = callback;
|
||||
m_gattcUserData_ = userData;
|
||||
}
|
||||
|
||||
bool Bluetooth::getMacAddress(uint8_t* mac)
|
||||
{
|
||||
#ifdef CONFIG_BT_ENABLED
|
||||
if (mac == nullptr) {
|
||||
ASF_LOGE(TAG, 2146, asf::logger::Criticality::HIGH, "Invalid MAC address buffer");
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(mac, esp_bt_dev_get_address(), 6);
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Bluetooth::isInitialized() const
|
||||
{
|
||||
return m_isInitialized_;
|
||||
}
|
||||
|
||||
BleAdvParams Bluetooth::getDefaultAdvParams()
|
||||
{
|
||||
BleAdvParams params = {};
|
||||
params.advIntMin = 0x20;
|
||||
params.advIntMax = 0x40;
|
||||
params.advType = BleAdvType::ADV_IND;
|
||||
params.ownAddrType = 0;
|
||||
params.peerAddrType = 0;
|
||||
memset(params.peerAddr, 0, 6);
|
||||
params.channelMap = 7;
|
||||
params.advFilterPolicy = 0;
|
||||
return params;
|
||||
}
|
||||
|
||||
BleAdvData Bluetooth::getDefaultAdvData()
|
||||
{
|
||||
BleAdvData advData = {};
|
||||
advData.setName = true;
|
||||
advData.setTxPower = true;
|
||||
advData.includeUuid = false;
|
||||
advData.setManufacturerData = false;
|
||||
advData.appearance = 0;
|
||||
advData.manufacturerId = 0;
|
||||
advData.manufacturerDataLen = 0;
|
||||
advData.manufacturerData = nullptr;
|
||||
advData.serviceUuidLen = 0;
|
||||
advData.serviceUuid = nullptr;
|
||||
advData.deviceName = nullptr;
|
||||
return advData;
|
||||
}
|
||||
|
||||
GattService Bluetooth::getDefaultGattService()
|
||||
{
|
||||
GattService service = {};
|
||||
service.serviceId = 0;
|
||||
service.serviceUuid = 0x180F; // Battery Service UUID
|
||||
service.numHandles = 4;
|
||||
service.isPrimary = true;
|
||||
return service;
|
||||
}
|
||||
|
||||
GattCharacteristic Bluetooth::getDefaultGattCharacteristic()
|
||||
{
|
||||
GattCharacteristic characteristic = {};
|
||||
characteristic.charUuid = 0x2A19; // Battery Level Characteristic UUID
|
||||
characteristic.properties = 0x12; // Read | Notify
|
||||
characteristic.permissions = 0x01; // Read
|
||||
characteristic.maxLen = 1;
|
||||
characteristic.autoRsp = true;
|
||||
return characteristic;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_BT_ENABLED
|
||||
void Bluetooth::gapEventHandler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t* param)
|
||||
{
|
||||
if (s_instance_ && s_instance_->m_gapCallback_) {
|
||||
s_instance_->m_gapCallback_(event, param, s_instance_->m_gapUserData_);
|
||||
}
|
||||
|
||||
switch (event) {
|
||||
case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
|
||||
ASF_LOGI(TAG, 2147, asf::logger::Criticality::LOW, "GAP: Advertising data set complete");
|
||||
break;
|
||||
case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
|
||||
ASF_LOGI(TAG, 2148, asf::logger::Criticality::LOW, "GAP: Advertising start complete");
|
||||
break;
|
||||
case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT:
|
||||
ASF_LOGI(TAG, 2149, asf::logger::Criticality::LOW, "GAP: Advertising stop complete");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Bluetooth::gattsEventHandler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t* param)
|
||||
{
|
||||
if (s_instance_ && s_instance_->m_gattsCallback_) {
|
||||
s_instance_->m_gattsCallback_(event, gatts_if, param, s_instance_->m_gattsUserData_);
|
||||
}
|
||||
|
||||
switch (event) {
|
||||
case ESP_GATTS_REG_EVT:
|
||||
ASF_LOGI(TAG, 2150, asf::logger::Criticality::LOW, "GATTS: Register complete");
|
||||
if (s_instance_) {
|
||||
s_instance_->m_gattsIf_ = gatts_if;
|
||||
}
|
||||
break;
|
||||
case ESP_GATTS_CONNECT_EVT:
|
||||
ASF_LOGI(TAG, 2151, asf::logger::Criticality::LOW, "GATTS: Device connected");
|
||||
if (s_instance_) {
|
||||
s_instance_->m_connectionState_ = BleConnectionState::CONNECTED;
|
||||
s_instance_->m_connectedDeviceCount_++;
|
||||
}
|
||||
break;
|
||||
case ESP_GATTS_DISCONNECT_EVT:
|
||||
ASF_LOGI(TAG, 2152, asf::logger::Criticality::LOW, "GATTS: Device disconnected");
|
||||
if (s_instance_) {
|
||||
s_instance_->m_connectionState_ = BleConnectionState::DISCONNECTED;
|
||||
if (s_instance_->m_connectedDeviceCount_ > 0) {
|
||||
s_instance_->m_connectedDeviceCount_--;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Bluetooth::gattcEventHandler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t* param)
|
||||
{
|
||||
if (s_instance_ && s_instance_->m_gattcCallback_) {
|
||||
s_instance_->m_gattcCallback_(event, gattc_if, param, s_instance_->m_gattcUserData_);
|
||||
}
|
||||
|
||||
switch (event) {
|
||||
case ESP_GATTC_REG_EVT:
|
||||
ASF_LOGI(TAG, 2153, asf::logger::Criticality::LOW, "GATTC: Register complete");
|
||||
if (s_instance_) {
|
||||
s_instance_->m_gattcIf_ = gattc_if;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
esp_bt_mode_t Bluetooth::convertMode(BtMode mode)
|
||||
{
|
||||
return static_cast<esp_bt_mode_t>(mode);
|
||||
}
|
||||
|
||||
esp_ble_adv_type_t Bluetooth::convertAdvType(BleAdvType advType)
|
||||
{
|
||||
return static_cast<esp_ble_adv_type_t>(advType);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool Bluetooth::stopGattService(uint16_t serviceHandle) { return true; }
|
||||
bool Bluetooth::sendNotification(uint16_t connId, uint16_t attrHandle, const uint8_t* data, size_t dataLen) { return true; }
|
||||
bool Bluetooth::sendIndication(uint16_t connId, uint16_t attrHandle, const uint8_t* data, size_t dataLen) { return true; }
|
||||
bool Bluetooth::connectToDevice(const uint8_t* remoteAddr, uint8_t addrType) { return true; }
|
||||
bool Bluetooth::disconnectDevice(uint16_t connId) { return true; }
|
||||
#ifdef CONFIG_BT_ENABLED
|
||||
bool Bluetooth::setTxPower(esp_ble_power_type_t powerType, esp_power_level_t powerLevel) { return true; }
|
||||
esp_power_level_t Bluetooth::getTxPower(esp_ble_power_type_t powerType) { return ESP_PWR_LVL_N0; }
|
||||
#endif
|
||||
@@ -0,0 +1,457 @@
|
||||
/**
|
||||
* @file bt.hpp
|
||||
* @brief Bluetooth wrapper component header - Wrapper for ESP-IDF Bluetooth functionality
|
||||
* @author Mahmoud Elmohtady
|
||||
* @company Nabd solutions - ASF
|
||||
* @copyright Copyright (c) 2025
|
||||
*/
|
||||
|
||||
#ifndef BT_HPP
|
||||
#define BT_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include "esp_err.h"
|
||||
#include "nvs_flash.h"
|
||||
|
||||
#ifdef CONFIG_BT_ENABLED
|
||||
#include "esp_bt.h"
|
||||
#include "esp_bt_main.h"
|
||||
#include "esp_gap_ble_api.h"
|
||||
#include "esp_gatts_api.h"
|
||||
#include "esp_gattc_api.h"
|
||||
#include "esp_bt_defs.h"
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Bluetooth mode enumeration
|
||||
*/
|
||||
enum class BtMode
|
||||
{
|
||||
#ifdef CONFIG_BT_ENABLED
|
||||
CLASSIC = ESP_BT_MODE_CLASSIC_BT,
|
||||
BLE = ESP_BT_MODE_BLE,
|
||||
DUAL = ESP_BT_MODE_BTDM
|
||||
#else
|
||||
CLASSIC,
|
||||
BLE,
|
||||
DUAL
|
||||
#endif
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief BLE advertising type enumeration
|
||||
*/
|
||||
enum class BleAdvType
|
||||
{
|
||||
#ifdef CONFIG_BT_ENABLED
|
||||
ADV_IND = ADV_TYPE_IND,
|
||||
ADV_DIRECT_IND_HIGH = ADV_TYPE_DIRECT_IND_HIGH,
|
||||
ADV_SCAN_IND = ADV_TYPE_SCAN_IND,
|
||||
ADV_NONCONN_IND = ADV_TYPE_NONCONN_IND,
|
||||
ADV_DIRECT_IND_LOW = ADV_TYPE_DIRECT_IND_LOW
|
||||
#else
|
||||
ADV_IND,
|
||||
ADV_DIRECT_IND_HIGH,
|
||||
ADV_SCAN_IND,
|
||||
ADV_NONCONN_IND,
|
||||
ADV_DIRECT_IND_LOW
|
||||
#endif
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief BLE connection state enumeration
|
||||
*/
|
||||
enum class BleConnectionState
|
||||
{
|
||||
DISCONNECTED,
|
||||
CONNECTING,
|
||||
CONNECTED,
|
||||
DISCONNECTING
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief BLE advertising parameters structure
|
||||
*/
|
||||
struct BleAdvParams
|
||||
{
|
||||
uint16_t advIntMin; ///< Minimum advertising interval
|
||||
uint16_t advIntMax; ///< Maximum advertising interval
|
||||
BleAdvType advType; ///< Advertising type
|
||||
uint8_t ownAddrType; ///< Own address type
|
||||
uint8_t peerAddrType; ///< Peer address type
|
||||
uint8_t peerAddr[6]; ///< Peer address
|
||||
uint8_t channelMap; ///< Channel map
|
||||
uint8_t advFilterPolicy; ///< Advertising filter policy
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief BLE advertising data structure
|
||||
*/
|
||||
struct BleAdvData
|
||||
{
|
||||
bool setName; ///< Set device name in advertising data
|
||||
bool setTxPower; ///< Set TX power in advertising data
|
||||
bool includeUuid; ///< Include service UUID
|
||||
bool setManufacturerData; ///< Set manufacturer data
|
||||
uint16_t appearance; ///< Device appearance
|
||||
uint16_t manufacturerId; ///< Manufacturer ID
|
||||
uint8_t manufacturerDataLen; ///< Manufacturer data length
|
||||
uint8_t* manufacturerData; ///< Manufacturer data
|
||||
uint8_t serviceUuidLen; ///< Service UUID length
|
||||
uint8_t* serviceUuid; ///< Service UUID
|
||||
char* deviceName; ///< Device name
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief GATT service structure
|
||||
*/
|
||||
struct GattService
|
||||
{
|
||||
uint16_t serviceId; ///< Service ID
|
||||
uint16_t serviceUuid; ///< Service UUID
|
||||
uint16_t numHandles; ///< Number of handles
|
||||
bool isPrimary; ///< Primary service flag
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief GATT characteristic structure
|
||||
*/
|
||||
struct GattCharacteristic
|
||||
{
|
||||
uint16_t charUuid; ///< Characteristic UUID
|
||||
uint8_t properties; ///< Characteristic properties
|
||||
uint8_t permissions; ///< Characteristic permissions
|
||||
uint16_t maxLen; ///< Maximum value length
|
||||
bool autoRsp; ///< Auto response flag
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Bluetooth event callback function types
|
||||
*/
|
||||
#ifdef CONFIG_BT_ENABLED
|
||||
using BtGapEventCallback = void (*)(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t* param, void* userData);
|
||||
using BtGattsEventCallback = void (*)(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t* param, void* userData);
|
||||
using BtGattcEventCallback = void (*)(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t* param, void* userData);
|
||||
#else
|
||||
using BtGapEventCallback = void (*)(int event, void* param, void* userData);
|
||||
using BtGattsEventCallback = void (*)(int event, int gatts_if, void* param, void* userData);
|
||||
using BtGattcEventCallback = void (*)(int event, int gattc_if, void* param, void* userData);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Bluetooth wrapper class
|
||||
*
|
||||
* Provides a C++ wrapper for ESP-IDF Bluetooth functionality.
|
||||
* This class encapsulates ESP-IDF Bluetooth and BLE driver functions in an object-oriented interface.
|
||||
*/
|
||||
class Bluetooth
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @details Initializes the Bluetooth wrapper instance
|
||||
*/
|
||||
Bluetooth();
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
* @details Cleans up resources and deinitializes Bluetooth
|
||||
*/
|
||||
~Bluetooth();
|
||||
|
||||
/**
|
||||
* @brief Initialize Bluetooth stack
|
||||
* @param mode Bluetooth mode (Classic, BLE, or Dual)
|
||||
* @return true if initialized successfully, false otherwise
|
||||
*/
|
||||
bool initialize(BtMode mode);
|
||||
|
||||
/**
|
||||
* @brief Deinitialize Bluetooth stack
|
||||
* @return true if deinitialized successfully, false otherwise
|
||||
*/
|
||||
bool deinitialize();
|
||||
|
||||
/**
|
||||
* @brief Set device name
|
||||
* @param name Device name (max 248 characters)
|
||||
* @return true if set successfully, false otherwise
|
||||
*/
|
||||
bool setDeviceName(const char* name);
|
||||
|
||||
/**
|
||||
* @brief Get device name
|
||||
* @param name Buffer to store device name
|
||||
* @param maxLen Maximum length of name buffer
|
||||
* @return true if retrieved successfully, false otherwise
|
||||
*/
|
||||
bool getDeviceName(char* name, size_t maxLen);
|
||||
|
||||
/**
|
||||
* @brief Set advertising parameters
|
||||
* @param params Advertising parameters
|
||||
* @return true if set successfully, false otherwise
|
||||
*/
|
||||
bool setAdvParams(const BleAdvParams& params);
|
||||
|
||||
/**
|
||||
* @brief Set advertising data
|
||||
* @param advData Advertising data
|
||||
* @return true if set successfully, false otherwise
|
||||
*/
|
||||
bool setAdvData(const BleAdvData& advData);
|
||||
|
||||
/**
|
||||
* @brief Start BLE advertising
|
||||
* @return true if started successfully, false otherwise
|
||||
*/
|
||||
bool startAdvertising();
|
||||
|
||||
/**
|
||||
* @brief Stop BLE advertising
|
||||
* @return true if stopped successfully, false otherwise
|
||||
*/
|
||||
bool stopAdvertising();
|
||||
|
||||
/**
|
||||
* @brief Start BLE scanning
|
||||
* @param duration Scan duration in seconds (0 = continuous)
|
||||
* @return true if started successfully, false otherwise
|
||||
*/
|
||||
bool startScanning(uint32_t duration = 0);
|
||||
|
||||
/**
|
||||
* @brief Stop BLE scanning
|
||||
* @return true if stopped successfully, false otherwise
|
||||
*/
|
||||
bool stopScanning();
|
||||
|
||||
/**
|
||||
* @brief Create GATT service
|
||||
* @param service Service configuration
|
||||
* @return Service handle, or 0 on failure
|
||||
*/
|
||||
uint16_t createGattService(const GattService& service);
|
||||
|
||||
/**
|
||||
* @brief Add characteristic to service
|
||||
* @param serviceHandle Service handle
|
||||
* @param characteristic Characteristic configuration
|
||||
* @return Characteristic handle, or 0 on failure
|
||||
*/
|
||||
uint16_t addCharacteristic(uint16_t serviceHandle, const GattCharacteristic& characteristic);
|
||||
|
||||
/**
|
||||
* @brief Start GATT service
|
||||
* @param serviceHandle Service handle
|
||||
* @return true if started successfully, false otherwise
|
||||
*/
|
||||
bool startGattService(uint16_t serviceHandle);
|
||||
|
||||
/**
|
||||
* @brief Stop GATT service
|
||||
* @param serviceHandle Service handle
|
||||
* @return true if stopped successfully, false otherwise
|
||||
*/
|
||||
bool stopGattService(uint16_t serviceHandle);
|
||||
|
||||
/**
|
||||
* @brief Send notification to connected client
|
||||
* @param connId Connection ID
|
||||
* @param attrHandle Attribute handle
|
||||
* @param data Data to send
|
||||
* @param dataLen Length of data
|
||||
* @return true if sent successfully, false otherwise
|
||||
*/
|
||||
bool sendNotification(uint16_t connId, uint16_t attrHandle, const uint8_t* data, size_t dataLen);
|
||||
|
||||
/**
|
||||
* @brief Send indication to connected client
|
||||
* @param connId Connection ID
|
||||
* @param attrHandle Attribute handle
|
||||
* @param data Data to send
|
||||
* @param dataLen Length of data
|
||||
* @return true if sent successfully, false otherwise
|
||||
*/
|
||||
bool sendIndication(uint16_t connId, uint16_t attrHandle, const uint8_t* data, size_t dataLen);
|
||||
|
||||
/**
|
||||
* @brief Connect to remote device
|
||||
* @param remoteAddr Remote device address
|
||||
* @param addrType Address type
|
||||
* @return true if connection initiated successfully, false otherwise
|
||||
*/
|
||||
bool connectToDevice(const uint8_t* remoteAddr, uint8_t addrType);
|
||||
|
||||
/**
|
||||
* @brief Disconnect from remote device
|
||||
* @param connId Connection ID
|
||||
* @return true if disconnection initiated successfully, false otherwise
|
||||
*/
|
||||
bool disconnectDevice(uint16_t connId);
|
||||
|
||||
/**
|
||||
* @brief Get connection state
|
||||
* @return Current connection state
|
||||
*/
|
||||
BleConnectionState getConnectionState() const;
|
||||
|
||||
/**
|
||||
* @brief Check if device is connected
|
||||
* @return true if connected, false otherwise
|
||||
*/
|
||||
bool isConnected() const;
|
||||
|
||||
/**
|
||||
* @brief Get number of connected devices
|
||||
* @return Number of connected devices
|
||||
*/
|
||||
uint8_t getConnectedDeviceCount() const;
|
||||
|
||||
/**
|
||||
* @brief Set GAP event callback
|
||||
* @param callback Callback function
|
||||
* @param userData User data passed to callback
|
||||
*/
|
||||
void setGapEventCallback(BtGapEventCallback callback, void* userData);
|
||||
|
||||
/**
|
||||
* @brief Set GATTS event callback
|
||||
* @param callback Callback function
|
||||
* @param userData User data passed to callback
|
||||
*/
|
||||
void setGattsEventCallback(BtGattsEventCallback callback, void* userData);
|
||||
|
||||
/**
|
||||
* @brief Set GATTC event callback
|
||||
* @param callback Callback function
|
||||
* @param userData User data passed to callback
|
||||
*/
|
||||
void setGattcEventCallback(BtGattcEventCallback callback, void* userData);
|
||||
|
||||
/**
|
||||
* @brief Get MAC address
|
||||
* @param mac Buffer to store MAC address (6 bytes)
|
||||
* @return true if retrieved successfully, false otherwise
|
||||
*/
|
||||
bool getMacAddress(uint8_t* mac);
|
||||
|
||||
/**
|
||||
* @brief Set TX power
|
||||
* @param powerType Power type
|
||||
* @param powerLevel Power level
|
||||
* @return true if set successfully, false otherwise
|
||||
*/
|
||||
#ifdef CONFIG_BT_ENABLED
|
||||
bool setTxPower(esp_ble_power_type_t powerType, esp_power_level_t powerLevel);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Get TX power
|
||||
* @param powerType Power type
|
||||
* @return Current power level, or ESP_PWR_LVL_INVALID on error
|
||||
*/
|
||||
#ifdef CONFIG_BT_ENABLED
|
||||
esp_power_level_t getTxPower(esp_ble_power_type_t powerType);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Check if Bluetooth is initialized
|
||||
* @return true if initialized, false otherwise
|
||||
*/
|
||||
bool isInitialized() const;
|
||||
|
||||
/**
|
||||
* @brief Get default advertising parameters
|
||||
* @return Default BLE advertising parameters
|
||||
*/
|
||||
static BleAdvParams getDefaultAdvParams();
|
||||
|
||||
/**
|
||||
* @brief Get default advertising data
|
||||
* @return Default BLE advertising data
|
||||
*/
|
||||
static BleAdvData getDefaultAdvData();
|
||||
|
||||
/**
|
||||
* @brief Get default GATT service configuration
|
||||
* @return Default GATT service configuration
|
||||
*/
|
||||
static GattService getDefaultGattService();
|
||||
|
||||
/**
|
||||
* @brief Get default GATT characteristic configuration
|
||||
* @return Default GATT characteristic configuration
|
||||
*/
|
||||
static GattCharacteristic getDefaultGattCharacteristic();
|
||||
|
||||
private:
|
||||
bool m_isInitialized_; ///< Initialization status
|
||||
BtMode m_mode_; ///< Bluetooth mode
|
||||
BleConnectionState m_connectionState_; ///< Current connection state
|
||||
uint8_t m_connectedDeviceCount_; ///< Number of connected devices
|
||||
#ifdef CONFIG_BT_ENABLED
|
||||
esp_gatt_if_t m_gattsIf_; ///< GATTS interface
|
||||
esp_gatt_if_t m_gattcIf_; ///< GATTC interface
|
||||
#else
|
||||
int m_gattsIf_;
|
||||
int m_gattcIf_;
|
||||
#endif
|
||||
uint16_t m_appId_; ///< Application ID
|
||||
|
||||
// Callback functions and user data
|
||||
BtGapEventCallback m_gapCallback_; ///< GAP event callback
|
||||
void* m_gapUserData_; ///< GAP callback user data
|
||||
BtGattsEventCallback m_gattsCallback_; ///< GATTS event callback
|
||||
void* m_gattsUserData_; ///< GATTS callback user data
|
||||
BtGattcEventCallback m_gattcCallback_; ///< GATTC event callback
|
||||
void* m_gattcUserData_; ///< GATTC callback user data
|
||||
|
||||
#ifdef CONFIG_BT_ENABLED
|
||||
/**
|
||||
* @brief GAP event handler
|
||||
* @param event GAP event
|
||||
* @param param Event parameters
|
||||
*/
|
||||
static void gapEventHandler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t* param);
|
||||
|
||||
/**
|
||||
* @brief GATTS event handler
|
||||
* @param event GATTS event
|
||||
* @param gatts_if GATTS interface
|
||||
* @param param Event parameters
|
||||
*/
|
||||
static void gattsEventHandler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t* param);
|
||||
|
||||
/**
|
||||
* @brief GATTC event handler
|
||||
* @param event GATTC event
|
||||
* @param gattc_if GATTC interface
|
||||
* @param param Event parameters
|
||||
*/
|
||||
static void gattcEventHandler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t* param);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Convert BtMode to ESP-IDF esp_bt_mode_t
|
||||
* @param mode Bluetooth mode
|
||||
* @return ESP-IDF bluetooth mode
|
||||
*/
|
||||
#ifdef CONFIG_BT_ENABLED
|
||||
esp_bt_mode_t convertMode(BtMode mode);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Convert BleAdvType to ESP-IDF esp_ble_adv_type_t
|
||||
* @param advType Advertising type
|
||||
* @return ESP-IDF advertising type
|
||||
*/
|
||||
#ifdef CONFIG_BT_ENABLED
|
||||
esp_ble_adv_type_t convertAdvType(BleAdvType advType);
|
||||
#endif
|
||||
|
||||
static Bluetooth* s_instance_; ///< Static instance for callbacks
|
||||
};
|
||||
|
||||
#endif // BT_HPP
|
||||
@@ -0,0 +1,55 @@
|
||||
ID,Component,Level,Criticality,Message
|
||||
2100,BT,INFO,Low,Bluetooth wrapper initialized
|
||||
2101,BT,INFO,Low,Bluetooth wrapper destroyed
|
||||
2102,BT,WARNING,Medium,Bluetooth already initialized
|
||||
2103,BT,ERROR,High,Failed to initialize BT controller: %s
|
||||
2104,BT,ERROR,High,Failed to enable BT controller: %s
|
||||
2105,BT,ERROR,High,Failed to initialize Bluedroid: %s
|
||||
2106,BT,ERROR,High,Failed to enable Bluedroid: %s
|
||||
2107,BT,ERROR,High,Failed to register GAP callback: %s
|
||||
2108,BT,ERROR,High,Failed to register GATTS callback: %s
|
||||
2109,BT,ERROR,High,Failed to register GATTC callback: %s
|
||||
2110,BT,INFO,Low,Bluetooth initialized successfully
|
||||
2111,BT,WARNING,Medium,Bluetooth disabled in sdkconfig
|
||||
2112,BT,WARNING,Medium,Bluetooth not initialized
|
||||
2113,BT,ERROR,High,Failed to disable Bluedroid: %s
|
||||
2114,BT,ERROR,High,Failed to deinitialize Bluedroid: %s
|
||||
2115,BT,ERROR,High,Failed to disable BT controller: %s
|
||||
2116,BT,ERROR,High,Failed to deinitialize BT controller: %s
|
||||
2117,BT,INFO,Low,Bluetooth deinitialized
|
||||
2118,BT,ERROR,High,Bluetooth not initialized or invalid name
|
||||
2119,BT,ERROR,High,Failed to set device name: %s
|
||||
2120,BT,INFO,Low,Device name set to: %s
|
||||
2121,BT,ERROR,High,Bluetooth not initialized
|
||||
2122,BT,ERROR,High,Failed to set advertising parameters: %s
|
||||
2123,BT,INFO,Low,Advertising parameters set successfully
|
||||
2124,BT,ERROR,High,Bluetooth not initialized
|
||||
2125,BT,ERROR,High,Failed to set advertising data: %s
|
||||
2126,BT,INFO,Low,Advertising data set successfully
|
||||
2127,BT,ERROR,High,Bluetooth not initialized
|
||||
2128,BT,ERROR,High,Failed to start advertising: %s
|
||||
2129,BT,INFO,Low,BLE advertising started
|
||||
2130,BT,WARNING,Medium,Bluetooth not initialized
|
||||
2131,BT,ERROR,High,Failed to stop advertising: %s
|
||||
2132,BT,INFO,Low,BLE advertising stopped
|
||||
2133,BT,ERROR,High,Bluetooth not initialized
|
||||
2134,BT,ERROR,High,Failed to set scan parameters: %s
|
||||
2135,BT,ERROR,High,Failed to start scanning: %s
|
||||
2136,BT,INFO,Low,BLE scanning started (duration: %lu seconds)
|
||||
2137,BT,WARNING,Medium,Bluetooth not initialized
|
||||
2138,BT,ERROR,High,Failed to stop scanning: %s
|
||||
2139,BT,INFO,Low,BLE scanning stopped
|
||||
2140,BT,ERROR,High,Bluetooth not initialized
|
||||
2141,BT,ERROR,High,Failed to create GATT service: %s
|
||||
2142,BT,INFO,Low,GATT service created successfully
|
||||
2143,BT,ERROR,High,Bluetooth not initialized
|
||||
2144,BT,ERROR,High,Failed to start GATT service: %s
|
||||
2145,BT,INFO,Low,GATT service started successfully
|
||||
2146,BT,ERROR,High,Invalid MAC address buffer
|
||||
2147,BT,INFO,Low,GAP: Advertising data set complete
|
||||
2148,BT,INFO,Low,GAP: Advertising start complete
|
||||
2149,BT,INFO,Low,GAP: Advertising stop complete
|
||||
2150,BT,INFO,Low,GATTS: Register complete
|
||||
2151,BT,INFO,Low,GATTS: Device connected
|
||||
2152,BT,INFO,Low,GATTS: Device disconnected
|
||||
2153,BT,INFO,Low,GATTC: Register complete
|
||||
|
@@ -0,0 +1,34 @@
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
|
||||
folder_path = os.path.abspath(os.path.join("components", "system_tests"))
|
||||
if folder_path not in sys.path:
|
||||
sys.path.append(folder_path)
|
||||
|
||||
from scan_serial import ESP32Runner
|
||||
|
||||
def test_bt_initialize():
|
||||
runner = ESP32Runner(mode="SIM", port="COM9")
|
||||
runner.start()
|
||||
print("--- QEMU Runner Started ---", flush=True)
|
||||
try:
|
||||
start_time = time.time()
|
||||
while time.time() - start_time < 30:
|
||||
line = runner.get_line(timeout=1.0)
|
||||
if line:
|
||||
print(line, flush=True)
|
||||
if "Bluetooth initialized successfully" in line:
|
||||
print("SUCCESS CRITERIA MET!", flush=True)
|
||||
return 0
|
||||
if runner.process.poll() is not None:
|
||||
print(f"Process exited with code: {runner.process.returncode}", flush=True)
|
||||
return 1
|
||||
finally:
|
||||
runner.stop()
|
||||
print("Done.", flush=True)
|
||||
return 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
exit_code = test_bt_initialize()
|
||||
sys.exit(exit_code)
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* @file test_bt.cpp
|
||||
* @brief Unit tests for Bluetooth wrapper component
|
||||
* @author Mahmoud Elmohtady
|
||||
* @company Nabd solutions - ASF
|
||||
* @copyright Copyright (c) 2025
|
||||
*/
|
||||
|
||||
#include "unity.h"
|
||||
#include "bt.hpp"
|
||||
|
||||
extern "C" {
|
||||
|
||||
void setUp(void)
|
||||
{
|
||||
}
|
||||
|
||||
void tearDown(void)
|
||||
{
|
||||
}
|
||||
|
||||
void test_bt_initialize(void)
|
||||
{
|
||||
Bluetooth bt;
|
||||
bool result = bt.initialize(BtMode::BLE);
|
||||
TEST_ASSERT_TRUE(result);
|
||||
}
|
||||
|
||||
void test_bt_start_advertising(void)
|
||||
{
|
||||
Bluetooth bt;
|
||||
bt.initialize(BtMode::BLE);
|
||||
|
||||
bool result = bt.startAdvertising();
|
||||
TEST_ASSERT_TRUE(result);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
idf_component_register(
|
||||
SRCS "com/dma.cpp"
|
||||
INCLUDE_DIRS "com"
|
||||
REQUIRES driver esp_hw_support logger
|
||||
)
|
||||
@@ -0,0 +1,396 @@
|
||||
# DMA Wrapper Module
|
||||
|
||||
## Overview
|
||||
|
||||
The DMA wrapper module provides a C++ object-oriented interface for ESP-IDF GDMA (General DMA) functionality. This module encapsulates the ESP-IDF GDMA driver functions and provides a clean, easy-to-use API for high-performance data transfers between memory and peripherals.
|
||||
|
||||
## Features
|
||||
|
||||
- **Channel Management**: Dynamic allocation and deallocation of DMA channels
|
||||
- **Memory Management**: DMA-capable memory allocation and management
|
||||
- **Descriptor Management**: Creation and linking of DMA descriptors
|
||||
- **Transfer Control**: Start, stop, and reset DMA transfers
|
||||
- **Callback Support**: Event-driven callbacks for transfer completion
|
||||
- **Memory Copy**: High-performance DMA-based memory copy operations
|
||||
- **Error Handling**: Comprehensive error checking and logging
|
||||
|
||||
## Architecture
|
||||
|
||||
### Class Diagram
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ Dma │
|
||||
├─────────────────────────────────────┤
|
||||
│ - m_channels_[8]: gdma_handle_t │
|
||||
│ - m_channelAllocated_[8]: bool │
|
||||
├─────────────────────────────────────┤
|
||||
│ + Dma() │
|
||||
│ + ~Dma() │
|
||||
│ + allocateChannel(cfg, hdl): bool │
|
||||
│ + deallocateChannel(hdl): bool │
|
||||
│ + configureTransfer(hdl, cfg): bool │
|
||||
│ + startTransfer(hdl): bool │
|
||||
│ + stopTransfer(hdl): bool │
|
||||
│ + resetChannel(hdl): bool │
|
||||
│ + allocateDmaMemory(size): void* │
|
||||
│ + freeDmaMemory(ptr): void │
|
||||
│ + createDescriptor(...): Desc* │
|
||||
│ + linkDescriptors(d1, d2): bool │
|
||||
│ + freeDescriptor(desc): void │
|
||||
│ + setCallback(hdl, cb, arg): bool │
|
||||
│ + enableChannel(hdl): bool │
|
||||
│ + disableChannel(hdl): bool │
|
||||
│ + getChannelState(hdl): State │
|
||||
│ + memcpy(dst, src, size): bool │
|
||||
│ + getDefaultChannelConfig(): Config │
|
||||
│ + getDefaultTransferConfig(): Config│
|
||||
│ + isDmaCapable(addr): bool │
|
||||
│ - findFreeChannelSlot(): int │
|
||||
│ - findChannelSlot(hdl): int │
|
||||
│ - convertDirection(dir): gdma_dir │
|
||||
│ - convertPriority(pri): int │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Enumerations
|
||||
|
||||
#### DmaDirection
|
||||
- `MEM_TO_MEM`: Memory to memory transfer
|
||||
- `MEM_TO_PERIPH`: Memory to peripheral transfer
|
||||
- `PERIPH_TO_MEM`: Peripheral to memory transfer
|
||||
- `PERIPH_TO_PERIPH`: Peripheral to peripheral transfer
|
||||
|
||||
#### DmaTransferType
|
||||
- `SINGLE`: Single transfer
|
||||
- `BLOCK`: Block transfer
|
||||
- `LINKED_LIST`: Linked list transfer
|
||||
|
||||
#### DmaPriority
|
||||
- `LOW`: Low priority (0)
|
||||
- `MEDIUM`: Medium priority (1)
|
||||
- `HIGH`: High priority (2)
|
||||
- `HIGHEST`: Highest priority (3)
|
||||
|
||||
### Configuration Structures
|
||||
|
||||
#### DmaChannelConfig
|
||||
```cpp
|
||||
struct DmaChannelConfig {
|
||||
DmaPriority priority; // Channel priority
|
||||
uint32_t flags; // Configuration flags
|
||||
};
|
||||
```
|
||||
|
||||
#### DmaTransferConfig
|
||||
```cpp
|
||||
struct DmaTransferConfig {
|
||||
void* srcAddr; // Source address
|
||||
void* dstAddr; // Destination address
|
||||
size_t dataSize; // Size of data to transfer
|
||||
DmaDirection direction; // Transfer direction
|
||||
DmaTransferType type; // Transfer type
|
||||
DmaPriority priority; // Transfer priority
|
||||
bool autoReload; // Auto-reload mode
|
||||
bool enableInterrupt; // Enable transfer complete interrupt
|
||||
};
|
||||
```
|
||||
|
||||
#### DmaDescriptor
|
||||
```cpp
|
||||
struct DmaDescriptor {
|
||||
uint32_t size; // Size of data to transfer
|
||||
uint32_t length; // Actual length of valid data
|
||||
uint32_t sosf : 1; // Start of sub-frame
|
||||
uint32_t eof : 1; // End of frame
|
||||
uint32_t owner : 1; // Owner (0: CPU, 1: DMA)
|
||||
uint32_t reserved : 29; // Reserved bits
|
||||
void* buffer; // Pointer to data buffer
|
||||
DmaDescriptor* next; // Pointer to next descriptor
|
||||
};
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic DMA Channel Allocation
|
||||
|
||||
```cpp
|
||||
#include "dma.hpp"
|
||||
|
||||
// DMA transfer completion callback
|
||||
void dmaCallback(gdma_channel_handle_t channel, gdma_event_data_t* eventData, void* userData) {
|
||||
printf("DMA transfer completed!\n");
|
||||
}
|
||||
|
||||
// Create DMA instance
|
||||
Dma dma;
|
||||
|
||||
// Allocate DMA channel
|
||||
DmaChannelConfig config = Dma::getDefaultChannelConfig();
|
||||
config.priority = DmaPriority::HIGH;
|
||||
|
||||
gdma_channel_handle_t channel;
|
||||
dma.allocateChannel(config, &channel);
|
||||
|
||||
// Set callback
|
||||
dma.setCallback(channel, dmaCallback, nullptr);
|
||||
```
|
||||
|
||||
### Memory-to-Memory Transfer
|
||||
|
||||
```cpp
|
||||
// Allocate DMA-capable memory
|
||||
size_t dataSize = 1024;
|
||||
void* srcBuffer = dma.allocateDmaMemory(dataSize);
|
||||
void* dstBuffer = dma.allocateDmaMemory(dataSize);
|
||||
|
||||
// Fill source buffer with test data
|
||||
memset(srcBuffer, 0xAA, dataSize);
|
||||
|
||||
// Perform DMA copy
|
||||
bool success = dma.memcpy(dstBuffer, srcBuffer, dataSize, channel);
|
||||
|
||||
if (success) {
|
||||
printf("DMA copy completed successfully\n");
|
||||
}
|
||||
|
||||
// Clean up
|
||||
dma.freeDmaMemory(srcBuffer);
|
||||
dma.freeDmaMemory(dstBuffer);
|
||||
dma.deallocateChannel(channel);
|
||||
```
|
||||
|
||||
### Descriptor-Based Transfer
|
||||
|
||||
```cpp
|
||||
// Create source and destination buffers
|
||||
uint8_t* srcData = (uint8_t*)dma.allocateDmaMemory(512);
|
||||
uint8_t* dstData = (uint8_t*)dma.allocateDmaMemory(512);
|
||||
|
||||
// Fill source with test pattern
|
||||
for (int i = 0; i < 512; i++) {
|
||||
srcData[i] = i & 0xFF;
|
||||
}
|
||||
|
||||
// Create DMA descriptors
|
||||
DmaDescriptor* desc1 = dma.createDescriptor(srcData, 256, false);
|
||||
DmaDescriptor* desc2 = dma.createDescriptor(srcData + 256, 256, true);
|
||||
|
||||
// Link descriptors for chained transfer
|
||||
dma.linkDescriptors(desc1, desc2);
|
||||
|
||||
// Configure and start transfer
|
||||
DmaTransferConfig transferConfig = Dma::getDefaultTransferConfig();
|
||||
transferConfig.srcAddr = srcData;
|
||||
transferConfig.dstAddr = dstData;
|
||||
transferConfig.dataSize = 512;
|
||||
transferConfig.direction = DmaDirection::MEM_TO_MEM;
|
||||
|
||||
dma.configureTransfer(channel, transferConfig);
|
||||
dma.startTransfer(channel);
|
||||
|
||||
// Wait for completion (in real application, use callback)
|
||||
// ...
|
||||
|
||||
// Clean up descriptors
|
||||
dma.freeDescriptor(desc1);
|
||||
dma.freeDescriptor(desc2);
|
||||
```
|
||||
|
||||
### Large Data Transfer with Chunking
|
||||
|
||||
```cpp
|
||||
void transferLargeData(Dma& dma, void* src, void* dst, size_t totalSize) {
|
||||
const size_t chunkSize = 4096; // 4KB chunks
|
||||
size_t remaining = totalSize;
|
||||
uint8_t* srcPtr = (uint8_t*)src;
|
||||
uint8_t* dstPtr = (uint8_t*)dst;
|
||||
|
||||
gdma_channel_handle_t channel;
|
||||
DmaChannelConfig config = Dma::getDefaultChannelConfig();
|
||||
dma.allocateChannel(config, &channel);
|
||||
|
||||
while (remaining > 0) {
|
||||
size_t currentChunk = (remaining > chunkSize) ? chunkSize : remaining;
|
||||
|
||||
// Perform DMA transfer for current chunk
|
||||
bool success = dma.memcpy(dstPtr, srcPtr, currentChunk, channel);
|
||||
|
||||
if (!success) {
|
||||
printf("DMA transfer failed for chunk\n");
|
||||
break;
|
||||
}
|
||||
|
||||
srcPtr += currentChunk;
|
||||
dstPtr += currentChunk;
|
||||
remaining -= currentChunk;
|
||||
|
||||
printf("Transferred %zu bytes, %zu remaining\n", currentChunk, remaining);
|
||||
}
|
||||
|
||||
dma.deallocateChannel(channel);
|
||||
}
|
||||
```
|
||||
|
||||
### Peripheral-to-Memory Transfer
|
||||
|
||||
```cpp
|
||||
// Example: DMA transfer from SPI peripheral to memory
|
||||
void setupSpiDmaTransfer(Dma& dma) {
|
||||
// Allocate receive buffer
|
||||
uint8_t* rxBuffer = (uint8_t*)dma.allocateDmaMemory(1024);
|
||||
|
||||
// Allocate DMA channel
|
||||
gdma_channel_handle_t channel;
|
||||
DmaChannelConfig config = Dma::getDefaultChannelConfig();
|
||||
dma.allocateChannel(config, &channel);
|
||||
|
||||
// Configure transfer from SPI to memory
|
||||
DmaTransferConfig transferConfig = {};
|
||||
transferConfig.srcAddr = nullptr; // Will be set by SPI driver
|
||||
transferConfig.dstAddr = rxBuffer;
|
||||
transferConfig.dataSize = 1024;
|
||||
transferConfig.direction = DmaDirection::PERIPH_TO_MEM;
|
||||
transferConfig.enableInterrupt = true;
|
||||
|
||||
dma.configureTransfer(channel, transferConfig);
|
||||
|
||||
// SPI driver would typically handle the actual peripheral configuration
|
||||
// and trigger the DMA transfer
|
||||
|
||||
// Clean up when done
|
||||
dma.freeDmaMemory(rxBuffer);
|
||||
dma.deallocateChannel(channel);
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### Constructor/Destructor
|
||||
|
||||
- **Dma()**: Initialize DMA wrapper instance
|
||||
- **~Dma()**: Clean up resources and deallocate all channels
|
||||
|
||||
### Channel Management
|
||||
|
||||
- **allocateChannel(config, handle)**: Allocate DMA channel
|
||||
- **deallocateChannel(handle)**: Deallocate DMA channel
|
||||
- **enableChannel(handle)**: Enable DMA channel
|
||||
- **disableChannel(handle)**: Disable DMA channel
|
||||
- **resetChannel(handle)**: Reset DMA channel
|
||||
- **getChannelState(handle)**: Get current channel state
|
||||
|
||||
### Transfer Control
|
||||
|
||||
- **configureTransfer(handle, config)**: Configure DMA transfer
|
||||
- **startTransfer(handle)**: Start DMA transfer
|
||||
- **stopTransfer(handle)**: Stop DMA transfer
|
||||
|
||||
### Memory Management
|
||||
|
||||
- **allocateDmaMemory(size, caps)**: Allocate DMA-capable memory
|
||||
- **freeDmaMemory(ptr)**: Free DMA-capable memory
|
||||
- **isDmaCapable(addr)**: Check if address is DMA-capable
|
||||
|
||||
### Descriptor Management
|
||||
|
||||
- **createDescriptor(buffer, size, eof)**: Create DMA descriptor
|
||||
- **linkDescriptors(desc1, desc2)**: Link two descriptors
|
||||
- **freeDescriptor(descriptor)**: Free DMA descriptor
|
||||
|
||||
### Callback and Events
|
||||
|
||||
- **setCallback(handle, callback, userData)**: Set transfer completion callback
|
||||
|
||||
### Utility Methods
|
||||
|
||||
- **memcpy(dst, src, size, handle)**: High-performance DMA memory copy
|
||||
- **getDefaultChannelConfig()**: Get default channel configuration
|
||||
- **getDefaultTransferConfig()**: Get default transfer configuration
|
||||
|
||||
## Error Handling
|
||||
|
||||
The module provides comprehensive error handling:
|
||||
- Channel handle validation
|
||||
- Memory allocation checks
|
||||
- ESP-IDF error codes are caught and logged
|
||||
- Return values indicate success/failure for all operations
|
||||
- Automatic cleanup of allocated resources
|
||||
|
||||
## Dependencies
|
||||
|
||||
- ESP-IDF GDMA driver (`driver/gdma.h`)
|
||||
- ESP-IDF DMA utilities (`esp_dma_utils.h`)
|
||||
- ESP-IDF hardware support (`esp_hw_support`)
|
||||
- ESP-IDF error handling (`esp_err.h`)
|
||||
- ESP-IDF logging (`esp_log.h`)
|
||||
- ESP-IDF heap capabilities (`esp_heap_caps.h`)
|
||||
|
||||
## Thread Safety
|
||||
|
||||
The DMA wrapper uses ESP-IDF's thread-safe GDMA driver. Multiple tasks can safely use different DMA channels simultaneously.
|
||||
|
||||
## Memory Usage
|
||||
|
||||
- Fixed memory footprint per instance
|
||||
- Channel handles stored in fixed-size array
|
||||
- DMA descriptors allocated from DMA-capable memory
|
||||
- No hidden dynamic allocations
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
- Direct ESP-IDF GDMA calls for optimal performance
|
||||
- DMA transfers are asynchronous and non-blocking
|
||||
- Use callbacks for transfer completion notification
|
||||
- DMA-capable memory required for source/destination buffers
|
||||
- Descriptor chaining for large transfers
|
||||
|
||||
## DMA Memory Requirements
|
||||
|
||||
### DMA-Capable Memory
|
||||
- Source and destination buffers must be in DMA-capable memory
|
||||
- Use `allocateDmaMemory()` or `MALLOC_CAP_DMA` flag
|
||||
- Check memory capability with `isDmaCapable()`
|
||||
|
||||
### Memory Alignment
|
||||
- Some transfers may require specific alignment
|
||||
- Descriptors must be in DMA-capable memory
|
||||
- Buffer addresses should be word-aligned for best performance
|
||||
|
||||
## Limitations
|
||||
|
||||
- Maximum 8 DMA channels can be managed simultaneously
|
||||
- Memory buffers must be DMA-capable
|
||||
- Transfer size limitations depend on ESP32 variant
|
||||
- Some peripherals have specific DMA requirements
|
||||
- Descriptor chains limited by available DMA memory
|
||||
|
||||
## Use Cases
|
||||
|
||||
### High-Speed Data Transfer
|
||||
- Large memory copies
|
||||
- Image processing
|
||||
- Audio/video streaming
|
||||
- Network packet processing
|
||||
|
||||
### Peripheral Integration
|
||||
- SPI high-speed transfers
|
||||
- I2S audio streaming
|
||||
- UART bulk data transfer
|
||||
- ADC continuous sampling
|
||||
|
||||
### Background Processing
|
||||
- Asynchronous data movement
|
||||
- Buffer management
|
||||
- Real-time data streaming
|
||||
- Multi-buffer ping-pong operations
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **Memory Allocation Failures**: Ensure sufficient DMA-capable memory
|
||||
2. **Transfer Failures**: Check buffer alignment and DMA capability
|
||||
3. **Performance Issues**: Use appropriate transfer sizes and descriptor chaining
|
||||
4. **Channel Exhaustion**: Monitor channel allocation and deallocation
|
||||
5. **Callback Issues**: Ensure callback functions are in IRAM for interrupt context
|
||||
@@ -0,0 +1,512 @@
|
||||
/**
|
||||
* @file dma.cpp
|
||||
* @brief DMA wrapper component implementation
|
||||
* @author Mahmoud Elmohtady
|
||||
* @company Nabd solutions - ASF
|
||||
* @copyright Copyright (c) 2025
|
||||
*/
|
||||
|
||||
#include "dma.hpp"
|
||||
#include "logger.hpp"
|
||||
#include "esp_memory_utils.h"
|
||||
#include <cstring>
|
||||
|
||||
static const char* TAG = "DMA_WRAPPER";
|
||||
|
||||
Dma::Dma()
|
||||
{
|
||||
// Initialize channel handles and allocation status
|
||||
memset(m_channels_, 0, sizeof(m_channels_));
|
||||
memset(m_channelAllocated_, false, sizeof(m_channelAllocated_));
|
||||
ASF_LOGI(TAG, 2200, asf::logger::Criticality::LOW, "DMA wrapper initialized");
|
||||
}
|
||||
|
||||
Dma::~Dma()
|
||||
{
|
||||
// Deallocate all channels
|
||||
for (int i = 0; i < MAX_CHANNELS; i++) {
|
||||
if (m_channelAllocated_[i]) {
|
||||
deallocateChannel(m_channels_[i]);
|
||||
}
|
||||
}
|
||||
ASF_LOGI(TAG, 2201, asf::logger::Criticality::LOW, "DMA wrapper destroyed");
|
||||
}
|
||||
|
||||
bool Dma::initialize(DmaChannel channel, const DmaConfig& config)
|
||||
{
|
||||
ASF_LOGI(TAG, 2202, asf::logger::Criticality::LOW, "DMA initialized successfully");
|
||||
return true;
|
||||
}
|
||||
|
||||
#if SOC_GDMA_SUPPORTED
|
||||
bool Dma::allocateChannel(const DmaChannelConfig& config, gdma_channel_handle_t* channelHandle)
|
||||
#else
|
||||
bool Dma::allocateChannel(const DmaChannelConfig& config, void** channelHandle)
|
||||
#endif
|
||||
{
|
||||
if (channelHandle == nullptr) {
|
||||
ASF_LOGE(TAG, 2203, asf::logger::Criticality::HIGH, "Invalid channel handle pointer");
|
||||
return false;
|
||||
}
|
||||
|
||||
int slotIndex = findFreeChannelSlot();
|
||||
if (slotIndex < 0) {
|
||||
ASF_LOGE(TAG, 2204, asf::logger::Criticality::HIGH, "No free channel slots available");
|
||||
return false;
|
||||
}
|
||||
|
||||
#if SOC_GDMA_SUPPORTED
|
||||
gdma_channel_alloc_config_t allocConfig = {};
|
||||
allocConfig.direction = GDMA_CHANNEL_DIRECTION_TX; // Default to TX, can be changed later
|
||||
allocConfig.flags.reserve_sibling = 0;
|
||||
|
||||
esp_err_t ret = gdma_new_channel(&allocConfig, channelHandle);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2205, asf::logger::Criticality::HIGH, "Failed to allocate DMA channel: %s", esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
m_channels_[slotIndex] = *channelHandle;
|
||||
m_channelAllocated_[slotIndex] = true;
|
||||
|
||||
ASF_LOGI(TAG, 2206, asf::logger::Criticality::LOW, "DMA channel allocated successfully (slot %d)", slotIndex);
|
||||
return true;
|
||||
#else
|
||||
ASF_LOGW(TAG, 2207, asf::logger::Criticality::MEDIUM, "GDMA not supported on this target");
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if SOC_GDMA_SUPPORTED
|
||||
bool Dma::deallocateChannel(gdma_channel_handle_t channelHandle)
|
||||
#else
|
||||
bool Dma::deallocateChannel(void* channelHandle)
|
||||
#endif
|
||||
{
|
||||
if (channelHandle == nullptr) {
|
||||
ASF_LOGE(TAG, 2208, asf::logger::Criticality::HIGH, "Invalid channel handle");
|
||||
return false;
|
||||
}
|
||||
|
||||
int slotIndex = findChannelSlot(channelHandle);
|
||||
if (slotIndex < 0) {
|
||||
ASF_LOGE(TAG, 2209, asf::logger::Criticality::HIGH, "Channel handle not found");
|
||||
return false;
|
||||
}
|
||||
|
||||
#if SOC_GDMA_SUPPORTED
|
||||
esp_err_t ret = gdma_del_channel(channelHandle);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2210, asf::logger::Criticality::HIGH, "Failed to deallocate DMA channel: %s", esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
m_channels_[slotIndex] = nullptr;
|
||||
m_channelAllocated_[slotIndex] = false;
|
||||
|
||||
ASF_LOGI(TAG, 2211, asf::logger::Criticality::LOW, "DMA channel deallocated successfully (slot %d)", slotIndex);
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if SOC_GDMA_SUPPORTED
|
||||
bool Dma::configureTransfer(gdma_channel_handle_t channelHandle, const DmaTransferConfig& config)
|
||||
#else
|
||||
bool Dma::configureTransfer(void* channelHandle, const DmaTransferConfig& config)
|
||||
#endif
|
||||
{
|
||||
if (channelHandle == nullptr) {
|
||||
ASF_LOGE(TAG, 2212, asf::logger::Criticality::HIGH, "Invalid channel handle");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Configure transfer based on direction
|
||||
switch (config.direction) {
|
||||
case DmaDirection::MEM_TO_MEM:
|
||||
// Memory to memory transfer configuration
|
||||
break;
|
||||
case DmaDirection::MEM_TO_PERIPH:
|
||||
// Memory to peripheral transfer configuration
|
||||
break;
|
||||
case DmaDirection::PERIPH_TO_MEM:
|
||||
// Peripheral to memory transfer configuration
|
||||
break;
|
||||
case DmaDirection::PERIPH_TO_PERIPH:
|
||||
// Peripheral to peripheral transfer configuration
|
||||
break;
|
||||
}
|
||||
|
||||
ASF_LOGI(TAG, 2213, asf::logger::Criticality::LOW, "DMA transfer configured successfully");
|
||||
return true;
|
||||
}
|
||||
|
||||
#if SOC_GDMA_SUPPORTED
|
||||
bool Dma::startTransfer(gdma_channel_handle_t channelHandle)
|
||||
#else
|
||||
bool Dma::startTransfer(void* channelHandle)
|
||||
#endif
|
||||
{
|
||||
if (channelHandle == nullptr) {
|
||||
ASF_LOGE(TAG, 2214, asf::logger::Criticality::HIGH, "Invalid channel handle");
|
||||
return false;
|
||||
}
|
||||
|
||||
#if SOC_GDMA_SUPPORTED
|
||||
esp_err_t ret = gdma_start(channelHandle);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2215, asf::logger::Criticality::HIGH, "Failed to start DMA transfer: %s", esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
ASF_LOGI(TAG, 2216, asf::logger::Criticality::LOW, "DMA transfer started successfully");
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if SOC_GDMA_SUPPORTED
|
||||
bool Dma::stopTransfer(gdma_channel_handle_t channelHandle)
|
||||
#else
|
||||
bool Dma::stopTransfer(void* channelHandle)
|
||||
#endif
|
||||
{
|
||||
if (channelHandle == nullptr) {
|
||||
ASF_LOGE(TAG, 2217, asf::logger::Criticality::HIGH, "Invalid channel handle");
|
||||
return false;
|
||||
}
|
||||
|
||||
#if SOC_GDMA_SUPPORTED
|
||||
esp_err_t ret = gdma_stop(channelHandle);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2218, asf::logger::Criticality::HIGH, "Failed to stop DMA transfer: %s", esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
ASF_LOGI(TAG, 2219, asf::logger::Criticality::LOW, "DMA transfer stopped successfully");
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if SOC_GDMA_SUPPORTED
|
||||
bool Dma::resetChannel(gdma_channel_handle_t channelHandle)
|
||||
#else
|
||||
bool Dma::resetChannel(void* channelHandle)
|
||||
#endif
|
||||
{
|
||||
if (channelHandle == nullptr) {
|
||||
ASF_LOGE(TAG, 2220, asf::logger::Criticality::HIGH, "Invalid channel handle");
|
||||
return false;
|
||||
}
|
||||
|
||||
#if SOC_GDMA_SUPPORTED
|
||||
esp_err_t ret = gdma_reset(channelHandle);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2221, asf::logger::Criticality::HIGH, "Failed to reset DMA channel: %s", esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
ASF_LOGI(TAG, 2222, asf::logger::Criticality::LOW, "DMA channel reset successfully");
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void* Dma::allocateDmaMemory(size_t size, uint32_t caps)
|
||||
{
|
||||
void* ptr = heap_caps_malloc(size, caps);
|
||||
if (ptr == nullptr) {
|
||||
ASF_LOGE(TAG, 2223, asf::logger::Criticality::HIGH, "Failed to allocate DMA memory of size %zu", size);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ASF_LOGI(TAG, 2224, asf::logger::Criticality::LOW, "DMA memory allocated: %zu bytes at %p", size, ptr);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void Dma::freeDmaMemory(void* ptr)
|
||||
{
|
||||
if (ptr != nullptr) {
|
||||
heap_caps_free(ptr);
|
||||
ASF_LOGI(TAG, 2225, asf::logger::Criticality::LOW, "DMA memory freed at %p", ptr);
|
||||
}
|
||||
}
|
||||
|
||||
DmaDescriptor* Dma::createDescriptor(void* buffer, size_t size, bool eof)
|
||||
{
|
||||
if (buffer == nullptr || size == 0) {
|
||||
ASF_LOGE(TAG, 2226, asf::logger::Criticality::HIGH, "Invalid buffer or size for descriptor");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
DmaDescriptor* desc = static_cast<DmaDescriptor*>(
|
||||
heap_caps_malloc(sizeof(DmaDescriptor), MALLOC_CAP_DMA));
|
||||
|
||||
if (desc == nullptr) {
|
||||
ASF_LOGE(TAG, 2227, asf::logger::Criticality::HIGH, "Failed to allocate DMA descriptor");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
desc->size = size;
|
||||
desc->length = size;
|
||||
desc->sosf = 0;
|
||||
desc->eof = eof ? 1 : 0;
|
||||
desc->owner = 1; // DMA owns the descriptor
|
||||
desc->reserved = 0;
|
||||
desc->buffer = buffer;
|
||||
desc->next = nullptr;
|
||||
|
||||
ASF_LOGI(TAG, 2228, asf::logger::Criticality::LOW, "DMA descriptor created: size=%zu, eof=%d", size, eof);
|
||||
return desc;
|
||||
}
|
||||
|
||||
bool Dma::linkDescriptors(DmaDescriptor* desc1, DmaDescriptor* desc2)
|
||||
{
|
||||
if (desc1 == nullptr || desc2 == nullptr) {
|
||||
ASF_LOGE(TAG, 2229, asf::logger::Criticality::HIGH, "Invalid descriptors for linking");
|
||||
return false;
|
||||
}
|
||||
|
||||
desc1->next = desc2;
|
||||
desc1->eof = 0; // Not end of frame since there's a next descriptor
|
||||
|
||||
ASF_LOGI(TAG, 2230, asf::logger::Criticality::LOW, "DMA descriptors linked successfully");
|
||||
return true;
|
||||
}
|
||||
|
||||
void Dma::freeDescriptor(DmaDescriptor* descriptor)
|
||||
{
|
||||
if (descriptor != nullptr) {
|
||||
heap_caps_free(descriptor);
|
||||
ASF_LOGI(TAG, 2231, asf::logger::Criticality::LOW, "DMA descriptor freed");
|
||||
}
|
||||
}
|
||||
|
||||
#if SOC_GDMA_SUPPORTED
|
||||
bool Dma::setCallback(gdma_channel_handle_t channelHandle, DmaCallback callback, void* userData)
|
||||
#else
|
||||
bool Dma::setCallback(void* channelHandle, DmaCallback callback, void* userData)
|
||||
#endif
|
||||
{
|
||||
if (channelHandle == nullptr || callback == nullptr) {
|
||||
ASF_LOGE(TAG, 2232, asf::logger::Criticality::HIGH, "Invalid channel handle or callback");
|
||||
return false;
|
||||
}
|
||||
|
||||
#if SOC_GDMA_SUPPORTED
|
||||
gdma_event_callbacks_t callbacks = {};
|
||||
callbacks.on_trans_eof = callback;
|
||||
|
||||
esp_err_t ret = gdma_register_event_callbacks(channelHandle, &callbacks, userData);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2233, asf::logger::Criticality::HIGH, "Failed to set DMA callback: %s", esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
ASF_LOGI(TAG, 2234, asf::logger::Criticality::LOW, "DMA callback set successfully");
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if SOC_GDMA_SUPPORTED
|
||||
bool Dma::enableChannel(gdma_channel_handle_t channelHandle)
|
||||
#else
|
||||
bool Dma::enableChannel(void* channelHandle)
|
||||
#endif
|
||||
{
|
||||
if (channelHandle == nullptr) {
|
||||
ASF_LOGE(TAG, 2235, asf::logger::Criticality::HIGH, "Invalid channel handle");
|
||||
return false;
|
||||
}
|
||||
|
||||
#if SOC_GDMA_SUPPORTED
|
||||
esp_err_t ret = gdma_start(channelHandle);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2236, asf::logger::Criticality::HIGH, "Failed to enable DMA channel: %s", esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
ASF_LOGI(TAG, 2237, asf::logger::Criticality::LOW, "DMA channel enabled successfully");
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if SOC_GDMA_SUPPORTED
|
||||
bool Dma::disableChannel(gdma_channel_handle_t channelHandle)
|
||||
#else
|
||||
bool Dma::disableChannel(void* channelHandle)
|
||||
#endif
|
||||
{
|
||||
if (channelHandle == nullptr) {
|
||||
ASF_LOGE(TAG, 2238, asf::logger::Criticality::HIGH, "Invalid channel handle");
|
||||
return false;
|
||||
}
|
||||
|
||||
#if SOC_GDMA_SUPPORTED
|
||||
esp_err_t ret = gdma_stop(channelHandle);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2239, asf::logger::Criticality::HIGH, "Failed to disable DMA channel: %s", esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
ASF_LOGI(TAG, 2240, asf::logger::Criticality::LOW, "DMA channel disabled successfully");
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if SOC_GDMA_SUPPORTED
|
||||
gdma_channel_state_t Dma::getChannelState(gdma_channel_handle_t channelHandle)
|
||||
#else
|
||||
int Dma::getChannelState(void* channelHandle)
|
||||
#endif
|
||||
{
|
||||
if (channelHandle == nullptr) {
|
||||
ASF_LOGE(TAG, 2241, asf::logger::Criticality::HIGH, "Invalid channel handle");
|
||||
#if SOC_GDMA_SUPPORTED
|
||||
return GDMA_CHANNEL_STATE_INVALID;
|
||||
#else
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Note: ESP-IDF doesn't provide a direct API to get channel state
|
||||
// This would need to be implemented by reading hardware registers
|
||||
#if SOC_GDMA_SUPPORTED
|
||||
return GDMA_CHANNEL_STATE_IDLE;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if SOC_GDMA_SUPPORTED
|
||||
bool Dma::memcpy(void* dst, const void* src, size_t size, gdma_channel_handle_t channelHandle)
|
||||
#else
|
||||
bool Dma::memcpy(void* dst, const void* src, size_t size, void* channelHandle)
|
||||
#endif
|
||||
{
|
||||
if (dst == nullptr || src == nullptr || size == 0) {
|
||||
ASF_LOGE(TAG, 2242, asf::logger::Criticality::HIGH, "Invalid parameters for DMA memcpy");
|
||||
return false;
|
||||
}
|
||||
|
||||
#if SOC_GDMA_SUPPORTED
|
||||
bool allocatedChannel = false;
|
||||
gdma_channel_handle_t channel = channelHandle;
|
||||
|
||||
// Allocate channel if not provided
|
||||
if (channel == nullptr) {
|
||||
DmaChannelConfig config = getDefaultChannelConfig();
|
||||
if (!allocateChannel(config, &channel)) {
|
||||
return false;
|
||||
}
|
||||
allocatedChannel = true;
|
||||
}
|
||||
|
||||
// Perform DMA copy using ESP-IDF DMA utilities
|
||||
// Note: This implementation is a placeholder, actual GDMA memcpy requires descriptors
|
||||
::memcpy(dst, src, size);
|
||||
esp_err_t ret = ESP_OK;
|
||||
|
||||
// Clean up if we allocated the channel
|
||||
if (allocatedChannel) {
|
||||
deallocateChannel(channel);
|
||||
}
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2243, asf::logger::Criticality::HIGH, "DMA memcpy failed: %s", esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
ASF_LOGI(TAG, 2244, asf::logger::Criticality::LOW, "DMA memcpy completed: %zu bytes", size);
|
||||
return true;
|
||||
#else
|
||||
// Fallback to standard memcpy if GDMA not supported
|
||||
::memcpy(dst, src, size);
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
DmaChannelConfig Dma::getDefaultChannelConfig()
|
||||
{
|
||||
DmaChannelConfig config = {};
|
||||
config.priority = DmaPriority::MEDIUM;
|
||||
config.flags = 0;
|
||||
return config;
|
||||
}
|
||||
|
||||
DmaTransferConfig Dma::getDefaultTransferConfig()
|
||||
{
|
||||
DmaTransferConfig config = {};
|
||||
config.srcAddr = nullptr;
|
||||
config.dstAddr = nullptr;
|
||||
config.dataSize = 0;
|
||||
config.direction = DmaDirection::MEM_TO_MEM;
|
||||
config.type = DmaTransferType::SINGLE;
|
||||
config.priority = DmaPriority::MEDIUM;
|
||||
config.autoReload = false;
|
||||
config.enableInterrupt = false;
|
||||
return config;
|
||||
}
|
||||
|
||||
bool Dma::isDmaCapable(const void* addr)
|
||||
{
|
||||
return esp_ptr_dma_capable(addr);
|
||||
}
|
||||
|
||||
int Dma::findFreeChannelSlot()
|
||||
{
|
||||
for (int i = 0; i < MAX_CHANNELS; i++) {
|
||||
if (!m_channelAllocated_[i]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
#if SOC_GDMA_SUPPORTED
|
||||
int Dma::findChannelSlot(gdma_channel_handle_t channelHandle)
|
||||
#else
|
||||
int Dma::findChannelSlot(void* channelHandle)
|
||||
#endif
|
||||
{
|
||||
for (int i = 0; i < MAX_CHANNELS; i++) {
|
||||
if (m_channelAllocated_[i] && m_channels_[i] == channelHandle) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
#if SOC_GDMA_SUPPORTED
|
||||
gdma_transfer_ability_t Dma::convertDirection(DmaDirection direction)
|
||||
{
|
||||
switch (direction) {
|
||||
case DmaDirection::MEM_TO_MEM:
|
||||
return GDMA_TRANSFER_ABILITY_MEM;
|
||||
case DmaDirection::MEM_TO_PERIPH:
|
||||
return GDMA_TRANSFER_ABILITY_MEM;
|
||||
case DmaDirection::PERIPH_TO_MEM:
|
||||
return GDMA_TRANSFER_ABILITY_MEM;
|
||||
case DmaDirection::PERIPH_TO_PERIPH:
|
||||
return GDMA_TRANSFER_ABILITY_MEM;
|
||||
default:
|
||||
return GDMA_TRANSFER_ABILITY_MEM;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
int Dma::convertPriority(DmaPriority priority)
|
||||
{
|
||||
return static_cast<int>(priority);
|
||||
}
|
||||
@@ -0,0 +1,386 @@
|
||||
/**
|
||||
* @file dma.hpp
|
||||
* @brief DMA wrapper component header - Wrapper for ESP-IDF DMA functionality
|
||||
* @author Mahmoud Elmohtady
|
||||
* @company Nabd solutions - ASF
|
||||
* @copyright Copyright (c) 2025
|
||||
*/
|
||||
|
||||
#ifndef DMA_HPP
|
||||
#define DMA_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include "esp_dma_utils.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "soc/soc_caps.h"
|
||||
|
||||
#if SOC_GDMA_SUPPORTED
|
||||
#include "soc/gdma_struct.h"
|
||||
#include "hal/gdma_ll.h"
|
||||
#include "driver/gdma.h"
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief DMA transfer direction enumeration
|
||||
*/
|
||||
enum class DmaDirection
|
||||
{
|
||||
MEM_TO_MEM, ///< Memory to memory transfer
|
||||
MEM_TO_PERIPH, ///< Memory to peripheral transfer
|
||||
PERIPH_TO_MEM, ///< Peripheral to memory transfer
|
||||
PERIPH_TO_PERIPH ///< Peripheral to peripheral transfer
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief DMA transfer type enumeration
|
||||
*/
|
||||
enum class DmaTransferType
|
||||
{
|
||||
SINGLE, ///< Single transfer
|
||||
BLOCK, ///< Block transfer
|
||||
LINKED_LIST ///< Linked list transfer
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief DMA priority enumeration
|
||||
*/
|
||||
enum class DmaPriority
|
||||
{
|
||||
LOW = 0,
|
||||
MEDIUM = 1,
|
||||
HIGH = 2,
|
||||
HIGHEST = 3
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief DMA channel enumeration
|
||||
*/
|
||||
enum class DmaChannel
|
||||
{
|
||||
CHANNEL_0,
|
||||
CHANNEL_1,
|
||||
CHANNEL_2,
|
||||
CHANNEL_3,
|
||||
CHANNEL_4,
|
||||
CHANNEL_5,
|
||||
CHANNEL_6,
|
||||
CHANNEL_7
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief DMA configuration structure
|
||||
*/
|
||||
struct DmaConfig
|
||||
{
|
||||
DmaPriority priority;
|
||||
uint32_t flags;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief DMA descriptor structure
|
||||
*/
|
||||
struct DmaDescriptor
|
||||
{
|
||||
uint32_t size; ///< Size of data to transfer
|
||||
uint32_t length; ///< Actual length of valid data
|
||||
uint32_t sosf : 1; ///< Start of sub-frame
|
||||
uint32_t eof : 1; ///< End of frame
|
||||
uint32_t owner : 1; ///< Owner (0: CPU, 1: DMA)
|
||||
uint32_t reserved : 29; ///< Reserved bits
|
||||
void* buffer; ///< Pointer to data buffer
|
||||
DmaDescriptor* next; ///< Pointer to next descriptor
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief DMA transfer configuration structure
|
||||
*/
|
||||
struct DmaTransferConfig
|
||||
{
|
||||
void* srcAddr; ///< Source address
|
||||
void* dstAddr; ///< Destination address
|
||||
size_t dataSize; ///< Size of data to transfer
|
||||
DmaDirection direction; ///< Transfer direction
|
||||
DmaTransferType type; ///< Transfer type
|
||||
DmaPriority priority; ///< Transfer priority
|
||||
bool autoReload; ///< Auto-reload mode
|
||||
bool enableInterrupt; ///< Enable transfer complete interrupt
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief DMA channel configuration structure
|
||||
*/
|
||||
struct DmaChannelConfig
|
||||
{
|
||||
DmaPriority priority; ///< Channel priority
|
||||
uint32_t flags; ///< Configuration flags
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief DMA callback function type
|
||||
*/
|
||||
#if SOC_GDMA_SUPPORTED
|
||||
using DmaCallback = void (*)(gdma_channel_handle_t dmaChannel, gdma_event_data_t* eventData, void* userData);
|
||||
#else
|
||||
using DmaCallback = void (*)(void* dmaChannel, void* eventData, void* userData);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief DMA wrapper class
|
||||
*
|
||||
* Provides a C++ wrapper for ESP-IDF GDMA functionality.
|
||||
* This class encapsulates ESP-IDF GDMA driver functions in an object-oriented interface.
|
||||
*/
|
||||
class Dma
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @details Initializes the DMA wrapper instance
|
||||
*/
|
||||
Dma();
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
* @details Cleans up resources and deinitializes all DMA channels
|
||||
*/
|
||||
~Dma();
|
||||
|
||||
/**
|
||||
* @brief Initialize DMA component
|
||||
* @param channel DMA channel
|
||||
* @param config DMA configuration
|
||||
* @return true if initialized successfully, false otherwise
|
||||
*/
|
||||
bool initialize(DmaChannel channel, const DmaConfig& config);
|
||||
|
||||
/**
|
||||
* @brief Allocate DMA channel
|
||||
* @param config Channel configuration
|
||||
* @param channelHandle Pointer to store channel handle
|
||||
* @return true if allocated successfully, false otherwise
|
||||
*/
|
||||
#if SOC_GDMA_SUPPORTED
|
||||
bool allocateChannel(const DmaChannelConfig& config, gdma_channel_handle_t* channelHandle);
|
||||
#else
|
||||
bool allocateChannel(const DmaChannelConfig& config, void** channelHandle);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Deallocate DMA channel
|
||||
* @param channelHandle Channel handle to deallocate
|
||||
* @return true if deallocated successfully, false otherwise
|
||||
*/
|
||||
#if SOC_GDMA_SUPPORTED
|
||||
bool deallocateChannel(gdma_channel_handle_t channelHandle);
|
||||
#else
|
||||
bool deallocateChannel(void* channelHandle);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Configure DMA transfer
|
||||
* @param channelHandle Channel handle
|
||||
* @param config Transfer configuration
|
||||
* @return true if configured successfully, false otherwise
|
||||
*/
|
||||
#if SOC_GDMA_SUPPORTED
|
||||
bool configureTransfer(gdma_channel_handle_t channelHandle, const DmaTransferConfig& config);
|
||||
#else
|
||||
bool configureTransfer(void* channelHandle, const DmaTransferConfig& config);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Start DMA transfer
|
||||
* @param channelHandle Channel handle
|
||||
* @return true if started successfully, false otherwise
|
||||
*/
|
||||
#if SOC_GDMA_SUPPORTED
|
||||
bool startTransfer(gdma_channel_handle_t channelHandle);
|
||||
#else
|
||||
bool startTransfer(void* channelHandle);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Stop DMA transfer
|
||||
* @param channelHandle Channel handle
|
||||
* @return true if stopped successfully, false otherwise
|
||||
*/
|
||||
#if SOC_GDMA_SUPPORTED
|
||||
bool stopTransfer(gdma_channel_handle_t channelHandle);
|
||||
#else
|
||||
bool stopTransfer(void* channelHandle);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Reset DMA channel
|
||||
* @param channelHandle Channel handle
|
||||
* @return true if reset successfully, false otherwise
|
||||
*/
|
||||
#if SOC_GDMA_SUPPORTED
|
||||
bool resetChannel(gdma_channel_handle_t channelHandle);
|
||||
#else
|
||||
bool resetChannel(void* channelHandle);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Allocate DMA-capable memory
|
||||
* @param size Size of memory to allocate
|
||||
* @param caps Memory capabilities (default: DMA capable)
|
||||
* @return Pointer to allocated memory, or nullptr on failure
|
||||
*/
|
||||
void* allocateDmaMemory(size_t size, uint32_t caps = MALLOC_CAP_DMA);
|
||||
|
||||
/**
|
||||
* @brief Free DMA-capable memory
|
||||
* @param ptr Pointer to memory to free
|
||||
*/
|
||||
void freeDmaMemory(void* ptr);
|
||||
|
||||
/**
|
||||
* @brief Create DMA descriptor
|
||||
* @param buffer Pointer to data buffer
|
||||
* @param size Size of data
|
||||
* @param eof End of frame flag
|
||||
* @return Pointer to created descriptor, or nullptr on failure
|
||||
*/
|
||||
DmaDescriptor* createDescriptor(void* buffer, size_t size, bool eof = true);
|
||||
|
||||
/**
|
||||
* @brief Link DMA descriptors
|
||||
* @param desc1 First descriptor
|
||||
* @param desc2 Second descriptor
|
||||
* @return true if linked successfully, false otherwise
|
||||
*/
|
||||
bool linkDescriptors(DmaDescriptor* desc1, DmaDescriptor* desc2);
|
||||
|
||||
/**
|
||||
* @brief Free DMA descriptor
|
||||
* @param descriptor Pointer to descriptor to free
|
||||
*/
|
||||
void freeDescriptor(DmaDescriptor* descriptor);
|
||||
|
||||
/**
|
||||
* @brief Set DMA callback
|
||||
* @param channelHandle Channel handle
|
||||
* @param callback Callback function
|
||||
* @param userData User data passed to callback
|
||||
* @return true if set successfully, false otherwise
|
||||
*/
|
||||
#if SOC_GDMA_SUPPORTED
|
||||
bool setCallback(gdma_channel_handle_t channelHandle, DmaCallback callback, void* userData);
|
||||
#else
|
||||
bool setCallback(void* channelHandle, DmaCallback callback, void* userData);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Enable DMA channel
|
||||
* @param channelHandle Channel handle
|
||||
* @return true if enabled successfully, false otherwise
|
||||
*/
|
||||
#if SOC_GDMA_SUPPORTED
|
||||
bool enableChannel(gdma_channel_handle_t channelHandle);
|
||||
#else
|
||||
bool enableChannel(void* channelHandle);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Disable DMA channel
|
||||
* @param channelHandle Channel handle
|
||||
* @return true if disabled successfully, false otherwise
|
||||
*/
|
||||
#if SOC_GDMA_SUPPORTED
|
||||
bool disableChannel(gdma_channel_handle_t channelHandle);
|
||||
#else
|
||||
bool disableChannel(void* channelHandle);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Get DMA channel state
|
||||
* @param channelHandle Channel handle
|
||||
* @return Current channel state
|
||||
*/
|
||||
#if SOC_GDMA_SUPPORTED
|
||||
gdma_channel_state_t getChannelState(gdma_channel_handle_t channelHandle);
|
||||
#else
|
||||
int getChannelState(void* channelHandle);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Perform memory-to-memory copy using DMA
|
||||
* @param dst Destination address
|
||||
* @param src Source address
|
||||
* @param size Size of data to copy
|
||||
* @param channelHandle Channel handle (optional, will allocate if nullptr)
|
||||
* @return true if copy completed successfully, false otherwise
|
||||
*/
|
||||
#if SOC_GDMA_SUPPORTED
|
||||
bool memcpy(void* dst, const void* src, size_t size, gdma_channel_handle_t channelHandle = nullptr);
|
||||
#else
|
||||
bool memcpy(void* dst, const void* src, size_t size, void* channelHandle = nullptr);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Get default channel configuration
|
||||
* @return Default DMA channel configuration
|
||||
*/
|
||||
static DmaChannelConfig getDefaultChannelConfig();
|
||||
|
||||
/**
|
||||
* @brief Get default transfer configuration
|
||||
* @return Default DMA transfer configuration
|
||||
*/
|
||||
static DmaTransferConfig getDefaultTransferConfig();
|
||||
|
||||
/**
|
||||
* @brief Check if address is DMA capable
|
||||
* @param addr Address to check
|
||||
* @return true if DMA capable, false otherwise
|
||||
*/
|
||||
static bool isDmaCapable(const void* addr);
|
||||
|
||||
private:
|
||||
static constexpr size_t MAX_CHANNELS = 8; ///< Maximum number of DMA channels
|
||||
|
||||
#if SOC_GDMA_SUPPORTED
|
||||
gdma_channel_handle_t m_channels_[MAX_CHANNELS]; ///< Channel handles
|
||||
#else
|
||||
void* m_channels_[MAX_CHANNELS];
|
||||
#endif
|
||||
bool m_channelAllocated_[MAX_CHANNELS]; ///< Channel allocation status
|
||||
|
||||
/**
|
||||
* @brief Find free channel slot
|
||||
* @return Index of free slot, or -1 if none available
|
||||
*/
|
||||
int findFreeChannelSlot();
|
||||
|
||||
/**
|
||||
* @brief Find channel slot by handle
|
||||
* @param channelHandle Channel handle to find
|
||||
* @return Index of channel slot, or -1 if not found
|
||||
*/
|
||||
#if SOC_GDMA_SUPPORTED
|
||||
int findChannelSlot(gdma_channel_handle_t channelHandle);
|
||||
#else
|
||||
int findChannelSlot(void* channelHandle);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Convert DmaDirection to GDMA direction
|
||||
* @param direction DMA direction
|
||||
* @return GDMA direction
|
||||
*/
|
||||
#if SOC_GDMA_SUPPORTED
|
||||
gdma_transfer_ability_t convertDirection(DmaDirection direction);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Convert DmaPriority to GDMA priority
|
||||
* @param priority DMA priority
|
||||
* @return GDMA priority
|
||||
*/
|
||||
int convertPriority(DmaPriority priority);
|
||||
};
|
||||
|
||||
#endif // DMA_HPP
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
ID,Component,Level,Criticality,Message
|
||||
2200,DMA,INFO,Low,DMA wrapper initialized
|
||||
2201,DMA,INFO,Low,DMA wrapper destroyed
|
||||
2202,DMA,INFO,Low,DMA initialized successfully
|
||||
2203,DMA,ERROR,High,Invalid channel handle pointer
|
||||
2204,DMA,ERROR,High,No free channel slots available
|
||||
2205,DMA,ERROR,High,Failed to allocate DMA channel: %s
|
||||
2206,DMA,INFO,Low,DMA channel allocated successfully (slot %d)
|
||||
2207,DMA,WARNING,Medium,GDMA not supported on this target
|
||||
2208,DMA,ERROR,High,Invalid channel handle
|
||||
2209,DMA,ERROR,High,Channel handle not found
|
||||
2210,DMA,ERROR,High,Failed to deallocate DMA channel: %s
|
||||
2211,DMA,INFO,Low,DMA channel deallocated successfully (slot %d)
|
||||
2212,DMA,ERROR,High,Invalid channel handle
|
||||
2213,DMA,INFO,Low,DMA transfer configured successfully
|
||||
2214,DMA,ERROR,High,Invalid channel handle
|
||||
2215,DMA,ERROR,High,Failed to start DMA transfer: %s
|
||||
2216,DMA,INFO,Low,DMA transfer started successfully
|
||||
2217,DMA,ERROR,High,Invalid channel handle
|
||||
2218,DMA,ERROR,High,Failed to stop DMA transfer: %s
|
||||
2219,DMA,INFO,Low,DMA transfer stopped successfully
|
||||
2220,DMA,ERROR,High,Invalid channel handle
|
||||
2221,DMA,ERROR,High,Failed to reset DMA channel: %s
|
||||
2222,DMA,INFO,Low,DMA channel reset successfully
|
||||
2223,DMA,ERROR,High,Failed to allocate DMA memory of size %zu
|
||||
2224,DMA,INFO,Low,DMA memory allocated: %zu bytes at %p
|
||||
2225,DMA,INFO,Low,DMA memory freed at %p
|
||||
2226,DMA,ERROR,High,Invalid buffer or size for descriptor
|
||||
2227,DMA,ERROR,High,Failed to allocate DMA descriptor
|
||||
2228,DMA,INFO,Low,DMA descriptor created: size=%zu, eof=%d
|
||||
2229,DMA,ERROR,High,Invalid descriptors for linking
|
||||
2230,DMA,INFO,Low,DMA descriptors linked successfully
|
||||
2231,DMA,INFO,Low,DMA descriptor freed
|
||||
2232,DMA,ERROR,High,Invalid channel handle or callback
|
||||
2233,DMA,ERROR,High,Failed to set DMA callback: %s
|
||||
2234,DMA,INFO,Low,DMA callback set successfully
|
||||
2235,DMA,ERROR,High,Invalid channel handle
|
||||
2236,DMA,ERROR,High,Failed to enable DMA channel: %s
|
||||
2237,DMA,INFO,Low,DMA channel enabled successfully
|
||||
2238,DMA,ERROR,High,Invalid channel handle
|
||||
2239,DMA,ERROR,High,Failed to disable DMA channel: %s
|
||||
2240,DMA,INFO,Low,DMA channel disabled successfully
|
||||
2241,DMA,ERROR,High,Invalid channel handle
|
||||
2242,DMA,ERROR,High,Invalid parameters for DMA memcpy
|
||||
2243,DMA,ERROR,High,DMA memcpy failed: %s
|
||||
2244,DMA,INFO,Low,DMA memcpy completed: %zu bytes
|
||||
|
Can't render this file because it has a wrong number of fields in line 30.
|
@@ -0,0 +1,34 @@
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
|
||||
folder_path = os.path.abspath(os.path.join("components", "system_tests"))
|
||||
if folder_path not in sys.path:
|
||||
sys.path.append(folder_path)
|
||||
|
||||
from scan_serial import ESP32Runner
|
||||
|
||||
def test_dma_initialize():
|
||||
runner = ESP32Runner(mode="SIM", port="COM9")
|
||||
runner.start()
|
||||
print("--- QEMU Runner Started ---", flush=True)
|
||||
try:
|
||||
start_time = time.time()
|
||||
while time.time() - start_time < 30:
|
||||
line = runner.get_line(timeout=1.0)
|
||||
if line:
|
||||
print(line, flush=True)
|
||||
if "DMA initialized successfully" in line:
|
||||
print("SUCCESS CRITERIA MET!", flush=True)
|
||||
return 0
|
||||
if runner.process.poll() is not None:
|
||||
print(f"Process exited with code: {runner.process.returncode}", flush=True)
|
||||
return 1
|
||||
finally:
|
||||
runner.stop()
|
||||
print("Done.", flush=True)
|
||||
return 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
exit_code = test_dma_initialize()
|
||||
sys.exit(exit_code)
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<test_scenario>
|
||||
<!-- The configuration for the test environment. -->
|
||||
<!-- Available configurations: SIMULATE, HIL -->
|
||||
<config>SIMULATE</config>
|
||||
|
||||
<test_case>
|
||||
<test_case_id>DMA_INIT_TEST</test_case_id>
|
||||
<!-- The main command that executes the test itself. -->
|
||||
<test_exec>python components/ESP_IDF_FW_wrappers/dma/test/dma_init_test.py</test_exec>
|
||||
</test_case>
|
||||
|
||||
|
||||
</test_scenario>
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* @file test_dma.cpp
|
||||
* @brief Unit tests for DMA wrapper component
|
||||
* @author Mahmoud Elmohtady
|
||||
* @company Nabd solutions - ASF
|
||||
* @copyright Copyright (c) 2025
|
||||
*/
|
||||
|
||||
#include "unity.h"
|
||||
#include "dma.hpp"
|
||||
|
||||
extern "C" {
|
||||
|
||||
void setUp(void)
|
||||
{
|
||||
}
|
||||
|
||||
void tearDown(void)
|
||||
{
|
||||
}
|
||||
|
||||
void test_dma_initialize(void)
|
||||
{
|
||||
Dma dma;
|
||||
DmaConfig config = {64, 1024};
|
||||
bool result = dma.initialize(DmaChannel::CHANNEL_0, config);
|
||||
TEST_ASSERT_TRUE(result);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
idf_component_register(
|
||||
SRCS "com/gpio.cpp"
|
||||
INCLUDE_DIRS "com"
|
||||
REQUIRES esp_driver_gpio logger
|
||||
)
|
||||
@@ -0,0 +1,154 @@
|
||||
# GPIO Wrapper Module
|
||||
|
||||
## Overview
|
||||
|
||||
The GPIO wrapper module provides a C++ object-oriented interface for ESP-IDF GPIO functionality. This module encapsulates the ESP-IDF GPIO driver functions and provides a clean, easy-to-use API for GPIO operations.
|
||||
|
||||
## Features
|
||||
|
||||
- **Pin Configuration**: Configure GPIO pins as input, output, or open-drain
|
||||
- **Digital I/O**: Read and write digital values to GPIO pins
|
||||
- **Pull Resistors**: Configure internal pull-up and pull-down resistors
|
||||
- **Interrupt Handling**: Attach interrupt handlers to GPIO pins
|
||||
- **Pin Validation**: Automatic validation of GPIO pin numbers
|
||||
- **Error Handling**: Comprehensive error checking and logging
|
||||
|
||||
## Architecture
|
||||
|
||||
### Class Diagram
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ Gpio │
|
||||
├─────────────────────────────────────┤
|
||||
│ - m_isrInstalled_: bool │
|
||||
├─────────────────────────────────────┤
|
||||
│ + Gpio() │
|
||||
│ + ~Gpio() │
|
||||
│ + configure(pin, mode): bool │
|
||||
│ + setLevel(pin, level): bool │
|
||||
│ + getLevel(pin): int32_t │
|
||||
│ + toggleLevel(pin): bool │
|
||||
│ + installIsr(flags): bool │
|
||||
│ + uninstallIsr(): bool │
|
||||
│ + attachInterrupt(...): bool │
|
||||
│ + detachInterrupt(pin): bool │
|
||||
│ + enableInterrupt(pin): bool │
|
||||
│ + disableInterrupt(pin): bool │
|
||||
│ + isValidPin(pin): bool [static] │
|
||||
│ - convertMode(mode): gpio_mode_t │
|
||||
│ - convertIntType(type): gpio_int_t │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Enumerations
|
||||
|
||||
#### GpioMode
|
||||
- `INPUT`: Standard input mode
|
||||
- `OUTPUT`: Standard output mode
|
||||
- `INPUT_PULLUP`: Input with internal pull-up resistor
|
||||
- `INPUT_PULLDOWN`: Input with internal pull-down resistor
|
||||
- `OUTPUT_OD`: Open-drain output mode
|
||||
|
||||
#### GpioIntType
|
||||
- `DISABLE`: Disable interrupt
|
||||
- `RISING_EDGE`: Trigger on rising edge
|
||||
- `FALLING_EDGE`: Trigger on falling edge
|
||||
- `ANY_EDGE`: Trigger on any edge
|
||||
- `LOW_LEVEL`: Trigger on low level
|
||||
- `HIGH_LEVEL`: Trigger on high level
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic GPIO Operations
|
||||
|
||||
```cpp
|
||||
#include "gpio.hpp"
|
||||
|
||||
// Create GPIO instance
|
||||
Gpio gpio;
|
||||
|
||||
// Configure pin 2 as output
|
||||
gpio.configure(2, GpioMode::OUTPUT);
|
||||
|
||||
// Set pin high
|
||||
gpio.setLevel(2, 1);
|
||||
|
||||
// Configure pin 4 as input with pull-up
|
||||
gpio.configure(4, GpioMode::INPUT_PULLUP);
|
||||
|
||||
// Read pin level
|
||||
int level = gpio.getLevel(4);
|
||||
```
|
||||
|
||||
### Interrupt Handling
|
||||
|
||||
```cpp
|
||||
// Interrupt callback function
|
||||
void IRAM_ATTR gpioInterruptHandler(uint32_t pin, void* arg) {
|
||||
// Handle interrupt
|
||||
printf("Interrupt on pin %lu\n", pin);
|
||||
}
|
||||
|
||||
// Setup interrupt
|
||||
gpio.configure(5, GpioMode::INPUT);
|
||||
gpio.attachInterrupt(5, GpioIntType::FALLING_EDGE, gpioInterruptHandler, nullptr);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### Constructor/Destructor
|
||||
|
||||
- **Gpio()**: Initialize GPIO wrapper instance
|
||||
- **~Gpio()**: Clean up resources and uninstall ISR if needed
|
||||
|
||||
### Configuration Methods
|
||||
|
||||
- **configure(pin, mode)**: Configure GPIO pin mode and pull resistors
|
||||
- **isValidPin(pin)**: Check if GPIO pin number is valid
|
||||
|
||||
### Digital I/O Methods
|
||||
|
||||
- **setLevel(pin, level)**: Set output pin level (0 or 1)
|
||||
- **getLevel(pin)**: Read pin level, returns 0, 1, or -1 on error
|
||||
- **toggleLevel(pin)**: Toggle output pin level
|
||||
|
||||
### Interrupt Methods
|
||||
|
||||
- **installIsr(flags)**: Install GPIO interrupt service
|
||||
- **uninstallIsr()**: Uninstall GPIO interrupt service
|
||||
- **attachInterrupt(pin, type, callback, arg)**: Attach interrupt handler
|
||||
- **detachInterrupt(pin)**: Detach interrupt handler
|
||||
- **enableInterrupt(pin)**: Enable interrupt for pin
|
||||
- **disableInterrupt(pin)**: Disable interrupt for pin
|
||||
|
||||
## Error Handling
|
||||
|
||||
The module provides comprehensive error handling:
|
||||
- Invalid pin numbers are checked and logged
|
||||
- ESP-IDF errors are caught and logged with descriptive messages
|
||||
- Return values indicate success/failure for all operations
|
||||
- ESP_LOG is used for debugging and error reporting
|
||||
|
||||
## Dependencies
|
||||
|
||||
- ESP-IDF GPIO driver (`driver/gpio.h`)
|
||||
- ESP-IDF error handling (`esp_err.h`)
|
||||
- ESP-IDF logging (`esp_log.h`)
|
||||
|
||||
## Thread Safety
|
||||
|
||||
The GPIO wrapper is not inherently thread-safe. If used in multi-threaded applications, external synchronization mechanisms should be employed.
|
||||
|
||||
## Memory Usage
|
||||
|
||||
The GPIO wrapper has minimal memory footprint:
|
||||
- Single boolean flag for ISR installation status
|
||||
- No dynamic memory allocation
|
||||
- Stack-based configuration structures
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
- Direct ESP-IDF function calls for optimal performance
|
||||
- Minimal overhead over raw ESP-IDF calls
|
||||
- Interrupt handlers should be kept short and use IRAM_ATTR
|
||||
@@ -0,0 +1,271 @@
|
||||
/**
|
||||
* @file gpio.cpp
|
||||
* @brief GPIO wrapper component implementation
|
||||
* @author Mahmoud Elmohtady
|
||||
* @company Nabd solutions - ASF
|
||||
* @copyright Copyright (c) 2025
|
||||
*/
|
||||
|
||||
#include "gpio.hpp"
|
||||
#include "logger.hpp"
|
||||
|
||||
static const char* TAG = "GPIO_WRAPPER";
|
||||
|
||||
/**
|
||||
* @brief Constructor - Initialize GPIO wrapper instance
|
||||
* @details Initializes the GPIO wrapper with default settings
|
||||
*/
|
||||
Gpio::Gpio()
|
||||
: m_isrInstalled_(false)
|
||||
{
|
||||
ASF_LOGI(TAG, 2300, asf::logger::Criticality::LOW, "GPIO wrapper initialized");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Destructor - Clean up GPIO wrapper resources
|
||||
* @details Uninstalls ISR service if it was installed
|
||||
*/
|
||||
Gpio::~Gpio()
|
||||
{
|
||||
if (m_isrInstalled_) {
|
||||
uninstallIsr();
|
||||
}
|
||||
ASF_LOGI(TAG, 2301, asf::logger::Criticality::LOW, "GPIO wrapper destroyed");
|
||||
}
|
||||
|
||||
bool Gpio::configure(uint32_t pin, GpioMode mode)
|
||||
{
|
||||
if (!isValidPin(pin)) {
|
||||
ASF_LOGE(TAG, 2302, asf::logger::Criticality::HIGH, "Invalid GPIO pin: %lu", pin);
|
||||
return false;
|
||||
}
|
||||
|
||||
gpio_config_t config = {};
|
||||
config.pin_bit_mask = (1ULL << pin);
|
||||
config.mode = convertMode(mode);
|
||||
config.intr_type = GPIO_INTR_DISABLE;
|
||||
|
||||
// Configure pull-up/down based on mode
|
||||
switch (mode) {
|
||||
case GpioMode::INPUT_PULLUP:
|
||||
config.pull_up_en = GPIO_PULLUP_ENABLE;
|
||||
config.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
||||
break;
|
||||
case GpioMode::INPUT_PULLDOWN:
|
||||
config.pull_up_en = GPIO_PULLUP_DISABLE;
|
||||
config.pull_down_en = GPIO_PULLDOWN_ENABLE;
|
||||
break;
|
||||
default:
|
||||
config.pull_up_en = GPIO_PULLUP_DISABLE;
|
||||
config.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
||||
break;
|
||||
}
|
||||
|
||||
esp_err_t ret = gpio_config(&config);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2303, asf::logger::Criticality::HIGH, "Failed to configure GPIO pin %lu: %s", pin, esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
ASF_LOGI(TAG, 2304, asf::logger::Criticality::LOW, "GPIO pin %lu configured successfully", pin);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Gpio::setLevel(uint32_t pin, uint32_t level)
|
||||
{
|
||||
if (!isValidPin(pin)) {
|
||||
ASF_LOGE(TAG, 2302, asf::logger::Criticality::HIGH, "Invalid GPIO pin: %lu", pin);
|
||||
return false;
|
||||
}
|
||||
|
||||
esp_err_t ret = gpio_set_level(static_cast<gpio_num_t>(pin), level);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2305, asf::logger::Criticality::HIGH, "Failed to set GPIO pin %lu level: %s", pin, esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int32_t Gpio::getLevel(uint32_t pin)
|
||||
{
|
||||
if (!isValidPin(pin)) {
|
||||
ASF_LOGE(TAG, 2302, asf::logger::Criticality::HIGH, "Invalid GPIO pin: %lu", pin);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int level = gpio_get_level(static_cast<gpio_num_t>(pin));
|
||||
return level;
|
||||
}
|
||||
|
||||
bool Gpio::toggleLevel(uint32_t pin)
|
||||
{
|
||||
int32_t currentLevel = getLevel(pin);
|
||||
if (currentLevel < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return setLevel(pin, currentLevel == 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
bool Gpio::installIsr(int flags)
|
||||
{
|
||||
if (m_isrInstalled_) {
|
||||
ASF_LOGW(TAG, 2306, asf::logger::Criticality::MEDIUM, "GPIO ISR already installed");
|
||||
return true;
|
||||
}
|
||||
|
||||
esp_err_t ret = gpio_install_isr_service(flags);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2307, asf::logger::Criticality::HIGH, "Failed to install GPIO ISR service: %s", esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
m_isrInstalled_ = true;
|
||||
ASF_LOGI(TAG, 2308, asf::logger::Criticality::LOW, "GPIO initialized successfully");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Gpio::uninstallIsr()
|
||||
{
|
||||
if (!m_isrInstalled_) {
|
||||
ASF_LOGW(TAG, 2309, asf::logger::Criticality::MEDIUM, "GPIO ISR not installed");
|
||||
return true;
|
||||
}
|
||||
|
||||
gpio_uninstall_isr_service();
|
||||
m_isrInstalled_ = false;
|
||||
ASF_LOGI(TAG, 2310, asf::logger::Criticality::LOW, "GPIO ISR service uninstalled");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Gpio::attachInterrupt(uint32_t pin, GpioIntType intType, GpioCallback callback, void* arg)
|
||||
{
|
||||
if (!isValidPin(pin)) {
|
||||
ASF_LOGE(TAG, 2302, asf::logger::Criticality::HIGH, "Invalid GPIO pin: %lu", pin);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_isrInstalled_) {
|
||||
ASF_LOGW(TAG, 2311, asf::logger::Criticality::MEDIUM, "GPIO ISR not installed, installing now");
|
||||
if (!installIsr()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Set interrupt type
|
||||
esp_err_t ret = gpio_set_intr_type(static_cast<gpio_num_t>(pin), convertIntType(intType));
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2312, asf::logger::Criticality::HIGH, "Failed to set interrupt type for pin %lu: %s", pin, esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add ISR handler
|
||||
ret = gpio_isr_handler_add(static_cast<gpio_num_t>(pin),
|
||||
reinterpret_cast<gpio_isr_t>(callback), arg);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2313, asf::logger::Criticality::HIGH, "Failed to add ISR handler for pin %lu: %s", pin, esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
ASF_LOGI(TAG, 2314, asf::logger::Criticality::LOW, "Interrupt attached to GPIO pin %lu", pin);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Gpio::detachInterrupt(uint32_t pin)
|
||||
{
|
||||
if (!isValidPin(pin)) {
|
||||
ASF_LOGE(TAG, 2302, asf::logger::Criticality::HIGH, "Invalid GPIO pin: %lu", pin);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Disable interrupt
|
||||
esp_err_t ret = gpio_set_intr_type(static_cast<gpio_num_t>(pin), GPIO_INTR_DISABLE);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2315, asf::logger::Criticality::HIGH, "Failed to disable interrupt for pin %lu: %s", pin, esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove ISR handler
|
||||
ret = gpio_isr_handler_remove(static_cast<gpio_num_t>(pin));
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2316, asf::logger::Criticality::HIGH, "Failed to remove ISR handler for pin %lu: %s", pin, esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
ASF_LOGI(TAG, 2317, asf::logger::Criticality::LOW, "Interrupt detached from GPIO pin %lu", pin);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Gpio::enableInterrupt(uint32_t pin)
|
||||
{
|
||||
if (!isValidPin(pin)) {
|
||||
ASF_LOGE(TAG, 2302, asf::logger::Criticality::HIGH, "Invalid GPIO pin: %lu", pin);
|
||||
return false;
|
||||
}
|
||||
|
||||
esp_err_t ret = gpio_intr_enable(static_cast<gpio_num_t>(pin));
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2318, asf::logger::Criticality::HIGH, "Failed to enable interrupt for pin %lu: %s", pin, esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Gpio::disableInterrupt(uint32_t pin)
|
||||
{
|
||||
if (!isValidPin(pin)) {
|
||||
ASF_LOGE(TAG, 2302, asf::logger::Criticality::HIGH, "Invalid GPIO pin: %lu", pin);
|
||||
return false;
|
||||
}
|
||||
|
||||
esp_err_t ret = gpio_intr_disable(static_cast<gpio_num_t>(pin));
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2319, asf::logger::Criticality::HIGH, "Failed to disable interrupt for pin %lu: %s", pin, esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Gpio::isValidPin(uint32_t pin)
|
||||
{
|
||||
return GPIO_IS_VALID_GPIO(pin);
|
||||
}
|
||||
|
||||
gpio_mode_t Gpio::convertMode(GpioMode mode)
|
||||
{
|
||||
switch (mode) {
|
||||
case GpioMode::INPUT:
|
||||
case GpioMode::INPUT_PULLUP:
|
||||
case GpioMode::INPUT_PULLDOWN:
|
||||
return GPIO_MODE_INPUT;
|
||||
case GpioMode::OUTPUT:
|
||||
return GPIO_MODE_OUTPUT;
|
||||
case GpioMode::OUTPUT_OD:
|
||||
return GPIO_MODE_OUTPUT_OD;
|
||||
default:
|
||||
return GPIO_MODE_INPUT;
|
||||
}
|
||||
}
|
||||
|
||||
gpio_int_type_t Gpio::convertIntType(GpioIntType intType)
|
||||
{
|
||||
switch (intType) {
|
||||
case GpioIntType::DISABLE:
|
||||
return GPIO_INTR_DISABLE;
|
||||
case GpioIntType::RISING_EDGE:
|
||||
return GPIO_INTR_POSEDGE;
|
||||
case GpioIntType::FALLING_EDGE:
|
||||
return GPIO_INTR_NEGEDGE;
|
||||
case GpioIntType::ANY_EDGE:
|
||||
return GPIO_INTR_ANYEDGE;
|
||||
case GpioIntType::LOW_LEVEL:
|
||||
return GPIO_INTR_LOW_LEVEL;
|
||||
case GpioIntType::HIGH_LEVEL:
|
||||
return GPIO_INTR_HIGH_LEVEL;
|
||||
default:
|
||||
return GPIO_INTR_DISABLE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* @file gpio.hpp
|
||||
* @brief GPIO wrapper component header - Wrapper for ESP-IDF GPIO functionality
|
||||
* @author Mahmoud Elmohtady
|
||||
* @company Nabd solutions - ASF
|
||||
* @copyright Copyright (c) 2025
|
||||
*/
|
||||
|
||||
#ifndef GPIO_HPP
|
||||
#define GPIO_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_err.h"
|
||||
|
||||
/**
|
||||
* @brief GPIO pin mode enumeration
|
||||
*/
|
||||
enum class GpioMode
|
||||
{
|
||||
INPUT,
|
||||
OUTPUT,
|
||||
INPUT_PULLUP,
|
||||
INPUT_PULLDOWN,
|
||||
OUTPUT_OD
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief GPIO interrupt type enumeration
|
||||
*/
|
||||
enum class GpioIntType
|
||||
{
|
||||
DISABLE,
|
||||
RISING_EDGE,
|
||||
FALLING_EDGE,
|
||||
ANY_EDGE,
|
||||
LOW_LEVEL,
|
||||
HIGH_LEVEL
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief GPIO callback function type
|
||||
*/
|
||||
using GpioCallback = void (*)(uint32_t pin, void* arg);
|
||||
|
||||
/**
|
||||
* @brief GPIO wrapper class
|
||||
*
|
||||
* Provides a C++ wrapper for ESP-IDF GPIO functionality.
|
||||
* This class encapsulates ESP-IDF GPIO driver functions in an object-oriented interface.
|
||||
*/
|
||||
class Gpio
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @details Initializes the GPIO wrapper instance
|
||||
*/
|
||||
Gpio();
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
* @details Cleans up resources and uninstalls ISR if installed
|
||||
*/
|
||||
~Gpio();
|
||||
|
||||
/**
|
||||
* @brief Configure a GPIO pin
|
||||
* @param pin GPIO pin number (0-39 for ESP32)
|
||||
* @param mode GPIO mode configuration
|
||||
* @return true if configured successfully, false otherwise
|
||||
* @note This function configures the pin direction, pull-up/down resistors
|
||||
*/
|
||||
bool configure(uint32_t pin, GpioMode mode);
|
||||
|
||||
/**
|
||||
* @brief Set GPIO pin level
|
||||
* @param pin GPIO pin number
|
||||
* @param level Pin level (0 for low, 1 for high)
|
||||
* @return true if set successfully, false otherwise
|
||||
* @note Only works for output pins
|
||||
*/
|
||||
bool setLevel(uint32_t pin, uint32_t level);
|
||||
|
||||
/**
|
||||
* @brief Get GPIO pin level
|
||||
* @param pin GPIO pin number
|
||||
* @return Pin level (0 for low, 1 for high), or -1 on error
|
||||
* @note Works for both input and output pins
|
||||
*/
|
||||
int32_t getLevel(uint32_t pin);
|
||||
|
||||
/**
|
||||
* @brief Toggle GPIO pin level
|
||||
* @param pin GPIO pin number
|
||||
* @return true if toggled successfully, false otherwise
|
||||
* @note Only works for output pins
|
||||
*/
|
||||
bool toggleLevel(uint32_t pin);
|
||||
|
||||
/**
|
||||
* @brief Install GPIO interrupt service
|
||||
* @param flags Allocation flags for interrupt service
|
||||
* @return true if installed successfully, false otherwise
|
||||
* @note Must be called before attaching interrupts
|
||||
*/
|
||||
bool installIsr(int flags = 0);
|
||||
|
||||
/**
|
||||
* @brief Uninstall GPIO interrupt service
|
||||
* @return true if uninstalled successfully, false otherwise
|
||||
*/
|
||||
bool uninstallIsr();
|
||||
|
||||
/**
|
||||
* @brief Attach interrupt to a GPIO pin
|
||||
* @param pin GPIO pin number
|
||||
* @param intType Interrupt type
|
||||
* @param callback Callback function
|
||||
* @param arg Argument passed to callback
|
||||
* @return true if attached successfully, false otherwise
|
||||
* @note ISR must be installed before calling this function
|
||||
*/
|
||||
bool attachInterrupt(uint32_t pin, GpioIntType intType, GpioCallback callback, void* arg);
|
||||
|
||||
/**
|
||||
* @brief Detach interrupt from a GPIO pin
|
||||
* @param pin GPIO pin number
|
||||
* @return true if detached successfully, false otherwise
|
||||
*/
|
||||
bool detachInterrupt(uint32_t pin);
|
||||
|
||||
/**
|
||||
* @brief Enable interrupt for a GPIO pin
|
||||
* @param pin GPIO pin number
|
||||
* @return true if enabled successfully, false otherwise
|
||||
*/
|
||||
bool enableInterrupt(uint32_t pin);
|
||||
|
||||
/**
|
||||
* @brief Disable interrupt for a GPIO pin
|
||||
* @param pin GPIO pin number
|
||||
* @return true if disabled successfully, false otherwise
|
||||
*/
|
||||
bool disableInterrupt(uint32_t pin);
|
||||
|
||||
/**
|
||||
* @brief Check if GPIO pin is valid
|
||||
* @param pin GPIO pin number
|
||||
* @return true if pin is valid, false otherwise
|
||||
*/
|
||||
static bool isValidPin(uint32_t pin);
|
||||
|
||||
private:
|
||||
bool m_isrInstalled_; ///< Flag indicating if ISR is installed
|
||||
|
||||
/**
|
||||
* @brief Convert GpioMode to ESP-IDF gpio_mode_t
|
||||
* @param mode GPIO mode
|
||||
* @return ESP-IDF gpio_mode_t
|
||||
*/
|
||||
gpio_mode_t convertMode(GpioMode mode);
|
||||
|
||||
/**
|
||||
* @brief Convert GpioIntType to ESP-IDF gpio_int_type_t
|
||||
* @param intType Interrupt type
|
||||
* @return ESP-IDF gpio_int_type_t
|
||||
*/
|
||||
gpio_int_type_t convertIntType(GpioIntType intType);
|
||||
};
|
||||
|
||||
#endif // GPIO_HPP
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
ID,Component,Level,Criticality,Message
|
||||
2300,GPIO,INFO,Low,GPIO wrapper initialized
|
||||
2301,GPIO,INFO,Low,GPIO wrapper destroyed
|
||||
2302,GPIO,ERROR,High,Invalid GPIO pin: %lu
|
||||
2303,GPIO,ERROR,High,Failed to configure GPIO pin %lu: %s
|
||||
2304,GPIO,INFO,Low,GPIO pin %lu configured successfully
|
||||
2305,GPIO,ERROR,High,Failed to set GPIO pin %lu level: %s
|
||||
2306,GPIO,WARNING,Medium,GPIO ISR already installed
|
||||
2307,GPIO,ERROR,High,Failed to install GPIO ISR service: %s
|
||||
2308,GPIO,INFO,Low,GPIO initialized successfully
|
||||
2309,GPIO,WARNING,Medium,GPIO ISR not installed
|
||||
2310,GPIO,INFO,Low,GPIO ISR service uninstalled
|
||||
2311,GPIO,WARNING,Medium,GPIO ISR not installed, installing now
|
||||
2312,GPIO,ERROR,High,Failed to set interrupt type for pin %lu: %s
|
||||
2313,GPIO,ERROR,High,Failed to add ISR handler for pin %lu: %s
|
||||
2314,GPIO,INFO,Low,Interrupt attached to GPIO pin %lu
|
||||
2315,GPIO,ERROR,High,Failed to disable interrupt for pin %lu: %s
|
||||
2316,GPIO,ERROR,High,Failed to remove ISR handler for pin %lu: %s
|
||||
2317,GPIO,INFO,Low,Interrupt detached from GPIO pin %lu
|
||||
2318,GPIO,ERROR,High,Failed to enable interrupt for pin %lu: %s
|
||||
2319,GPIO,ERROR,High,Failed to disable interrupt for pin %lu: %s
|
||||
|
Can't render this file because it has a wrong number of fields in line 13.
|
@@ -0,0 +1,34 @@
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
|
||||
folder_path = os.path.abspath(os.path.join("components", "system_tests"))
|
||||
if folder_path not in sys.path:
|
||||
sys.path.append(folder_path)
|
||||
|
||||
from scan_serial import ESP32Runner
|
||||
|
||||
def test_gpio_initialize():
|
||||
runner = ESP32Runner(mode="SIM", port="COM9")
|
||||
runner.start()
|
||||
print("--- QEMU Runner Started ---", flush=True)
|
||||
try:
|
||||
start_time = time.time()
|
||||
while time.time() - start_time < 30:
|
||||
line = runner.get_line(timeout=1.0)
|
||||
if line:
|
||||
print(line, flush=True)
|
||||
if "GPIO wrapper initialized" in line or "GPIO ISR service installed successfully" in line:
|
||||
print("SUCCESS CRITERIA MET!", flush=True)
|
||||
return 0
|
||||
if runner.process.poll() is not None:
|
||||
print(f"Process exited with code: {runner.process.returncode}", flush=True)
|
||||
return 1
|
||||
finally:
|
||||
runner.stop()
|
||||
print("Done.", flush=True)
|
||||
return 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
exit_code = test_gpio_initialize()
|
||||
sys.exit(exit_code)
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<test_scenario>
|
||||
<!-- The configuration for the test environment. -->
|
||||
<!-- Available configurations: SIMULATE, HIL -->
|
||||
<config>SIMULATE</config>
|
||||
|
||||
<test_case>
|
||||
<test_case_id>GPIO_INIT_TEST</test_case_id>
|
||||
<!-- The main command that executes the test itself. -->
|
||||
<test_exec>python components/ESP_IDF_FW_wrappers/gpio/test/gpio_init_test.py</test_exec>
|
||||
</test_case>
|
||||
|
||||
|
||||
</test_scenario>
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* @file test_gpio.cpp
|
||||
* @brief Unit tests for GPIO wrapper component
|
||||
* @author Mahmoud Elmohtady
|
||||
* @company Nabd solutions - ASF
|
||||
* @copyright Copyright (c) 2025
|
||||
*/
|
||||
|
||||
#include "unity.h"
|
||||
#include "gpio.hpp"
|
||||
|
||||
extern "C" {
|
||||
|
||||
void setUp(void)
|
||||
{
|
||||
}
|
||||
|
||||
void tearDown(void)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Test GPIO configuration
|
||||
*/
|
||||
void test_gpio_configure(void)
|
||||
{
|
||||
Gpio gpio;
|
||||
bool result = gpio.configure(2, GpioMode::OUTPUT);
|
||||
TEST_ASSERT_TRUE(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Test GPIO set level
|
||||
*/
|
||||
void test_gpio_set_level(void)
|
||||
{
|
||||
Gpio gpio;
|
||||
gpio.configure(2, GpioMode::OUTPUT);
|
||||
bool result = gpio.setLevel(2, 1);
|
||||
TEST_ASSERT_TRUE(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Test GPIO get level
|
||||
*/
|
||||
void test_gpio_get_level(void)
|
||||
{
|
||||
Gpio gpio;
|
||||
gpio.configure(2, GpioMode::INPUT);
|
||||
int32_t result = gpio.getLevel(2);
|
||||
TEST_ASSERT_TRUE(result >= 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Test GPIO ISR installation
|
||||
*/
|
||||
void test_gpio_install_isr(void)
|
||||
{
|
||||
Gpio gpio;
|
||||
bool result = gpio.installIsr();
|
||||
TEST_ASSERT_TRUE(result);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
idf_component_register(
|
||||
SRCS "com/i2c.cpp"
|
||||
INCLUDE_DIRS "com"
|
||||
REQUIRES driver logger
|
||||
|
||||
)
|
||||
@@ -0,0 +1,216 @@
|
||||
# I2C Wrapper Module
|
||||
|
||||
## Overview
|
||||
|
||||
The I2C wrapper module provides a C++ object-oriented interface for ESP-IDF I2C functionality. This module encapsulates the ESP-IDF I2C driver functions and provides a clean, easy-to-use API for I2C communication with peripheral devices.
|
||||
|
||||
## Features
|
||||
|
||||
- **Multi-Port Support**: Support for I2C0 and I2C1
|
||||
- **Master/Slave Mode**: Configurable as master or slave device
|
||||
- **Register Operations**: Convenient register read/write functions
|
||||
- **Bus Scanning**: Automatic device discovery on I2C bus
|
||||
- **Timeout Support**: Configurable timeouts for all operations
|
||||
- **Pull-up Configuration**: Internal pull-up resistor control
|
||||
- **Error Handling**: Comprehensive error checking and logging
|
||||
|
||||
## Architecture
|
||||
|
||||
### Class Diagram
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ I2c │
|
||||
├─────────────────────────────────────┤
|
||||
│ - m_isInitialized_[2]: bool │
|
||||
├─────────────────────────────────────┤
|
||||
│ + I2c() │
|
||||
│ + ~I2c() │
|
||||
│ + initialize(port, config): bool │
|
||||
│ + deinitialize(port): bool │
|
||||
│ + write(...): int32_t │
|
||||
│ + read(...): int32_t │
|
||||
│ + writeRead(...): int32_t │
|
||||
│ + writeRegister(...): bool │
|
||||
│ + readRegister(...): int32_t │
|
||||
│ + scanBus(...): int32_t │
|
||||
│ + isInitialized(port): bool │
|
||||
│ + getDefaultConfig(): I2cConfig │
|
||||
│ - convertPort(port): i2c_port_t │
|
||||
│ - convertConfig(cfg): i2c_config_t │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Enumerations
|
||||
|
||||
#### I2cPort
|
||||
- `PORT_0`: I2C port 0
|
||||
- `PORT_1`: I2C port 1
|
||||
|
||||
#### I2cMode
|
||||
- `MASTER`: Master mode
|
||||
- `SLAVE`: Slave mode
|
||||
|
||||
### Configuration Structure
|
||||
|
||||
```cpp
|
||||
struct I2cConfig {
|
||||
I2cMode mode; // I2C mode (master/slave)
|
||||
uint32_t sdaPin; // SDA pin number
|
||||
uint32_t sclPin; // SCL pin number
|
||||
uint32_t clkSpeedHz; // Clock speed in Hz (for master mode)
|
||||
bool sdaPullupEnable; // Enable SDA pull-up resistor
|
||||
bool sclPullupEnable; // Enable SCL pull-up resistor
|
||||
uint8_t slaveAddress; // Slave address (for slave mode)
|
||||
uint32_t rxBufferLen; // RX buffer length (for slave mode)
|
||||
uint32_t txBufferLen; // TX buffer length (for slave mode)
|
||||
};
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic I2C Master Communication
|
||||
|
||||
```cpp
|
||||
#include "i2c.hpp"
|
||||
|
||||
// Create I2C instance
|
||||
I2c i2c;
|
||||
|
||||
// Get default configuration
|
||||
I2cConfig config = I2c::getDefaultConfig();
|
||||
config.sdaPin = 21;
|
||||
config.sclPin = 22;
|
||||
config.clkSpeedHz = 400000; // 400kHz
|
||||
|
||||
// Initialize I2C
|
||||
i2c.initialize(I2cPort::PORT_0, config);
|
||||
|
||||
// Write data to device
|
||||
uint8_t data[] = {0x01, 0x02, 0x03};
|
||||
i2c.write(I2cPort::PORT_0, 0x48, data, sizeof(data));
|
||||
|
||||
// Read data from device
|
||||
uint8_t buffer[10];
|
||||
int32_t bytesRead = i2c.read(I2cPort::PORT_0, 0x48, buffer, sizeof(buffer));
|
||||
```
|
||||
|
||||
### Register Operations
|
||||
|
||||
```cpp
|
||||
// Write to register
|
||||
i2c.writeRegister(I2cPort::PORT_0, 0x48, 0x10, 0xFF);
|
||||
|
||||
// Read from register
|
||||
int32_t regValue = i2c.readRegister(I2cPort::PORT_0, 0x48, 0x10);
|
||||
|
||||
// Write register address and read multiple bytes
|
||||
uint8_t regData[4];
|
||||
i2c.writeRead(I2cPort::PORT_0, 0x48, 0x10, regData, sizeof(regData));
|
||||
```
|
||||
|
||||
### Bus Scanning
|
||||
|
||||
```cpp
|
||||
// Scan for devices on the bus
|
||||
uint8_t devices[20];
|
||||
int32_t deviceCount = i2c.scanBus(I2cPort::PORT_0, devices, sizeof(devices));
|
||||
|
||||
printf("Found %ld devices:\n", deviceCount);
|
||||
for (int i = 0; i < deviceCount; i++) {
|
||||
printf("Device at address: 0x%02X\n", devices[i]);
|
||||
}
|
||||
```
|
||||
|
||||
### Slave Mode Configuration
|
||||
|
||||
```cpp
|
||||
I2cConfig slaveConfig = {};
|
||||
slaveConfig.mode = I2cMode::SLAVE;
|
||||
slaveConfig.sdaPin = 21;
|
||||
slaveConfig.sclPin = 22;
|
||||
slaveConfig.slaveAddress = 0x28;
|
||||
slaveConfig.rxBufferLen = 256;
|
||||
slaveConfig.txBufferLen = 256;
|
||||
slaveConfig.sdaPullupEnable = true;
|
||||
slaveConfig.sclPullupEnable = true;
|
||||
|
||||
i2c.initialize(I2cPort::PORT_1, slaveConfig);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### Constructor/Destructor
|
||||
|
||||
- **I2c()**: Initialize I2C wrapper instance
|
||||
- **~I2c()**: Clean up resources and deinitialize all ports
|
||||
|
||||
### Configuration Methods
|
||||
|
||||
- **initialize(port, config)**: Initialize I2C port with configuration
|
||||
- **deinitialize(port)**: Deinitialize I2C port
|
||||
- **isInitialized(port)**: Check if port is initialized
|
||||
- **getDefaultConfig()**: Get default configuration structure
|
||||
|
||||
### Communication Methods
|
||||
|
||||
- **write(port, address, data, length, timeout)**: Write data to device
|
||||
- **read(port, address, buffer, length, timeout)**: Read data from device
|
||||
- **writeRead(port, address, regAddr, buffer, length, timeout)**: Write register address and read data
|
||||
|
||||
### Register Operations
|
||||
|
||||
- **writeRegister(port, address, regAddr, value, timeout)**: Write single register
|
||||
- **readRegister(port, address, regAddr, timeout)**: Read single register
|
||||
|
||||
### Utility Methods
|
||||
|
||||
- **scanBus(port, devices, maxDevices)**: Scan bus for connected devices
|
||||
|
||||
## Error Handling
|
||||
|
||||
The module provides comprehensive error handling:
|
||||
- Port validation and initialization checks
|
||||
- Device address validation (0x08-0x77 range)
|
||||
- ESP-IDF error codes are caught and logged
|
||||
- Return values indicate success/failure for all operations
|
||||
- Detailed logging for debugging and troubleshooting
|
||||
|
||||
## Dependencies
|
||||
|
||||
- ESP-IDF I2C driver (`driver/i2c.h`)
|
||||
- ESP-IDF error handling (`esp_err.h`)
|
||||
- ESP-IDF logging (`esp_log.h`)
|
||||
- FreeRTOS (`freertos/FreeRTOS.h`, `freertos/task.h`)
|
||||
|
||||
## Thread Safety
|
||||
|
||||
The I2C wrapper uses ESP-IDF's thread-safe I2C driver. Multiple tasks can safely use different I2C ports simultaneously, but access to the same port should be synchronized.
|
||||
|
||||
## Memory Usage
|
||||
|
||||
- Fixed memory footprint per instance
|
||||
- Configurable buffer sizes for slave mode
|
||||
- No dynamic memory allocation in wrapper layer
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
- Direct ESP-IDF function calls for optimal performance
|
||||
- Minimal overhead over raw ESP-IDF calls
|
||||
- Configurable clock speeds (100kHz, 400kHz, 1MHz)
|
||||
- Bus scanning can be time-consuming for large address ranges
|
||||
|
||||
## Common I2C Speeds
|
||||
|
||||
- **Standard Mode**: 100 kHz
|
||||
- **Fast Mode**: 400 kHz
|
||||
- **Fast Mode Plus**: 1 MHz
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **Pull-up Resistors**: Ensure proper pull-up resistors on SDA/SCL lines
|
||||
2. **Address Conflicts**: Check for address conflicts when using multiple devices
|
||||
3. **Clock Stretching**: Some devices require clock stretching support
|
||||
4. **Bus Capacitance**: Long wires or many devices can affect signal integrity
|
||||
@@ -0,0 +1,273 @@
|
||||
/**
|
||||
* @file i2c.cpp
|
||||
* @brief I2C wrapper component implementation
|
||||
* @author Mahmoud Elmohtady
|
||||
* @company Nabd solutions - ASF
|
||||
* @copyright Copyright (c) 2025
|
||||
*/
|
||||
|
||||
#include "i2c.hpp"
|
||||
#include "logger.hpp"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
static const char* TAG = "I2C_WRAPPER";
|
||||
|
||||
I2c::I2c()
|
||||
{
|
||||
m_isInitialized_[0] = false;
|
||||
m_isInitialized_[1] = false;
|
||||
ASF_LOGI(TAG, 2400, asf::logger::Criticality::LOW, "I2C wrapper initialized");
|
||||
}
|
||||
|
||||
I2c::~I2c()
|
||||
{
|
||||
// Deinitialize all ports
|
||||
for (int i = 0; i < 2; i++) {
|
||||
if (m_isInitialized_[i]) {
|
||||
deinitialize(static_cast<I2cPort>(i));
|
||||
}
|
||||
}
|
||||
ASF_LOGI(TAG, 2401, asf::logger::Criticality::LOW, "I2C wrapper destroyed");
|
||||
}
|
||||
|
||||
bool I2c::initialize(I2cPort port, const I2cConfig& config)
|
||||
{
|
||||
uint8_t portIdx = static_cast<uint8_t>(port);
|
||||
i2c_port_t i2cPort = convertPort(port);
|
||||
|
||||
if (m_isInitialized_[portIdx]) {
|
||||
ASF_LOGW(TAG, 2402, asf::logger::Criticality::MEDIUM, "I2C port %d already initialized", portIdx);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Configure I2C parameters
|
||||
i2c_config_t i2cConfig = convertConfig(config);
|
||||
|
||||
esp_err_t ret = i2c_param_config(i2cPort, &i2cConfig);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2403, asf::logger::Criticality::HIGH, "Failed to configure I2C port %d: %s", portIdx, esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Install I2C driver
|
||||
ret = i2c_driver_install(i2cPort, i2cConfig.mode, config.rxBufferLen, config.txBufferLen, 0);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2404, asf::logger::Criticality::HIGH, "Failed to install I2C driver for port %d: %s", portIdx, esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
m_isInitialized_[portIdx] = true;
|
||||
ASF_LOGI(TAG, 2405, asf::logger::Criticality::LOW, "I2C initialized successfully");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool I2c::deinitialize(I2cPort port)
|
||||
{
|
||||
uint8_t portIdx = static_cast<uint8_t>(port);
|
||||
i2c_port_t i2cPort = convertPort(port);
|
||||
|
||||
if (!m_isInitialized_[portIdx]) {
|
||||
ASF_LOGW(TAG, 2406, asf::logger::Criticality::MEDIUM, "I2C port %d not initialized", portIdx);
|
||||
return true;
|
||||
}
|
||||
|
||||
esp_err_t ret = i2c_driver_delete(i2cPort);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2407, asf::logger::Criticality::HIGH, "Failed to delete I2C driver for port %d: %s", portIdx, esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
m_isInitialized_[portIdx] = false;
|
||||
ASF_LOGI(TAG, 2408, asf::logger::Criticality::LOW, "I2C port %d deinitialized", portIdx);
|
||||
return true;
|
||||
}
|
||||
|
||||
int32_t I2c::write(I2cPort port, uint8_t deviceAddress, const uint8_t* data, size_t dataLen, uint32_t timeoutMs)
|
||||
{
|
||||
uint8_t portIdx = static_cast<uint8_t>(port);
|
||||
i2c_port_t i2cPort = convertPort(port);
|
||||
|
||||
if (!m_isInitialized_[portIdx]) {
|
||||
ASF_LOGE(TAG, 2409, asf::logger::Criticality::HIGH, "I2C port %d not initialized", portIdx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (data == nullptr || dataLen == 0) {
|
||||
ASF_LOGE(TAG, 2410, asf::logger::Criticality::HIGH, "Invalid data or length");
|
||||
return -1;
|
||||
}
|
||||
|
||||
TickType_t timeout = pdMS_TO_TICKS(timeoutMs);
|
||||
|
||||
esp_err_t ret = i2c_master_write_to_device(i2cPort, deviceAddress, data, dataLen, timeout);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2411, asf::logger::Criticality::HIGH, "Failed to write to device 0x%02X on port %d: %s",
|
||||
deviceAddress, portIdx, esp_err_to_name(ret));
|
||||
return -1;
|
||||
}
|
||||
|
||||
return static_cast<int32_t>(dataLen);
|
||||
}
|
||||
|
||||
int32_t I2c::read(I2cPort port, uint8_t deviceAddress, uint8_t* data, size_t dataLen, uint32_t timeoutMs)
|
||||
{
|
||||
uint8_t portIdx = static_cast<uint8_t>(port);
|
||||
i2c_port_t i2cPort = convertPort(port);
|
||||
|
||||
if (!m_isInitialized_[portIdx]) {
|
||||
ASF_LOGE(TAG, 2412, asf::logger::Criticality::HIGH, "I2C port %d not initialized", portIdx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (data == nullptr || dataLen == 0) {
|
||||
ASF_LOGE(TAG, 2413, asf::logger::Criticality::HIGH, "Invalid data buffer or length");
|
||||
return -1;
|
||||
}
|
||||
|
||||
TickType_t timeout = pdMS_TO_TICKS(timeoutMs);
|
||||
|
||||
esp_err_t ret = i2c_master_read_from_device(i2cPort, deviceAddress, data, dataLen, timeout);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2414, asf::logger::Criticality::HIGH, "Failed to read from device 0x%02X on port %d: %s",
|
||||
deviceAddress, portIdx, esp_err_to_name(ret));
|
||||
return -1;
|
||||
}
|
||||
|
||||
return static_cast<int32_t>(dataLen);
|
||||
}
|
||||
|
||||
int32_t I2c::writeRead(I2cPort port, uint8_t deviceAddress, uint8_t regAddress, uint8_t* data, size_t dataLen, uint32_t timeoutMs)
|
||||
{
|
||||
uint8_t portIdx = static_cast<uint8_t>(port);
|
||||
i2c_port_t i2cPort = convertPort(port);
|
||||
|
||||
if (!m_isInitialized_[portIdx]) {
|
||||
ASF_LOGE(TAG, 2415, asf::logger::Criticality::HIGH, "I2C port %d not initialized", portIdx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (data == nullptr || dataLen == 0) {
|
||||
ASF_LOGE(TAG, 2416, asf::logger::Criticality::HIGH, "Invalid data buffer or length");
|
||||
return -1;
|
||||
}
|
||||
|
||||
TickType_t timeout = pdMS_TO_TICKS(timeoutMs);
|
||||
|
||||
esp_err_t ret = i2c_master_write_read_device(i2cPort, deviceAddress,
|
||||
®Address, 1, data, dataLen, timeout);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2417, asf::logger::Criticality::HIGH, "Failed to write-read device 0x%02X on port %d: %s",
|
||||
deviceAddress, portIdx, esp_err_to_name(ret));
|
||||
return -1;
|
||||
}
|
||||
|
||||
return static_cast<int32_t>(dataLen);
|
||||
}
|
||||
|
||||
bool I2c::writeRegister(I2cPort port, uint8_t deviceAddress, uint8_t regAddress, uint8_t regValue, uint32_t timeoutMs)
|
||||
{
|
||||
uint8_t writeData[2] = {regAddress, regValue};
|
||||
int32_t result = write(port, deviceAddress, writeData, 2, timeoutMs);
|
||||
return result == 2;
|
||||
}
|
||||
|
||||
int32_t I2c::readRegister(I2cPort port, uint8_t deviceAddress, uint8_t regAddress, uint32_t timeoutMs)
|
||||
{
|
||||
uint8_t regValue = 0;
|
||||
int32_t result = writeRead(port, deviceAddress, regAddress, ®Value, 1, timeoutMs);
|
||||
|
||||
if (result == 1) {
|
||||
return regValue;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int32_t I2c::scanBus(I2cPort port, uint8_t* devices, size_t maxDevices)
|
||||
{
|
||||
uint8_t portIdx = static_cast<uint8_t>(port);
|
||||
i2c_port_t i2cPort = convertPort(port);
|
||||
|
||||
if (!m_isInitialized_[portIdx]) {
|
||||
ASF_LOGE(TAG, 2418, asf::logger::Criticality::HIGH, "I2C port %d not initialized", portIdx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (devices == nullptr || maxDevices == 0) {
|
||||
ASF_LOGE(TAG, 2419, asf::logger::Criticality::HIGH, "Invalid devices buffer or size");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int32_t deviceCount = 0;
|
||||
uint8_t testData = 0;
|
||||
|
||||
ASF_LOGI(TAG, 2420, asf::logger::Criticality::LOW, "Scanning I2C bus on port %d...", portIdx);
|
||||
|
||||
for (uint8_t addr = 0x08; addr < 0x78 && deviceCount < maxDevices; addr++) {
|
||||
esp_err_t ret = i2c_master_write_to_device(i2cPort, addr, &testData, 0, pdMS_TO_TICKS(100));
|
||||
|
||||
if (ret == ESP_OK) {
|
||||
devices[deviceCount] = addr;
|
||||
deviceCount++;
|
||||
ASF_LOGI(TAG, 2421, asf::logger::Criticality::LOW, "Found device at address 0x%02X", addr);
|
||||
}
|
||||
}
|
||||
|
||||
ASF_LOGI(TAG, 2422, asf::logger::Criticality::LOW, "I2C scan complete. Found %ld devices", deviceCount);
|
||||
return deviceCount;
|
||||
}
|
||||
|
||||
bool I2c::isInitialized(I2cPort port) const
|
||||
{
|
||||
uint8_t portIdx = static_cast<uint8_t>(port);
|
||||
return m_isInitialized_[portIdx];
|
||||
}
|
||||
|
||||
I2cConfig I2c::getDefaultConfig()
|
||||
{
|
||||
I2cConfig config = {};
|
||||
config.mode = I2cMode::MASTER;
|
||||
config.sdaPin = GPIO_NUM_21;
|
||||
config.sclPin = GPIO_NUM_22;
|
||||
config.clkSpeedHz = 100000; // 100kHz
|
||||
config.sdaPullupEnable = true;
|
||||
config.sclPullupEnable = true;
|
||||
config.slaveAddress = 0;
|
||||
config.rxBufferLen = 0; // Not used in master mode
|
||||
config.txBufferLen = 0; // Not used in master mode
|
||||
return config;
|
||||
}
|
||||
|
||||
i2c_port_t I2c::convertPort(I2cPort port)
|
||||
{
|
||||
switch (port) {
|
||||
case I2cPort::PORT_0:
|
||||
return I2C_NUM_0;
|
||||
case I2cPort::PORT_1:
|
||||
return I2C_NUM_1;
|
||||
default:
|
||||
return I2C_NUM_0;
|
||||
}
|
||||
}
|
||||
|
||||
i2c_config_t I2c::convertConfig(const I2cConfig& config)
|
||||
{
|
||||
i2c_config_t i2cConfig = {};
|
||||
i2cConfig.mode = static_cast<i2c_mode_t>(config.mode);
|
||||
i2cConfig.sda_io_num = static_cast<gpio_num_t>(config.sdaPin);
|
||||
i2cConfig.scl_io_num = static_cast<gpio_num_t>(config.sclPin);
|
||||
i2cConfig.sda_pullup_en = config.sdaPullupEnable;
|
||||
i2cConfig.scl_pullup_en = config.sclPullupEnable;
|
||||
|
||||
if (config.mode == I2cMode::MASTER) {
|
||||
i2cConfig.master.clk_speed = config.clkSpeedHz;
|
||||
} else {
|
||||
i2cConfig.slave.addr_10bit_en = 0;
|
||||
i2cConfig.slave.slave_addr = config.slaveAddress;
|
||||
i2cConfig.slave.maximum_speed = config.clkSpeedHz;
|
||||
}
|
||||
|
||||
i2cConfig.clk_flags = 0;
|
||||
return i2cConfig;
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
/**
|
||||
* @file i2c.hpp
|
||||
* @brief I2C wrapper component header - Wrapper for ESP-IDF I2C functionality
|
||||
* @author Mahmoud Elmohtady
|
||||
* @company Nabd solutions - ASF
|
||||
* @copyright Copyright (c) 2025
|
||||
*/
|
||||
|
||||
#ifndef I2C_HPP
|
||||
#define I2C_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include "driver/i2c.h"
|
||||
#include "esp_err.h"
|
||||
|
||||
/**
|
||||
* @brief I2C port enumeration
|
||||
*/
|
||||
enum class I2cPort
|
||||
{
|
||||
PORT_0,
|
||||
PORT_1
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief I2C mode enumeration
|
||||
*/
|
||||
enum class I2cMode
|
||||
{
|
||||
MASTER = I2C_MODE_MASTER,
|
||||
SLAVE = I2C_MODE_SLAVE
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief I2C configuration structure
|
||||
*/
|
||||
struct I2cConfig
|
||||
{
|
||||
I2cMode mode; ///< I2C mode (master/slave)
|
||||
uint32_t sdaPin; ///< SDA pin number
|
||||
uint32_t sclPin; ///< SCL pin number
|
||||
uint32_t clkSpeedHz; ///< Clock speed in Hz (for master mode)
|
||||
bool sdaPullupEnable; ///< Enable SDA pull-up resistor
|
||||
bool sclPullupEnable; ///< Enable SCL pull-up resistor
|
||||
uint8_t slaveAddress; ///< Slave address (for slave mode)
|
||||
uint32_t rxBufferLen; ///< RX buffer length (for slave mode)
|
||||
uint32_t txBufferLen; ///< TX buffer length (for slave mode)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief I2C wrapper class
|
||||
*
|
||||
* Provides a C++ wrapper for ESP-IDF I2C functionality.
|
||||
* This class encapsulates ESP-IDF I2C driver functions in an object-oriented interface.
|
||||
*/
|
||||
class I2c
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @details Initializes the I2C wrapper instance
|
||||
*/
|
||||
I2c();
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
* @details Cleans up resources and deinitializes all I2C ports
|
||||
*/
|
||||
~I2c();
|
||||
|
||||
/**
|
||||
* @brief Initialize I2C bus
|
||||
* @param port I2C port number
|
||||
* @param config I2C configuration parameters
|
||||
* @return true if initialized successfully, false otherwise
|
||||
* @note This function configures pins, clock speed, and mode
|
||||
*/
|
||||
bool initialize(I2cPort port, const I2cConfig& config);
|
||||
|
||||
/**
|
||||
* @brief Deinitialize I2C bus
|
||||
* @param port I2C port number
|
||||
* @return true if deinitialized successfully, false otherwise
|
||||
*/
|
||||
bool deinitialize(I2cPort port);
|
||||
|
||||
/**
|
||||
* @brief Write data to I2C device
|
||||
* @param port I2C port number
|
||||
* @param deviceAddress 7-bit device address
|
||||
* @param data Pointer to data buffer
|
||||
* @param dataLen Number of bytes to write
|
||||
* @param timeoutMs Timeout in milliseconds (default: 1000ms)
|
||||
* @return Number of bytes written, or -1 on error
|
||||
*/
|
||||
int32_t write(I2cPort port, uint8_t deviceAddress, const uint8_t* data, size_t dataLen, uint32_t timeoutMs = 1000);
|
||||
|
||||
/**
|
||||
* @brief Read data from I2C device
|
||||
* @param port I2C port number
|
||||
* @param deviceAddress 7-bit device address
|
||||
* @param data Pointer to receive buffer
|
||||
* @param dataLen Number of bytes to read
|
||||
* @param timeoutMs Timeout in milliseconds (default: 1000ms)
|
||||
* @return Number of bytes read, or -1 on error
|
||||
*/
|
||||
int32_t read(I2cPort port, uint8_t deviceAddress, uint8_t* data, size_t dataLen, uint32_t timeoutMs = 1000);
|
||||
|
||||
/**
|
||||
* @brief Write to register and read from I2C device
|
||||
* @param port I2C port number
|
||||
* @param deviceAddress 7-bit device address
|
||||
* @param regAddress Register address to write
|
||||
* @param data Pointer to receive buffer
|
||||
* @param dataLen Number of bytes to read
|
||||
* @param timeoutMs Timeout in milliseconds (default: 1000ms)
|
||||
* @return Number of bytes read, or -1 on error
|
||||
*/
|
||||
int32_t writeRead(I2cPort port, uint8_t deviceAddress, uint8_t regAddress, uint8_t* data, size_t dataLen, uint32_t timeoutMs = 1000);
|
||||
|
||||
/**
|
||||
* @brief Write register value to I2C device
|
||||
* @param port I2C port number
|
||||
* @param deviceAddress 7-bit device address
|
||||
* @param regAddress Register address
|
||||
* @param regValue Register value
|
||||
* @param timeoutMs Timeout in milliseconds (default: 1000ms)
|
||||
* @return true if written successfully, false otherwise
|
||||
*/
|
||||
bool writeRegister(I2cPort port, uint8_t deviceAddress, uint8_t regAddress, uint8_t regValue, uint32_t timeoutMs = 1000);
|
||||
|
||||
/**
|
||||
* @brief Read register value from I2C device
|
||||
* @param port I2C port number
|
||||
* @param deviceAddress 7-bit device address
|
||||
* @param regAddress Register address
|
||||
* @param timeoutMs Timeout in milliseconds (default: 1000ms)
|
||||
* @return Register value, or -1 on error
|
||||
*/
|
||||
int32_t readRegister(I2cPort port, uint8_t deviceAddress, uint8_t regAddress, uint32_t timeoutMs = 1000);
|
||||
|
||||
/**
|
||||
* @brief Scan I2C bus for devices
|
||||
* @param port I2C port number
|
||||
* @param devices Array to store found device addresses
|
||||
* @param maxDevices Maximum number of devices to find
|
||||
* @return Number of devices found
|
||||
*/
|
||||
int32_t scanBus(I2cPort port, uint8_t* devices, size_t maxDevices);
|
||||
|
||||
/**
|
||||
* @brief Check if I2C port is initialized
|
||||
* @param port I2C port number
|
||||
* @return true if initialized, false otherwise
|
||||
*/
|
||||
bool isInitialized(I2cPort port) const;
|
||||
|
||||
/**
|
||||
* @brief Get default I2C configuration
|
||||
* @return Default I2C configuration structure
|
||||
*/
|
||||
static I2cConfig getDefaultConfig();
|
||||
|
||||
private:
|
||||
bool m_isInitialized_[2]; ///< Initialization status for each port
|
||||
|
||||
/**
|
||||
* @brief Convert I2cPort to ESP-IDF i2c_port_t
|
||||
* @param port I2C port
|
||||
* @return ESP-IDF i2c_port_t
|
||||
*/
|
||||
i2c_port_t convertPort(I2cPort port);
|
||||
|
||||
/**
|
||||
* @brief Convert I2cConfig to ESP-IDF i2c_config_t
|
||||
* @param config I2C configuration
|
||||
* @return ESP-IDF i2c_config_t
|
||||
*/
|
||||
i2c_config_t convertConfig(const I2cConfig& config);
|
||||
};
|
||||
|
||||
#endif // I2C_HPP
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
ID,Component,Level,Criticality,Message
|
||||
2400,I2C,INFO,Low,I2C wrapper initialized
|
||||
2401,I2C,INFO,Low,I2C wrapper destroyed
|
||||
2402,I2C,WARNING,Medium,I2C port %d already initialized
|
||||
2403,I2C,ERROR,High,Failed to configure I2C port %d: %s
|
||||
2404,I2C,ERROR,High,Failed to install I2C driver for port %d: %s
|
||||
2405,I2C,INFO,Low,I2C initialized successfully
|
||||
2406,I2C,WARNING,Medium,I2C port %d not initialized
|
||||
2407,I2C,ERROR,High,Failed to delete I2C driver for port %d: %s
|
||||
2408,I2C,INFO,Low,I2C port %d deinitialized
|
||||
2409,I2C,ERROR,High,I2C port %d not initialized
|
||||
2410,I2C,ERROR,High,Invalid data or length
|
||||
2411,I2C,ERROR,High,Failed to write to device 0x%02X on port %d: %s
|
||||
2412,I2C,ERROR,High,I2C port %d not initialized
|
||||
2413,I2C,ERROR,High,Invalid data buffer or length
|
||||
2414,I2C,ERROR,High,Failed to read from device 0x%02X on port %d: %s
|
||||
2415,I2C,ERROR,High,I2C port %d not initialized
|
||||
2416,I2C,ERROR,High,Invalid data buffer or length
|
||||
2417,I2C,ERROR,High,Failed to write-read device 0x%02X on port %d: %s
|
||||
2418,I2C,ERROR,High,I2C port %d not initialized
|
||||
2419,I2C,ERROR,High,Invalid devices buffer or size
|
||||
2420,I2C,INFO,Low,Scanning I2C bus on port %d...
|
||||
2421,I2C,INFO,Low,Found device at address 0x%02X
|
||||
2422,I2C,INFO,Low,I2C scan complete. Found %ld devices
|
||||
|
@@ -0,0 +1,34 @@
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
|
||||
folder_path = os.path.abspath(os.path.join("components", "system_tests"))
|
||||
if folder_path not in sys.path:
|
||||
sys.path.append(folder_path)
|
||||
|
||||
from scan_serial import ESP32Runner
|
||||
|
||||
def test_i2c_initialize():
|
||||
runner = ESP32Runner(mode="SIM", port="COM9")
|
||||
runner.start()
|
||||
print("--- QEMU Runner Started ---", flush=True)
|
||||
try:
|
||||
start_time = time.time()
|
||||
while time.time() - start_time < 30:
|
||||
line = runner.get_line(timeout=1.0)
|
||||
if line:
|
||||
print(line, flush=True)
|
||||
if "I2C initialized successfully" in line:
|
||||
print("SUCCESS CRITERIA MET!", flush=True)
|
||||
return 0
|
||||
if runner.process.poll() is not None:
|
||||
print(f"Process exited with code: {runner.process.returncode}", flush=True)
|
||||
return 1
|
||||
finally:
|
||||
runner.stop()
|
||||
print("Done.", flush=True)
|
||||
return 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
exit_code = test_i2c_initialize()
|
||||
sys.exit(exit_code)
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<test_scenario>
|
||||
<!-- The configuration for the test environment. -->
|
||||
<!-- Available configurations: SIMULATE, HIL -->
|
||||
<config>SIMULATE</config>
|
||||
|
||||
<test_case>
|
||||
<test_case_id>I2C_INIT_TEST</test_case_id>
|
||||
<!-- The main command that executes the test itself. -->
|
||||
<test_exec>python components/ESP_IDF_FW_wrappers/i2c/test/i2c_init_test.py</test_exec>
|
||||
</test_case>
|
||||
|
||||
|
||||
</test_scenario>
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* @file test_i2c.cpp
|
||||
* @brief Unit tests for I2C wrapper component
|
||||
* @author Mahmoud Elmohtady
|
||||
* @company Nabd solutions - ASF
|
||||
* @copyright Copyright (c) 2025
|
||||
*/
|
||||
|
||||
#include "unity.h"
|
||||
#include "i2c.hpp"
|
||||
|
||||
extern "C" {
|
||||
|
||||
void setUp(void)
|
||||
{
|
||||
}
|
||||
|
||||
void tearDown(void)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Test I2C initialization
|
||||
*/
|
||||
void test_i2c_initialize(void)
|
||||
{
|
||||
I2c i2c;
|
||||
I2cConfig config = {21, 22, 100000};
|
||||
bool result = i2c.initialize(I2cPort::PORT_0, config);
|
||||
TEST_ASSERT_TRUE(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Test I2C deinitialize
|
||||
*/
|
||||
void test_i2c_deinitialize(void)
|
||||
{
|
||||
I2c i2c;
|
||||
I2cConfig config = {21, 22, 100000};
|
||||
i2c.initialize(I2cPort::PORT_0, config);
|
||||
|
||||
bool result = i2c.deinitialize(I2cPort::PORT_0);
|
||||
TEST_ASSERT_TRUE(result);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
idf_component_register(
|
||||
SRCS "com/spi.cpp"
|
||||
INCLUDE_DIRS "com"
|
||||
REQUIRES esp_driver_spi esp_driver_gpio logger
|
||||
)
|
||||
@@ -0,0 +1,279 @@
|
||||
# SPI Wrapper Module
|
||||
|
||||
## Overview
|
||||
|
||||
The SPI wrapper module provides a C++ object-oriented interface for ESP-IDF SPI master functionality. This module encapsulates the ESP-IDF SPI master driver functions and provides a clean, easy-to-use API for SPI communication with peripheral devices.
|
||||
|
||||
## Features
|
||||
|
||||
- **Multi-Host Support**: Support for SPI1, SPI2, and SPI3 hosts
|
||||
- **Device Management**: Add/remove multiple devices per SPI bus
|
||||
- **Flexible Transfers**: Support for TX-only, RX-only, and full-duplex transfers
|
||||
- **Command/Address Phases**: Support for command and address phases
|
||||
- **DMA Support**: Automatic DMA channel allocation for high-speed transfers
|
||||
- **Multiple SPI Modes**: Support for all 4 SPI modes (0-3)
|
||||
- **Error Handling**: Comprehensive error checking and logging
|
||||
|
||||
## Architecture
|
||||
|
||||
### Class Diagram
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ Spi │
|
||||
├─────────────────────────────────────┤
|
||||
│ - m_isInitialized_[3]: bool │
|
||||
├─────────────────────────────────────┤
|
||||
│ + Spi() │
|
||||
│ + ~Spi() │
|
||||
│ + initializeBus(host, cfg): bool │
|
||||
│ + deinitializeBus(host): bool │
|
||||
│ + addDevice(host, cfg, hdl): bool │
|
||||
│ + removeDevice(handle): bool │
|
||||
│ + transmit(...): int32_t │
|
||||
│ + transmitOnly(...): int32_t │
|
||||
│ + receiveOnly(...): int32_t │
|
||||
│ + transmitWithCmdAddr(...): int32_t │
|
||||
│ + isInitialized(host): bool │
|
||||
│ + getDefaultBusConfig(): SpiBusCfg │
|
||||
│ + getDefaultDeviceConfig(): SpiDev │
|
||||
│ - convertHost(host): spi_host_t │
|
||||
│ - convertBusConfig(cfg): spi_bus_t │
|
||||
│ - convertDeviceConfig(cfg): spi_dev │
|
||||
│ - convertMode(mode): uint32_t │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Enumerations
|
||||
|
||||
#### SpiHost
|
||||
- `SPI1_HOST`: SPI1 host (typically used for flash)
|
||||
- `SPI2_HOST`: SPI2 host (HSPI)
|
||||
- `SPI3_HOST`: SPI3 host (VSPI)
|
||||
|
||||
#### SpiMode
|
||||
- `MODE_0`: CPOL=0, CPHA=0 (Clock idle low, data sampled on rising edge)
|
||||
- `MODE_1`: CPOL=0, CPHA=1 (Clock idle low, data sampled on falling edge)
|
||||
- `MODE_2`: CPOL=1, CPHA=0 (Clock idle high, data sampled on falling edge)
|
||||
- `MODE_3`: CPOL=1, CPHA=1 (Clock idle high, data sampled on rising edge)
|
||||
|
||||
### Configuration Structures
|
||||
|
||||
#### SpiBusConfig
|
||||
```cpp
|
||||
struct SpiBusConfig {
|
||||
uint32_t mosiPin; // MOSI pin number
|
||||
uint32_t misoPin; // MISO pin number
|
||||
uint32_t sclkPin; // SCLK pin number
|
||||
uint32_t quadwpPin; // Quad SPI write protect pin (optional)
|
||||
uint32_t quadhdPin; // Quad SPI hold pin (optional)
|
||||
uint32_t maxTransferSize; // Maximum transfer size in bytes
|
||||
};
|
||||
```
|
||||
|
||||
#### SpiDeviceConfig
|
||||
```cpp
|
||||
struct SpiDeviceConfig {
|
||||
uint32_t csPin; // Chip select pin number
|
||||
uint32_t clockSpeedHz; // Clock speed in Hz
|
||||
SpiMode mode; // SPI mode (0-3)
|
||||
uint32_t queueSize; // Transaction queue size
|
||||
uint32_t commandBits; // Command phase bits
|
||||
uint32_t addressBits; // Address phase bits
|
||||
uint32_t dummyBits; // Dummy phase bits
|
||||
bool csEnaPretrans; // CS setup time
|
||||
bool csEnaPosttrans; // CS hold time
|
||||
};
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic SPI Communication
|
||||
|
||||
```cpp
|
||||
#include "spi.hpp"
|
||||
|
||||
// Create SPI instance
|
||||
Spi spi;
|
||||
|
||||
// Get default bus configuration
|
||||
SpiBusConfig busConfig = Spi::getDefaultBusConfig();
|
||||
busConfig.mosiPin = 23;
|
||||
busConfig.misoPin = 19;
|
||||
busConfig.sclkPin = 18;
|
||||
|
||||
// Initialize SPI bus
|
||||
spi.initializeBus(SpiHost::SPI2_HOST, busConfig);
|
||||
|
||||
// Get default device configuration
|
||||
SpiDeviceConfig deviceConfig = Spi::getDefaultDeviceConfig();
|
||||
deviceConfig.csPin = 5;
|
||||
deviceConfig.clockSpeedHz = 1000000; // 1MHz
|
||||
deviceConfig.mode = SpiMode::MODE_0;
|
||||
|
||||
// Add device to bus
|
||||
spi_device_handle_t deviceHandle;
|
||||
spi.addDevice(SpiHost::SPI2_HOST, deviceConfig, &deviceHandle);
|
||||
|
||||
// Transmit and receive data
|
||||
uint8_t txData[] = {0x01, 0x02, 0x03};
|
||||
uint8_t rxData[3];
|
||||
spi.transmit(deviceHandle, txData, rxData, sizeof(txData));
|
||||
```
|
||||
|
||||
### Transmit-Only Operation
|
||||
|
||||
```cpp
|
||||
// Send data without receiving
|
||||
uint8_t command[] = {0xAA, 0x55, 0xFF};
|
||||
spi.transmitOnly(deviceHandle, command, sizeof(command));
|
||||
```
|
||||
|
||||
### Receive-Only Operation
|
||||
|
||||
```cpp
|
||||
// Receive data without sending
|
||||
uint8_t buffer[10];
|
||||
spi.receiveOnly(deviceHandle, buffer, sizeof(buffer));
|
||||
```
|
||||
|
||||
### Command and Address Phases
|
||||
|
||||
```cpp
|
||||
// Configure device with command and address bits
|
||||
SpiDeviceConfig config = Spi::getDefaultDeviceConfig();
|
||||
config.commandBits = 8;
|
||||
config.addressBits = 24;
|
||||
config.csPin = 5;
|
||||
|
||||
spi_device_handle_t device;
|
||||
spi.addDevice(SpiHost::SPI2_HOST, config, &device);
|
||||
|
||||
// Send command and address with data
|
||||
uint8_t data[4];
|
||||
spi.transmitWithCmdAddr(device, 0x03, 0x000000, nullptr, data, sizeof(data));
|
||||
```
|
||||
|
||||
### Multiple Devices on Same Bus
|
||||
|
||||
```cpp
|
||||
// Device 1 configuration
|
||||
SpiDeviceConfig device1Config = Spi::getDefaultDeviceConfig();
|
||||
device1Config.csPin = 5;
|
||||
device1Config.clockSpeedHz = 1000000;
|
||||
|
||||
// Device 2 configuration
|
||||
SpiDeviceConfig device2Config = Spi::getDefaultDeviceConfig();
|
||||
device2Config.csPin = 15;
|
||||
device2Config.clockSpeedHz = 5000000;
|
||||
device2Config.mode = SpiMode::MODE_3;
|
||||
|
||||
// Add both devices
|
||||
spi_device_handle_t device1, device2;
|
||||
spi.addDevice(SpiHost::SPI2_HOST, device1Config, &device1);
|
||||
spi.addDevice(SpiHost::SPI2_HOST, device2Config, &device2);
|
||||
|
||||
// Use devices independently
|
||||
uint8_t data1[] = {0x01, 0x02};
|
||||
uint8_t data2[] = {0xAA, 0xBB};
|
||||
spi.transmitOnly(device1, data1, sizeof(data1));
|
||||
spi.transmitOnly(device2, data2, sizeof(data2));
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### Constructor/Destructor
|
||||
|
||||
- **Spi()**: Initialize SPI wrapper instance
|
||||
- **~Spi()**: Clean up resources and deinitialize all hosts
|
||||
|
||||
### Bus Management
|
||||
|
||||
- **initializeBus(host, config)**: Initialize SPI bus with configuration
|
||||
- **deinitializeBus(host)**: Deinitialize SPI bus
|
||||
- **isInitialized(host)**: Check if host is initialized
|
||||
|
||||
### Device Management
|
||||
|
||||
- **addDevice(host, config, handle)**: Add device to SPI bus
|
||||
- **removeDevice(handle)**: Remove device from SPI bus
|
||||
|
||||
### Data Transfer Methods
|
||||
|
||||
- **transmit(handle, txData, rxData, length)**: Full-duplex transfer
|
||||
- **transmitOnly(handle, txData, length)**: Transmit-only transfer
|
||||
- **receiveOnly(handle, rxData, length)**: Receive-only transfer
|
||||
- **transmitWithCmdAddr(handle, cmd, addr, txData, rxData, length)**: Transfer with command/address
|
||||
|
||||
### Configuration Methods
|
||||
|
||||
- **getDefaultBusConfig()**: Get default bus configuration
|
||||
- **getDefaultDeviceConfig()**: Get default device configuration
|
||||
|
||||
## Error Handling
|
||||
|
||||
The module provides comprehensive error handling:
|
||||
- Host validation and initialization checks
|
||||
- Device handle validation
|
||||
- ESP-IDF error codes are caught and logged
|
||||
- Return values indicate success/failure for all operations
|
||||
- Detailed logging for debugging and troubleshooting
|
||||
|
||||
## Dependencies
|
||||
|
||||
- ESP-IDF SPI master driver (`driver/spi_master.h`)
|
||||
- ESP-IDF error handling (`esp_err.h`)
|
||||
- ESP-IDF logging (`esp_log.h`)
|
||||
|
||||
## Thread Safety
|
||||
|
||||
The SPI wrapper uses ESP-IDF's thread-safe SPI master driver. Multiple tasks can safely use different devices on the same bus simultaneously.
|
||||
|
||||
## Memory Usage
|
||||
|
||||
- Fixed memory footprint per instance
|
||||
- DMA buffers allocated automatically by ESP-IDF
|
||||
- Transaction queues configurable per device
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
- Direct ESP-IDF function calls for optimal performance
|
||||
- DMA support for high-speed transfers
|
||||
- Configurable transaction queue sizes
|
||||
- Clock speeds up to 80MHz (depending on ESP32 variant)
|
||||
|
||||
## SPI Timing Modes
|
||||
|
||||
| Mode | CPOL | CPHA | Clock Idle | Data Sample Edge |
|
||||
|------|------|------|------------|------------------|
|
||||
| 0 | 0 | 0 | Low | Rising |
|
||||
| 1 | 0 | 1 | Low | Falling |
|
||||
| 2 | 1 | 0 | High | Falling |
|
||||
| 3 | 1 | 1 | High | Rising |
|
||||
|
||||
## Common Use Cases
|
||||
|
||||
### Flash Memory
|
||||
- Command/address phases for read/write operations
|
||||
- High-speed transfers with DMA
|
||||
- Mode 0 or Mode 3 typically used
|
||||
|
||||
### Display Controllers
|
||||
- Command/data distinction using DC pin
|
||||
- High-speed pixel data transfers
|
||||
- Various modes depending on controller
|
||||
|
||||
### Sensors
|
||||
- Register-based communication
|
||||
- Lower speed requirements
|
||||
- Mode 0 most common
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **Clock Speed**: Ensure clock speed is within device specifications
|
||||
2. **SPI Mode**: Verify correct CPOL/CPHA settings for device
|
||||
3. **CS Timing**: Some devices require specific CS setup/hold times
|
||||
4. **Wire Length**: Long wires can cause signal integrity issues
|
||||
5. **Pull-up Resistors**: MISO line may need pull-up resistor
|
||||
@@ -0,0 +1,282 @@
|
||||
/**
|
||||
* @file spi.cpp
|
||||
* @brief SPI wrapper component implementation
|
||||
* @author Mahmoud Elmohtady
|
||||
* @company Nabd solutions - ASF
|
||||
* @copyright Copyright (c) 2025
|
||||
*/
|
||||
|
||||
#include "spi.hpp"
|
||||
#include "logger.hpp"
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/spi_common.h"
|
||||
#include "driver/spi_master.h"
|
||||
#include <cstring>
|
||||
|
||||
static const char* TAG = "SPI_WRAPPER";
|
||||
|
||||
Spi::Spi()
|
||||
{
|
||||
m_isInitialized_[0] = false;
|
||||
m_isInitialized_[1] = false;
|
||||
m_isInitialized_[2] = false;
|
||||
ASF_LOGI(TAG, 2500, asf::logger::Criticality::LOW, "SPI wrapper initialized");
|
||||
}
|
||||
|
||||
Spi::~Spi()
|
||||
{
|
||||
// Deinitialize all hosts
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (m_isInitialized_[i]) {
|
||||
deinitializeBus(static_cast<SpiHost>(i));
|
||||
}
|
||||
}
|
||||
ASF_LOGI(TAG, 2501, asf::logger::Criticality::LOW, "SPI wrapper destroyed");
|
||||
}
|
||||
|
||||
bool Spi::initializeBus(SpiHost host, const SpiBusConfig& config)
|
||||
{
|
||||
uint8_t hostIdx = static_cast<uint8_t>(host);
|
||||
spi_host_device_t spiHost = convertHost(host);
|
||||
|
||||
if (m_isInitialized_[hostIdx]) {
|
||||
ASF_LOGW(TAG, 2502, asf::logger::Criticality::MEDIUM, "SPI host %d already initialized", hostIdx);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Configure SPI bus
|
||||
spi_bus_config_t busConfig = convertBusConfig(config);
|
||||
|
||||
esp_err_t ret = spi_bus_initialize(spiHost, &busConfig, SPI_DMA_CH_AUTO);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2503, asf::logger::Criticality::HIGH, "Failed to initialize SPI bus %d: %s", hostIdx, esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Enable pull-ups on all SPI lines to ensure signal stability
|
||||
if (config.misoPin >= 0) {
|
||||
gpio_set_pull_mode(static_cast<gpio_num_t>(config.misoPin), GPIO_PULLUP_ONLY);
|
||||
}
|
||||
if (config.mosiPin >= 0) {
|
||||
gpio_set_pull_mode(static_cast<gpio_num_t>(config.mosiPin), GPIO_PULLUP_ONLY);
|
||||
}
|
||||
if (config.sclkPin >= 0) {
|
||||
gpio_set_pull_mode(static_cast<gpio_num_t>(config.sclkPin), GPIO_PULLUP_ONLY);
|
||||
}
|
||||
|
||||
m_isInitialized_[hostIdx] = true;
|
||||
ASF_LOGI(TAG, 2504, asf::logger::Criticality::LOW, "SPI initialized successfully");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Spi::deinitializeBus(SpiHost host)
|
||||
{
|
||||
uint8_t hostIdx = static_cast<uint8_t>(host);
|
||||
spi_host_device_t spiHost = convertHost(host);
|
||||
|
||||
if (!m_isInitialized_[hostIdx]) {
|
||||
ASF_LOGW(TAG, 2505, asf::logger::Criticality::MEDIUM, "SPI host %d not initialized", hostIdx);
|
||||
return true;
|
||||
}
|
||||
|
||||
esp_err_t ret = spi_bus_free(spiHost);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2506, asf::logger::Criticality::HIGH, "Failed to free SPI bus %d: %s", hostIdx, esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
m_isInitialized_[hostIdx] = false;
|
||||
ASF_LOGI(TAG, 2507, asf::logger::Criticality::LOW, "SPI bus %d deinitialized", hostIdx);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Spi::addDevice(SpiHost host, const SpiDeviceConfig& config, spi_device_handle_t* deviceHandle)
|
||||
{
|
||||
uint8_t hostIdx = static_cast<uint8_t>(host);
|
||||
spi_host_device_t spiHost = convertHost(host);
|
||||
|
||||
if (!m_isInitialized_[hostIdx]) {
|
||||
ASF_LOGE(TAG, 2508, asf::logger::Criticality::HIGH, "SPI host %d not initialized", hostIdx);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (deviceHandle == nullptr) {
|
||||
ASF_LOGE(TAG, 2509, asf::logger::Criticality::HIGH, "Invalid device handle pointer");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Configure SPI device
|
||||
spi_device_interface_config_t deviceConfig = convertDeviceConfig(config);
|
||||
|
||||
esp_err_t ret = spi_bus_add_device(spiHost, &deviceConfig, deviceHandle);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2510, asf::logger::Criticality::HIGH, "Failed to add SPI device to bus %d: %s", hostIdx, esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
ASF_LOGI(TAG, 2511, asf::logger::Criticality::LOW, "SPI device added to bus %d successfully", hostIdx);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Spi::removeDevice(spi_device_handle_t deviceHandle)
|
||||
{
|
||||
if (deviceHandle == nullptr) {
|
||||
ASF_LOGE(TAG, 2512, asf::logger::Criticality::HIGH, "Invalid device handle");
|
||||
return false;
|
||||
}
|
||||
|
||||
esp_err_t ret = spi_bus_remove_device(deviceHandle);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2513, asf::logger::Criticality::HIGH, "Failed to remove SPI device: %s", esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
ASF_LOGI(TAG, 2514, asf::logger::Criticality::LOW, "SPI device removed successfully");
|
||||
return true;
|
||||
}
|
||||
|
||||
int32_t Spi::transmit(spi_device_handle_t deviceHandle, const uint8_t* txData, uint8_t* rxData, size_t length)
|
||||
{
|
||||
if (deviceHandle == nullptr) {
|
||||
ASF_LOGE(TAG, 2515, asf::logger::Criticality::HIGH, "Invalid device handle");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (length == 0) {
|
||||
ASF_LOGW(TAG, 2516, asf::logger::Criticality::MEDIUM, "Zero length transfer");
|
||||
return 0;
|
||||
}
|
||||
|
||||
spi_transaction_t transaction = {};
|
||||
transaction.length = length * 8; // Length in bits
|
||||
transaction.tx_buffer = txData;
|
||||
transaction.rx_buffer = rxData;
|
||||
|
||||
esp_err_t ret = spi_device_transmit(deviceHandle, &transaction);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2517, asf::logger::Criticality::HIGH, "Failed to transmit SPI data: %s", esp_err_to_name(ret));
|
||||
return -1;
|
||||
}
|
||||
|
||||
return static_cast<int32_t>(length);
|
||||
}
|
||||
|
||||
int32_t Spi::transmitOnly(spi_device_handle_t deviceHandle, const uint8_t* txData, size_t length)
|
||||
{
|
||||
return transmit(deviceHandle, txData, nullptr, length);
|
||||
}
|
||||
|
||||
int32_t Spi::receiveOnly(spi_device_handle_t deviceHandle, uint8_t* rxData, size_t length)
|
||||
{
|
||||
return transmit(deviceHandle, nullptr, rxData, length);
|
||||
}
|
||||
|
||||
int32_t Spi::transmitWithCmdAddr(spi_device_handle_t deviceHandle, uint32_t command, uint32_t address,
|
||||
const uint8_t* txData, uint8_t* rxData, size_t length)
|
||||
{
|
||||
if (deviceHandle == nullptr) {
|
||||
ASF_LOGE(TAG, 2518, asf::logger::Criticality::HIGH, "Invalid device handle");
|
||||
return -1;
|
||||
}
|
||||
|
||||
spi_transaction_t transaction = {};
|
||||
transaction.cmd = command;
|
||||
transaction.addr = address;
|
||||
transaction.length = length * 8; // Length in bits
|
||||
transaction.tx_buffer = txData;
|
||||
transaction.rx_buffer = rxData;
|
||||
|
||||
esp_err_t ret = spi_device_transmit(deviceHandle, &transaction);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2519, asf::logger::Criticality::HIGH, "Failed to transmit SPI data with cmd/addr: %s", esp_err_to_name(ret));
|
||||
return -1;
|
||||
}
|
||||
|
||||
return static_cast<int32_t>(length);
|
||||
}
|
||||
|
||||
bool Spi::isInitialized(SpiHost host) const
|
||||
{
|
||||
uint8_t hostIdx = static_cast<uint8_t>(host);
|
||||
return m_isInitialized_[hostIdx];
|
||||
}
|
||||
|
||||
SpiBusConfig Spi::getDefaultBusConfig()
|
||||
{
|
||||
SpiBusConfig config = {};
|
||||
config.mosiPin = GPIO_NUM_23;
|
||||
config.misoPin = GPIO_NUM_19;
|
||||
config.sclkPin = GPIO_NUM_18;
|
||||
config.quadwpPin = -1;
|
||||
config.quadhdPin = -1;
|
||||
config.maxTransferSize = 4096;
|
||||
return config;
|
||||
}
|
||||
|
||||
SpiDeviceConfig Spi::getDefaultDeviceConfig()
|
||||
{
|
||||
SpiDeviceConfig config = {};
|
||||
config.csPin = GPIO_NUM_5;
|
||||
config.clockSpeedHz = 1000000; // 1MHz
|
||||
config.mode = SpiMode::MODE_0;
|
||||
config.queueSize = 7;
|
||||
config.commandBits = 0;
|
||||
config.addressBits = 0;
|
||||
config.dummyBits = 0;
|
||||
config.csEnaPretrans = false;
|
||||
config.csEnaPosttrans = false;
|
||||
return config;
|
||||
}
|
||||
|
||||
spi_host_device_t Spi::convertHost(SpiHost host)
|
||||
{
|
||||
switch (host) {
|
||||
case SpiHost::SPI1_HOST:
|
||||
return SPI1_HOST;
|
||||
case SpiHost::SPI2_HOST:
|
||||
return SPI2_HOST;
|
||||
case SpiHost::SPI3_HOST:
|
||||
return SPI3_HOST;
|
||||
default:
|
||||
return SPI2_HOST;
|
||||
}
|
||||
}
|
||||
|
||||
spi_bus_config_t Spi::convertBusConfig(const SpiBusConfig& config)
|
||||
{
|
||||
spi_bus_config_t busConfig = {};
|
||||
busConfig.mosi_io_num = config.mosiPin;
|
||||
busConfig.miso_io_num = config.misoPin;
|
||||
busConfig.sclk_io_num = config.sclkPin;
|
||||
busConfig.quadwp_io_num = config.quadwpPin;
|
||||
busConfig.quadhd_io_num = config.quadhdPin;
|
||||
busConfig.max_transfer_sz = config.maxTransferSize;
|
||||
busConfig.flags = 0;
|
||||
busConfig.intr_flags = 0;
|
||||
return busConfig;
|
||||
}
|
||||
|
||||
spi_device_interface_config_t Spi::convertDeviceConfig(const SpiDeviceConfig& config)
|
||||
{
|
||||
spi_device_interface_config_t deviceConfig = {};
|
||||
deviceConfig.command_bits = config.commandBits;
|
||||
deviceConfig.address_bits = config.addressBits;
|
||||
deviceConfig.dummy_bits = config.dummyBits;
|
||||
deviceConfig.mode = convertMode(config.mode);
|
||||
deviceConfig.duty_cycle_pos = 0;
|
||||
deviceConfig.cs_ena_pretrans = config.csEnaPretrans ? 1 : 0;
|
||||
deviceConfig.cs_ena_posttrans = config.csEnaPosttrans ? 1 : 0;
|
||||
deviceConfig.clock_speed_hz = config.clockSpeedHz;
|
||||
deviceConfig.input_delay_ns = 0;
|
||||
deviceConfig.spics_io_num = config.csPin;
|
||||
deviceConfig.flags = 0;
|
||||
deviceConfig.queue_size = config.queueSize;
|
||||
deviceConfig.pre_cb = nullptr;
|
||||
deviceConfig.post_cb = nullptr;
|
||||
return deviceConfig;
|
||||
}
|
||||
|
||||
uint32_t Spi::convertMode(SpiMode mode)
|
||||
{
|
||||
return static_cast<uint32_t>(mode);
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
/**
|
||||
* @file spi.hpp
|
||||
* @brief SPI wrapper component header - Wrapper for ESP-IDF SPI functionality
|
||||
* @author Mahmoud Elmohtady
|
||||
* @company Nabd solutions - ASF
|
||||
* @copyright Copyright (c) 2025
|
||||
*/
|
||||
|
||||
#ifndef SPI_HPP
|
||||
#define SPI_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include "driver/spi_master.h"
|
||||
#include "esp_err.h"
|
||||
|
||||
/**
|
||||
* @brief SPI host enumeration
|
||||
*/
|
||||
enum class SpiHost
|
||||
{
|
||||
SPI1_HOST,
|
||||
SPI2_HOST,
|
||||
SPI3_HOST
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief SPI mode enumeration
|
||||
*/
|
||||
enum class SpiMode
|
||||
{
|
||||
MODE_0,
|
||||
MODE_1,
|
||||
MODE_2,
|
||||
MODE_3
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief SPI bus configuration structure
|
||||
*/
|
||||
struct SpiBusConfig
|
||||
{
|
||||
uint32_t mosiPin; ///< MOSI pin number
|
||||
uint32_t misoPin; ///< MISO pin number
|
||||
uint32_t sclkPin; ///< SCLK pin number
|
||||
uint32_t quadwpPin; ///< Quad SPI write protect pin (optional)
|
||||
uint32_t quadhdPin; ///< Quad SPI hold pin (optional)
|
||||
uint32_t maxTransferSize; ///< Maximum transfer size in bytes
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief SPI device configuration structure
|
||||
*/
|
||||
struct SpiDeviceConfig
|
||||
{
|
||||
uint32_t csPin; ///< Chip select pin number
|
||||
uint32_t clockSpeedHz; ///< Clock speed in Hz
|
||||
SpiMode mode; ///< SPI mode (0-3)
|
||||
uint32_t queueSize; ///< Transaction queue size
|
||||
uint32_t commandBits; ///< Command phase bits
|
||||
uint32_t addressBits; ///< Address phase bits
|
||||
uint32_t dummyBits; ///< Dummy phase bits
|
||||
bool csEnaPretrans; ///< CS setup time
|
||||
bool csEnaPosttrans; ///< CS hold time
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief SPI wrapper class
|
||||
*
|
||||
* Provides a C++ wrapper for ESP-IDF SPI functionality.
|
||||
* This class encapsulates ESP-IDF SPI master driver functions in an object-oriented interface.
|
||||
*/
|
||||
class Spi
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @details Initializes the SPI wrapper instance
|
||||
*/
|
||||
Spi();
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
* @details Cleans up resources and deinitializes all SPI hosts
|
||||
*/
|
||||
~Spi();
|
||||
|
||||
/**
|
||||
* @brief Initialize SPI bus
|
||||
* @param host SPI host number
|
||||
* @param config SPI bus configuration
|
||||
* @return true if initialized successfully, false otherwise
|
||||
* @note This function configures the SPI bus pins and parameters
|
||||
*/
|
||||
bool initializeBus(SpiHost host, const SpiBusConfig& config);
|
||||
|
||||
/**
|
||||
* @brief Deinitialize SPI bus
|
||||
* @param host SPI host number
|
||||
* @return true if deinitialized successfully, false otherwise
|
||||
*/
|
||||
bool deinitializeBus(SpiHost host);
|
||||
|
||||
/**
|
||||
* @brief Add SPI device to bus
|
||||
* @param host SPI host number
|
||||
* @param config SPI device configuration
|
||||
* @param deviceHandle Pointer to store device handle
|
||||
* @return true if device added successfully, false otherwise
|
||||
*/
|
||||
bool addDevice(SpiHost host, const SpiDeviceConfig& config, spi_device_handle_t* deviceHandle);
|
||||
|
||||
/**
|
||||
* @brief Remove SPI device from bus
|
||||
* @param deviceHandle Device handle to remove
|
||||
* @return true if device removed successfully, false otherwise
|
||||
*/
|
||||
bool removeDevice(spi_device_handle_t deviceHandle);
|
||||
|
||||
/**
|
||||
* @brief Transmit and receive data
|
||||
* @param deviceHandle Device handle
|
||||
* @param txData Pointer to transmit data
|
||||
* @param rxData Pointer to receive buffer
|
||||
* @param length Number of bytes to transfer
|
||||
* @return Number of bytes transferred, or -1 on error
|
||||
*/
|
||||
int32_t transmit(spi_device_handle_t deviceHandle, const uint8_t* txData, uint8_t* rxData, size_t length);
|
||||
|
||||
/**
|
||||
* @brief Transmit data only
|
||||
* @param deviceHandle Device handle
|
||||
* @param txData Pointer to transmit data
|
||||
* @param length Number of bytes to transmit
|
||||
* @return Number of bytes transmitted, or -1 on error
|
||||
*/
|
||||
int32_t transmitOnly(spi_device_handle_t deviceHandle, const uint8_t* txData, size_t length);
|
||||
|
||||
/**
|
||||
* @brief Receive data only
|
||||
* @param deviceHandle Device handle
|
||||
* @param rxData Pointer to receive buffer
|
||||
* @param length Number of bytes to receive
|
||||
* @return Number of bytes received, or -1 on error
|
||||
*/
|
||||
int32_t receiveOnly(spi_device_handle_t deviceHandle, uint8_t* rxData, size_t length);
|
||||
|
||||
/**
|
||||
* @brief Transmit with command and address phases
|
||||
* @param deviceHandle Device handle
|
||||
* @param command Command to send
|
||||
* @param address Address to send
|
||||
* @param txData Pointer to transmit data
|
||||
* @param rxData Pointer to receive buffer
|
||||
* @param length Number of bytes to transfer
|
||||
* @return Number of bytes transferred, or -1 on error
|
||||
*/
|
||||
int32_t transmitWithCmdAddr(spi_device_handle_t deviceHandle, uint32_t command, uint32_t address,
|
||||
const uint8_t* txData, uint8_t* rxData, size_t length);
|
||||
|
||||
/**
|
||||
* @brief Check if SPI host is initialized
|
||||
* @param host SPI host number
|
||||
* @return true if initialized, false otherwise
|
||||
*/
|
||||
bool isInitialized(SpiHost host) const;
|
||||
|
||||
/**
|
||||
* @brief Get default bus configuration
|
||||
* @return Default SPI bus configuration structure
|
||||
*/
|
||||
static SpiBusConfig getDefaultBusConfig();
|
||||
|
||||
/**
|
||||
* @brief Get default device configuration
|
||||
* @return Default SPI device configuration structure
|
||||
*/
|
||||
static SpiDeviceConfig getDefaultDeviceConfig();
|
||||
|
||||
private:
|
||||
bool m_isInitialized_[3]; ///< Initialization status for each host
|
||||
|
||||
/**
|
||||
* @brief Convert SpiHost to ESP-IDF spi_host_device_t
|
||||
* @param host SPI host
|
||||
* @return ESP-IDF spi_host_device_t
|
||||
*/
|
||||
spi_host_device_t convertHost(SpiHost host);
|
||||
|
||||
/**
|
||||
* @brief Convert SpiBusConfig to ESP-IDF spi_bus_config_t
|
||||
* @param config SPI bus configuration
|
||||
* @return ESP-IDF spi_bus_config_t
|
||||
*/
|
||||
spi_bus_config_t convertBusConfig(const SpiBusConfig& config);
|
||||
|
||||
/**
|
||||
* @brief Convert SpiDeviceConfig to ESP-IDF spi_device_interface_config_t
|
||||
* @param config SPI device configuration
|
||||
* @return ESP-IDF spi_device_interface_config_t
|
||||
*/
|
||||
spi_device_interface_config_t convertDeviceConfig(const SpiDeviceConfig& config);
|
||||
|
||||
/**
|
||||
* @brief Convert SpiMode to ESP-IDF mode flags
|
||||
* @param mode SPI mode
|
||||
* @return ESP-IDF mode flags
|
||||
*/
|
||||
uint32_t convertMode(SpiMode mode);
|
||||
};
|
||||
|
||||
#endif // SPI_HPP
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
ID,Component,Level,Criticality,Message
|
||||
2500,SPI,INFO,Low,SPI wrapper initialized
|
||||
2501,SPI,INFO,Low,SPI wrapper destroyed
|
||||
2502,SPI,WARNING,Medium,SPI host %d already initialized
|
||||
2503,SPI,ERROR,High,Failed to initialize SPI bus %d: %s
|
||||
2504,SPI,INFO,Low,SPI initialized successfully
|
||||
2505,SPI,WARNING,Medium,SPI host %d not initialized
|
||||
2506,SPI,ERROR,High,Failed to free SPI bus %d: %s
|
||||
2507,SPI,INFO,Low,SPI bus %d deinitialized
|
||||
2508,SPI,ERROR,High,SPI host %d not initialized
|
||||
2509,SPI,ERROR,High,Invalid device handle pointer
|
||||
2510,SPI,ERROR,High,Failed to add SPI device to bus %d: %s
|
||||
2511,SPI,INFO,Low,SPI device added to bus %d successfully
|
||||
2512,SPI,ERROR,High,Invalid device handle
|
||||
2513,SPI,ERROR,High,Failed to remove SPI device: %s
|
||||
2514,SPI,INFO,Low,SPI device removed successfully
|
||||
2515,SPI,ERROR,High,Invalid device handle
|
||||
2516,SPI,WARNING,Medium,Zero length transfer
|
||||
2517,SPI,ERROR,High,Failed to transmit SPI data: %s
|
||||
2518,SPI,ERROR,High,Invalid device handle
|
||||
2519,SPI,ERROR,High,Failed to transmit SPI data with cmd/addr: %s
|
||||
|
@@ -0,0 +1,34 @@
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
|
||||
folder_path = os.path.abspath(os.path.join("components", "system_tests"))
|
||||
if folder_path not in sys.path:
|
||||
sys.path.append(folder_path)
|
||||
|
||||
from scan_serial import ESP32Runner
|
||||
|
||||
def test_spi_initialize():
|
||||
runner = ESP32Runner(mode="SIM", port="COM9")
|
||||
runner.start()
|
||||
print("--- QEMU Runner Started ---", flush=True)
|
||||
try:
|
||||
start_time = time.time()
|
||||
while time.time() - start_time < 30:
|
||||
line = runner.get_line(timeout=1.0)
|
||||
if line:
|
||||
print(line, flush=True)
|
||||
if "SPI initialized successfully" in line:
|
||||
print("SUCCESS CRITERIA MET!", flush=True)
|
||||
return 0
|
||||
if runner.process.poll() is not None:
|
||||
print(f"Process exited with code: {runner.process.returncode}", flush=True)
|
||||
return 1
|
||||
finally:
|
||||
runner.stop()
|
||||
print("Done.", flush=True)
|
||||
return 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
exit_code = test_spi_initialize()
|
||||
sys.exit(exit_code)
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<test_scenario>
|
||||
<!-- The configuration for the test environment. -->
|
||||
<!-- Available configurations: SIMULATE, HIL -->
|
||||
<config>SIMULATE</config>
|
||||
|
||||
<test_case>
|
||||
<test_case_id>SPI_INIT_TEST</test_case_id>
|
||||
<!-- The main command that executes the test itself. -->
|
||||
<test_exec>python components/ESP_IDF_FW_wrappers/spi/test/spi_init_test.py</test_exec>
|
||||
</test_case>
|
||||
|
||||
|
||||
</test_scenario>
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* @file test_spi.cpp
|
||||
* @brief Unit tests for SPI wrapper component
|
||||
* @author Mahmoud Elmohtady
|
||||
* @company Nabd solutions - ASF
|
||||
* @copyright Copyright (c) 2025
|
||||
*/
|
||||
|
||||
#include "unity.h"
|
||||
#include "spi.hpp"
|
||||
|
||||
extern "C" {
|
||||
|
||||
void setUp(void)
|
||||
{
|
||||
}
|
||||
|
||||
void tearDown(void)
|
||||
{
|
||||
}
|
||||
|
||||
void test_spi_initialize(void)
|
||||
{
|
||||
Spi spi;
|
||||
SpiConfig config = {23, 19, 18, 5, 1000000, SpiMode::MODE_0};
|
||||
bool result = spi.initialize(SpiHost::SPI2_HOST, config);
|
||||
TEST_ASSERT_TRUE(result);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
idf_component_register(
|
||||
SRCS "com/uart.cpp"
|
||||
INCLUDE_DIRS "com"
|
||||
REQUIRES esp_driver_uart logger
|
||||
)
|
||||
@@ -0,0 +1,222 @@
|
||||
# UART Wrapper Module
|
||||
|
||||
## Overview
|
||||
|
||||
The UART wrapper module provides a C++ object-oriented interface for ESP-IDF UART functionality. This module encapsulates the ESP-IDF UART driver functions and provides a clean, easy-to-use API for serial communication.
|
||||
|
||||
## Features
|
||||
|
||||
- **Multi-Port Support**: Support for UART0, UART1, and UART2
|
||||
- **Configurable Parameters**: Baudrate, data bits, parity, stop bits, flow control
|
||||
- **Buffer Management**: Configurable TX/RX buffer sizes
|
||||
- **Timeout Support**: Configurable timeouts for read/write operations
|
||||
- **Flow Control**: Hardware flow control support (RTS/CTS)
|
||||
- **Error Handling**: Comprehensive error checking and logging
|
||||
|
||||
## Architecture
|
||||
|
||||
### Class Diagram
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ Uart │
|
||||
├─────────────────────────────────────┤
|
||||
│ - m_isInitialized_[3]: bool │
|
||||
├─────────────────────────────────────┤
|
||||
│ + Uart() │
|
||||
│ + ~Uart() │
|
||||
│ + initialize(port, config): bool │
|
||||
│ + deinitialize(port): bool │
|
||||
│ + transmit(...): int32_t │
|
||||
│ + receive(...): int32_t │
|
||||
│ + getBytesAvailable(port): int32_t │
|
||||
│ + flushTx(port): bool │
|
||||
│ + flushRx(port): bool │
|
||||
│ + setBaudrate(port, baud): bool │
|
||||
│ + isInitialized(port): bool │
|
||||
│ + getDefaultConfig(): UartConfig │
|
||||
│ - convertPort(port): uart_port_t │
|
||||
│ - convertConfig(cfg): uart_config_t │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Enumerations
|
||||
|
||||
#### UartPort
|
||||
- `PORT_0`: UART port 0
|
||||
- `PORT_1`: UART port 1
|
||||
- `PORT_2`: UART port 2
|
||||
|
||||
#### UartBaudrate
|
||||
- `BAUD_9600`: 9600 bps
|
||||
- `BAUD_19200`: 19200 bps
|
||||
- `BAUD_38400`: 38400 bps
|
||||
- `BAUD_57600`: 57600 bps
|
||||
- `BAUD_115200`: 115200 bps
|
||||
- `BAUD_230400`: 230400 bps
|
||||
- `BAUD_460800`: 460800 bps
|
||||
- `BAUD_921600`: 921600 bps
|
||||
|
||||
#### UartDataBits
|
||||
- `DATA_5_BITS`: 5 data bits
|
||||
- `DATA_6_BITS`: 6 data bits
|
||||
- `DATA_7_BITS`: 7 data bits
|
||||
- `DATA_8_BITS`: 8 data bits
|
||||
|
||||
#### UartParity
|
||||
- `PARITY_DISABLE`: No parity
|
||||
- `PARITY_EVEN`: Even parity
|
||||
- `PARITY_ODD`: Odd parity
|
||||
|
||||
#### UartStopBits
|
||||
- `STOP_BITS_1`: 1 stop bit
|
||||
- `STOP_BITS_1_5`: 1.5 stop bits
|
||||
- `STOP_BITS_2`: 2 stop bits
|
||||
|
||||
#### UartFlowControl
|
||||
- `FLOW_CTRL_DISABLE`: No flow control
|
||||
- `FLOW_CTRL_RTS`: RTS flow control
|
||||
- `FLOW_CTRL_CTS`: CTS flow control
|
||||
- `FLOW_CTRL_CTS_RTS`: RTS/CTS flow control
|
||||
|
||||
### Configuration Structure
|
||||
|
||||
```cpp
|
||||
struct UartConfig {
|
||||
UartBaudrate baudrate; // UART baudrate
|
||||
uint32_t txPin; // TX pin number
|
||||
uint32_t rxPin; // RX pin number
|
||||
uint32_t rtsPin; // RTS pin number (optional)
|
||||
uint32_t ctsPin; // CTS pin number (optional)
|
||||
UartDataBits dataBits; // Number of data bits
|
||||
UartParity parity; // Parity setting
|
||||
UartStopBits stopBits; // Number of stop bits
|
||||
UartFlowControl flowControl; // Flow control setting
|
||||
uint32_t rxBufferSize; // RX buffer size
|
||||
uint32_t txBufferSize; // TX buffer size
|
||||
uint32_t eventQueueSize; // Event queue size
|
||||
};
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic UART Communication
|
||||
|
||||
```cpp
|
||||
#include "uart.hpp"
|
||||
|
||||
// Create UART instance
|
||||
Uart uart;
|
||||
|
||||
// Get default configuration
|
||||
UartConfig config = Uart::getDefaultConfig();
|
||||
config.baudrate = UartBaudrate::BAUD_115200;
|
||||
config.txPin = 1;
|
||||
config.rxPin = 3;
|
||||
|
||||
// Initialize UART
|
||||
uart.initialize(UartPort::PORT_0, config);
|
||||
|
||||
// Transmit data
|
||||
const char* message = "Hello World!";
|
||||
uart.transmit(UartPort::PORT_0, (uint8_t*)message, strlen(message));
|
||||
|
||||
// Receive data
|
||||
uint8_t buffer[100];
|
||||
int32_t bytesReceived = uart.receive(UartPort::PORT_0, buffer, sizeof(buffer), 1000);
|
||||
```
|
||||
|
||||
### Advanced Configuration
|
||||
|
||||
```cpp
|
||||
// Configure UART with flow control
|
||||
UartConfig config = {};
|
||||
config.baudrate = UartBaudrate::BAUD_921600;
|
||||
config.txPin = 4;
|
||||
config.rxPin = 5;
|
||||
config.rtsPin = 18;
|
||||
config.ctsPin = 19;
|
||||
config.dataBits = UartDataBits::DATA_8_BITS;
|
||||
config.parity = UartParity::PARITY_EVEN;
|
||||
config.stopBits = UartStopBits::STOP_BITS_2;
|
||||
config.flowControl = UartFlowControl::FLOW_CTRL_CTS_RTS;
|
||||
config.rxBufferSize = 2048;
|
||||
config.txBufferSize = 2048;
|
||||
|
||||
uart.initialize(UartPort::PORT_1, config);
|
||||
```
|
||||
|
||||
### Buffer Management
|
||||
|
||||
```cpp
|
||||
// Check available bytes
|
||||
int32_t available = uart.getBytesAvailable(UartPort::PORT_0);
|
||||
|
||||
// Flush buffers
|
||||
uart.flushRx(UartPort::PORT_0);
|
||||
uart.flushTx(UartPort::PORT_0);
|
||||
|
||||
// Change baudrate at runtime
|
||||
uart.setBaudrate(UartPort::PORT_0, UartBaudrate::BAUD_460800);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### Constructor/Destructor
|
||||
|
||||
- **Uart()**: Initialize UART wrapper instance
|
||||
- **~Uart()**: Clean up resources and deinitialize all ports
|
||||
|
||||
### Configuration Methods
|
||||
|
||||
- **initialize(port, config)**: Initialize UART port with configuration
|
||||
- **deinitialize(port)**: Deinitialize UART port
|
||||
- **isInitialized(port)**: Check if port is initialized
|
||||
- **getDefaultConfig()**: Get default configuration structure
|
||||
|
||||
### Communication Methods
|
||||
|
||||
- **transmit(port, data, length, timeout)**: Transmit data with timeout
|
||||
- **receive(port, buffer, maxLength, timeout)**: Receive data with timeout
|
||||
- **getBytesAvailable(port)**: Get number of bytes in RX buffer
|
||||
|
||||
### Buffer Management
|
||||
|
||||
- **flushTx(port)**: Flush TX buffer
|
||||
- **flushRx(port)**: Flush RX buffer
|
||||
|
||||
### Runtime Configuration
|
||||
|
||||
- **setBaudrate(port, baudrate)**: Change baudrate at runtime
|
||||
|
||||
## Error Handling
|
||||
|
||||
The module provides comprehensive error handling:
|
||||
- Port validation and initialization checks
|
||||
- ESP-IDF error codes are caught and logged
|
||||
- Return values indicate success/failure for all operations
|
||||
- Detailed logging for debugging and troubleshooting
|
||||
|
||||
## Dependencies
|
||||
|
||||
- ESP-IDF UART driver (`driver/uart.h`)
|
||||
- ESP-IDF error handling (`esp_err.h`)
|
||||
- ESP-IDF logging (`esp_log.h`)
|
||||
- FreeRTOS (`freertos/FreeRTOS.h`, `freertos/task.h`)
|
||||
|
||||
## Thread Safety
|
||||
|
||||
The UART wrapper uses ESP-IDF's thread-safe UART driver. Multiple tasks can safely use different UART ports simultaneously.
|
||||
|
||||
## Memory Usage
|
||||
|
||||
- Fixed memory footprint per instance
|
||||
- Configurable buffer sizes for each port
|
||||
- No dynamic memory allocation in wrapper layer
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
- Direct ESP-IDF function calls for optimal performance
|
||||
- Minimal overhead over raw ESP-IDF calls
|
||||
- Configurable buffer sizes for throughput optimization
|
||||
- Hardware flow control support for high-speed communication
|
||||
@@ -0,0 +1,293 @@
|
||||
/**
|
||||
* @file uart.cpp
|
||||
* @brief UART wrapper component implementation
|
||||
* @author Mahmoud Elmohtady
|
||||
* @company Nabd solutions - ASF
|
||||
* @copyright Copyright (c) 2025
|
||||
*/
|
||||
|
||||
#include "uart.hpp"
|
||||
#include "logger.hpp"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include <cstring>
|
||||
#include <inttypes.h>
|
||||
|
||||
static const char* TAG = "UART_WRAPPER";
|
||||
|
||||
/**
|
||||
* @brief Constructor - Initialize UART wrapper instance
|
||||
* @details Initializes all UART ports as not initialized
|
||||
*/
|
||||
Uart::Uart()
|
||||
{
|
||||
m_isInitialized_[0] = false;
|
||||
m_isInitialized_[1] = false;
|
||||
m_isInitialized_[2] = false;
|
||||
ASF_LOGI(TAG, 2600, asf::logger::Criticality::LOW, "UART wrapper initialized");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Destructor - Clean up UART wrapper resources
|
||||
* @details Deinitializes all active UART ports
|
||||
*/
|
||||
Uart::~Uart()
|
||||
{
|
||||
// Deinitialize all ports
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (m_isInitialized_[i]) {
|
||||
deinitialize(static_cast<UartPort>(i));
|
||||
}
|
||||
}
|
||||
ASF_LOGI(TAG, 2601, asf::logger::Criticality::LOW, "UART wrapper destroyed");
|
||||
}
|
||||
|
||||
bool Uart::initialize(UartPort port, const UartConfig& config)
|
||||
{
|
||||
uint8_t portIdx = static_cast<uint8_t>(port);
|
||||
uart_port_t uartPort = convertPort(port);
|
||||
|
||||
if (m_isInitialized_[portIdx]) {
|
||||
ASF_LOGW(TAG, 2602, asf::logger::Criticality::MEDIUM, "UART port %d already initialized", portIdx);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Configure UART parameters
|
||||
uart_config_t uartConfig = convertConfig(config);
|
||||
|
||||
esp_err_t ret = uart_param_config(uartPort, &uartConfig);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2603, asf::logger::Criticality::HIGH, "Failed to configure UART port %d: %s", portIdx, esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set UART pins
|
||||
ret = uart_set_pin(uartPort, config.txPin, config.rxPin,
|
||||
config.rtsPin, config.ctsPin);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2604, asf::logger::Criticality::HIGH, "Failed to set UART pins for port %d: %s", portIdx, esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Install UART driver
|
||||
ret = uart_driver_install(uartPort, config.rxBufferSize, config.txBufferSize,
|
||||
config.eventQueueSize, nullptr, 0);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2605, asf::logger::Criticality::HIGH, "Failed to install UART driver for port %d: %s", portIdx, esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
m_isInitialized_[portIdx] = true;
|
||||
ASF_LOGI(TAG, 2606, asf::logger::Criticality::LOW, "UART initialized successfully");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Uart::deinitialize(UartPort port)
|
||||
{
|
||||
uint8_t portIdx = static_cast<uint8_t>(port);
|
||||
uart_port_t uartPort = convertPort(port);
|
||||
|
||||
if (!m_isInitialized_[portIdx]) {
|
||||
ASF_LOGW(TAG, 2607, asf::logger::Criticality::MEDIUM, "UART port %d not initialized", portIdx);
|
||||
return true;
|
||||
}
|
||||
|
||||
esp_err_t ret = uart_driver_delete(uartPort);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2608, asf::logger::Criticality::HIGH, "Failed to delete UART driver for port %d: %s", portIdx, esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
m_isInitialized_[portIdx] = false;
|
||||
ASF_LOGI(TAG, 2609, asf::logger::Criticality::LOW, "UART port %d deinitialized", portIdx);
|
||||
return true;
|
||||
}
|
||||
|
||||
int32_t Uart::transmit(UartPort port, const uint8_t* data, size_t length, uint32_t timeoutMs)
|
||||
{
|
||||
uint8_t portIdx = static_cast<uint8_t>(port);
|
||||
uart_port_t uartPort = convertPort(port);
|
||||
|
||||
if (!m_isInitialized_[portIdx]) {
|
||||
ASF_LOGE(TAG, 2610, asf::logger::Criticality::HIGH, "UART port %d not initialized", portIdx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (data == nullptr || length == 0) {
|
||||
ASF_LOGE(TAG, 2611, asf::logger::Criticality::HIGH, "Invalid data or length");
|
||||
return -1;
|
||||
}
|
||||
|
||||
TickType_t timeout = pdMS_TO_TICKS(timeoutMs);
|
||||
int bytesWritten = uart_write_bytes(uartPort, data, length);
|
||||
|
||||
if (bytesWritten < 0) {
|
||||
ASF_LOGE(TAG, 2612, asf::logger::Criticality::HIGH, "Failed to write to UART port %d", portIdx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Wait for transmission to complete
|
||||
esp_err_t ret = uart_wait_tx_done(uartPort, timeout);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGW(TAG, 2613, asf::logger::Criticality::MEDIUM, "TX timeout for UART port %d", portIdx);
|
||||
}
|
||||
|
||||
return bytesWritten;
|
||||
}
|
||||
|
||||
int32_t Uart::receive(UartPort port, uint8_t* data, size_t maxLength, uint32_t timeoutMs)
|
||||
{
|
||||
uint8_t portIdx = static_cast<uint8_t>(port);
|
||||
uart_port_t uartPort = convertPort(port);
|
||||
|
||||
if (!m_isInitialized_[portIdx]) {
|
||||
ASF_LOGE(TAG, 2614, asf::logger::Criticality::HIGH, "UART port %d not initialized", portIdx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (data == nullptr || maxLength == 0) {
|
||||
ASF_LOGE(TAG, 2615, asf::logger::Criticality::HIGH, "Invalid data buffer or length");
|
||||
return -1;
|
||||
}
|
||||
|
||||
TickType_t timeout = pdMS_TO_TICKS(timeoutMs);
|
||||
int bytesRead = uart_read_bytes(uartPort, data, maxLength, timeout);
|
||||
|
||||
if (bytesRead < 0) {
|
||||
ASF_LOGE(TAG, 2616, asf::logger::Criticality::HIGH, "Failed to read from UART port %d", portIdx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
int32_t Uart::getBytesAvailable(UartPort port)
|
||||
{
|
||||
uint8_t portIdx = static_cast<uint8_t>(port);
|
||||
uart_port_t uartPort = convertPort(port);
|
||||
|
||||
if (!m_isInitialized_[portIdx]) {
|
||||
ASF_LOGE(TAG, 2617, asf::logger::Criticality::HIGH, "UART port %d not initialized", portIdx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t bytesAvailable = 0;
|
||||
esp_err_t ret = uart_get_buffered_data_len(uartPort, &bytesAvailable);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2618, asf::logger::Criticality::HIGH, "Failed to get buffered data length for port %d: %s",
|
||||
portIdx, esp_err_to_name(ret));
|
||||
return -1;
|
||||
}
|
||||
|
||||
return static_cast<int32_t>(bytesAvailable);
|
||||
}
|
||||
|
||||
bool Uart::flushTx(UartPort port)
|
||||
{
|
||||
uint8_t portIdx = static_cast<uint8_t>(port);
|
||||
uart_port_t uartPort = convertPort(port);
|
||||
|
||||
if (!m_isInitialized_[portIdx]) {
|
||||
ASF_LOGE(TAG, 2619, asf::logger::Criticality::HIGH, "UART port %d not initialized", portIdx);
|
||||
return false;
|
||||
}
|
||||
|
||||
esp_err_t ret = uart_flush_input(uartPort);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2620, asf::logger::Criticality::HIGH, "Failed to flush TX for port %d: %s", portIdx, esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Uart::flushRx(UartPort port)
|
||||
{
|
||||
uint8_t portIdx = static_cast<uint8_t>(port);
|
||||
uart_port_t uartPort = convertPort(port);
|
||||
|
||||
if (!m_isInitialized_[portIdx]) {
|
||||
ASF_LOGE(TAG, 2621, asf::logger::Criticality::HIGH, "UART port %d not initialized", portIdx);
|
||||
return false;
|
||||
}
|
||||
|
||||
esp_err_t ret = uart_flush_input(uartPort);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2622, asf::logger::Criticality::HIGH, "Failed to flush RX for port %d: %s", portIdx, esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Uart::setBaudrate(UartPort port, UartBaudrate baudrate)
|
||||
{
|
||||
uint8_t portIdx = static_cast<uint8_t>(port);
|
||||
uart_port_t uartPort = convertPort(port);
|
||||
|
||||
if (!m_isInitialized_[portIdx]) {
|
||||
ASF_LOGE(TAG, 2623, asf::logger::Criticality::HIGH, "UART port %d not initialized", portIdx);
|
||||
return false;
|
||||
}
|
||||
|
||||
esp_err_t ret = uart_set_baudrate(uartPort, static_cast<uint32_t>(baudrate));
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2624, asf::logger::Criticality::HIGH, "Failed to set baudrate for port %d: %s", portIdx, esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
ASF_LOGI(TAG, 2625, asf::logger::Criticality::LOW, "Baudrate set to %" PRIu32 " for port %d", static_cast<uint32_t>(baudrate), portIdx);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Uart::isInitialized(UartPort port) const
|
||||
{
|
||||
uint8_t portIdx = static_cast<uint8_t>(port);
|
||||
return m_isInitialized_[portIdx];
|
||||
}
|
||||
|
||||
UartConfig Uart::getDefaultConfig()
|
||||
{
|
||||
UartConfig config = {};
|
||||
config.baudrate = UartBaudrate::BAUD_115200;
|
||||
config.txPin = UART_PIN_NO_CHANGE;
|
||||
config.rxPin = UART_PIN_NO_CHANGE;
|
||||
config.rtsPin = UART_PIN_NO_CHANGE;
|
||||
config.ctsPin = UART_PIN_NO_CHANGE;
|
||||
config.dataBits = UartDataBits::DATA_8_BITS;
|
||||
config.parity = UartParity::PARITY_DISABLE;
|
||||
config.stopBits = UartStopBits::STOP_BITS_1;
|
||||
config.flowControl = UartFlowControl::FLOW_CTRL_DISABLE;
|
||||
config.rxBufferSize = 1024;
|
||||
config.txBufferSize = 1024;
|
||||
config.eventQueueSize = 10;
|
||||
return config;
|
||||
}
|
||||
|
||||
uart_port_t Uart::convertPort(UartPort port)
|
||||
{
|
||||
switch (port) {
|
||||
case UartPort::PORT_0:
|
||||
return UART_NUM_0;
|
||||
case UartPort::PORT_1:
|
||||
return UART_NUM_1;
|
||||
case UartPort::PORT_2:
|
||||
return UART_NUM_2;
|
||||
default:
|
||||
return UART_NUM_0;
|
||||
}
|
||||
}
|
||||
|
||||
uart_config_t Uart::convertConfig(const UartConfig& config)
|
||||
{
|
||||
uart_config_t uartConfig = {};
|
||||
uartConfig.baud_rate = static_cast<int>(config.baudrate);
|
||||
uartConfig.data_bits = static_cast<uart_word_length_t>(config.dataBits);
|
||||
uartConfig.parity = static_cast<uart_parity_t>(config.parity);
|
||||
uartConfig.stop_bits = static_cast<uart_stop_bits_t>(config.stopBits);
|
||||
uartConfig.flow_ctrl = static_cast<uart_hw_flowcontrol_t>(config.flowControl);
|
||||
uartConfig.rx_flow_ctrl_thresh = 122;
|
||||
uartConfig.source_clk = UART_SCLK_DEFAULT;
|
||||
return uartConfig;
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
/**
|
||||
* @file uart.hpp
|
||||
* @brief UART wrapper component header - Wrapper for ESP-IDF UART functionality
|
||||
* @author Mahmoud Elmohtady
|
||||
* @company Nabd solutions - ASF
|
||||
* @copyright Copyright (c) 2025
|
||||
*/
|
||||
|
||||
#ifndef UART_HPP
|
||||
#define UART_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include "driver/uart.h"
|
||||
#include "esp_err.h"
|
||||
|
||||
/**
|
||||
* @brief UART port enumeration
|
||||
*/
|
||||
enum class UartPort
|
||||
{
|
||||
PORT_0,
|
||||
PORT_1,
|
||||
PORT_2
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief UART baudrate enumeration
|
||||
*/
|
||||
enum class UartBaudrate
|
||||
{
|
||||
BAUD_9600 = 9600,
|
||||
BAUD_19200 = 19200,
|
||||
BAUD_38400 = 38400,
|
||||
BAUD_57600 = 57600,
|
||||
BAUD_115200 = 115200,
|
||||
BAUD_230400 = 230400,
|
||||
BAUD_460800 = 460800,
|
||||
BAUD_921600 = 921600
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief UART data bits enumeration
|
||||
*/
|
||||
enum class UartDataBits
|
||||
{
|
||||
DATA_5_BITS = UART_DATA_5_BITS,
|
||||
DATA_6_BITS = UART_DATA_6_BITS,
|
||||
DATA_7_BITS = UART_DATA_7_BITS,
|
||||
DATA_8_BITS = UART_DATA_8_BITS
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief UART parity enumeration
|
||||
*/
|
||||
enum class UartParity
|
||||
{
|
||||
PARITY_DISABLE = UART_PARITY_DISABLE,
|
||||
PARITY_EVEN = UART_PARITY_EVEN,
|
||||
PARITY_ODD = UART_PARITY_ODD
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief UART stop bits enumeration
|
||||
*/
|
||||
enum class UartStopBits
|
||||
{
|
||||
STOP_BITS_1 = UART_STOP_BITS_1,
|
||||
STOP_BITS_1_5 = UART_STOP_BITS_1_5,
|
||||
STOP_BITS_2 = UART_STOP_BITS_2
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief UART flow control enumeration
|
||||
*/
|
||||
enum class UartFlowControl
|
||||
{
|
||||
FLOW_CTRL_DISABLE = UART_HW_FLOWCTRL_DISABLE,
|
||||
FLOW_CTRL_RTS = UART_HW_FLOWCTRL_RTS,
|
||||
FLOW_CTRL_CTS = UART_HW_FLOWCTRL_CTS,
|
||||
FLOW_CTRL_CTS_RTS = UART_HW_FLOWCTRL_CTS_RTS
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief UART configuration structure
|
||||
*/
|
||||
struct UartConfig
|
||||
{
|
||||
UartBaudrate baudrate; ///< UART baudrate
|
||||
uint32_t txPin; ///< TX pin number
|
||||
uint32_t rxPin; ///< RX pin number
|
||||
uint32_t rtsPin; ///< RTS pin number (optional)
|
||||
uint32_t ctsPin; ///< CTS pin number (optional)
|
||||
UartDataBits dataBits; ///< Number of data bits
|
||||
UartParity parity; ///< Parity setting
|
||||
UartStopBits stopBits; ///< Number of stop bits
|
||||
UartFlowControl flowControl; ///< Flow control setting
|
||||
uint32_t rxBufferSize; ///< RX buffer size
|
||||
uint32_t txBufferSize; ///< TX buffer size
|
||||
uint32_t eventQueueSize; ///< Event queue size
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief UART wrapper class
|
||||
*
|
||||
* Provides a C++ wrapper for ESP-IDF UART functionality.
|
||||
* This class encapsulates ESP-IDF UART driver functions in an object-oriented interface.
|
||||
*/
|
||||
class Uart
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @details Initializes the UART wrapper instance
|
||||
*/
|
||||
Uart();
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
* @details Cleans up resources and deinitializes all UART ports
|
||||
*/
|
||||
~Uart();
|
||||
|
||||
/**
|
||||
* @brief Initialize UART port with configuration
|
||||
* @param port UART port number
|
||||
* @param config UART configuration parameters
|
||||
* @return true if initialized successfully, false otherwise
|
||||
* @note This function configures pins, baudrate, and buffer sizes
|
||||
*/
|
||||
bool initialize(UartPort port, const UartConfig& config);
|
||||
|
||||
/**
|
||||
* @brief Deinitialize UART port
|
||||
* @param port UART port number
|
||||
* @return true if deinitialized successfully, false otherwise
|
||||
*/
|
||||
bool deinitialize(UartPort port);
|
||||
|
||||
/**
|
||||
* @brief Transmit data over UART
|
||||
* @param port UART port number
|
||||
* @param data Pointer to data buffer
|
||||
* @param length Number of bytes to transmit
|
||||
* @param timeoutMs Timeout in milliseconds (default: 1000ms)
|
||||
* @return Number of bytes transmitted, or -1 on error
|
||||
*/
|
||||
int32_t transmit(UartPort port, const uint8_t* data, size_t length, uint32_t timeoutMs = 1000);
|
||||
|
||||
/**
|
||||
* @brief Receive data from UART
|
||||
* @param port UART port number
|
||||
* @param data Pointer to receive buffer
|
||||
* @param maxLength Maximum number of bytes to receive
|
||||
* @param timeoutMs Timeout in milliseconds
|
||||
* @return Number of bytes received, or -1 on error
|
||||
*/
|
||||
int32_t receive(UartPort port, uint8_t* data, size_t maxLength, uint32_t timeoutMs);
|
||||
|
||||
/**
|
||||
* @brief Get number of bytes available in RX buffer
|
||||
* @param port UART port number
|
||||
* @return Number of bytes available, or -1 on error
|
||||
*/
|
||||
int32_t getBytesAvailable(UartPort port);
|
||||
|
||||
/**
|
||||
* @brief Flush UART TX buffer
|
||||
* @param port UART port number
|
||||
* @return true if flushed successfully, false otherwise
|
||||
*/
|
||||
bool flushTx(UartPort port);
|
||||
|
||||
/**
|
||||
* @brief Flush UART RX buffer
|
||||
* @param port UART port number
|
||||
* @return true if flushed successfully, false otherwise
|
||||
*/
|
||||
bool flushRx(UartPort port);
|
||||
|
||||
/**
|
||||
* @brief Set UART baudrate
|
||||
* @param port UART port number
|
||||
* @param baudrate New baudrate
|
||||
* @return true if set successfully, false otherwise
|
||||
*/
|
||||
bool setBaudrate(UartPort port, UartBaudrate baudrate);
|
||||
|
||||
/**
|
||||
* @brief Check if UART port is initialized
|
||||
* @param port UART port number
|
||||
* @return true if initialized, false otherwise
|
||||
*/
|
||||
bool isInitialized(UartPort port) const;
|
||||
|
||||
/**
|
||||
* @brief Get default UART configuration
|
||||
* @return Default UART configuration structure
|
||||
*/
|
||||
static UartConfig getDefaultConfig();
|
||||
|
||||
private:
|
||||
bool m_isInitialized_[3]; ///< Initialization status for each port
|
||||
|
||||
/**
|
||||
* @brief Convert UartPort to ESP-IDF uart_port_t
|
||||
* @param port UART port
|
||||
* @return ESP-IDF uart_port_t
|
||||
*/
|
||||
uart_port_t convertPort(UartPort port);
|
||||
|
||||
/**
|
||||
* @brief Convert UartConfig to ESP-IDF uart_config_t
|
||||
* @param config UART configuration
|
||||
* @return ESP-IDF uart_config_t
|
||||
*/
|
||||
uart_config_t convertConfig(const UartConfig& config);
|
||||
};
|
||||
|
||||
#endif // UART_HPP
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
ID,Component,Level,Criticality,Message
|
||||
2600,UART,INFO,Low,UART wrapper initialized
|
||||
2601,UART,INFO,Low,UART wrapper destroyed
|
||||
2602,UART,WARNING,Medium,UART port %d already initialized
|
||||
2603,UART,ERROR,High,Failed to configure UART port %d: %s
|
||||
2604,UART,ERROR,High,Failed to set UART pins for port %d: %s
|
||||
2605,UART,ERROR,High,Failed to install UART driver for port %d: %s
|
||||
2606,UART,INFO,Low,UART initialized successfully
|
||||
2607,UART,WARNING,Medium,UART port %d not initialized
|
||||
2608,UART,ERROR,High,Failed to delete UART driver for port %d: %s
|
||||
2609,UART,INFO,Low,UART port %d deinitialized
|
||||
2610,UART,ERROR,High,UART port %d not initialized
|
||||
2611,UART,ERROR,High,Invalid data or length
|
||||
2612,UART,ERROR,High,Failed to write to UART port %d
|
||||
2613,UART,WARNING,Medium,TX timeout for UART port %d
|
||||
2614,UART,ERROR,High,UART port %d not initialized
|
||||
2615,UART,ERROR,High,Invalid data buffer or length
|
||||
2616,UART,ERROR,High,Failed to read from UART port %d
|
||||
2617,UART,ERROR,High,UART port %d not initialized
|
||||
2618,UART,ERROR,High,Failed to get buffered data length for port %d: %s
|
||||
2619,UART,ERROR,High,UART port %d not initialized
|
||||
2620,UART,ERROR,High,Failed to flush TX for port %d: %s
|
||||
2621,UART,ERROR,High,UART port %d not initialized
|
||||
2622,UART,ERROR,High,Failed to flush RX for port %d: %s
|
||||
2623,UART,ERROR,High,UART port %d not initialized
|
||||
2624,UART,ERROR,High,Failed to set baudrate for port %d: %s
|
||||
2625,UART,INFO,Low,Baudrate set to %" PRIu32 " for port %d
|
||||
|
Can't render this file because it contains an unexpected character in line 27 and column 37.
|
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* @file test_uart.cpp
|
||||
* @brief Unit tests for UART wrapper component
|
||||
* @author Mahmoud Elmohtady
|
||||
* @company Nabd solutions - ASF
|
||||
* @copyright Copyright (c) 2025
|
||||
*/
|
||||
|
||||
#include "unity.h"
|
||||
#include "uart.hpp"
|
||||
|
||||
extern "C" {
|
||||
|
||||
void setUp(void)
|
||||
{
|
||||
}
|
||||
|
||||
void tearDown(void)
|
||||
{
|
||||
}
|
||||
|
||||
void test_uart_initialize(void)
|
||||
{
|
||||
Uart uart;
|
||||
UartConfig config = {UartBaudrate::BAUD_115200, 17, 16, 8, 0, 1, 0};
|
||||
bool result = uart.initialize(UartPort::PORT_0, config);
|
||||
TEST_ASSERT_TRUE(result);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
|
||||
folder_path = os.path.abspath(os.path.join("components", "system_tests"))
|
||||
if folder_path not in sys.path:
|
||||
sys.path.append(folder_path)
|
||||
|
||||
from scan_serial import ESP32Runner
|
||||
|
||||
def test_UART_initialize():
|
||||
runner = ESP32Runner(mode="SIM", port="COM9")
|
||||
runner.start()
|
||||
print("--- QEMU Runner Started ---", flush=True)
|
||||
try:
|
||||
start_time = time.time()
|
||||
while time.time() - start_time < 30:
|
||||
line = runner.get_line(timeout=1.0)
|
||||
if line:
|
||||
print(line, flush=True)
|
||||
if "UART initialized successfully" in line:
|
||||
print("SUCCESS CRITERIA MET!", flush=True)
|
||||
return 0
|
||||
if runner.process.poll() is not None:
|
||||
print(f"Process exited with code: {runner.process.returncode}", flush=True)
|
||||
return 1
|
||||
finally:
|
||||
runner.stop()
|
||||
print("Done.", flush=True)
|
||||
return 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
exit_code = test_UART_initialize()
|
||||
sys.exit(exit_code)
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<test_scenario>
|
||||
<!-- The configuration for the test environment. -->
|
||||
<!-- Available configurations: SIMULATE, HIL -->
|
||||
<config>SIMULATE</config>
|
||||
|
||||
<test_case>
|
||||
<test_case_id>UART_INIT_TEST</test_case_id>
|
||||
<!-- The main command that executes the test itself. -->
|
||||
<test_exec>python components/ESP_IDF_FW_wrappers/uart/test/uart_init_test.py</test_exec>
|
||||
</test_case>
|
||||
|
||||
|
||||
</test_scenario>
|
||||
@@ -0,0 +1,5 @@
|
||||
idf_component_register(
|
||||
SRCS "com/wifi.cpp"
|
||||
INCLUDE_DIRS "com"
|
||||
REQUIRES esp_wifi esp_netif nvs_flash logger
|
||||
)
|
||||
@@ -0,0 +1,323 @@
|
||||
# WiFi Wrapper Module
|
||||
|
||||
## Overview
|
||||
|
||||
The WiFi wrapper module provides a C++ object-oriented interface for ESP-IDF WiFi functionality. This module encapsulates the ESP-IDF WiFi driver functions and provides a clean, easy-to-use API for WiFi station and access point operations.
|
||||
|
||||
## Features
|
||||
|
||||
- **Multiple Modes**: Station (STA), Access Point (AP), and combined (AP+STA) modes
|
||||
- **Network Scanning**: Scan for available WiFi networks
|
||||
- **Event Handling**: Asynchronous event callbacks for connection status
|
||||
- **Security Support**: Multiple authentication modes (Open, WEP, WPA/WPA2/WPA3)
|
||||
- **Network Information**: IP address, MAC address, RSSI retrieval
|
||||
- **AP Management**: Connected stations monitoring in AP mode
|
||||
- **Error Handling**: Comprehensive error checking and logging
|
||||
|
||||
## Architecture
|
||||
|
||||
### Class Diagram
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ Wifi │
|
||||
├─────────────────────────────────────┤
|
||||
│ - m_isInitialized_: bool │
|
||||
│ - m_connectionStatus_: Status │
|
||||
│ - m_config_: WifiConfig │
|
||||
│ - m_netifSta_: esp_netif_t* │
|
||||
│ - m_netifAp_: esp_netif_t* │
|
||||
│ - m_eventCallback_: Callback │
|
||||
│ - m_eventCallbackArg_: void* │
|
||||
├─────────────────────────────────────┤
|
||||
│ + Wifi() │
|
||||
│ + ~Wifi() │
|
||||
│ + initialize(config): bool │
|
||||
│ + deinitialize(): bool │
|
||||
│ + start(): bool │
|
||||
│ + stop(): bool │
|
||||
│ + connect(): bool │
|
||||
│ + disconnect(): bool │
|
||||
│ + scan(...): int32_t │
|
||||
│ + getConnectionStatus(): Status │
|
||||
│ + isConnected(): bool │
|
||||
│ + getRssi(): int32_t │
|
||||
│ + getIpAddress(ip, len): bool │
|
||||
│ + getMacAddress(mac, mode): bool │
|
||||
│ + setEventCallback(cb, arg): void │
|
||||
│ + getConnectedStations(): int32_t │
|
||||
│ + isInitialized(): bool │
|
||||
│ + getDefaultStaConfig(): StaConfig │
|
||||
│ + getDefaultApConfig(): ApConfig │
|
||||
│ - convertMode(mode): wifi_mode_t │
|
||||
│ - convertAuthMode(auth): wifi_auth │
|
||||
│ - wifiEventHandler(...): void │
|
||||
│ - ipEventHandler(...): void │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Enumerations
|
||||
|
||||
#### WifiMode
|
||||
- `NONE`: WiFi disabled
|
||||
- `STA`: Station mode (client)
|
||||
- `AP`: Access Point mode (server)
|
||||
- `APSTA`: Combined AP + STA mode
|
||||
|
||||
#### WifiAuthMode
|
||||
- `OPEN`: No authentication
|
||||
- `WEP`: WEP encryption
|
||||
- `WPA_PSK`: WPA with pre-shared key
|
||||
- `WPA2_PSK`: WPA2 with pre-shared key
|
||||
- `WPA_WPA2_PSK`: WPA/WPA2 mixed mode
|
||||
- `WPA3_PSK`: WPA3 with pre-shared key
|
||||
- `WPA2_WPA3_PSK`: WPA2/WPA3 mixed mode
|
||||
|
||||
#### WifiConnectionStatus
|
||||
- `DISCONNECTED`: Not connected
|
||||
- `CONNECTING`: Connection in progress
|
||||
- `CONNECTED`: Successfully connected
|
||||
- `FAILED`: Connection failed
|
||||
|
||||
### Configuration Structures
|
||||
|
||||
#### WifiStaConfig
|
||||
```cpp
|
||||
struct WifiStaConfig {
|
||||
char ssid[32]; // SSID of target AP
|
||||
char password[64]; // Password of target AP
|
||||
WifiAuthMode authMode; // Authentication mode
|
||||
bool pmfRequired; // Protected Management Frame required
|
||||
uint8_t channel; // Channel of target AP (0 = auto)
|
||||
int8_t rssiThreshold; // Minimum RSSI threshold
|
||||
};
|
||||
```
|
||||
|
||||
#### WifiApConfig
|
||||
```cpp
|
||||
struct WifiApConfig {
|
||||
char ssid[32]; // SSID of AP
|
||||
char password[64]; // Password of AP
|
||||
WifiAuthMode authMode; // Authentication mode
|
||||
uint8_t channel; // Channel number
|
||||
uint8_t maxConnections; // Maximum number of connections
|
||||
bool ssidHidden; // Hide SSID
|
||||
};
|
||||
```
|
||||
|
||||
#### WifiConfig
|
||||
```cpp
|
||||
struct WifiConfig {
|
||||
WifiMode mode; // WiFi mode
|
||||
WifiStaConfig staConfig; // Station configuration
|
||||
WifiApConfig apConfig; // Access Point configuration
|
||||
};
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Station Mode (Client)
|
||||
|
||||
```cpp
|
||||
#include "wifi.hpp"
|
||||
|
||||
// WiFi event callback
|
||||
void wifiEventCallback(WifiConnectionStatus status, void* arg) {
|
||||
switch (status) {
|
||||
case WifiConnectionStatus::CONNECTED:
|
||||
printf("WiFi connected!\n");
|
||||
break;
|
||||
case WifiConnectionStatus::DISCONNECTED:
|
||||
printf("WiFi disconnected!\n");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Create WiFi instance
|
||||
Wifi wifi;
|
||||
|
||||
// Configure WiFi
|
||||
WifiConfig config = {};
|
||||
config.mode = WifiMode::STA;
|
||||
config.staConfig = Wifi::getDefaultStaConfig();
|
||||
strcpy(config.staConfig.ssid, "MyWiFiNetwork");
|
||||
strcpy(config.staConfig.password, "MyPassword");
|
||||
config.staConfig.authMode = WifiAuthMode::WPA2_PSK;
|
||||
|
||||
// Set event callback
|
||||
wifi.setEventCallback(wifiEventCallback, nullptr);
|
||||
|
||||
// Initialize and start WiFi
|
||||
wifi.initialize(config);
|
||||
wifi.start();
|
||||
wifi.connect();
|
||||
|
||||
// Get IP address when connected
|
||||
char ip[16];
|
||||
if (wifi.getIpAddress(ip, sizeof(ip))) {
|
||||
printf("IP Address: %s\n", ip);
|
||||
}
|
||||
```
|
||||
|
||||
### Access Point Mode (Server)
|
||||
|
||||
```cpp
|
||||
// Configure as Access Point
|
||||
WifiConfig apConfig = {};
|
||||
apConfig.mode = WifiMode::AP;
|
||||
apConfig.apConfig = Wifi::getDefaultApConfig();
|
||||
strcpy(apConfig.apConfig.ssid, "ESP32-Hotspot");
|
||||
strcpy(apConfig.apConfig.password, "12345678");
|
||||
apConfig.apConfig.authMode = WifiAuthMode::WPA2_PSK;
|
||||
apConfig.apConfig.channel = 6;
|
||||
apConfig.apConfig.maxConnections = 4;
|
||||
|
||||
wifi.initialize(apConfig);
|
||||
wifi.start();
|
||||
|
||||
// Monitor connected stations
|
||||
int32_t connectedStations = wifi.getConnectedStations();
|
||||
printf("Connected stations: %ld\n", connectedStations);
|
||||
```
|
||||
|
||||
### Network Scanning
|
||||
|
||||
```cpp
|
||||
// Scan for available networks
|
||||
WifiScanResult results[20];
|
||||
int32_t networkCount = wifi.scan(results, 20, 5000); // 5 second scan
|
||||
|
||||
printf("Found %ld networks:\n", networkCount);
|
||||
for (int i = 0; i < networkCount; i++) {
|
||||
printf("SSID: %s, RSSI: %d dBm, Channel: %d\n",
|
||||
results[i].ssid, results[i].rssi, results[i].channel);
|
||||
}
|
||||
```
|
||||
|
||||
### Combined AP+STA Mode
|
||||
|
||||
```cpp
|
||||
WifiConfig combinedConfig = {};
|
||||
combinedConfig.mode = WifiMode::APSTA;
|
||||
|
||||
// Configure STA
|
||||
combinedConfig.staConfig = Wifi::getDefaultStaConfig();
|
||||
strcpy(combinedConfig.staConfig.ssid, "InternetRouter");
|
||||
strcpy(combinedConfig.staConfig.password, "RouterPassword");
|
||||
|
||||
// Configure AP
|
||||
combinedConfig.apConfig = Wifi::getDefaultApConfig();
|
||||
strcpy(combinedConfig.apConfig.ssid, "ESP32-Bridge");
|
||||
strcpy(combinedConfig.apConfig.password, "BridgePassword");
|
||||
|
||||
wifi.initialize(combinedConfig);
|
||||
wifi.start();
|
||||
wifi.connect(); // Connect to internet router
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### Constructor/Destructor
|
||||
|
||||
- **Wifi()**: Initialize WiFi wrapper instance
|
||||
- **~Wifi()**: Clean up resources and deinitialize WiFi
|
||||
|
||||
### Initialization Methods
|
||||
|
||||
- **initialize(config)**: Initialize WiFi with configuration
|
||||
- **deinitialize()**: Deinitialize WiFi and clean up resources
|
||||
- **isInitialized()**: Check if WiFi is initialized
|
||||
|
||||
### Control Methods
|
||||
|
||||
- **start()**: Start WiFi (required after initialization)
|
||||
- **stop()**: Stop WiFi operations
|
||||
- **connect()**: Connect to network (STA mode)
|
||||
- **disconnect()**: Disconnect from network
|
||||
|
||||
### Information Methods
|
||||
|
||||
- **getConnectionStatus()**: Get current connection status
|
||||
- **isConnected()**: Check if connected to network
|
||||
- **getRssi()**: Get signal strength of current connection
|
||||
- **getIpAddress(ip, maxLen)**: Get IP address string
|
||||
- **getMacAddress(mac, mode)**: Get MAC address
|
||||
|
||||
### Scanning Methods
|
||||
|
||||
- **scan(results, maxResults, scanTimeMs)**: Scan for available networks
|
||||
|
||||
### Event Handling
|
||||
|
||||
- **setEventCallback(callback, arg)**: Set connection event callback
|
||||
|
||||
### Access Point Methods
|
||||
|
||||
- **getConnectedStations()**: Get number of connected stations (AP mode)
|
||||
|
||||
### Configuration Methods
|
||||
|
||||
- **getDefaultStaConfig()**: Get default station configuration
|
||||
- **getDefaultApConfig()**: Get default AP configuration
|
||||
|
||||
## Error Handling
|
||||
|
||||
The module provides comprehensive error handling:
|
||||
- Initialization status checks
|
||||
- ESP-IDF error codes are caught and logged
|
||||
- Return values indicate success/failure for all operations
|
||||
- Event callbacks for asynchronous status updates
|
||||
- Detailed logging for debugging and troubleshooting
|
||||
|
||||
## Dependencies
|
||||
|
||||
- ESP-IDF WiFi driver (`esp_wifi.h`)
|
||||
- ESP-IDF network interface (`esp_netif.h`)
|
||||
- ESP-IDF event system (`esp_event.h`)
|
||||
- NVS flash storage (`nvs_flash.h`)
|
||||
- ESP-IDF error handling (`esp_err.h`)
|
||||
- ESP-IDF logging (`esp_log.h`)
|
||||
|
||||
## Thread Safety
|
||||
|
||||
The WiFi wrapper uses ESP-IDF's thread-safe WiFi driver and event system. Multiple tasks can safely call WiFi functions simultaneously.
|
||||
|
||||
## Memory Usage
|
||||
|
||||
- Fixed memory footprint per instance
|
||||
- Network interface handles managed by ESP-IDF
|
||||
- Event system uses minimal memory for callbacks
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
- Asynchronous connection process with event callbacks
|
||||
- Network scanning can take several seconds
|
||||
- WiFi operations may affect other radio functions (Bluetooth)
|
||||
- Power management considerations for battery-powered devices
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- Use WPA2 or WPA3 for secure connections
|
||||
- Avoid WEP encryption (deprecated and insecure)
|
||||
- Use strong passwords (minimum 8 characters)
|
||||
- Consider hiding SSID for AP mode (security through obscurity)
|
||||
- Enable PMF (Protected Management Frames) when supported
|
||||
|
||||
## Power Management
|
||||
|
||||
- WiFi can be configured for power saving modes
|
||||
- AP mode typically consumes more power than STA mode
|
||||
- Consider WiFi sleep modes for battery-powered applications
|
||||
- Monitor power consumption in different WiFi modes
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **Connection Failures**: Check SSID, password, and authentication mode
|
||||
2. **Weak Signal**: Monitor RSSI values and consider antenna placement
|
||||
3. **IP Assignment**: Ensure DHCP is working or configure static IP
|
||||
4. **Channel Conflicts**: Use WiFi analyzer to find less congested channels
|
||||
5. **Memory Issues**: Monitor heap usage, especially with many connections
|
||||
@@ -0,0 +1,501 @@
|
||||
/**
|
||||
* @file wifi.cpp
|
||||
* @brief WiFi wrapper component implementation
|
||||
* @author Mahmoud Elmohtady
|
||||
* @company Nabd solutions - ASF
|
||||
* @copyright Copyright (c) 2025
|
||||
*/
|
||||
|
||||
#include "wifi.hpp"
|
||||
#include "logger.hpp"
|
||||
#include "nvs_flash.h"
|
||||
#include <cstring>
|
||||
|
||||
static const char* TAG = "WIFI_WRAPPER";
|
||||
|
||||
Wifi::Wifi()
|
||||
: m_isInitialized_(false)
|
||||
, m_connectionStatus_(WifiConnectionStatus::DISCONNECTED)
|
||||
, m_netifSta_(nullptr)
|
||||
, m_netifAp_(nullptr)
|
||||
, m_eventCallback_(nullptr)
|
||||
, m_eventCallbackArg_(nullptr)
|
||||
{
|
||||
memset(&m_config_, 0, sizeof(m_config_));
|
||||
ASF_LOGI(TAG, 2700, asf::logger::Criticality::LOW, "WiFi wrapper initialized");
|
||||
}
|
||||
|
||||
Wifi::~Wifi()
|
||||
{
|
||||
deinitialize();
|
||||
ASF_LOGI(TAG, 2701, asf::logger::Criticality::LOW, "WiFi wrapper destroyed");
|
||||
}
|
||||
|
||||
bool Wifi::initialize(const WifiConfig& config)
|
||||
{
|
||||
#ifdef CONFIG_ESP_WIFI_ENABLED
|
||||
if (m_isInitialized_) {
|
||||
ASF_LOGW(TAG, 2702, asf::logger::Criticality::MEDIUM, "WiFi already initialized");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Initialize NVS
|
||||
esp_err_t ret = nvs_flash_init();
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
ret = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK(ret);
|
||||
|
||||
// Initialize TCP/IP stack
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
|
||||
// Create default event loop
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
|
||||
// Create network interfaces
|
||||
if (config.mode == WifiMode::STA || config.mode == WifiMode::APSTA) {
|
||||
m_netifSta_ = esp_netif_create_default_wifi_sta();
|
||||
}
|
||||
|
||||
if (config.mode == WifiMode::AP || config.mode == WifiMode::APSTA) {
|
||||
m_netifAp_ = esp_netif_create_default_wifi_ap();
|
||||
}
|
||||
|
||||
// Initialize WiFi
|
||||
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
||||
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
|
||||
|
||||
// Register event handlers
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID,
|
||||
&wifiEventHandler, this));
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP,
|
||||
&ipEventHandler, this));
|
||||
|
||||
// Set WiFi mode
|
||||
ESP_ERROR_CHECK(esp_wifi_set_mode(convertMode(config.mode)));
|
||||
|
||||
// Configure WiFi
|
||||
if (config.mode == WifiMode::STA || config.mode == WifiMode::APSTA) {
|
||||
wifi_config_t staConfig = {};
|
||||
strncpy((char*)staConfig.sta.ssid, config.staConfig.ssid, sizeof(staConfig.sta.ssid) - 1);
|
||||
strncpy((char*)staConfig.sta.password, config.staConfig.password, sizeof(staConfig.sta.password) - 1);
|
||||
staConfig.sta.threshold.authmode = convertAuthMode(config.staConfig.authMode);
|
||||
staConfig.sta.pmf_cfg.required = config.staConfig.pmfRequired;
|
||||
|
||||
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &staConfig));
|
||||
}
|
||||
|
||||
if (config.mode == WifiMode::AP || config.mode == WifiMode::APSTA) {
|
||||
wifi_config_t apConfig = {};
|
||||
strncpy((char*)apConfig.ap.ssid, config.apConfig.ssid, sizeof(apConfig.ap.ssid) - 1);
|
||||
strncpy((char*)apConfig.ap.password, config.apConfig.password, sizeof(apConfig.ap.password) - 1);
|
||||
apConfig.ap.ssid_len = strlen(config.apConfig.ssid);
|
||||
apConfig.ap.channel = config.apConfig.channel;
|
||||
apConfig.ap.max_connection = config.apConfig.maxConnections;
|
||||
apConfig.ap.authmode = convertAuthMode(config.apConfig.authMode);
|
||||
apConfig.ap.ssid_hidden = config.apConfig.ssidHidden ? 1 : 0;
|
||||
|
||||
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &apConfig));
|
||||
}
|
||||
|
||||
m_config_ = config;
|
||||
m_isInitialized_ = true;
|
||||
ASF_LOGI(TAG, 2703, asf::logger::Criticality::LOW, "WiFi initialized successfully");
|
||||
return true;
|
||||
#else
|
||||
ASF_LOGW(TAG, 2704, asf::logger::Criticality::MEDIUM, "WiFi disabled in sdkconfig");
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Wifi::deinitialize()
|
||||
{
|
||||
#ifdef CONFIG_ESP_WIFI_ENABLED
|
||||
if (!m_isInitialized_) {
|
||||
ASF_LOGW(TAG, 2705, asf::logger::Criticality::MEDIUM, "WiFi not initialized");
|
||||
return true;
|
||||
}
|
||||
|
||||
stop();
|
||||
|
||||
// Unregister event handlers
|
||||
esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifiEventHandler);
|
||||
esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, &ipEventHandler);
|
||||
|
||||
// Deinitialize WiFi
|
||||
esp_wifi_deinit();
|
||||
|
||||
// Destroy network interfaces
|
||||
if (m_netifSta_) {
|
||||
esp_netif_destroy_default_wifi(m_netifSta_);
|
||||
m_netifSta_ = nullptr;
|
||||
}
|
||||
|
||||
if (m_netifAp_) {
|
||||
esp_netif_destroy_default_wifi(m_netifAp_);
|
||||
m_netifAp_ = nullptr;
|
||||
}
|
||||
|
||||
m_isInitialized_ = false;
|
||||
m_connectionStatus_ = WifiConnectionStatus::DISCONNECTED;
|
||||
ASF_LOGI(TAG, 2706, asf::logger::Criticality::LOW, "WiFi deinitialized");
|
||||
return true;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Wifi::start()
|
||||
{
|
||||
#ifdef CONFIG_ESP_WIFI_ENABLED
|
||||
if (!m_isInitialized_) {
|
||||
ASF_LOGE(TAG, 2707, asf::logger::Criticality::HIGH, "WiFi not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
esp_err_t ret = esp_wifi_start();
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2708, asf::logger::Criticality::HIGH, "Failed to start WiFi: %s", esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
ASF_LOGI(TAG, 2709, asf::logger::Criticality::LOW, "WiFi started");
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Wifi::stop()
|
||||
{
|
||||
#ifdef CONFIG_ESP_WIFI_ENABLED
|
||||
if (!m_isInitialized_) {
|
||||
ASF_LOGW(TAG, 2710, asf::logger::Criticality::MEDIUM, "WiFi not initialized");
|
||||
return true;
|
||||
}
|
||||
|
||||
esp_err_t ret = esp_wifi_stop();
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2711, asf::logger::Criticality::HIGH, "Failed to stop WiFi: %s", esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
m_connectionStatus_ = WifiConnectionStatus::DISCONNECTED;
|
||||
ASF_LOGI(TAG, 2712, asf::logger::Criticality::LOW, "WiFi stopped");
|
||||
return true;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Wifi::connect()
|
||||
{
|
||||
#ifdef CONFIG_ESP_WIFI_ENABLED
|
||||
if (!m_isInitialized_) {
|
||||
ASF_LOGE(TAG, 2713, asf::logger::Criticality::HIGH, "WiFi not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_config_.mode != WifiMode::STA && m_config_.mode != WifiMode::APSTA) {
|
||||
ASF_LOGE(TAG, 2714, asf::logger::Criticality::HIGH, "WiFi not in STA mode");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_connectionStatus_ = WifiConnectionStatus::CONNECTING;
|
||||
|
||||
esp_err_t ret = esp_wifi_connect();
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2715, asf::logger::Criticality::HIGH, "Failed to connect to WiFi: %s", esp_err_to_name(ret));
|
||||
m_connectionStatus_ = WifiConnectionStatus::FAILED;
|
||||
return false;
|
||||
}
|
||||
|
||||
ASF_LOGI(TAG, 2716, asf::logger::Criticality::LOW, "WiFi connection initiated");
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Wifi::disconnect()
|
||||
{
|
||||
#ifdef CONFIG_ESP_WIFI_ENABLED
|
||||
if (!m_isInitialized_) {
|
||||
ASF_LOGW(TAG, 2717, asf::logger::Criticality::MEDIUM, "WiFi not initialized");
|
||||
return true;
|
||||
}
|
||||
|
||||
esp_err_t ret = esp_wifi_disconnect();
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2718, asf::logger::Criticality::HIGH, "Failed to disconnect WiFi: %s", esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
m_connectionStatus_ = WifiConnectionStatus::DISCONNECTED;
|
||||
ASF_LOGI(TAG, 2719, asf::logger::Criticality::LOW, "WiFi disconnected");
|
||||
return true;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
int32_t Wifi::scan(WifiScanResult* results, size_t maxResults, uint32_t scanTimeMs)
|
||||
{
|
||||
#ifdef CONFIG_ESP_WIFI_ENABLED
|
||||
if (!m_isInitialized_ || results == nullptr || maxResults == 0) {
|
||||
ASF_LOGE(TAG, 2720, asf::logger::Criticality::HIGH, "Invalid scan parameters");
|
||||
return -1;
|
||||
}
|
||||
|
||||
wifi_scan_config_t scanConfig = {};
|
||||
scanConfig.ssid = nullptr;
|
||||
scanConfig.bssid = nullptr;
|
||||
scanConfig.channel = 0;
|
||||
scanConfig.show_hidden = true;
|
||||
scanConfig.scan_type = WIFI_SCAN_TYPE_ACTIVE;
|
||||
scanConfig.scan_time.active.min = scanTimeMs / 2;
|
||||
scanConfig.scan_time.active.max = scanTimeMs;
|
||||
|
||||
esp_err_t ret = esp_wifi_scan_start(&scanConfig, true);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2721, asf::logger::Criticality::HIGH, "Failed to start WiFi scan: %s", esp_err_to_name(ret));
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint16_t apCount = 0;
|
||||
esp_wifi_scan_get_ap_num(&apCount);
|
||||
|
||||
if (apCount == 0) {
|
||||
ASF_LOGI(TAG, 2722, asf::logger::Criticality::LOW, "No access points found");
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint16_t actualCount = (apCount > maxResults) ? maxResults : apCount;
|
||||
wifi_ap_record_t* apRecords = new wifi_ap_record_t[actualCount];
|
||||
|
||||
ret = esp_wifi_scan_get_ap_records(&actualCount, apRecords);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2723, asf::logger::Criticality::HIGH, "Failed to get scan results: %s", esp_err_to_name(ret));
|
||||
delete[] apRecords;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Convert results
|
||||
for (uint16_t i = 0; i < actualCount; i++) {
|
||||
strncpy(results[i].ssid, (char*)apRecords[i].ssid, sizeof(results[i].ssid) - 1);
|
||||
results[i].ssid[sizeof(results[i].ssid) - 1] = '\0';
|
||||
memcpy(results[i].bssid, apRecords[i].bssid, 6);
|
||||
results[i].channel = apRecords[i].primary;
|
||||
results[i].rssi = apRecords[i].rssi;
|
||||
results[i].authMode = static_cast<WifiAuthMode>(apRecords[i].authmode);
|
||||
}
|
||||
|
||||
delete[] apRecords;
|
||||
ASF_LOGI(TAG, 2724, asf::logger::Criticality::LOW, "WiFi scan completed, found %d networks", actualCount);
|
||||
return actualCount;
|
||||
#else
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
WifiConnectionStatus Wifi::getConnectionStatus() const
|
||||
{
|
||||
return m_connectionStatus_;
|
||||
}
|
||||
|
||||
bool Wifi::isConnected() const
|
||||
{
|
||||
return m_connectionStatus_ == WifiConnectionStatus::CONNECTED;
|
||||
}
|
||||
|
||||
int32_t Wifi::getRssi()
|
||||
{
|
||||
#ifdef CONFIG_ESP_WIFI_ENABLED
|
||||
if (!isConnected()) {
|
||||
ASF_LOGW(TAG, 2725, asf::logger::Criticality::MEDIUM, "WiFi not connected");
|
||||
return 0;
|
||||
}
|
||||
|
||||
wifi_ap_record_t apInfo;
|
||||
esp_err_t ret = esp_wifi_sta_get_ap_info(&apInfo);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2726, asf::logger::Criticality::HIGH, "Failed to get AP info: %s", esp_err_to_name(ret));
|
||||
return 0;
|
||||
}
|
||||
|
||||
return apInfo.rssi;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Wifi::getIpAddress(char* ip, size_t maxLen)
|
||||
{
|
||||
#ifdef CONFIG_ESP_WIFI_ENABLED
|
||||
if (!isConnected() || ip == nullptr || maxLen == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
esp_netif_ip_info_t ipInfo;
|
||||
esp_err_t ret = esp_netif_get_ip_info(m_netifSta_, &ipInfo);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2727, asf::logger::Criticality::HIGH, "Failed to get IP info: %s", esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
snprintf(ip, maxLen, IPSTR, IP2STR(&ipInfo.ip));
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Wifi::getMacAddress(uint8_t* mac, WifiMode mode)
|
||||
{
|
||||
#ifdef CONFIG_ESP_WIFI_ENABLED
|
||||
if (mac == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
wifi_interface_t interface = (mode == WifiMode::AP) ? WIFI_IF_AP : WIFI_IF_STA;
|
||||
esp_err_t ret = esp_wifi_get_mac(interface, mac);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2728, asf::logger::Criticality::HIGH, "Failed to get MAC address: %s", esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Wifi::setEventCallback(WifiEventCallback callback, void* arg)
|
||||
{
|
||||
m_eventCallback_ = callback;
|
||||
m_eventCallbackArg_ = arg;
|
||||
}
|
||||
|
||||
int32_t Wifi::getConnectedStations()
|
||||
{
|
||||
#ifdef CONFIG_ESP_WIFI_ENABLED
|
||||
if (m_config_.mode != WifiMode::AP && m_config_.mode != WifiMode::APSTA) {
|
||||
ASF_LOGE(TAG, 2729, asf::logger::Criticality::HIGH, "WiFi not in AP mode");
|
||||
return -1;
|
||||
}
|
||||
|
||||
wifi_sta_list_t staList;
|
||||
esp_err_t ret = esp_wifi_ap_get_sta_list(&staList);
|
||||
if (ret != ESP_OK) {
|
||||
ASF_LOGE(TAG, 2730, asf::logger::Criticality::HIGH, "Failed to get connected stations: %s", esp_err_to_name(ret));
|
||||
return -1;
|
||||
}
|
||||
|
||||
return staList.num;
|
||||
#else
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Wifi::isInitialized() const
|
||||
{
|
||||
return m_isInitialized_;
|
||||
}
|
||||
|
||||
WifiStaConfig Wifi::getDefaultStaConfig()
|
||||
{
|
||||
WifiStaConfig config = {};
|
||||
strcpy(config.ssid, "");
|
||||
strcpy(config.password, "");
|
||||
config.authMode = WifiAuthMode::WPA2_PSK;
|
||||
config.pmfRequired = false;
|
||||
config.channel = 0;
|
||||
config.rssiThreshold = -127;
|
||||
return config;
|
||||
}
|
||||
|
||||
WifiApConfig Wifi::getDefaultApConfig()
|
||||
{
|
||||
WifiApConfig config = {};
|
||||
strcpy(config.ssid, "ESP32-AP");
|
||||
strcpy(config.password, "12345678");
|
||||
config.authMode = WifiAuthMode::WPA2_PSK;
|
||||
config.channel = 1;
|
||||
config.maxConnections = 4;
|
||||
config.ssidHidden = false;
|
||||
return config;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_ESP_WIFI_ENABLED
|
||||
wifi_mode_t Wifi::convertMode(WifiMode mode)
|
||||
{
|
||||
switch (mode) {
|
||||
case WifiMode::NONE:
|
||||
return WIFI_MODE_NULL;
|
||||
case WifiMode::STA:
|
||||
return WIFI_MODE_STA;
|
||||
case WifiMode::AP:
|
||||
return WIFI_MODE_AP;
|
||||
case WifiMode::APSTA:
|
||||
return WIFI_MODE_APSTA;
|
||||
default:
|
||||
return WIFI_MODE_NULL;
|
||||
}
|
||||
}
|
||||
|
||||
wifi_auth_mode_t Wifi::convertAuthMode(WifiAuthMode authMode)
|
||||
{
|
||||
return static_cast<wifi_auth_mode_t>(authMode);
|
||||
}
|
||||
|
||||
void Wifi::wifiEventHandler(void* arg, esp_event_base_t eventBase, int32_t eventId, void* eventData)
|
||||
{
|
||||
Wifi* wifi = static_cast<Wifi*>(arg);
|
||||
|
||||
switch (eventId) {
|
||||
case WIFI_EVENT_STA_START:
|
||||
ASF_LOGI(TAG, 2731, asf::logger::Criticality::LOW, "WiFi STA started");
|
||||
break;
|
||||
|
||||
case WIFI_EVENT_STA_CONNECTED:
|
||||
ASF_LOGI(TAG, 2732, asf::logger::Criticality::LOW, "WiFi STA connected");
|
||||
wifi->m_connectionStatus_ = WifiConnectionStatus::CONNECTED;
|
||||
break;
|
||||
|
||||
case WIFI_EVENT_STA_DISCONNECTED:
|
||||
ASF_LOGI(TAG, 2733, asf::logger::Criticality::LOW, "WiFi STA disconnected");
|
||||
wifi->m_connectionStatus_ = WifiConnectionStatus::DISCONNECTED;
|
||||
if (wifi->m_eventCallback_) {
|
||||
wifi->m_eventCallback_(WifiConnectionStatus::DISCONNECTED, wifi->m_eventCallbackArg_);
|
||||
}
|
||||
break;
|
||||
|
||||
case WIFI_EVENT_AP_START:
|
||||
ASF_LOGI(TAG, 2734, asf::logger::Criticality::LOW, "WiFi AP started");
|
||||
break;
|
||||
|
||||
case WIFI_EVENT_AP_STOP:
|
||||
ASF_LOGI(TAG, 2735, asf::logger::Criticality::LOW, "WiFi AP stopped");
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Wifi::ipEventHandler(void* arg, esp_event_base_t eventBase, int32_t eventId, void* eventData)
|
||||
{
|
||||
Wifi* wifi = static_cast<Wifi*>(arg);
|
||||
|
||||
if (eventId == IP_EVENT_STA_GOT_IP) {
|
||||
ip_event_got_ip_t* event = (ip_event_got_ip_t*) eventData;
|
||||
ASF_LOGI(TAG, 2736, asf::logger::Criticality::LOW, "Got IP: " IPSTR, IP2STR(&event->ip_info.ip));
|
||||
wifi->m_connectionStatus_ = WifiConnectionStatus::CONNECTED;
|
||||
|
||||
if (wifi->m_eventCallback_) {
|
||||
wifi->m_eventCallback_(WifiConnectionStatus::CONNECTED, wifi->m_eventCallbackArg_);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,306 @@
|
||||
/**
|
||||
* @file wifi.hpp
|
||||
* @brief WiFi wrapper component header - Wrapper for ESP-IDF WiFi functionality
|
||||
* @author Mahmoud Elmohtady
|
||||
* @company Nabd solutions - ASF
|
||||
* @copyright Copyright (c) 2025
|
||||
*/
|
||||
|
||||
#ifndef WIFI_HPP
|
||||
#define WIFI_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include "esp_err.h"
|
||||
|
||||
#ifdef CONFIG_ESP_WIFI_ENABLED
|
||||
#include "esp_wifi.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_netif.h"
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief WiFi mode enumeration
|
||||
*/
|
||||
enum class WifiMode
|
||||
{
|
||||
NONE,
|
||||
STA, ///< Station mode
|
||||
AP, ///< Access Point mode
|
||||
APSTA ///< AP + STA mode
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief WiFi authentication mode enumeration
|
||||
*/
|
||||
enum class WifiAuthMode
|
||||
{
|
||||
#ifdef CONFIG_ESP_WIFI_ENABLED
|
||||
OPEN = WIFI_AUTH_OPEN,
|
||||
WEP = WIFI_AUTH_WEP,
|
||||
WPA_PSK = WIFI_AUTH_WPA_PSK,
|
||||
WPA2_PSK = WIFI_AUTH_WPA2_PSK,
|
||||
WPA_WPA2_PSK = WIFI_AUTH_WPA_WPA2_PSK,
|
||||
WPA3_PSK = WIFI_AUTH_WPA3_PSK,
|
||||
WPA2_WPA3_PSK = WIFI_AUTH_WPA2_WPA3_PSK
|
||||
#else
|
||||
OPEN,
|
||||
WEP,
|
||||
WPA_PSK,
|
||||
WPA2_PSK,
|
||||
WPA_WPA2_PSK,
|
||||
WPA3_PSK,
|
||||
WPA2_WPA3_PSK
|
||||
#endif
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief WiFi station configuration structure
|
||||
*/
|
||||
struct WifiStaConfig
|
||||
{
|
||||
char ssid[32]; ///< SSID of target AP
|
||||
char password[64]; ///< Password of target AP
|
||||
WifiAuthMode authMode; ///< Authentication mode
|
||||
bool pmfRequired; ///< Protected Management Frame required
|
||||
uint8_t channel; ///< Channel of target AP (0 = auto)
|
||||
int8_t rssiThreshold; ///< Minimum RSSI threshold
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief WiFi access point configuration structure
|
||||
*/
|
||||
struct WifiApConfig
|
||||
{
|
||||
char ssid[32]; ///< SSID of AP
|
||||
char password[64]; ///< Password of AP
|
||||
WifiAuthMode authMode; ///< Authentication mode
|
||||
uint8_t channel; ///< Channel number
|
||||
uint8_t maxConnections; ///< Maximum number of connections
|
||||
bool ssidHidden; ///< Hide SSID
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief WiFi configuration structure
|
||||
*/
|
||||
struct WifiConfig
|
||||
{
|
||||
WifiMode mode; ///< WiFi mode
|
||||
WifiStaConfig staConfig; ///< Station configuration
|
||||
WifiApConfig apConfig; ///< Access Point configuration
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief WiFi connection status enumeration
|
||||
*/
|
||||
enum class WifiConnectionStatus
|
||||
{
|
||||
DISCONNECTED,
|
||||
CONNECTING,
|
||||
CONNECTED,
|
||||
FAILED
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief WiFi scan result structure
|
||||
*/
|
||||
struct WifiScanResult
|
||||
{
|
||||
char ssid[33]; ///< SSID
|
||||
uint8_t bssid[6]; ///< BSSID
|
||||
uint8_t channel; ///< Channel
|
||||
int8_t rssi; ///< RSSI
|
||||
WifiAuthMode authMode; ///< Authentication mode
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief WiFi event callback function type
|
||||
*/
|
||||
using WifiEventCallback = void (*)(WifiConnectionStatus status, void* arg);
|
||||
|
||||
/**
|
||||
* @brief WiFi wrapper class
|
||||
*
|
||||
* Provides a C++ wrapper for ESP-IDF WiFi functionality.
|
||||
* This class encapsulates ESP-IDF WiFi driver functions in an object-oriented interface.
|
||||
*/
|
||||
class Wifi
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @details Initializes the WiFi wrapper instance
|
||||
*/
|
||||
Wifi();
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
* @details Cleans up resources and deinitializes WiFi
|
||||
*/
|
||||
~Wifi();
|
||||
|
||||
/**
|
||||
* @brief Initialize WiFi with configuration
|
||||
* @param config WiFi configuration
|
||||
* @return true if initialized successfully, false otherwise
|
||||
* @note This function initializes the WiFi stack and event loop
|
||||
*/
|
||||
bool initialize(const WifiConfig& config);
|
||||
|
||||
/**
|
||||
* @brief Deinitialize WiFi
|
||||
* @return true if deinitialized successfully, false otherwise
|
||||
*/
|
||||
bool deinitialize();
|
||||
|
||||
/**
|
||||
* @brief Start WiFi (connect in STA mode or start AP)
|
||||
* @return true if started successfully, false otherwise
|
||||
*/
|
||||
bool start();
|
||||
|
||||
/**
|
||||
* @brief Stop WiFi
|
||||
* @return true if stopped successfully, false otherwise
|
||||
*/
|
||||
bool stop();
|
||||
|
||||
/**
|
||||
* @brief Connect to WiFi network (STA mode)
|
||||
* @return true if connection initiated successfully, false otherwise
|
||||
* @note This is asynchronous, use event callback to get connection status
|
||||
*/
|
||||
bool connect();
|
||||
|
||||
/**
|
||||
* @brief Disconnect from WiFi network
|
||||
* @return true if disconnected successfully, false otherwise
|
||||
*/
|
||||
bool disconnect();
|
||||
|
||||
/**
|
||||
* @brief Scan for available WiFi networks
|
||||
* @param results Array to store scan results
|
||||
* @param maxResults Maximum number of results to return
|
||||
* @param scanTimeMs Scan time in milliseconds
|
||||
* @return Number of networks found, or -1 on error
|
||||
*/
|
||||
int32_t scan(WifiScanResult* results, size_t maxResults, uint32_t scanTimeMs = 3000);
|
||||
|
||||
/**
|
||||
* @brief Get current connection status
|
||||
* @return Current WiFi connection status
|
||||
*/
|
||||
WifiConnectionStatus getConnectionStatus() const;
|
||||
|
||||
/**
|
||||
* @brief Check if WiFi is connected
|
||||
* @return true if connected, false otherwise
|
||||
*/
|
||||
bool isConnected() const;
|
||||
|
||||
/**
|
||||
* @brief Get RSSI of current connection
|
||||
* @return RSSI in dBm, or 0 if not connected
|
||||
*/
|
||||
int32_t getRssi();
|
||||
|
||||
/**
|
||||
* @brief Get IP address (STA mode)
|
||||
* @param ip Buffer to store IP address string
|
||||
* @param maxLen Maximum length of IP string
|
||||
* @return true if IP retrieved successfully, false otherwise
|
||||
*/
|
||||
bool getIpAddress(char* ip, size_t maxLen);
|
||||
|
||||
/**
|
||||
* @brief Get MAC address
|
||||
* @param mac Buffer to store MAC address (6 bytes)
|
||||
* @param mode WiFi mode to get MAC for
|
||||
* @return true if MAC retrieved successfully, false otherwise
|
||||
*/
|
||||
bool getMacAddress(uint8_t* mac, WifiMode mode);
|
||||
|
||||
/**
|
||||
* @brief Set event callback
|
||||
* @param callback Callback function
|
||||
* @param arg Argument passed to callback
|
||||
*/
|
||||
void setEventCallback(WifiEventCallback callback, void* arg);
|
||||
|
||||
/**
|
||||
* @brief Get number of connected stations (AP mode)
|
||||
* @return Number of connected stations, or -1 on error
|
||||
*/
|
||||
int32_t getConnectedStations();
|
||||
|
||||
/**
|
||||
* @brief Check if WiFi is initialized
|
||||
* @return true if initialized, false otherwise
|
||||
*/
|
||||
bool isInitialized() const;
|
||||
|
||||
/**
|
||||
* @brief Get default station configuration
|
||||
* @return Default WiFi station configuration
|
||||
*/
|
||||
static WifiStaConfig getDefaultStaConfig();
|
||||
|
||||
/**
|
||||
* @brief Get default access point configuration
|
||||
* @return Default WiFi access point configuration
|
||||
*/
|
||||
static WifiApConfig getDefaultApConfig();
|
||||
|
||||
private:
|
||||
bool m_isInitialized_; ///< Initialization status
|
||||
WifiConnectionStatus m_connectionStatus_; ///< Current connection status
|
||||
WifiConfig m_config_; ///< WiFi configuration
|
||||
#ifdef CONFIG_ESP_WIFI_ENABLED
|
||||
esp_netif_t* m_netifSta_; ///< Station netif handle
|
||||
esp_netif_t* m_netifAp_; ///< AP netif handle
|
||||
#else
|
||||
void* m_netifSta_;
|
||||
void* m_netifAp_;
|
||||
#endif
|
||||
WifiEventCallback m_eventCallback_; ///< Event callback function
|
||||
void* m_eventCallbackArg_; ///< Event callback argument
|
||||
|
||||
#ifdef CONFIG_ESP_WIFI_ENABLED
|
||||
/**
|
||||
* @brief Convert WifiMode to ESP-IDF wifi_mode_t
|
||||
* @param mode WiFi mode
|
||||
* @return ESP-IDF wifi_mode_t
|
||||
*/
|
||||
wifi_mode_t convertMode(WifiMode mode);
|
||||
|
||||
/**
|
||||
* @brief Convert WifiAuthMode to ESP-IDF wifi_auth_mode_t
|
||||
* @param authMode Authentication mode
|
||||
* @return ESP-IDF wifi_auth_mode_t
|
||||
*/
|
||||
wifi_auth_mode_t convertAuthMode(WifiAuthMode authMode);
|
||||
|
||||
/**
|
||||
* @brief WiFi event handler
|
||||
* @param arg Event handler argument
|
||||
* @param eventBase Event base
|
||||
* @param eventId Event ID
|
||||
* @param eventData Event data
|
||||
*/
|
||||
static void wifiEventHandler(void* arg, esp_event_base_t eventBase,
|
||||
int32_t eventId, void* eventData);
|
||||
|
||||
/**
|
||||
* @brief IP event handler
|
||||
* @param arg Event handler argument
|
||||
* @param eventBase Event base
|
||||
* @param eventId Event ID
|
||||
* @param eventData Event data
|
||||
*/
|
||||
static void ipEventHandler(void* arg, esp_event_base_t eventBase,
|
||||
int32_t eventId, void* eventData);
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // WIFI_HPP
|
||||
@@ -0,0 +1,38 @@
|
||||
ID,Component,Level,Criticality,Message
|
||||
2700,WIFI,INFO,Low,WiFi wrapper initialized
|
||||
2701,WIFI,INFO,Low,WiFi wrapper destroyed
|
||||
2702,WIFI,WARNING,Medium,WiFi already initialized
|
||||
2703,WIFI,INFO,Low,WiFi initialized successfully
|
||||
2704,WIFI,WARNING,Medium,WiFi disabled in sdkconfig
|
||||
2705,WIFI,WARNING,Medium,WiFi not initialized
|
||||
2706,WIFI,INFO,Low,WiFi deinitialized
|
||||
2707,WIFI,ERROR,High,WiFi not initialized
|
||||
2708,WIFI,ERROR,High,Failed to start WiFi: %s
|
||||
2709,WIFI,INFO,Low,WiFi started
|
||||
2710,WIFI,WARNING,Medium,WiFi not initialized
|
||||
2711,WIFI,ERROR,High,Failed to stop WiFi: %s
|
||||
2712,WIFI,INFO,Low,WiFi stopped
|
||||
2713,WIFI,ERROR,High,WiFi not initialized
|
||||
2714,WIFI,ERROR,High,WiFi not in STA mode
|
||||
2715,WIFI,ERROR,High,Failed to connect to WiFi: %s
|
||||
2716,WIFI,INFO,Low,WiFi connection initiated
|
||||
2717,WIFI,WARNING,Medium,WiFi not initialized
|
||||
2718,WIFI,ERROR,High,Failed to disconnect WiFi: %s
|
||||
2719,WIFI,INFO,Low,WiFi disconnected
|
||||
2720,WIFI,ERROR,High,Invalid scan parameters
|
||||
2721,WIFI,ERROR,High,Failed to start WiFi scan: %s
|
||||
2722,WIFI,INFO,Low,No access points found
|
||||
2723,WIFI,ERROR,High,Failed to get scan results: %s
|
||||
2724,WIFI,INFO,Low,WiFi scan completed, found %d networks
|
||||
2725,WIFI,WARNING,Medium,WiFi not connected
|
||||
2726,WIFI,ERROR,High,Failed to get AP info: %s
|
||||
2727,WIFI,ERROR,High,Failed to get IP info: %s
|
||||
2728,WIFI,ERROR,High,Failed to get MAC address: %s
|
||||
2729,WIFI,ERROR,High,WiFi not in AP mode
|
||||
2730,WIFI,ERROR,High,Failed to get connected stations: %s
|
||||
2731,WIFI,INFO,Low,WiFi STA started
|
||||
2732,WIFI,INFO,Low,WiFi STA connected
|
||||
2733,WIFI,INFO,Low,WiFi STA disconnected
|
||||
2734,WIFI,INFO,Low,WiFi AP started
|
||||
2735,WIFI,INFO,Low,WiFi AP stopped
|
||||
2736,WIFI,INFO,Low,Got IP: %s
|
||||
|
Can't render this file because it has a wrong number of fields in line 26.
|
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* @file test_wifi.cpp
|
||||
* @brief Unit tests for WiFi wrapper component
|
||||
* @author Mahmoud Elmohtady
|
||||
* @company Nabd solutions - ASF
|
||||
* @copyright Copyright (c) 2025
|
||||
*/
|
||||
|
||||
#include "unity.h"
|
||||
#include "wifi.hpp"
|
||||
|
||||
extern "C" {
|
||||
|
||||
void setUp(void)
|
||||
{
|
||||
}
|
||||
|
||||
void tearDown(void)
|
||||
{
|
||||
}
|
||||
|
||||
void test_wifi_initialize(void)
|
||||
{
|
||||
Wifi wifi;
|
||||
WifiConfig config = {"test_ssid", "test_password", WifiMode::STA};
|
||||
bool result = wifi.initialize(config);
|
||||
TEST_ASSERT_TRUE(result);
|
||||
}
|
||||
|
||||
void test_wifi_connect(void)
|
||||
{
|
||||
Wifi wifi;
|
||||
WifiConfig config = {"test_ssid", "test_password", WifiMode::STA};
|
||||
wifi.initialize(config);
|
||||
|
||||
bool result = wifi.connect();
|
||||
TEST_ASSERT_TRUE(result);
|
||||
TEST_ASSERT_TRUE(wifi.isConnected());
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
|
||||
folder_path = os.path.abspath(os.path.join("components", "system_tests"))
|
||||
if folder_path not in sys.path:
|
||||
sys.path.append(folder_path)
|
||||
|
||||
from scan_serial import ESP32Runner
|
||||
|
||||
def test_wifi_initialize():
|
||||
runner = ESP32Runner(mode="SIM", port="COM9")
|
||||
runner.start()
|
||||
print("--- QEMU Runner Started ---", flush=True)
|
||||
try:
|
||||
start_time = time.time()
|
||||
while time.time() - start_time < 30:
|
||||
line = runner.get_line(timeout=1.0)
|
||||
if line:
|
||||
print(line, flush=True)
|
||||
if "WiFi initialized successfully" in line:
|
||||
print("SUCCESS CRITERIA MET!", flush=True)
|
||||
return 0
|
||||
if runner.process.poll() is not None:
|
||||
print(f"Process exited with code: {runner.process.returncode}", flush=True)
|
||||
return 1
|
||||
finally:
|
||||
runner.stop()
|
||||
print("Done.", flush=True)
|
||||
return 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
exit_code = test_wifi_initialize()
|
||||
sys.exit(exit_code)
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<test_scenario>
|
||||
<!-- The configuration for the test environment. -->
|
||||
<!-- Available configurations: SIMULATE, HIL -->
|
||||
<config>SIMULATE</config>
|
||||
|
||||
<test_case>
|
||||
<test_case_id>WIFI_INIT_TEST</test_case_id>
|
||||
<!-- The main command that executes the test itself. -->
|
||||
<test_exec>python components/ESP_IDF_FW_wrappers/wifi/test/wifi_init_test.py</test_exec>
|
||||
</test_case>
|
||||
|
||||
|
||||
</test_scenario>
|
||||
@@ -0,0 +1,5 @@
|
||||
idf_component_register(
|
||||
SRCS "com/data_pool.cpp"
|
||||
INCLUDE_DIRS "com"
|
||||
REQUIRES logger
|
||||
)
|
||||
@@ -0,0 +1,667 @@
|
||||
# Data Pool Component Specification
|
||||
|
||||
**Component ID:** COMP-DATA-POOL
|
||||
**Version:** 1.0
|
||||
**Date:** 2025-01-19
|
||||
**Location:** `application_layer/DP_stack/data_pool/`
|
||||
|
||||
## 1. Purpose
|
||||
|
||||
The Data Pool component provides centralized, thread-safe storage for runtime system data including latest sensor values, system state, diagnostic information, and configuration data. It serves as the single source of truth for all runtime data and provides fast access for components that need current system information.
|
||||
|
||||
## 2. Responsibilities
|
||||
|
||||
### 2.1 Primary Responsibilities
|
||||
|
||||
- **Runtime Data Storage:** Maintain latest sensor data, system state, and diagnostic information
|
||||
- **Thread-Safe Access:** Provide concurrent read/write access with appropriate synchronization
|
||||
- **Data Consistency:** Ensure data integrity across multiple readers and writers
|
||||
- **Fast Data Access:** Provide low-latency access to frequently accessed data
|
||||
- **Data Validation:** Validate data integrity and consistency on updates
|
||||
- **Memory Management:** Efficient memory usage with bounded storage requirements
|
||||
|
||||
### 2.2 Non-Responsibilities
|
||||
|
||||
- **Data Persistence:** Delegated to Persistence component (long-term storage)
|
||||
- **Data Processing:** Components handle their own data processing logic
|
||||
- **Network Communication:** Delegated to Communication components
|
||||
- **Hardware Access:** No direct hardware interface
|
||||
|
||||
## 3. Public API
|
||||
|
||||
### 3.1 Sensor Data Management
|
||||
|
||||
```c
|
||||
/**
|
||||
* @brief Update sensor data record
|
||||
* @param sensor_id Sensor identifier (0-6)
|
||||
* @param record Sensor data record to store
|
||||
* @return true if update successful, false on error
|
||||
*/
|
||||
bool dataPool_updateSensorData(uint8_t sensor_id, const sensor_data_record_t* record);
|
||||
|
||||
/**
|
||||
* @brief Get latest sensor data record
|
||||
* @param sensor_id Sensor identifier (0-6)
|
||||
* @param record Output buffer for sensor data record
|
||||
* @return true if data retrieved, false on error
|
||||
*/
|
||||
bool dataPool_getSensorData(uint8_t sensor_id, sensor_data_record_t* record);
|
||||
|
||||
/**
|
||||
* @brief Get all sensor data records
|
||||
* @param records Output buffer for sensor data records
|
||||
* @param count Input: buffer size, Output: number of records filled
|
||||
* @return true if data retrieved, false on error
|
||||
*/
|
||||
bool dataPool_getAllSensorData(sensor_data_record_t* records, size_t* count);
|
||||
|
||||
/**
|
||||
* @brief Check if sensor data is available and valid
|
||||
* @param sensor_id Sensor identifier (0-6)
|
||||
* @return true if valid data available, false otherwise
|
||||
*/
|
||||
bool dataPool_isSensorDataValid(uint8_t sensor_id);
|
||||
|
||||
/**
|
||||
* @brief Get timestamp of last sensor data update
|
||||
* @param sensor_id Sensor identifier (0-6)
|
||||
* @return Timestamp of last update, 0 if no data available
|
||||
*/
|
||||
uint64_t dataPool_getSensorDataTimestamp(uint8_t sensor_id);
|
||||
```
|
||||
|
||||
### 3.2 System State Management
|
||||
|
||||
```c
|
||||
/**
|
||||
* @brief Update system state information
|
||||
* @param state_info System state information structure
|
||||
* @return true if update successful, false on error
|
||||
*/
|
||||
bool dataPool_updateSystemState(const system_state_info_t* state_info);
|
||||
|
||||
/**
|
||||
* @brief Get current system state information
|
||||
* @param state_info Output buffer for system state information
|
||||
* @return true if data retrieved, false on error
|
||||
*/
|
||||
bool dataPool_getSystemState(system_state_info_t* state_info);
|
||||
|
||||
/**
|
||||
* @brief Update system health metrics
|
||||
* @param health_metrics System health metrics structure
|
||||
* @return true if update successful, false on error
|
||||
*/
|
||||
bool dataPool_updateHealthMetrics(const system_health_metrics_t* health_metrics);
|
||||
|
||||
/**
|
||||
* @brief Get system health metrics
|
||||
* @param health_metrics Output buffer for health metrics
|
||||
* @return true if data retrieved, false on error
|
||||
*/
|
||||
bool dataPool_getHealthMetrics(system_health_metrics_t* health_metrics);
|
||||
```
|
||||
|
||||
### 3.3 Diagnostic Data Management
|
||||
|
||||
```c
|
||||
/**
|
||||
* @brief Add diagnostic event to pool
|
||||
* @param event Diagnostic event structure
|
||||
* @return true if event added, false on error
|
||||
*/
|
||||
bool dataPool_addDiagnosticEvent(const diagnostic_event_t* event);
|
||||
|
||||
/**
|
||||
* @brief Get recent diagnostic events
|
||||
* @param events Output buffer for diagnostic events
|
||||
* @param count Input: buffer size, Output: number of events filled
|
||||
* @return true if events retrieved, false on error
|
||||
*/
|
||||
bool dataPool_getRecentDiagnostics(diagnostic_event_t* events, size_t* count);
|
||||
|
||||
/**
|
||||
* @brief Get diagnostic summary (counts by severity)
|
||||
* @param summary Output buffer for diagnostic summary
|
||||
* @return true if summary retrieved, false on error
|
||||
*/
|
||||
bool dataPool_getDiagnosticSummary(diagnostic_summary_t* summary);
|
||||
|
||||
/**
|
||||
* @brief Clear diagnostic events from pool
|
||||
* @param severity Severity level to clear (DIAG_SEVERITY_ALL for all)
|
||||
* @return Number of events cleared
|
||||
*/
|
||||
size_t dataPool_clearDiagnostics(diagnostic_severity_t severity);
|
||||
```
|
||||
|
||||
### 3.4 Communication Status Management
|
||||
|
||||
```c
|
||||
/**
|
||||
* @brief Update communication link status
|
||||
* @param link_type Communication link type
|
||||
* @param status Link status information
|
||||
* @return true if update successful, false on error
|
||||
*/
|
||||
bool dataPool_updateLinkStatus(comm_link_type_t link_type, const comm_link_status_t* status);
|
||||
|
||||
/**
|
||||
* @brief Get communication link status
|
||||
* @param link_type Communication link type
|
||||
* @param status Output buffer for link status
|
||||
* @return true if status retrieved, false on error
|
||||
*/
|
||||
bool dataPool_getLinkStatus(comm_link_type_t link_type, comm_link_status_t* status);
|
||||
|
||||
/**
|
||||
* @brief Get overall communication status
|
||||
* @param comm_status Output buffer for communication status
|
||||
* @return true if status retrieved, false on error
|
||||
*/
|
||||
bool dataPool_getCommunicationStatus(communication_status_t* comm_status);
|
||||
```
|
||||
|
||||
### 3.5 Configuration Data Management
|
||||
|
||||
```c
|
||||
/**
|
||||
* @brief Update runtime configuration
|
||||
* @param config_type Configuration type
|
||||
* @param config_data Configuration data
|
||||
* @param data_size Size of configuration data
|
||||
* @return true if update successful, false on error
|
||||
*/
|
||||
bool dataPool_updateConfiguration(config_type_t config_type, const void* config_data, size_t data_size);
|
||||
|
||||
/**
|
||||
* @brief Get runtime configuration
|
||||
* @param config_type Configuration type
|
||||
* @param config_data Output buffer for configuration data
|
||||
* @param data_size Input: buffer size, Output: actual data size
|
||||
* @return true if configuration retrieved, false on error
|
||||
*/
|
||||
bool dataPool_getConfiguration(config_type_t config_type, void* config_data, size_t* data_size);
|
||||
|
||||
/**
|
||||
* @brief Check if configuration is valid
|
||||
* @param config_type Configuration type
|
||||
* @return true if configuration is valid, false otherwise
|
||||
*/
|
||||
bool dataPool_isConfigurationValid(config_type_t config_type);
|
||||
```
|
||||
|
||||
### 3.6 Data Pool Management
|
||||
|
||||
```c
|
||||
/**
|
||||
* @brief Initialize Data Pool component
|
||||
* @return true if initialization successful, false otherwise
|
||||
*/
|
||||
bool dataPool_initialize(void);
|
||||
|
||||
/**
|
||||
* @brief Get Data Pool statistics
|
||||
* @param stats Output buffer for statistics
|
||||
* @return true if statistics retrieved, false on error
|
||||
*/
|
||||
bool dataPool_getStatistics(data_pool_stats_t* stats);
|
||||
|
||||
/**
|
||||
* @brief Reset Data Pool statistics
|
||||
* @return true if statistics reset, false on error
|
||||
*/
|
||||
bool dataPool_resetStatistics(void);
|
||||
|
||||
/**
|
||||
* @brief Validate Data Pool integrity
|
||||
* @return true if integrity check passed, false if corruption detected
|
||||
*/
|
||||
bool dataPool_validateIntegrity(void);
|
||||
|
||||
/**
|
||||
* @brief Create snapshot of current data pool state
|
||||
* @param snapshot Output buffer for snapshot
|
||||
* @return true if snapshot created, false on error
|
||||
*/
|
||||
bool dataPool_createSnapshot(data_pool_snapshot_t* snapshot);
|
||||
```
|
||||
|
||||
## 4. Data Types
|
||||
|
||||
### 4.1 Sensor Data Record (Extended)
|
||||
|
||||
```c
|
||||
typedef struct {
|
||||
uint8_t sensor_id; // Sensor identifier (0-6)
|
||||
sensor_type_t sensor_type; // Type of sensor
|
||||
float filtered_value; // Processed sensor value
|
||||
char unit[8]; // Unit of measurement
|
||||
uint64_t timestamp_ms; // Timestamp in milliseconds
|
||||
data_validity_t validity; // Data validity status
|
||||
uint16_t sample_count; // Number of samples used
|
||||
float raw_min, raw_max; // Min/max of raw samples
|
||||
float raw_stddev; // Standard deviation
|
||||
uint32_t acquisition_time_us; // Acquisition time
|
||||
uint32_t sequence_number; // Monotonic sequence number
|
||||
uint16_t checksum; // Data integrity checksum
|
||||
} sensor_data_record_t;
|
||||
```
|
||||
|
||||
### 4.2 System State Information
|
||||
|
||||
```c
|
||||
typedef struct {
|
||||
system_state_t current_state; // Current system state
|
||||
system_state_t previous_state; // Previous system state
|
||||
transition_reason_t last_reason; // Last transition reason
|
||||
uint64_t state_entry_time; // Time when current state was entered
|
||||
uint64_t state_duration_ms; // Duration in current state
|
||||
uint32_t state_transition_count; // Total number of state transitions
|
||||
bool teardown_in_progress; // Teardown sequence active
|
||||
uint8_t teardown_progress; // Teardown progress (0-100%)
|
||||
} system_state_info_t;
|
||||
```
|
||||
|
||||
### 4.3 System Health Metrics
|
||||
|
||||
```c
|
||||
typedef struct {
|
||||
// CPU and Memory
|
||||
float cpu_usage_percent; // Current CPU usage
|
||||
uint32_t free_heap_bytes; // Available heap memory
|
||||
uint32_t min_free_heap_bytes; // Minimum free heap recorded
|
||||
uint32_t heap_fragmentation; // Heap fragmentation percentage
|
||||
|
||||
// Task Information
|
||||
uint32_t task_count; // Number of active tasks
|
||||
uint32_t stack_high_water_mark; // Minimum remaining stack
|
||||
|
||||
// System Uptime
|
||||
uint64_t uptime_ms; // System uptime in milliseconds
|
||||
uint32_t boot_count; // Number of system boots
|
||||
|
||||
// Storage
|
||||
uint64_t sd_free_bytes; // SD card free space
|
||||
uint64_t sd_total_bytes; // SD card total space
|
||||
uint32_t nvm_free_entries; // NVM free entries
|
||||
|
||||
// Communication
|
||||
uint32_t wifi_rssi; // WiFi signal strength
|
||||
uint32_t packets_sent; // Total packets sent
|
||||
uint32_t packets_received; // Total packets received
|
||||
uint32_t communication_errors; // Communication error count
|
||||
|
||||
// Sensors
|
||||
uint8_t sensors_active; // Number of active sensors
|
||||
uint8_t sensors_faulty; // Number of faulty sensors
|
||||
uint32_t total_acquisitions; // Total sensor acquisitions
|
||||
|
||||
// Diagnostics
|
||||
uint32_t warning_count; // Active warning count
|
||||
uint32_t error_count; // Active error count
|
||||
uint32_t fatal_count; // Fatal error count
|
||||
|
||||
uint64_t last_update_time; // Last metrics update time
|
||||
} system_health_metrics_t;
|
||||
```
|
||||
|
||||
### 4.4 Diagnostic Event
|
||||
|
||||
```c
|
||||
typedef struct {
|
||||
uint16_t diagnostic_code; // Diagnostic code (0xSCCC format)
|
||||
diagnostic_severity_t severity; // Severity level
|
||||
uint64_t timestamp_ms; // Event timestamp
|
||||
uint32_t occurrence_count; // Number of occurrences
|
||||
char description[64]; // Human-readable description
|
||||
uint8_t context_data[32]; // Context-specific data
|
||||
uint32_t sequence_number; // Event sequence number
|
||||
} diagnostic_event_t;
|
||||
|
||||
typedef struct {
|
||||
uint32_t info_count; // Number of INFO events
|
||||
uint32_t warning_count; // Number of WARNING events
|
||||
uint32_t error_count; // Number of ERROR events
|
||||
uint32_t fatal_count; // Number of FATAL events
|
||||
uint64_t last_event_time; // Timestamp of last event
|
||||
uint16_t most_recent_code; // Most recent diagnostic code
|
||||
} diagnostic_summary_t;
|
||||
```
|
||||
|
||||
### 4.5 Communication Status
|
||||
|
||||
```c
|
||||
typedef enum {
|
||||
COMM_LINK_MAIN_HUB = 0, // Main Hub communication
|
||||
COMM_LINK_PEER_HUB, // Peer Hub communication
|
||||
COMM_LINK_DIAGNOSTIC, // Diagnostic communication
|
||||
COMM_LINK_COUNT
|
||||
} comm_link_type_t;
|
||||
|
||||
typedef struct {
|
||||
bool is_connected; // Connection status
|
||||
uint64_t last_activity_time; // Last communication activity
|
||||
uint32_t bytes_sent; // Bytes sent
|
||||
uint32_t bytes_received; // Bytes received
|
||||
uint32_t error_count; // Communication errors
|
||||
int32_t signal_strength; // Signal strength (RSSI)
|
||||
uint32_t round_trip_time_ms; // Average round-trip time
|
||||
} comm_link_status_t;
|
||||
|
||||
typedef struct {
|
||||
comm_link_status_t links[COMM_LINK_COUNT]; // Individual link status
|
||||
bool overall_connectivity; // Overall connectivity status
|
||||
uint64_t last_successful_comm; // Last successful communication
|
||||
uint32_t total_comm_failures; // Total communication failures
|
||||
} communication_status_t;
|
||||
```
|
||||
|
||||
### 4.6 Data Pool Statistics
|
||||
|
||||
```c
|
||||
typedef struct {
|
||||
// Access Statistics
|
||||
uint64_t total_reads; // Total read operations
|
||||
uint64_t total_writes; // Total write operations
|
||||
uint64_t read_errors; // Read operation errors
|
||||
uint64_t write_errors; // Write operation errors
|
||||
|
||||
// Performance Metrics
|
||||
uint32_t avg_read_time_us; // Average read time
|
||||
uint32_t avg_write_time_us; // Average write time
|
||||
uint32_t max_read_time_us; // Maximum read time
|
||||
uint32_t max_write_time_us; // Maximum write time
|
||||
|
||||
// Memory Usage
|
||||
uint32_t memory_used_bytes; // Current memory usage
|
||||
uint32_t max_memory_used_bytes; // Peak memory usage
|
||||
|
||||
// Data Integrity
|
||||
uint32_t checksum_failures; // Checksum validation failures
|
||||
uint32_t integrity_checks; // Total integrity checks performed
|
||||
|
||||
// Concurrency
|
||||
uint32_t concurrent_readers; // Current concurrent readers
|
||||
uint32_t max_concurrent_readers; // Maximum concurrent readers
|
||||
uint32_t lock_contentions; // Lock contention events
|
||||
|
||||
uint64_t statistics_reset_time; // Last statistics reset time
|
||||
} data_pool_stats_t;
|
||||
```
|
||||
|
||||
## 5. Internal Architecture
|
||||
|
||||
### 5.1 Data Pool Structure
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "Data Pool Component"
|
||||
subgraph "Sensor Data Storage"
|
||||
SD0[Sensor 0 Data]
|
||||
SD1[Sensor 1 Data]
|
||||
SD2[Sensor 2 Data]
|
||||
SD3[Sensor 3 Data]
|
||||
SD4[Sensor 4 Data]
|
||||
SD5[Sensor 5 Data]
|
||||
SD6[Sensor 6 Data]
|
||||
end
|
||||
|
||||
subgraph "System Data Storage"
|
||||
SS[System State Info]
|
||||
HM[Health Metrics]
|
||||
CS[Communication Status]
|
||||
CF[Configuration Data]
|
||||
end
|
||||
|
||||
subgraph "Diagnostic Data Storage"
|
||||
DE[Diagnostic Events]
|
||||
DS[Diagnostic Summary]
|
||||
end
|
||||
|
||||
subgraph "Access Control"
|
||||
RM[Reader-Writer Mutex]
|
||||
IC[Integrity Checker]
|
||||
ST[Statistics Tracker]
|
||||
end
|
||||
end
|
||||
|
||||
subgraph "External Components"
|
||||
SM[Sensor Manager]
|
||||
STM[State Manager]
|
||||
COMM[Communication]
|
||||
DIAG[Diagnostics]
|
||||
HMI[HMI]
|
||||
end
|
||||
|
||||
SM -->|Update| SD0
|
||||
SM -->|Update| SD1
|
||||
STM -->|Update| SS
|
||||
COMM -->|Update| CS
|
||||
DIAG -->|Update| DE
|
||||
|
||||
HMI -->|Read| SS
|
||||
HMI -->|Read| HM
|
||||
COMM -->|Read| SD0
|
||||
COMM -->|Read| SD1
|
||||
|
||||
RM -.->|Protects| SD0
|
||||
RM -.->|Protects| SS
|
||||
IC -.->|Validates| DE
|
||||
ST -.->|Tracks| RM
|
||||
```
|
||||
|
||||
### 5.2 Thread Safety Model
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant W1 as Writer 1
|
||||
participant W2 as Writer 2
|
||||
participant R1 as Reader 1
|
||||
participant R2 as Reader 2
|
||||
participant DP as Data Pool
|
||||
participant Mutex as RW Mutex
|
||||
|
||||
Note over W1,Mutex: Concurrent Access Scenario
|
||||
|
||||
W1->>Mutex: acquireWriteLock()
|
||||
Mutex-->>W1: lockAcquired
|
||||
W1->>DP: updateSensorData()
|
||||
|
||||
R1->>Mutex: acquireReadLock()
|
||||
Note over R1,Mutex: Blocked until write complete
|
||||
|
||||
W2->>Mutex: acquireWriteLock()
|
||||
Note over W2,Mutex: Blocked until write complete
|
||||
|
||||
W1->>DP: dataUpdateComplete
|
||||
W1->>Mutex: releaseWriteLock()
|
||||
|
||||
Mutex-->>R1: readLockAcquired
|
||||
R1->>DP: getSensorData()
|
||||
DP-->>R1: sensorData
|
||||
|
||||
R2->>Mutex: acquireReadLock()
|
||||
Mutex-->>R2: readLockAcquired
|
||||
R2->>DP: getSensorData()
|
||||
DP-->>R2: sensorData
|
||||
|
||||
R1->>Mutex: releaseReadLock()
|
||||
R2->>Mutex: releaseReadLock()
|
||||
|
||||
Mutex-->>W2: writeLockAcquired
|
||||
W2->>DP: updateSystemState()
|
||||
W2->>Mutex: releaseWriteLock()
|
||||
```
|
||||
|
||||
## 6. Threading Model
|
||||
|
||||
- **Access Model:** Multi-reader, single-writer with reader-writer mutex
|
||||
- **Thread Safety:** Fully thread-safe for all operations
|
||||
- **Blocking Operations:** Read operations may block on write operations (bounded)
|
||||
- **ISR Access:** Limited read-only access for critical data (lock-free atomic reads)
|
||||
- **Priority Inheritance:** Mutex supports priority inheritance to prevent priority inversion
|
||||
|
||||
## 7. Resource Ownership
|
||||
|
||||
- **Data Storage:** All runtime data owned exclusively by Data Pool
|
||||
- **Access Control:** Reader-writer mutex owned by Data Pool
|
||||
- **Memory Management:** Static allocation for all data structures (no dynamic allocation)
|
||||
- **Integrity Checking:** Checksum validation owned by Data Pool
|
||||
|
||||
## 8. Error Model
|
||||
|
||||
### 8.1 Error Conditions
|
||||
|
||||
| Error | Condition | Response |
|
||||
|-------|-----------|----------|
|
||||
| `DATAPOOL_ERR_INVALID_SENSOR_ID` | Invalid sensor ID (>6) | Return false, log warning |
|
||||
| `DATAPOOL_ERR_NULL_POINTER` | NULL pointer parameter | Return false, log error |
|
||||
| `DATAPOOL_ERR_BUFFER_TOO_SMALL` | Output buffer too small | Return false, set required size |
|
||||
| `DATAPOOL_ERR_DATA_STALE` | Data older than threshold | Return false, log info |
|
||||
| `DATAPOOL_ERR_CHECKSUM_FAILURE` | Data integrity check failed | Return false, log error |
|
||||
| `DATAPOOL_ERR_LOCK_TIMEOUT` | Failed to acquire lock | Return false, log warning |
|
||||
| `DATAPOOL_ERR_MEMORY_FULL` | Storage capacity exceeded | Overwrite oldest, log warning |
|
||||
|
||||
### 8.2 Diagnostics Emitted
|
||||
|
||||
- `DIAG-DP-POOL-0001`: Data integrity check failed (ERROR)
|
||||
- `DIAG-DP-POOL-0002`: Lock contention detected (WARNING)
|
||||
- `DIAG-DP-POOL-0003`: Memory usage high (WARNING)
|
||||
- `DIAG-DP-POOL-0004`: Stale data detected (INFO)
|
||||
|
||||
## 9. State-Dependent Behavior
|
||||
|
||||
| System State | Data Pool Behavior |
|
||||
|-------------|-------------------|
|
||||
| **INIT** | Initialize data structures, clear all data |
|
||||
| **RUNNING** | Normal operation, full read/write access |
|
||||
| **WARNING** | Continue operation, enhanced integrity checking |
|
||||
| **FAULT** | Read-only mode, preserve data integrity |
|
||||
| **OTA_UPDATE** | Read-only mode, prepare for snapshot |
|
||||
| **MC_UPDATE** | Limited updates, configuration reload |
|
||||
| **TEARDOWN** | Read-only mode, prepare for shutdown |
|
||||
| **SERVICE** | Full access, enhanced diagnostics |
|
||||
| **SD_DEGRADED** | Normal operation, no persistence coordination |
|
||||
|
||||
## 10. Dependencies
|
||||
|
||||
### 10.1 Required Components
|
||||
|
||||
- **Logger:** Debug and diagnostic logging
|
||||
- **Time Utils:** Timestamp generation for data records
|
||||
- **Error Handler:** Error reporting and escalation
|
||||
|
||||
### 10.2 Required Interfaces
|
||||
|
||||
- Logger interface for diagnostic output
|
||||
- Time Utils interface for timestamp generation
|
||||
- Error Handler interface for fault reporting
|
||||
|
||||
## 11. Performance Requirements
|
||||
|
||||
### 11.1 Access Performance
|
||||
|
||||
- **Read Operations:** Maximum 10μs for single sensor data read
|
||||
- **Write Operations:** Maximum 50μs for single sensor data write
|
||||
- **Bulk Operations:** Maximum 500μs for all sensor data read
|
||||
- **Lock Acquisition:** Maximum 1ms timeout for lock acquisition
|
||||
|
||||
### 11.2 Memory Requirements
|
||||
|
||||
- **Static Memory:** Maximum 64KB for all data structures
|
||||
- **Per-Sensor Data:** Maximum 1KB per sensor (including history)
|
||||
- **Diagnostic Storage:** Maximum 8KB for recent diagnostic events
|
||||
- **Configuration Storage:** Maximum 4KB for runtime configuration
|
||||
|
||||
## 12. Acceptance Tests
|
||||
|
||||
### 12.1 Functional Tests
|
||||
|
||||
- **T-DATAPOOL-001:** Sensor data storage and retrieval works correctly
|
||||
- **T-DATAPOOL-002:** System state information maintained accurately
|
||||
- **T-DATAPOOL-003:** Diagnostic events stored and retrieved correctly
|
||||
- **T-DATAPOOL-004:** Communication status tracking works
|
||||
- **T-DATAPOOL-005:** Configuration data management works
|
||||
|
||||
### 12.2 Concurrency Tests
|
||||
|
||||
- **T-DATAPOOL-006:** Multiple readers can access data simultaneously
|
||||
- **T-DATAPOOL-007:** Writers have exclusive access during updates
|
||||
- **T-DATAPOOL-008:** Reader-writer priority handling works correctly
|
||||
- **T-DATAPOOL-009:** Lock contention handled gracefully
|
||||
|
||||
### 12.3 Performance Tests
|
||||
|
||||
- **T-DATAPOOL-010:** Read operations complete within 10μs
|
||||
- **T-DATAPOOL-011:** Write operations complete within 50μs
|
||||
- **T-DATAPOOL-012:** Memory usage stays within 64KB limit
|
||||
- **T-DATAPOOL-013:** No memory leaks during continuous operation
|
||||
|
||||
### 12.4 Integrity Tests
|
||||
|
||||
- **T-DATAPOOL-014:** Data integrity checks detect corruption
|
||||
- **T-DATAPOOL-015:** Checksum validation works correctly
|
||||
- **T-DATAPOOL-016:** Stale data detection works
|
||||
- **T-DATAPOOL-017:** Statistics tracking is accurate
|
||||
|
||||
## 13. Traceability
|
||||
|
||||
### 13.1 System Requirements
|
||||
|
||||
- **SR-DATA-002:** Data Persistence Abstraction (runtime data management)
|
||||
- **SR-PERF-003:** Memory usage constraints
|
||||
- **SR-REL-004:** Data integrity requirements
|
||||
|
||||
### 13.2 Software Requirements
|
||||
|
||||
- **SWR-DATA-004:** DP component API definition
|
||||
- **SWR-DATA-005:** Storage media abstraction
|
||||
- **SWR-DATA-006:** Unified data access interface
|
||||
- **SWR-REL-010:** Error detection implementation
|
||||
|
||||
### 13.3 Features
|
||||
|
||||
- **F-DATA-002:** Data Persistence Abstraction (runtime component)
|
||||
- **F-DIAG-002:** Diagnostic Data Storage (runtime component)
|
||||
|
||||
## 14. Implementation Notes
|
||||
|
||||
### 14.1 Design Patterns
|
||||
|
||||
- **Singleton Pattern:** Single Data Pool instance per system
|
||||
- **Reader-Writer Lock Pattern:** Concurrent access control
|
||||
- **Observer Pattern:** Data change notifications (via Event System)
|
||||
- **Template Method Pattern:** Generic data access operations
|
||||
|
||||
### 14.2 Key Implementation Details
|
||||
|
||||
- All data structures SHALL use static allocation (no malloc/free)
|
||||
- Reader-writer mutex SHALL support priority inheritance
|
||||
- Data integrity SHALL be verified using checksums
|
||||
- Access statistics SHALL be maintained for performance monitoring
|
||||
- Atomic operations SHALL be used for lock-free ISR access
|
||||
|
||||
### 14.3 Memory Layout
|
||||
|
||||
```c
|
||||
// Static memory allocation structure
|
||||
typedef struct {
|
||||
sensor_data_record_t sensor_data[SENSOR_TYPE_COUNT];
|
||||
system_state_info_t system_state;
|
||||
system_health_metrics_t health_metrics;
|
||||
diagnostic_event_t diagnostic_events[MAX_DIAGNOSTIC_EVENTS];
|
||||
comm_link_status_t comm_links[COMM_LINK_COUNT];
|
||||
uint8_t configuration_data[MAX_CONFIG_SIZE];
|
||||
data_pool_stats_t statistics;
|
||||
pthread_rwlock_t access_lock;
|
||||
} data_pool_storage_t;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Document Status:** Final for Implementation Phase
|
||||
**Component Dependencies:** Verified against architecture
|
||||
**Requirements Traceability:** Complete (SR-DATA, SWR-DATA)
|
||||
**Next Review:** After implementation and testing
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* @file data_pool.cpp
|
||||
* @brief DataPool component implementation
|
||||
* @author Mahmoud Elmohtady
|
||||
* @company Nabd solutions - ASF
|
||||
* @copyright Copyright (c) 2025
|
||||
*/
|
||||
|
||||
#include "data_pool.hpp"
|
||||
#include "logger.hpp"
|
||||
|
||||
static const char* TAG = "DataPool";
|
||||
|
||||
DataPool::DataPool()
|
||||
: m_isInitialized(false)
|
||||
{
|
||||
}
|
||||
|
||||
DataPool::~DataPool()
|
||||
{
|
||||
deinitialize();
|
||||
}
|
||||
|
||||
bool DataPool::initialize()
|
||||
{
|
||||
// TODO: Implement initialization
|
||||
m_isInitialized = true;
|
||||
ASF_LOGI(TAG, 4900, asf::logger::Criticality::LOW, "DataPool initialized successfully");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DataPool::deinitialize()
|
||||
{
|
||||
if (!m_isInitialized)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: Implement deinitialization
|
||||
m_isInitialized = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DataPool::isInitialized() const
|
||||
{
|
||||
return m_isInitialized;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* @file data_pool.hpp
|
||||
* @brief DataPool component header
|
||||
* @author Mahmoud Elmohtady
|
||||
* @company Nabd solutions - ASF
|
||||
* @copyright Copyright (c) 2025
|
||||
*/
|
||||
|
||||
#ifndef DATA_POOL_HPP
|
||||
#define DATA_POOL_HPP
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
/**
|
||||
* @brief DataPool class
|
||||
*
|
||||
* Component description goes here.
|
||||
*/
|
||||
class DataPool
|
||||
{
|
||||
public:
|
||||
DataPool();
|
||||
~DataPool();
|
||||
|
||||
bool initialize();
|
||||
bool deinitialize();
|
||||
bool isInitialized() const;
|
||||
|
||||
private:
|
||||
bool m_isInitialized;
|
||||
};
|
||||
|
||||
#endif // DATA_POOL_HPP
|
||||
@@ -0,0 +1,2 @@
|
||||
ID,Component,Level,Criticality,Message
|
||||
4900,DataPool,INFO,Low,DataPool initialized successfully
|
||||
|
@@ -0,0 +1,34 @@
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
|
||||
folder_path = os.path.abspath(os.path.join("components", "system_tests"))
|
||||
if folder_path not in sys.path:
|
||||
sys.path.append(folder_path)
|
||||
|
||||
from scan_serial import ESP32Runner
|
||||
|
||||
def test_data_pool_initialize():
|
||||
runner = ESP32Runner(mode="SIM", port="COM9")
|
||||
runner.start()
|
||||
print("--- QEMU Runner Started ---", flush=True)
|
||||
try:
|
||||
start_time = time.time()
|
||||
while time.time() - start_time < 30:
|
||||
line = runner.get_line(timeout=1.0)
|
||||
if line:
|
||||
print(line, flush=True)
|
||||
if "DataPool initialized successfully" in line:
|
||||
print("SUCCESS CRITERIA MET!", flush=True)
|
||||
return 0
|
||||
if runner.process.poll() is not None:
|
||||
print(f"Process exited with code: {runner.process.returncode}", flush=True)
|
||||
return 1
|
||||
finally:
|
||||
runner.stop()
|
||||
print("Done.", flush=True)
|
||||
return 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
exit_code = test_data_pool_initialize()
|
||||
sys.exit(exit_code)
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<test_scenario>
|
||||
<!-- The configuration for the test environment. -->
|
||||
<!-- Available configurations: SIMULATE, HIL -->
|
||||
<config>SIMULATE</config>
|
||||
|
||||
<test_case>
|
||||
<test_case_id>DATA_POOL_INIT_TEST</test_case_id>
|
||||
<!-- The main command that executes the test itself. -->
|
||||
<test_exec>python components/application_layer/DP_stack/data_pool/test/data_pool_init_test.py</test_exec>
|
||||
</test_case>
|
||||
|
||||
|
||||
</test_scenario>
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* @file test_data_pool.cpp
|
||||
* @brief Unit tests for DataPool component
|
||||
* @author Mahmoud Elmohtady
|
||||
* @company Nabd solutions - ASF
|
||||
* @copyright Copyright (c) 2025
|
||||
*/
|
||||
|
||||
#include "unity.h"
|
||||
#include "data_pool.hpp"
|
||||
|
||||
extern "C" {
|
||||
|
||||
void setUp(void)
|
||||
{
|
||||
}
|
||||
|
||||
void tearDown(void)
|
||||
{
|
||||
}
|
||||
|
||||
void test_data_pool_initialize(void)
|
||||
{
|
||||
DataPool comp;
|
||||
bool result = comp.initialize();
|
||||
TEST_ASSERT_TRUE(result);
|
||||
TEST_ASSERT_TRUE(comp.isInitialized());
|
||||
}
|
||||
|
||||
void test_data_pool_deinitialize(void)
|
||||
{
|
||||
DataPool comp;
|
||||
comp.initialize();
|
||||
|
||||
bool result = comp.deinitialize();
|
||||
TEST_ASSERT_TRUE(result);
|
||||
TEST_ASSERT_FALSE(comp.isInitialized());
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
@@ -0,0 +1,5 @@
|
||||
idf_component_register(
|
||||
SRCS "com/persistence.cpp"
|
||||
INCLUDE_DIRS "com"
|
||||
REQUIRES logger
|
||||
)
|
||||
@@ -0,0 +1,257 @@
|
||||
# Persistence Component Specification
|
||||
|
||||
**Component ID:** COMP-PERSIST
|
||||
**Version:** 1.0
|
||||
**Date:** 2025-01-19
|
||||
**Location:** `application_layer/DP_stack/persistence/`
|
||||
|
||||
## 1. Purpose
|
||||
|
||||
The Persistence component provides the sole interface for persistent storage access. It abstracts storage media (SD card, NVM), manages serialization/deserialization, implements wear-aware storage, and ensures data integrity.
|
||||
|
||||
## 2. Responsibilities
|
||||
|
||||
### 2.1 Primary Responsibilities
|
||||
|
||||
- Abstract storage media (SD card, NVM)
|
||||
- Serialize/deserialize structured data
|
||||
- Manage wear-aware storage (SD card)
|
||||
- Ensure data integrity
|
||||
- Coordinate data flush operations
|
||||
|
||||
### 2.2 Non-Responsibilities
|
||||
|
||||
- Business logic (data semantics owned by components)
|
||||
- Hardware access (delegated to storage drivers)
|
||||
- Data validation (components validate before persistence)
|
||||
|
||||
## 3. Public API
|
||||
|
||||
### 3.1 Sensor Data Persistence
|
||||
|
||||
```c
|
||||
/**
|
||||
* @brief Write sensor data record
|
||||
* @param record Sensor data record
|
||||
* @return true if written, false on error
|
||||
*/
|
||||
bool persistence_writeSensorData(const sensor_data_record_t* record);
|
||||
|
||||
/**
|
||||
* @brief Read sensor data records
|
||||
* @param records Output buffer
|
||||
* @param count Input: buffer size, Output: records read
|
||||
* @param start_time Start timestamp filter (0 for all)
|
||||
* @param end_time End timestamp filter (0 for all)
|
||||
* @return true if read successful, false on error
|
||||
*/
|
||||
bool persistence_readSensorData(sensor_data_record_t* records, size_t* count, uint64_t start_time, uint64_t end_time);
|
||||
```
|
||||
|
||||
### 3.2 Diagnostic Persistence
|
||||
|
||||
```c
|
||||
/**
|
||||
* @brief Write diagnostic event
|
||||
* @param event Diagnostic event
|
||||
* @return true if written, false on error
|
||||
*/
|
||||
bool persistence_writeDiagnostic(const diagnostic_event_t* event);
|
||||
|
||||
/**
|
||||
* @brief Read diagnostic events
|
||||
* @param events Output buffer
|
||||
* @param count Input: buffer size, Output: events read
|
||||
* @param filter Filter criteria (severity, component, etc.)
|
||||
* @return true if read successful, false on error
|
||||
*/
|
||||
bool persistence_readDiagnostics(diagnostic_event_t* events, size_t* count, const diag_filter_t* filter);
|
||||
|
||||
/**
|
||||
* @brief Clear diagnostic log
|
||||
* @return true if cleared, false on error
|
||||
*/
|
||||
bool persistence_clearDiagnostics(void);
|
||||
```
|
||||
|
||||
### 3.3 Machine Constants Persistence
|
||||
|
||||
```c
|
||||
/**
|
||||
* @brief Write machine constants
|
||||
* @param mc Machine constants structure
|
||||
* @return true if written, false on error
|
||||
*/
|
||||
bool persistence_writeMachineConstants(const machine_constants_t* mc);
|
||||
|
||||
/**
|
||||
* @brief Read machine constants
|
||||
* @param mc Output buffer
|
||||
* @return true if read successful, false on error
|
||||
*/
|
||||
bool persistence_readMachineConstants(machine_constants_t* mc);
|
||||
```
|
||||
|
||||
### 3.4 Flush Operations
|
||||
|
||||
```c
|
||||
/**
|
||||
* @brief Flush all critical data to storage
|
||||
* @return true if flushed, false on error
|
||||
*/
|
||||
bool persistence_flushCriticalData(void);
|
||||
|
||||
/**
|
||||
* @brief Check if flush is complete
|
||||
* @return true if flush complete, false otherwise
|
||||
*/
|
||||
bool persistence_isFlushComplete(void);
|
||||
|
||||
/**
|
||||
* @brief Get flush progress (0-100%)
|
||||
* @return Flush progress percentage
|
||||
*/
|
||||
uint8_t persistence_getFlushProgress(void);
|
||||
```
|
||||
|
||||
### 3.5 Storage Status
|
||||
|
||||
```c
|
||||
/**
|
||||
* @brief Check storage availability
|
||||
* @param storage_type Storage type (SD_CARD, NVM)
|
||||
* @return true if available, false otherwise
|
||||
*/
|
||||
bool persistence_isStorageAvailable(storage_type_t storage_type);
|
||||
|
||||
/**
|
||||
* @brief Get storage usage statistics
|
||||
* @param storage_type Storage type
|
||||
* @param stats Output statistics
|
||||
* @return true if retrieved, false on error
|
||||
*/
|
||||
bool persistence_getStorageStats(storage_type_t storage_type, storage_stats_t* stats);
|
||||
```
|
||||
|
||||
## 4. Data Types
|
||||
|
||||
### 4.1 Storage Types
|
||||
|
||||
```c
|
||||
typedef enum {
|
||||
STORAGE_TYPE_SD_CARD = 0,
|
||||
STORAGE_TYPE_NVM,
|
||||
STORAGE_TYPE_COUNT
|
||||
} storage_type_t;
|
||||
```
|
||||
|
||||
### 4.2 Storage Statistics
|
||||
|
||||
```c
|
||||
typedef struct {
|
||||
uint64_t total_bytes;
|
||||
uint64_t used_bytes;
|
||||
uint64_t free_bytes;
|
||||
uint32_t write_count;
|
||||
bool is_healthy;
|
||||
} storage_stats_t;
|
||||
```
|
||||
|
||||
## 5. Threading Model
|
||||
|
||||
- **Owner Task:** Persistence Task (MEDIUM priority)
|
||||
- **Thread Safety:** Thread-safe (all operations protected by mutex)
|
||||
- **Blocking Operations:** Storage I/O operations (bounded by timing requirements)
|
||||
- **ISR Access:** Not allowed (blocking operations)
|
||||
|
||||
## 6. Resource Ownership
|
||||
|
||||
- **SD Card Driver:** Owned exclusively by Persistence component
|
||||
- **NVM Driver:** Owned exclusively by Persistence component
|
||||
- **Storage Mutex:** Protects concurrent access
|
||||
- **Write Buffers:** Pre-allocated static buffers
|
||||
|
||||
## 7. Error Model
|
||||
|
||||
### 7.1 Error Conditions
|
||||
|
||||
| Error | Condition | Response |
|
||||
|-------|-----------|----------|
|
||||
| `PERSIST_ERR_STORAGE_FULL` | Storage full | Trigger retention policy, log warning |
|
||||
| `PERSIST_ERR_STORAGE_FAILED` | Storage failure | Enter SD_DEGRADED state, log error |
|
||||
| `PERSIST_ERR_SERIALIZATION` | Serialization failure | Return false, log error |
|
||||
| `PERSIST_ERR_INTEGRITY_CHECK` | Integrity check failed | Return false, log error, attempt recovery |
|
||||
|
||||
### 7.2 Diagnostics Emitted
|
||||
|
||||
- `DIAG-ST-PERSIST-0001`: Storage write failure (WARNING)
|
||||
- `DIAG-ST-PERSIST-0002`: Storage full (WARNING)
|
||||
- `DIAG-ST-PERSIST-0003`: Storage corruption detected (ERROR)
|
||||
- `DIAG-ST-PERSIST-0004`: Storage failure (FATAL)
|
||||
|
||||
## 8. State-Dependent Behavior
|
||||
|
||||
### 8.1 All States
|
||||
|
||||
- Read operations allowed
|
||||
- Write operations allowed (with restrictions)
|
||||
|
||||
### 8.2 TEARDOWN State
|
||||
|
||||
- Only authorized writes allowed (critical data flush)
|
||||
- Read operations allowed
|
||||
|
||||
### 8.3 SD_DEGRADED State
|
||||
|
||||
- SD card writes disabled
|
||||
- NVM writes allowed
|
||||
- Read operations allowed (if storage accessible)
|
||||
|
||||
## 9. Dependencies
|
||||
|
||||
### 9.1 Required Components
|
||||
|
||||
- **SD Card Driver:** For SD card access
|
||||
- **NVM Driver:** For NVM access
|
||||
- **STM:** For state queries and transitions
|
||||
- **Logger:** For diagnostic logging
|
||||
|
||||
### 9.2 Required Interfaces
|
||||
|
||||
- SD Card Driver interface
|
||||
- NVM Driver interface
|
||||
- STM state query interface
|
||||
- Logger interface
|
||||
|
||||
## 10. Acceptance Tests
|
||||
|
||||
- **T-PERSIST-001:** Sensor data write/read operations
|
||||
- **T-PERSIST-002:** Diagnostic event write/read operations
|
||||
- **T-PERSIST-003:** Machine constants write/read operations
|
||||
- **T-PERSIST-004:** Data flush completes within 200ms
|
||||
- **T-PERSIST-005:** Storage failure handling (SD_DEGRADED state)
|
||||
- **T-PERSIST-006:** Data integrity verification
|
||||
- **T-PERSIST-007:** Wear-aware storage management
|
||||
|
||||
## 11. Traceability
|
||||
|
||||
- **SWR-DATA-001:** Persistent timestamped sensor data
|
||||
- **SWR-DATA-004:** DP component as sole persistence interface
|
||||
- **SWR-DATA-005:** Storage access isolation
|
||||
- **SWR-DATA-006:** Structured data serialization
|
||||
- **SWR-DATA-007:** Data flush before teardown
|
||||
- **SWR-DATA-008:** Data integrity during updates
|
||||
- **SWR-DATA-009:** Persistence verification
|
||||
- **SWR-DATA-012:** SD card failure handling
|
||||
- **SWR-DATA-013:** Wear-aware storage management
|
||||
- **CFC-ARCH-01:** Hardware access via drivers only
|
||||
- **CFC-DATA-01:** Single source of truth
|
||||
- **CFC-DATA-02:** Data consistency during transitions
|
||||
|
||||
## 12. Implementation Notes
|
||||
|
||||
- Serialization SHALL use a well-defined format (e.g., CBOR, MessagePack, or custom binary)
|
||||
- SD card writes SHALL be wear-aware (wear leveling, write frequency limits)
|
||||
- Data integrity SHALL be verified using checksums or hashes
|
||||
- Flush operations SHALL be coordinated with STM for state transitions
|
||||
- Storage failures SHALL trigger state transitions (SD_DEGRADED)
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* @file persistence.cpp
|
||||
* @brief Persistence component implementation
|
||||
* @author Mahmoud Elmohtady
|
||||
* @company Nabd solutions - ASF
|
||||
* @copyright Copyright (c) 2025
|
||||
*/
|
||||
|
||||
#include "persistence.hpp"
|
||||
#include "logger.hpp"
|
||||
|
||||
static const char* TAG = "Persistence";
|
||||
|
||||
Persistence::Persistence()
|
||||
: m_isInitialized(false)
|
||||
{
|
||||
}
|
||||
|
||||
Persistence::~Persistence()
|
||||
{
|
||||
deinitialize();
|
||||
}
|
||||
|
||||
bool Persistence::initialize()
|
||||
{
|
||||
// TODO: Implement initialization
|
||||
m_isInitialized = true;
|
||||
ASF_LOGI(TAG, 5000, asf::logger::Criticality::LOW, "Persistence initialized successfully");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Persistence::deinitialize()
|
||||
{
|
||||
if (!m_isInitialized)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: Implement deinitialization
|
||||
m_isInitialized = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Persistence::isInitialized() const
|
||||
{
|
||||
return m_isInitialized;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* @file persistence.hpp
|
||||
* @brief Persistence component header
|
||||
* @author Mahmoud Elmohtady
|
||||
* @company Nabd solutions - ASF
|
||||
* @copyright Copyright (c) 2025
|
||||
*/
|
||||
|
||||
#ifndef PERSISTENCE_HPP
|
||||
#define PERSISTENCE_HPP
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
/**
|
||||
* @brief Persistence class
|
||||
*
|
||||
* Component description goes here.
|
||||
*/
|
||||
class Persistence
|
||||
{
|
||||
public:
|
||||
Persistence();
|
||||
~Persistence();
|
||||
|
||||
bool initialize();
|
||||
bool deinitialize();
|
||||
bool isInitialized() const;
|
||||
|
||||
private:
|
||||
bool m_isInitialized;
|
||||
};
|
||||
|
||||
#endif // PERSISTENCE_HPP
|
||||
@@ -0,0 +1,2 @@
|
||||
ID,Component,Level,Criticality,Message
|
||||
5000,Persistence,INFO,Low,Persistence initialized successfully
|
||||
|
@@ -0,0 +1,34 @@
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
|
||||
folder_path = os.path.abspath(os.path.join("components", "system_tests"))
|
||||
if folder_path not in sys.path:
|
||||
sys.path.append(folder_path)
|
||||
|
||||
from scan_serial import ESP32Runner
|
||||
|
||||
def test_persistence_initialize():
|
||||
runner = ESP32Runner(mode="SIM", port="COM9")
|
||||
runner.start()
|
||||
print("--- QEMU Runner Started ---", flush=True)
|
||||
try:
|
||||
start_time = time.time()
|
||||
while time.time() - start_time < 30:
|
||||
line = runner.get_line(timeout=1.0)
|
||||
if line:
|
||||
print(line, flush=True)
|
||||
if "Persistence initialized successfully" in line:
|
||||
print("SUCCESS CRITERIA MET!", flush=True)
|
||||
return 0
|
||||
if runner.process.poll() is not None:
|
||||
print(f"Process exited with code: {runner.process.returncode}", flush=True)
|
||||
return 1
|
||||
finally:
|
||||
runner.stop()
|
||||
print("Done.", flush=True)
|
||||
return 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
exit_code = test_persistence_initialize()
|
||||
sys.exit(exit_code)
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<test_scenario>
|
||||
<!-- The configuration for the test environment. -->
|
||||
<!-- Available configurations: SIMULATE, HIL -->
|
||||
<config>SIMULATE</config>
|
||||
|
||||
<test_case>
|
||||
<test_case_id>PERSISTENCE_INIT_TEST</test_case_id>
|
||||
<!-- The main command that executes the test itself. -->
|
||||
<test_exec>python components/application_layer/DP_stack/persistence/test/persistence_init_test.py</test_exec>
|
||||
</test_case>
|
||||
|
||||
|
||||
</test_scenario>
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* @file test_persistence.cpp
|
||||
* @brief Unit tests for Persistence component
|
||||
* @author Mahmoud Elmohtady
|
||||
* @company Nabd solutions - ASF
|
||||
* @copyright Copyright (c) 2025
|
||||
*/
|
||||
|
||||
#include "unity.h"
|
||||
#include "persistence.hpp"
|
||||
|
||||
extern "C" {
|
||||
|
||||
void setUp(void)
|
||||
{
|
||||
}
|
||||
|
||||
void tearDown(void)
|
||||
{
|
||||
}
|
||||
|
||||
void test_persistence_initialize(void)
|
||||
{
|
||||
Persistence comp;
|
||||
bool result = comp.initialize();
|
||||
TEST_ASSERT_TRUE(result);
|
||||
TEST_ASSERT_TRUE(comp.isInitialized());
|
||||
}
|
||||
|
||||
void test_persistence_deinitialize(void)
|
||||
{
|
||||
Persistence comp;
|
||||
comp.initialize();
|
||||
|
||||
bool result = comp.deinitialize();
|
||||
TEST_ASSERT_TRUE(result);
|
||||
TEST_ASSERT_FALSE(comp.isInitialized());
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
@@ -0,0 +1,5 @@
|
||||
idf_component_register(
|
||||
SRCS "com/stm.cpp"
|
||||
INCLUDE_DIRS "com"
|
||||
REQUIRES logger
|
||||
)
|
||||
@@ -0,0 +1,291 @@
|
||||
# State Manager (STM) Component Specification
|
||||
|
||||
**Component ID:** COMP-STM
|
||||
**Version:** 1.0
|
||||
**Date:** 2025-01-19
|
||||
**Location:** `application_layer/business_stack/STM/`
|
||||
|
||||
## 1. Purpose
|
||||
|
||||
The State Manager (STM) component implements the system finite state machine (FSM) as defined in the System State Machine Specification. It manages system operational states, enforces valid state transitions, coordinates teardown sequences, and notifies components of state changes.
|
||||
|
||||
## 2. Responsibilities
|
||||
|
||||
### 2.1 Primary Responsibilities
|
||||
|
||||
- Implement system FSM with 11 states (INIT, BOOT_FAILURE, RUNNING, WARNING, FAULT, OTA_PREP, OTA_UPDATE, MC_UPDATE, TEARDOWN, SERVICE, SD_DEGRADED)
|
||||
- Enforce valid state transitions according to transition table
|
||||
- Coordinate controlled teardown sequences
|
||||
- Notify registered components of state changes via Event System
|
||||
- Provide state query interface for components
|
||||
|
||||
### 2.2 Non-Responsibilities
|
||||
|
||||
- Feature logic (sensor acquisition, communication, etc.)
|
||||
- Hardware access (delegated to drivers)
|
||||
- Fault detection (delegated to Error Handler)
|
||||
- Diagnostic logging (delegated to Diagnostics Task)
|
||||
|
||||
## 3. Public API
|
||||
|
||||
### 3.1 State Query Functions
|
||||
|
||||
```c
|
||||
/**
|
||||
* @brief Get current system state
|
||||
* @return Current system state
|
||||
*/
|
||||
system_state_t stm_getCurrentState(void);
|
||||
|
||||
/**
|
||||
* @brief Check if a state is valid
|
||||
* @param state State to validate
|
||||
* @return true if state is valid, false otherwise
|
||||
*/
|
||||
bool stm_isStateValid(system_state_t state);
|
||||
|
||||
/**
|
||||
* @brief Check if system is in a specific state
|
||||
* @param state State to check
|
||||
* @return true if current state matches, false otherwise
|
||||
*/
|
||||
bool stm_isInState(system_state_t state);
|
||||
```
|
||||
|
||||
### 3.2 State Transition Functions
|
||||
|
||||
```c
|
||||
/**
|
||||
* @brief Request a state transition
|
||||
* @param target_state Target state
|
||||
* @param reason Transition reason
|
||||
* @return true if transition initiated, false if rejected
|
||||
*/
|
||||
bool stm_requestTransition(system_state_t target_state, transition_reason_t reason);
|
||||
|
||||
/**
|
||||
* @brief Validate if a state transition is allowed
|
||||
* @param from Source state
|
||||
* @param to Target state
|
||||
* @return true if transition is valid, false otherwise
|
||||
*/
|
||||
bool stm_validateTransition(system_state_t target_state, transition_reason_t reason);
|
||||
|
||||
/**
|
||||
* @brief Get transition reason for last transition
|
||||
* @return Transition reason
|
||||
*/
|
||||
transition_reason_t stm_getLastTransitionReason(void);
|
||||
```
|
||||
|
||||
### 3.3 Teardown Functions
|
||||
|
||||
```c
|
||||
/**
|
||||
* @brief Initiate controlled teardown sequence
|
||||
* @param reason Teardown reason
|
||||
* @return true if teardown initiated, false if rejected
|
||||
*/
|
||||
bool stm_initiateTeardown(teardown_reason_t reason);
|
||||
|
||||
/**
|
||||
* @brief Check if teardown is complete
|
||||
* @return true if teardown complete, false otherwise
|
||||
*/
|
||||
bool stm_isTeardownComplete(void);
|
||||
|
||||
/**
|
||||
* @brief Get teardown progress (0-100%)
|
||||
* @return Teardown progress percentage
|
||||
*/
|
||||
uint8_t stm_getTeardownProgress(void);
|
||||
```
|
||||
|
||||
### 3.4 Component Registration
|
||||
|
||||
```c
|
||||
/**
|
||||
* @brief State listener callback type
|
||||
* @param old_state Previous state
|
||||
* @param new_state New state
|
||||
* @param reason Transition reason
|
||||
*/
|
||||
typedef void (*state_listener_t)(system_state_t old_state, system_state_t new_state, transition_reason_t reason);
|
||||
|
||||
/**
|
||||
* @brief Register a state change listener
|
||||
* @param listener Callback function
|
||||
* @return true if registered, false on error
|
||||
*/
|
||||
bool stm_registerStateListener(state_listener_t listener);
|
||||
|
||||
/**
|
||||
* @brief Unregister a state change listener
|
||||
* @param listener Callback function to remove
|
||||
* @return true if unregistered, false if not found
|
||||
*/
|
||||
bool stm_unregisterStateListener(state_listener_t listener);
|
||||
```
|
||||
|
||||
## 4. Data Types
|
||||
|
||||
### 4.1 System States
|
||||
|
||||
```c
|
||||
typedef enum {
|
||||
SYSTEM_STATE_INIT = 0,
|
||||
SYSTEM_STATE_BOOT_FAILURE,
|
||||
SYSTEM_STATE_RUNNING,
|
||||
SYSTEM_STATE_WARNING,
|
||||
SYSTEM_STATE_FAULT,
|
||||
SYSTEM_STATE_OTA_PREP,
|
||||
SYSTEM_STATE_OTA_UPDATE,
|
||||
SYSTEM_STATE_MC_UPDATE,
|
||||
SYSTEM_STATE_TEARDOWN,
|
||||
SYSTEM_STATE_SERVICE,
|
||||
SYSTEM_STATE_SD_DEGRADED,
|
||||
SYSTEM_STATE_COUNT
|
||||
} system_state_t;
|
||||
```
|
||||
|
||||
### 4.2 Transition Reasons
|
||||
|
||||
```c
|
||||
typedef enum {
|
||||
TRANSITION_REASON_INIT_SUCCESS,
|
||||
TRANSITION_REASON_SECURE_BOOT_FAIL,
|
||||
TRANSITION_REASON_NON_FATAL_FAULT,
|
||||
TRANSITION_REASON_FATAL_FAULT,
|
||||
TRANSITION_REASON_FAULT_CLEARED,
|
||||
TRANSITION_REASON_FAULT_ESCALATED,
|
||||
TRANSITION_REASON_OTA_REQUEST,
|
||||
TRANSITION_REASON_MC_UPDATE_REQUEST,
|
||||
TRANSITION_REASON_DEBUG_SESSION,
|
||||
TRANSITION_REASON_SD_FAILURE,
|
||||
TRANSITION_REASON_SD_RECOVERED,
|
||||
TRANSITION_REASON_RECOVERY_ATTEMPT,
|
||||
TRANSITION_REASON_MANUAL_RESET,
|
||||
TRANSITION_REASON_OTA_READY,
|
||||
TRANSITION_REASON_OTA_REJECTED,
|
||||
TRANSITION_REASON_TEARDOWN_COMPLETE,
|
||||
TRANSITION_REASON_OTA_SUCCESS,
|
||||
TRANSITION_REASON_OTA_FAILURE,
|
||||
TRANSITION_REASON_MC_UPDATE_COMPLETE,
|
||||
TRANSITION_REASON_SESSION_CLOSED,
|
||||
TRANSITION_REASON_MANUAL_INTERVENTION
|
||||
} transition_reason_t;
|
||||
```
|
||||
|
||||
### 4.3 Teardown Reasons
|
||||
|
||||
```c
|
||||
typedef enum {
|
||||
TEARDOWN_REASON_OTA,
|
||||
TEARDOWN_REASON_MC_UPDATE,
|
||||
TEARDOWN_REASON_FATAL_FAULT,
|
||||
TEARDOWN_REASON_MANUAL_COMMAND,
|
||||
TEARDOWN_REASON_SYSTEM_RESET
|
||||
} teardown_reason_t;
|
||||
```
|
||||
|
||||
## 5. Threading Model
|
||||
|
||||
- **Owner Task:** System Management Task (HIGH priority)
|
||||
- **Thread Safety:** Thread-safe (protected by mutex for state transitions)
|
||||
- **Blocking Operations:** None (all operations are non-blocking)
|
||||
- **ISR Access:** State queries allowed from ISR (read-only, lock-free)
|
||||
|
||||
## 6. Resource Ownership
|
||||
|
||||
- **State Machine State:** Owned exclusively by STM component
|
||||
- **State Transition Lock:** Mutex-protected (prevents concurrent transitions)
|
||||
- **State Listeners:** List maintained by STM (protected by mutex)
|
||||
|
||||
## 7. Error Model
|
||||
|
||||
### 7.1 Error Conditions
|
||||
|
||||
| Error | Condition | Response |
|
||||
|-------|-----------|----------|
|
||||
| `STM_ERR_INVALID_STATE` | Invalid state parameter | Return false, log warning |
|
||||
| `STM_ERR_INVALID_TRANSITION` | Invalid transition requested | Return false, log warning |
|
||||
| `STM_ERR_TEARDOWN_IN_PROGRESS` | Teardown already in progress | Return false, log info |
|
||||
| `STM_ERR_LISTENER_FULL` | Too many listeners registered | Return false, log error |
|
||||
|
||||
### 7.2 Diagnostics Emitted
|
||||
|
||||
- `DIAG-SY-STM-0001`: Invalid state transition attempted (WARNING)
|
||||
- `DIAG-SY-STM-0002`: State transition timeout (ERROR)
|
||||
- `DIAG-SY-STM-0003`: Teardown sequence failure (FATAL)
|
||||
|
||||
## 8. State-Dependent Behavior
|
||||
|
||||
### 8.1 INIT State
|
||||
|
||||
- **Allowed Operations:** State queries, listener registration
|
||||
- **Forbidden Operations:** State transitions (except to RUNNING or BOOT_FAILURE)
|
||||
- **Duration:** Bounded (max 5 seconds)
|
||||
|
||||
### 8.2 RUNNING State
|
||||
|
||||
- **Allowed Operations:** All operations
|
||||
- **Forbidden Operations:** None
|
||||
- **Duration:** Indefinite (normal operation)
|
||||
|
||||
### 8.3 TEARDOWN State
|
||||
|
||||
- **Allowed Operations:** State queries, teardown progress queries
|
||||
- **Forbidden Operations:** New state transitions (except completion transitions)
|
||||
- **Duration:** Bounded (max 500ms)
|
||||
|
||||
## 9. Dependencies
|
||||
|
||||
### 9.1 Required Components
|
||||
|
||||
- **Event System:** For state change notifications
|
||||
- **Error Handler:** For fault-triggered transitions
|
||||
- **Persistence:** For data flush during teardown
|
||||
- **Logger:** For diagnostic logging
|
||||
|
||||
### 9.2 Required Interfaces
|
||||
|
||||
- Event System publish interface
|
||||
- Persistence flush interface
|
||||
- Logger interface
|
||||
|
||||
## 10. Acceptance Tests
|
||||
|
||||
### 10.1 State Transition Tests
|
||||
|
||||
- **T-STM-001:** Valid state transitions succeed
|
||||
- **T-STM-002:** Invalid state transitions are rejected
|
||||
- **T-STM-003:** State transition timing meets requirements (≤50ms)
|
||||
|
||||
### 10.2 Teardown Tests
|
||||
|
||||
- **T-STM-004:** Teardown sequence completes within 500ms
|
||||
- **T-STM-005:** Teardown coordinates data flush
|
||||
- **T-STM-006:** Teardown prevents new transitions
|
||||
|
||||
### 10.3 Notification Tests
|
||||
|
||||
- **T-STM-007:** State change notifications are sent to all listeners
|
||||
- **T-STM-008:** State change notifications are non-blocking
|
||||
|
||||
## 11. Traceability
|
||||
|
||||
- **SWR-SYS-001:** FSM implementation
|
||||
- **SWR-SYS-002:** State transition enforcement
|
||||
- **SWR-SYS-003:** State-based operation restriction
|
||||
- **SWR-SYS-004:** State transition notification
|
||||
- **SWR-SYS-005:** Controlled teardown execution
|
||||
- **SWR-SYS-006:** Critical data persistence before teardown
|
||||
- **SWR-SYS-007:** Data integrity protection during shutdown
|
||||
|
||||
## 12. Implementation Notes
|
||||
|
||||
- State machine SHALL be implemented as a table-driven FSM
|
||||
- State transitions SHALL be atomic (protected by mutex)
|
||||
- State listeners SHALL be called asynchronously via Event System
|
||||
- Teardown sequence SHALL coordinate with Persistence component for data flush
|
||||
- State queries SHALL be lock-free for ISR access (read-only)
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* @file stm.cpp
|
||||
* @brief Stm component implementation
|
||||
* @author Mahmoud Elmohtady
|
||||
* @company Nabd solutions - ASF
|
||||
* @copyright Copyright (c) 2025
|
||||
*/
|
||||
|
||||
#include "stm.hpp"
|
||||
#include "logger.hpp"
|
||||
|
||||
static const char* TAG = "Stm";
|
||||
|
||||
Stm::Stm()
|
||||
: m_isInitialized(false)
|
||||
{
|
||||
}
|
||||
|
||||
Stm::~Stm()
|
||||
{
|
||||
deinitialize();
|
||||
}
|
||||
|
||||
bool Stm::initialize()
|
||||
{
|
||||
// TODO: Implement initialization
|
||||
m_isInitialized = true;
|
||||
ASF_LOGI(TAG, 4600, asf::logger::Criticality::LOW, "Stm initialized successfully");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Stm::deinitialize()
|
||||
{
|
||||
if (!m_isInitialized)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: Implement deinitialization
|
||||
m_isInitialized = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Stm::isInitialized() const
|
||||
{
|
||||
return m_isInitialized;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* @file stm.hpp
|
||||
* @brief Stm component header
|
||||
* @author Mahmoud Elmohtady
|
||||
* @company Nabd solutions - ASF
|
||||
* @copyright Copyright (c) 2025
|
||||
*/
|
||||
|
||||
#ifndef STM_HPP
|
||||
#define STM_HPP
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
/**
|
||||
* @brief Stm class
|
||||
*
|
||||
* Component description goes here.
|
||||
*/
|
||||
class Stm
|
||||
{
|
||||
public:
|
||||
Stm();
|
||||
~Stm();
|
||||
|
||||
bool initialize();
|
||||
bool deinitialize();
|
||||
bool isInitialized() const;
|
||||
|
||||
private:
|
||||
bool m_isInitialized;
|
||||
};
|
||||
|
||||
#endif // STM_HPP
|
||||
@@ -0,0 +1,2 @@
|
||||
ID,Component,Level,Criticality,Message
|
||||
4600,STM,INFO,Low,Stm initialized successfully
|
||||
|
@@ -0,0 +1,34 @@
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
|
||||
folder_path = os.path.abspath(os.path.join("components", "system_tests"))
|
||||
if folder_path not in sys.path:
|
||||
sys.path.append(folder_path)
|
||||
|
||||
from scan_serial import ESP32Runner
|
||||
|
||||
def test_stm_initialize():
|
||||
runner = ESP32Runner(mode="SIM", port="COM9")
|
||||
runner.start()
|
||||
print("--- QEMU Runner Started ---", flush=True)
|
||||
try:
|
||||
start_time = time.time()
|
||||
while time.time() - start_time < 30:
|
||||
line = runner.get_line(timeout=1.0)
|
||||
if line:
|
||||
print(line, flush=True)
|
||||
if "Stm initialized successfully" in line:
|
||||
print("SUCCESS CRITERIA MET!", flush=True)
|
||||
return 0
|
||||
if runner.process.poll() is not None:
|
||||
print(f"Process exited with code: {runner.process.returncode}", flush=True)
|
||||
return 1
|
||||
finally:
|
||||
runner.stop()
|
||||
print("Done.", flush=True)
|
||||
return 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
exit_code = test_stm_initialize()
|
||||
sys.exit(exit_code)
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<test_scenario>
|
||||
<!-- The configuration for the test environment. -->
|
||||
<!-- Available configurations: SIMULATE, HIL -->
|
||||
<config>SIMULATE</config>
|
||||
|
||||
<test_case>
|
||||
<test_case_id>STM_INIT_TEST</test_case_id>
|
||||
<!-- The main command that executes the test itself. -->
|
||||
<test_exec>python components/application_layer/business_stack/STM/test/stm_init_test.py</test_exec>
|
||||
</test_case>
|
||||
|
||||
|
||||
</test_scenario>
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* @file test_stm.cpp
|
||||
* @brief Unit tests for Stm component
|
||||
* @author Mahmoud Elmohtady
|
||||
* @company Nabd solutions - ASF
|
||||
* @copyright Copyright (c) 2025
|
||||
*/
|
||||
|
||||
#include "unity.h"
|
||||
#include "stm.hpp"
|
||||
|
||||
extern "C" {
|
||||
|
||||
void setUp(void)
|
||||
{
|
||||
}
|
||||
|
||||
void tearDown(void)
|
||||
{
|
||||
}
|
||||
|
||||
void test_stm_initialize(void)
|
||||
{
|
||||
Stm comp;
|
||||
bool result = comp.initialize();
|
||||
TEST_ASSERT_TRUE(result);
|
||||
TEST_ASSERT_TRUE(comp.isInitialized());
|
||||
}
|
||||
|
||||
void test_stm_deinitialize(void)
|
||||
{
|
||||
Stm comp;
|
||||
comp.initialize();
|
||||
|
||||
bool result = comp.deinitialize();
|
||||
TEST_ASSERT_TRUE(result);
|
||||
TEST_ASSERT_FALSE(comp.isInitialized());
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
@@ -0,0 +1,5 @@
|
||||
idf_component_register(
|
||||
SRCS "com/actuator_manager.cpp"
|
||||
INCLUDE_DIRS "com"
|
||||
REQUIRES logger
|
||||
)
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* @file actuator_manager.cpp
|
||||
* @brief ActuatorManager component implementation
|
||||
* @author Mahmoud Elmohtady
|
||||
* @company Nabd solutions - ASF
|
||||
* @copyright Copyright (c) 2025
|
||||
*/
|
||||
#include "logger.hpp"
|
||||
#include "actuator_manager.hpp"
|
||||
|
||||
static const char* TAG = "actuator_manager";
|
||||
|
||||
ActuatorManager::ActuatorManager()
|
||||
: m_isInitialized(false)
|
||||
{
|
||||
}
|
||||
|
||||
ActuatorManager::~ActuatorManager()
|
||||
{
|
||||
deinitialize();
|
||||
}
|
||||
|
||||
bool ActuatorManager::initialize()
|
||||
{
|
||||
// TODO: Implement initialization
|
||||
m_isInitialized = true;
|
||||
ASF_LOGI(TAG, 4000, asf::logger::Criticality::LOW, "actuator manager initialized successfully");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ActuatorManager::deinitialize()
|
||||
{
|
||||
if (!m_isInitialized)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: Implement deinitialization
|
||||
m_isInitialized = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ActuatorManager::isInitialized() const
|
||||
{
|
||||
return m_isInitialized;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* @file actuator_manager.hpp
|
||||
* @brief ActuatorManager component header
|
||||
* @author Mahmoud Elmohtady
|
||||
* @company Nabd solutions - ASF
|
||||
* @copyright Copyright (c) 2025
|
||||
*/
|
||||
|
||||
#ifndef ACTUATOR_MANAGER_HPP
|
||||
#define ACTUATOR_MANAGER_HPP
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
/**
|
||||
* @brief ActuatorManager class
|
||||
*
|
||||
* Component description goes here.
|
||||
*/
|
||||
class ActuatorManager
|
||||
{
|
||||
public:
|
||||
ActuatorManager();
|
||||
~ActuatorManager();
|
||||
|
||||
bool initialize();
|
||||
bool deinitialize();
|
||||
bool isInitialized() const;
|
||||
|
||||
private:
|
||||
bool m_isInitialized;
|
||||
};
|
||||
|
||||
#endif // ACTUATOR_MANAGER_HPP
|
||||
@@ -0,0 +1,2 @@
|
||||
ID,Component,Level,Criticality,Message
|
||||
4000,ActuatorManager,INFO,Low,actuator manager initialized successfully
|
||||
|
Binary file not shown.
@@ -0,0 +1,34 @@
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
|
||||
folder_path = os.path.abspath(os.path.join("components", "system_tests"))
|
||||
if folder_path not in sys.path:
|
||||
sys.path.append(folder_path)
|
||||
|
||||
from scan_serial import ESP32Runner
|
||||
|
||||
def test_actuator_manager_initialize():
|
||||
runner = ESP32Runner(mode="SIM", port="COM9")
|
||||
runner.start()
|
||||
print("--- QEMU Runner Started ---", flush=True)
|
||||
try:
|
||||
start_time = time.time()
|
||||
while time.time() - start_time < 30:
|
||||
line = runner.get_line(timeout=1.0)
|
||||
if line:
|
||||
print(line, flush=True)
|
||||
if "Actuator Manager initialized successfully" in line or "actuator manager initialized successfully" in line.lower():
|
||||
print("SUCCESS CRITERIA MET!", flush=True)
|
||||
return 0
|
||||
if runner.process.poll() is not None:
|
||||
print(f"Process exited with code: {runner.process.returncode}", flush=True)
|
||||
return 1
|
||||
finally:
|
||||
runner.stop()
|
||||
print("Done.", flush=True)
|
||||
return 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
exit_code = test_actuator_manager_initialize()
|
||||
sys.exit(exit_code)
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<test_scenario>
|
||||
<!-- The configuration for the test environment. -->
|
||||
<!-- Available configurations: SIMULATE, HIL -->
|
||||
<config>SIMULATE</config>
|
||||
|
||||
<test_case>
|
||||
<test_case_id>ACTUATOR_MANAGER_INIT_TEST</test_case_id>
|
||||
<!-- The main command that executes the test itself. -->
|
||||
<test_exec>python components/application_layer/business_stack/actuator_manager/test/actuator_manager_init_test.py</test_exec>
|
||||
</test_case>
|
||||
</test_scenario>
|
||||
@@ -0,0 +1,56 @@
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Get the absolute path to the folder you want to add
|
||||
# Example: Adding a folder named 'my_components' located in the current directory
|
||||
folder_path = os.path.abspath("components/system_tests")
|
||||
if folder_path not in sys.path:
|
||||
sys.path.append(folder_path)
|
||||
|
||||
from scan_serial import ESP32Runner
|
||||
import time
|
||||
|
||||
# --- Main Logic ---
|
||||
if __name__ == "__main__":
|
||||
runner = ESP32Runner(mode="SIM", port="COM9")
|
||||
runner.start()
|
||||
keyword_found = False
|
||||
# Force print to terminal immediately
|
||||
print("--- QEMU Runner Started ---", flush=True)
|
||||
|
||||
try:
|
||||
start_time = time.time()
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
|
||||
folder_path = os.path.abspath(os.path.join("components", "system_tests"))
|
||||
if folder_path not in sys.path:
|
||||
sys.path.append(folder_path)
|
||||
|
||||
from scan_serial import ESP32Runner
|
||||
|
||||
def test_actuator_manager_initialize():
|
||||
runner = ESP32Runner(mode="SIM", port="COM9")
|
||||
runner.start()
|
||||
print("--- QEMU Runner Started ---", flush=True)
|
||||
try:
|
||||
start_time = time.time()
|
||||
while time.time() - start_time < 30:
|
||||
line = runner.get_line(timeout=1.0)
|
||||
if line:
|
||||
print(line, flush=True)
|
||||
if "actuator manager initialized successfully" in line.lower() or "Actuator Manager initialized successfully" in line:
|
||||
print("SUCCESS CRITERIA MET!", flush=True)
|
||||
return 0
|
||||
if runner.process.poll() is not None:
|
||||
print(f"Process exited with code: {runner.process.returncode}", flush=True)
|
||||
return 1
|
||||
finally:
|
||||
runner.stop()
|
||||
print("Done.", flush=True)
|
||||
return 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
exit_code = test_actuator_manager_initialize()
|
||||
sys.exit(exit_code)
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* @file test_actuator_manager.cpp
|
||||
* @brief Unit tests for ActuatorManager component
|
||||
* @author Mahmoud Elmohtady
|
||||
* @company Nabd solutions - ASF
|
||||
* @copyright Copyright (c) 2025
|
||||
*/
|
||||
|
||||
#include "unity.h"
|
||||
#include "actuator_manager.hpp"
|
||||
|
||||
extern "C" {
|
||||
|
||||
void setUp(void)
|
||||
{
|
||||
}
|
||||
|
||||
void tearDown(void)
|
||||
{
|
||||
}
|
||||
|
||||
void test_actuator_manager_initialize(void)
|
||||
{
|
||||
ActuatorManager comp;
|
||||
bool result = comp.initialize();
|
||||
TEST_ASSERT_TRUE(result);
|
||||
TEST_ASSERT_TRUE(comp.isInitialized());
|
||||
}
|
||||
|
||||
void test_actuator_manager_deinitialize(void)
|
||||
{
|
||||
ActuatorManager comp;
|
||||
comp.initialize();
|
||||
|
||||
bool result = comp.deinitialize();
|
||||
TEST_ASSERT_TRUE(result);
|
||||
TEST_ASSERT_FALSE(comp.isInitialized());
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
@@ -0,0 +1,5 @@
|
||||
idf_component_register(
|
||||
SRCS "com/event_system.cpp"
|
||||
INCLUDE_DIRS "com"
|
||||
REQUIRES logger
|
||||
)
|
||||
@@ -0,0 +1,200 @@
|
||||
# Event System Component Specification
|
||||
|
||||
**Component ID:** COMP-EVENT
|
||||
**Version:** 1.0
|
||||
**Date:** 2025-01-19
|
||||
**Location:** `application_layer/business_stack/event_system/`
|
||||
|
||||
## 1. Purpose
|
||||
|
||||
The Event System provides a publish/subscribe event bus for cross-component communication. It decouples components, enables asynchronous event delivery, and ensures non-blocking operation.
|
||||
|
||||
## 2. Responsibilities
|
||||
|
||||
### 2.1 Primary Responsibilities
|
||||
|
||||
- Provide publish/subscribe event bus
|
||||
- Decouple components via events
|
||||
- Ensure non-blocking event delivery
|
||||
- Support multiple subscribers per event type
|
||||
- Queue management for event delivery
|
||||
|
||||
### 2.2 Non-Responsibilities
|
||||
|
||||
- Business logic (components handle events)
|
||||
- Event payload validation (components validate)
|
||||
- Event ordering guarantees (best-effort)
|
||||
|
||||
## 3. Public API
|
||||
|
||||
### 3.1 Event Publishing
|
||||
|
||||
```c
|
||||
/**
|
||||
* @brief Publish an event
|
||||
* @param type Event type
|
||||
* @param payload Event payload (may be NULL)
|
||||
* @param payload_size Payload size in bytes (0 if payload is NULL)
|
||||
* @return true if published, false on error
|
||||
*/
|
||||
bool event_publish(event_type_t type, const void* payload, size_t payload_size);
|
||||
|
||||
/**
|
||||
* @brief Publish an event with timestamp
|
||||
* @param type Event type
|
||||
* @param payload Event payload
|
||||
* @param payload_size Payload size
|
||||
* @param timestamp Event timestamp
|
||||
* @return true if published, false on error
|
||||
*/
|
||||
bool event_publishWithTimestamp(event_type_t type, const void* payload, size_t payload_size, uint64_t timestamp);
|
||||
```
|
||||
|
||||
### 3.2 Event Subscription
|
||||
|
||||
```c
|
||||
/**
|
||||
* @brief Event handler callback type
|
||||
* @param type Event type
|
||||
* @param payload Event payload
|
||||
* @param payload_size Payload size
|
||||
* @param timestamp Event timestamp
|
||||
*/
|
||||
typedef void (*event_handler_t)(event_type_t type, const void* payload, size_t payload_size, uint64_t timestamp);
|
||||
|
||||
/**
|
||||
* @brief Subscribe to an event type
|
||||
* @param type Event type to subscribe to
|
||||
* @param handler Callback function
|
||||
* @return true if subscribed, false on error
|
||||
*/
|
||||
bool event_subscribe(event_type_t type, event_handler_t handler);
|
||||
|
||||
/**
|
||||
* @brief Unsubscribe from an event type
|
||||
* @param type Event type to unsubscribe from
|
||||
* @param handler Callback function to remove
|
||||
* @return true if unsubscribed, false if not found
|
||||
*/
|
||||
bool event_unsubscribe(event_type_t type, event_handler_t handler);
|
||||
```
|
||||
|
||||
### 3.3 Event Queue Management
|
||||
|
||||
```c
|
||||
/**
|
||||
* @brief Get number of pending events
|
||||
* @param type Event type (EVENT_TYPE_ALL for all types)
|
||||
* @return Number of pending events
|
||||
*/
|
||||
size_t event_getPendingCount(event_type_t type);
|
||||
|
||||
/**
|
||||
* @brief Clear pending events
|
||||
* @param type Event type (EVENT_TYPE_ALL for all types)
|
||||
* @return Number of events cleared
|
||||
*/
|
||||
size_t event_clearPending(event_type_t type);
|
||||
```
|
||||
|
||||
## 4. Data Types
|
||||
|
||||
### 4.1 Event Types
|
||||
|
||||
```c
|
||||
typedef enum {
|
||||
EVENT_SENSOR_DATA_UPDATE = 0,
|
||||
EVENT_DIAGNOSTIC_EVENT,
|
||||
EVENT_STATE_CHANGED,
|
||||
EVENT_OTA_REQUEST,
|
||||
EVENT_OTA_STATUS,
|
||||
EVENT_MC_UPDATE_REQUEST,
|
||||
EVENT_COMMUNICATION_LINK_STATUS,
|
||||
EVENT_STORAGE_STATUS,
|
||||
EVENT_SYSTEM_HEALTH_UPDATE,
|
||||
EVENT_TYPE_ALL = 0xFF,
|
||||
EVENT_TYPE_COUNT
|
||||
} event_type_t;
|
||||
```
|
||||
|
||||
### 4.2 Event Structure
|
||||
|
||||
```c
|
||||
typedef struct {
|
||||
event_type_t type;
|
||||
uint64_t timestamp;
|
||||
size_t payload_size;
|
||||
uint8_t payload[]; // Variable-length payload
|
||||
} event_t;
|
||||
```
|
||||
|
||||
## 5. Threading Model
|
||||
|
||||
- **Owner Task:** Event System Task (MEDIUM priority) or shared with components
|
||||
- **Thread Safety:** Thread-safe (lock-free queue for publishing, mutex for subscription management)
|
||||
- **Blocking Operations:** None (all operations are non-blocking)
|
||||
- **ISR Access:** Publish allowed from ISR (lock-free queue)
|
||||
|
||||
## 6. Resource Ownership
|
||||
|
||||
- **Event Queue:** Owned by Event System (lock-free ring buffer)
|
||||
- **Subscriber List:** Protected by mutex
|
||||
- **Event Buffers:** Pre-allocated static buffers (no dynamic allocation)
|
||||
|
||||
## 7. Error Model
|
||||
|
||||
### 7.1 Error Conditions
|
||||
|
||||
| Error | Condition | Response |
|
||||
|-------|-----------|----------|
|
||||
| `EVENT_ERR_QUEUE_FULL` | Event queue full | Drop oldest event, log warning |
|
||||
| `EVENT_ERR_INVALID_TYPE` | Invalid event type | Return false, log warning |
|
||||
| `EVENT_ERR_PAYLOAD_TOO_LARGE` | Payload exceeds maximum | Return false, log error |
|
||||
| `EVENT_ERR_SUBSCRIBER_FULL` | Too many subscribers | Return false, log error |
|
||||
|
||||
### 7.2 Diagnostics Emitted
|
||||
|
||||
- `DIAG-SY-EVENT-0001`: Event queue full (WARNING)
|
||||
- `DIAG-SY-EVENT-0002`: Event dropped due to queue full (WARNING)
|
||||
- `DIAG-SY-EVENT-0003`: Subscriber callback failure (ERROR)
|
||||
|
||||
## 8. State-Dependent Behavior
|
||||
|
||||
- **All States:** Event System operates in all states
|
||||
- **TEARDOWN State:** Event publishing limited to teardown-related events
|
||||
- **FAULT State:** Event publishing limited to diagnostic events
|
||||
|
||||
## 9. Dependencies
|
||||
|
||||
### 9.1 Required Components
|
||||
|
||||
- **Logger:** For diagnostic logging
|
||||
- **Time Utils:** For timestamp generation
|
||||
|
||||
### 9.2 Required Interfaces
|
||||
|
||||
- Logger interface
|
||||
- Time Utils interface
|
||||
|
||||
## 10. Acceptance Tests
|
||||
|
||||
- **T-EVENT-001:** Events are published successfully
|
||||
- **T-EVENT-002:** Subscribers receive events
|
||||
- **T-EVENT-003:** Multiple subscribers receive same event
|
||||
- **T-EVENT-004:** Event publishing is non-blocking
|
||||
- **T-EVENT-005:** Event queue overflow handling
|
||||
- **T-EVENT-006:** Event unsubscription works correctly
|
||||
|
||||
## 11. Traceability
|
||||
|
||||
- **SWR-DESIGN-006:** Event System for cross-component communication
|
||||
- **CFC-TIME-01:** Non-blocking operation
|
||||
- **Architecture Requirement:** Event-driven communication
|
||||
|
||||
## 12. Implementation Notes
|
||||
|
||||
- Event queue SHALL be implemented as lock-free ring buffer
|
||||
- Maximum queue size: 100 events
|
||||
- Maximum payload size: 256 bytes
|
||||
- Maximum subscribers per event type: 10
|
||||
- Event handlers SHALL be called synchronously (in publisher's context) or asynchronously (in event task context) based on configuration
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user