firmware-base/docs/components.md

168 lines
8.3 KiB
Markdown

# Component System Documentation
This document describes the component-based architecture used in the firmware.
## Overview
The firmware utilizes a modular design where different functionalities (hardware interfaces, communication managers, etc.) are implemented as separate "Components". This promotes code reusability, organization, and easier maintenance.
## Core Concepts
1. **`Component` Base Class (`src/Component.h`):**
* All functional units inherit from the base `Component` class.
* Provides common properties like `name`, `id` (unique identifier from `COMPONENT_KEY` enum in `src/enums.h`), `owner` (pointer to the parent component, usually `PHApp`), `flags` (for controlling behavior like setup/loop execution), and `nFlags` (for network capabilities).
* Defines standard virtual methods that can be overridden by derived classes:
* `setup()`: Called once during application initialization.
* `loop()`: Called repeatedly in the main application loop.
* `info()`: Used for printing component status/information (often called via serial command).
* `debug()`: Used for printing debugging information.
* `onRegisterMethods(Bridge* bridge)`: Used to register methods that can be called via the `Bridge` (e.g., through serial commands).
* `readNetworkValue(short address)` / `writeNetworkValue(short address, short value)`: Interface for network managers (like `ModbusManager`) to interact with the component's data.
* `notifyStateChange()`: Intended to be called by components when their state changes, although currently not actively used by managers.
2. **`App` / `PHApp` (`src/App.h`, `src/PHApp.h`):**
* `App` is the base application class, inheriting from `Component`.
* `PHApp` is the specific application implementation for this project.
* `PHApp` acts as the central orchestrator and owner of most components.
* It maintains a `Vector<Component*> components` list to hold pointers to all active components.
* **Initialization (`PHApp::setup()`):**
* Instantiates necessary components (like `Bridge`, `SerialMessage`, `ModbusManager`, `Relay`, etc.).
* Adds component pointers to the `components` vector using `components.push_back()`. **Crucially, `components.setStorage(componentsArray);` must be called in the `App` constructor (`App.cpp`) for the vector to function correctly.**
* Calls `App::setup()`, which iterates through the `components` vector and calls the `setup()` method of each component (if the `E_OF_SETUP` flag is set).
* Registers components with managers (e.g., `modbusManager->registerComponentAddress(...)`).
* Registers component methods with the `Bridge` (`registerComponents(bridge)`).
* **Main Loop (`PHApp::loop()`):**
* Calls `App::loop()`, which iterates through the `components` vector and calls the `loop()` method of each component (if the `E_OF_LOOP` flag is set).
3. **Configuration (`src/config.h`, `src/features.h`, `src/config_secrets.h`, `src/config-modbus.h`):**
* Hardware pin assignments (e.g., `MB_RELAY_0`, `STATUS_WARNING_PIN`) are typically defined in `config.h`.
* Feature flags (e.g., `ENABLE_MODBUS_TCP`, `HAS_STATUS`) used for conditional compilation are often in `features.h` or `config_adv.h`.
* Sensitive information like WiFi credentials should be in `config_secrets.h` (which should not be committed to version control).
* Modbus addresses are centralized in `config-modbus.h`.
* Component IDs are defined in `src/enums.h`.
## Adding a New Component
Let's illustrate with a hypothetical example: adding a simple Temperature Sensor component.
1. **Define Configuration:**
* Add pin definition to `config.h`: `#define TEMP_SENSOR_PIN 34`
* Add component ID to `enums.h` (`COMPONENT_KEY` enum): `COMPONENT_KEY_TEMP_SENSOR_0 = 800,`
* (Optional) Add Modbus address to `config-modbus.h`: `#define MB_IREG_TEMP_SENSOR_0 800`
* (Optional) Add feature flag to `features.h`: `#define HAS_TEMP_SENSOR_0`
2. **Create Component Class (`src/TempSensor.h`):**
```cpp
#ifndef TEMP_SENSOR_H
#define TEMP_SENSOR_H
#include "Component.h"
#include "enums.h"
#include <ArduinoLog.h>
// Include any specific sensor libraries if needed
class Bridge;
class TempSensor : public Component {
private:
const short pin;
float currentValue;
short modbusAddress;
public:
TempSensor(Component* owner, short _pin, short _id, short _modbusAddr)
: Component("TempSensor", _id, COMPONENT_DEFAULT, owner),
pin(_pin),
currentValue(0.0),
modbusAddress(_modbusAddr)
{
// Enable Modbus capability if needed
setNetCapability(OBJECT_NET_CAPS::E_NCAPS_MODBUS);
// Ensure loop() is called
// setFlag(OBJECT_RUN_FLAGS::E_OF_LOOP); // Already in COMPONENT_DEFAULT
}
short setup() override {
Component::setup();
// Initialize sensor library, set pin mode, etc.
// pinMode(pin, INPUT); // Example
Log.verboseln("TempSensor::setup - ID: %d, Pin: %d, Modbus Addr: %d", id, pin, modbusAddress);
return E_OK;
}
short loop() override {
Component::loop();
// Read sensor value periodically
// currentValue = analogRead(pin); // Simplified example
// Add proper timing logic (e.g., read every second)
return E_OK;
}
short info(short arg1 = 0, short arg2 = 0) override {
Log.verboseln("TempSensor::info - ID: %d, Pin: %d, Value: %F, Modbus Addr: %d", id, pin, currentValue, modbusAddress);
return E_OK;
}
// --- Network Interface ---
short readNetworkValue(short address) override {
if (address == modbusAddress) {
// Convert float to Modbus register format (e.g., integer degrees * 10)
return (short)(currentValue * 10.0);
}
return 0; // Not handled
}
// Optional: Implement writeNetworkValue if temperature could be set (e.g., for simulation)
// virtual short writeNetworkValue(short address, short value) override { ... }
// Register methods for serial commands
short onRegisterMethods(Bridge* bridge) override {
Component::onRegisterMethods(bridge);
bridge->registerMemberFunction(id, this, C_STR("info"), (ComponentFnPtr)&TempSensor::info);
// Add other methods if needed
return E_OK;
}
};
#endif // TEMP_SENSOR_H
```
3. **Instantiate and Register in `PHApp`:**
* **`PHApp.h`:**
* Include `TempSensor.h` (likely within `#ifdef HAS_TEMP_SENSOR_0`).
* Declare a pointer: `TempSensor* tempSensor_0;`.
* **`PHApp.cpp` (`PHApp::setup()`):**
```cpp
// ... other includes ...
#ifdef HAS_TEMP_SENSOR_0
#include "TempSensor.h"
#endif
// ... inside PHApp::setup() ...
#ifdef HAS_TEMP_SENSOR_0
tempSensor_0 = new TempSensor(this,
TEMP_SENSOR_PIN,
COMPONENT_KEY_TEMP_SENSOR_0,
MB_IREG_TEMP_SENSOR_0);
components.push_back(tempSensor_0);
#else
tempSensor_0 = nullptr;
#endif
// ... later, after App::setup() ...
// --- Register Components with Modbus Manager ---
#if defined(ENABLE_MODBUS_TCP)
if (modbusManager) {
// ... other registrations ...
#ifdef HAS_TEMP_SENSOR_0
if (tempSensor_0) modbusManager->registerComponentAddress(tempSensor_0, MB_IREG_TEMP_SENSOR_0, 1);
#endif
}
#endif
// ... registerComponents(bridge) will handle calling TempSensor::onRegisterMethods ...
```
4. **Build and Test:** Recompile (`npm run build`), upload (`npm run upload`), and test using serial commands (`npm run send -- "<<800;2;64;info:0:0>>"`) and Modbus tools (`python scripts/modbus_read_registers.py --address 800 --ip-address <IP>`).