8.3 KiB
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
-
ComponentBase Class (src/Component.h):- All functional units inherit from the base
Componentclass. - Provides common properties like
name,id(unique identifier fromCOMPONENT_KEYenum 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):Appis the base application class, inheriting fromComponent.PHAppis the specific application implementation for this project.PHAppacts as the central orchestrator and owner of most components.- It maintains a
Vector<Component*> componentslist to hold pointers to all active components. - Initialization (
PHApp::setup()):- Instantiates necessary components (like
Bridge,SerialMessage,ModbusManager,Relay, etc.). - Adds component pointers to the
componentsvector usingcomponents.push_back(). Crucially,components.setStorage(componentsArray);must be called in theAppconstructor (App.cpp) for the vector to function correctly. - Calls
App::setup(), which iterates through thecomponentsvector and calls thesetup()method of each component (if theE_OF_SETUPflag 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 thecomponentsvector and calls theloop()method of each component (if theE_OF_LOOPflag 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.horconfig_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
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_KEYenum):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 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 -
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_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 ...
-
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>).