HardwareManager
1. Overview
The HardwareManager class serves as the primary interface between the application’s logic and the physical hardware components of the device. It is responsible for managing GPIO pins, controlling visual feedback (like NeoPixels), handling physical lock outputs, and managing timed actions.
The class operates on an event-driven, asynchronous model. It subscribes to high-level application events (e.g., “lock the door,” “NFC tag tapped”) and translates them into low-level hardware actions (e.g., setting a GPIO pin high, flashing an LED). It utilizes FreeRTOS tasks and queues to handle these actions without blocking the main application flow.
Key System Features & Improvements
- GPIO Allocator & Pin Safety (
GPIOAllocator): Centralized, thread-safe GPIO allocation tracking system. Pin allocations across all hardware modules (NFC readers, Ethernet PHYs, Relays, Status LEDs, and HomeSpan pins) are leased via RAIIGPIOLeaseinstances.- Target-Specific Strapping Pin Protection: Validates requested GPIO pins against chip-specific boot strapping pin lists (ESP32, ESP32-S3, ESP32-C3, ESP32-C6).
- SPI Bus Intersection Checks: Validates SPI bus sharing between NFC controllers and SPI Ethernet modules, preventing pin ownership conflicts.
- Strapping Override Option: Supports
overrideStrappingRestrictionfor custom hardware designs.
- Memory Safety Improvements:
HardwareManagerinstances are managed usingstd::unique_ptrinmain.cpp, ensuring clean object lifecycles. - Timer Reliability: Hardware timers in
HardwareManageruse non-static member contexts and initialization checks, ensuring reliability across device re-initialization.
Key Responsibilities:
- Lock Control: Manages the GPIO pin(s) that physically control a lock mechanism.
- User Feedback: Provides visual feedback for success and failure events using NeoPixels and dedicated GPIO pins.
- Alternate Action: Implements a special “alternate action” feature, which can be armed by a physical button/input and then triggered by another event (like a HomeKey tap).
- Dynamic Pin Configuration: Listens for events that indicate GPIO pin assignments have changed and reconfigures the hardware accordingly.
Architecture:
- Event-Driven: Uses
AppEventLoopsystem to subscribe to commands and publish state changes. - Asynchronous Tasks: Offloads hardware operations to dedicated FreeRTOS tasks to prevent blocking:
- A lock control task processes requests to change the lock’s state.
- A feedback task manages timed visual indicators (LEDs, NeoPixels).
- An initiator task handles the arming mechanism for the alternate action.
- ESP-Timers: Uses one-shot timers to control the duration of feedback signals and timeouts.
2. Public API & GPIOAllocator
GPIOAllocator Subsystem
The GPIOAllocator class provides thread-safe GPIO allocation tracking. Pins are leased via RAII GPIOLease instances obtained from the singleton; a PinRole describes what the pin is used for (SPI/I2C bus pins are shareable as passive roles, LEDs are arbitrated, plain GPIOs are exclusive) and a PinConsumer identifies the subsystem (e.g., Nfc, Eth, HomeKit, Hardware):
// Example: Acquire a lease for a lock action output pin
auto lease = GPIOAllocator::instance().acquire(
static_cast<gpio_num_t>(pin), GPIO_MODE_OUTPUT,
GPIOAllocator::PinRole::GpioOut,
GPIOAllocator::PinConsumer::Hardware, "LOCK_ACTION");
if (!lease.has_value()) {
// Pin conflict, restricted/strapping pin, or invalid pin assignment
}acquire(gpio_num_t pin, gpio_mode_t mode, PinRole role, PinConsumer consumer, const char* tag): Acquires a GPIO pin lease. Validates against target-specific restricted and strapping pins (with theoverrideStrappingRestrictiondowngrade option for strapping pins) and active leases. Returnsstd::expected<GPIOLease, GPIOAllocatorError>.- A lease acquired for
PIN_UNSET(255) is an empty lease whose accessors safely no-op, so unset configuration values don’t need special-casing at call sites. - Leases are released automatically when the
GPIOLeasegoes out of scope (RAII). - Shared roles: passive bus pins (SPI SCK/MISO/MOSI, I2C SDA/SCL) may be co-held by multiple consumers (e.g., NFC reader + SPI Ethernet on the same bus);
PinRole::Ledpins are shareable and arbitrated viaSharedLed(HomeSpanBlinkable).
Constructor
HardwareManager()
Constructs a new HardwareManager instance. The constructor initializes its internal configuration and sets up all necessary event subscribers and publishers.
- Subscribes to (via AppEventLoop):
HW_EVENT(HW_ACTION): To receive commands to change the lock state.NFC_EVENT(NFC_TAP_EVENT): To trigger success/failure feedback or the alternate action based on NFC events.HW_EVENT(HW_CONFIG_CHANGED): To dynamically update GPIO configurations.
- Publishes (via AppEventLoop):
LOCK_EVENT(LOCK_UPDATE_STATE): To notify the system of a change in the physical lock state.HW_EVENT(HW_ALT_ACTION): To signal that the alternate action has been triggered.
Signature:
HardwareManager(const espConfig::actions_config_t& miscConfig);Parameters:
miscConfig: A constant reference to anespConfig::actions_config_tstruct containing all necessary pin numbers, timings, and color configurations.
Initialization
begin()
Initializes all hardware resources via GPIOAllocator and starts background tasks. This method must be called after the constructor and before any other methods.
It performs the following actions:
- Configures GPIO pins for feedback (success/fail LEDs), the lock action, and the alternate action mechanism.
- Initializes the NeoPixel driver if a valid pin is configured.
- Sets up ESP-IDF timers for all timed hardware events.
- Creates FreeRTOS queues and tasks for lock control, feedback, and the alternate action initiator.
- Installs an ISR (Interrupt Service Routine) if the alternate action initiator pin is configured.
Signature:
void begin();Public Methods
setLockOutput()
Asynchronously sends a command to change the state of the physical lock. The command is placed on a queue and processed by the internal lockControlTask.
Signature:
void setLockOutput(int state);Parameters:
state: The desired lock state (LockManager::LOCKEDorLockManager::UNLOCKED).
showSuccessFeedback()
Triggers the predefined hardware feedback sequence for a successful operation. Queues the request for feedbackTask.
Signature:
void showSuccessFeedback();showFailureFeedback()
Triggers the predefined hardware feedback sequence for a failed operation. Queues the request for feedbackTask.
Signature:
void showFailureFeedback();3. Internal Workings & Task Descriptions
Lock Control Task (lockControlTask)
This task runs in an infinite loop, waiting for state change commands on its queue (sent via setLockOutput).
- When a state (e.g.,
LOCKED) is received, it drives thegpioActionPinto the corresponding configured level (gpioActionLockStateorgpioActionUnlockState). - After changing the pin state, it publishes a
LOCK_UPDATE_STATEevent viaAppEventLoopto inform the rest of the system that the physical state has been updated.
Feedback Task (feedbackTask)
This task waits for feedback requests on its queue (sent by showSuccessFeedback or showFailureFeedback).
- On
SUCCESS:- Illuminates the NeoPixel with the configured
neopixelSuccessColor. - Activates the
nfcSuccessPin. - Starts one-shot timers to automatically turn off the NeoPixel and deactivate the success pin after configured durations (
neopixelSuccessTime,nfcSuccessTime).
- Illuminates the NeoPixel with the configured
- On
FAILURE:- Performs the same sequence using failure-specific colors, pins, and durations.
Alternate Action Mechanism
This feature allows a secondary action to be triggered under specific conditions, typically “arming” via a button press and “triggering” via an NFC HomeKey tap.
initiator_isr_handler(ISR): Attached tohkAltActionInitPin. Sends a message toinitiator_task’s queue upon button press.initiator_task: Arms the alternate action (m_altActionArmed = true), illuminates indicator LED (hkAltActionInitLedPin), and starts expiration timerm_altActionInitTimer.triggerAltAction()(Internal Method): If armed when a HomeKey tap occurs, publishesHW_ALT_ACTIONevent and triggers physical output onhkAltActionPinforhkAltActionTimeout.
Timer Callback (handleTimer)
A single callback used by all ESP-timers created by the manager to deactivate GPIO pins or turn off NeoPixels upon timer expiration.