# 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 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 // 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 `).