Components
Component System Documentation
Section titled “Component System Documentation”This document describes the component-based architecture used in the firmware.
Overview
Section titled “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
Section titled “Core Concepts”-
Component
Base Class (src/Component.h
):- All functional units inherit from the base
Component
class. - Provides common properties like
name
,id
(unique identifier fromCOMPONENT_KEY
enum insrc/enums.h
),owner
(pointer to the parent component, usuallyPHApp
),flags
(for controlling behavior like setup/loop execution), andnFlags
(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 theBridge
(e.g., through serial commands).readNetworkValue(short address)
/writeNetworkValue(short address, short value)
: Interface for network managers (likeModbusManager
) 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.
- All functional units inherit from the base
-
App
/PHApp
(src/App.h
,src/PHApp.h
):App
is the base application class, inheriting fromComponent
.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 usingcomponents.push_back()
. Crucially,components.setStorage(componentsArray);
must be called in theApp
constructor (App.cpp
) for the vector to function correctly. - Calls
App::setup()
, which iterates through thecomponents
vector and calls thesetup()
method of each component (if theE_OF_SETUP
flag is set). - Registers components with managers (e.g.,
modbusManager->registerComponentAddress(...)
). - Registers component methods with the
Bridge
(registerComponents(bridge)
).
- Instantiates necessary components (like
- Main Loop (
PHApp::loop()
):- Calls
App::loop()
, which iterates through thecomponents
vector and calls theloop()
method of each component (if theE_OF_LOOP
flag is set).
- Calls
-
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 inconfig.h
. - Feature flags (e.g.,
ENABLE_MODBUS_TCP
,HAS_STATUS
) used for conditional compilation are often infeatures.h
orconfig_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
.
- Hardware pin assignments (e.g.,
Adding a New Component
Section titled “Adding a New Component”Let’s illustrate with a hypothetical example: adding a simple Temperature Sensor component.
-
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
- Add pin definition to
-
Create Component Class (
src/TempSensor.h
):#ifndef TEMP_SENSOR_H#define TEMP_SENSOR_H#include "Component.h"#include "enums.h"#include <ArduinoLog.h>// Include any specific sensor libraries if neededclass 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 neededsetNetCapability(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); // ExampleLog.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 commandsshort onRegisterMethods(Bridge* bridge) override {Component::onRegisterMethods(bridge);bridge->registerMemberFunction(id, this, C_STR("info"), (ComponentFnPtr)&TempSensor::info);// Add other methods if neededreturn E_OK;}};#endif // TEMP_SENSOR_H -
Instantiate and Register in
PHApp
:PHApp.h
:- Include
TempSensor.h
(likely within#ifdef HAS_TEMP_SENSOR_0
). - Declare a pointer:
TempSensor* tempSensor_0;
.
- Include
PHApp.cpp
(PHApp::setup()
):// ... other includes ...#ifdef HAS_TEMP_SENSOR_0#include "TempSensor.h"#endif// ... inside PHApp::setup() ...#ifdef HAS_TEMP_SENSOR_0tempSensor_0 = new TempSensor(this,TEMP_SENSOR_PIN,COMPONENT_KEY_TEMP_SENSOR_0,MB_IREG_TEMP_SENSOR_0);components.push_back(tempSensor_0);#elsetempSensor_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_0if (tempSensor_0) modbusManager->registerComponentAddress(tempSensor_0, MB_IREG_TEMP_SENSOR_0, 1);#endif}#endif// ... registerComponents(bridge) will handle calling TempSensor::onRegisterMethods ...
-
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>
).