16 KiB
Amperage Budget Manager Design
1. Introduction
This document outlines the design for the AmperageBudgetManager component. This component is responsible for managing a group of OmronE5 temperature controllers (representing heating partitions) to ensure their total power consumption does not exceed a predefined budget. It achieves this by serializing the activation (run()) of the controllers based on heating demand (PV < SP) and available budget, using a fair scheduling algorithm.
2. Goals
- Limit the instantaneous total power consumption of a group of
OmronE5devices. - Provide a configurable power budget (in Watts).
- Implement a fair scheduling mechanism (round-robin with preemption) to ensure all partitions needing heat get a chance to run over time.
- Integrate seamlessly with the existing
PHAppandOmronE5components. - Control
OmronE5devices using their existingrun()andstop()methods.
3. Component Architecture
3.1 Class Diagram
classDiagram
Component <|-- AmperageBudgetManager
AmperageBudgetManager o-- "0..*" ManagedDevice : contains
ManagedDevice o-- OmronE5 : references
PHApp o-- AmperageBudgetManager : owns
PHApp o-- "*" OmronE5 : owns
class Component {
+setup() virtual
+loop() virtual
+info() virtual
}
class OmronE5 {
+run() bool
+stop() bool
+getPV(uint16_t&) bool
+getSP(uint16_t&) bool
+isRunning() bool
+getConsumption() uint32_t
-_consumption : uint32_t
}
class ManagedDevice {
+OmronE5* device
+ManagedState state
+uint8_t originalIndex
}
class AmperageBudgetManager {
+AmperageBudgetManager(uint32_t wattBudget)
+addManagedDevice(OmronE5* device)
+setup() override
+loop() override
+info() override
-uint32_t _wattBudget
-ManagedDevice _managedDevices[]
-uint8_t _numDevices
-uint8_t _maxDevices
-uint8_t _nextDeviceIndex
-ManagedState state // Enum definition reference
-_allocateBudget()
}
class PHApp {
+setup()
+loop()
}
enum ManagedState {
UNKNOWN
IDLE
REQUESTING_HEAT
HEATING
}
AmperageBudgetManager ..> ManagedState : uses
ManagedDevice ..> ManagedState : uses
3.2 Data Structures
_wattBudget:uint32_t- The maximum combined wattage allowed for managed devices in theHEATINGstate._managedDevices: An array or similar structure holding pointers to the managedOmronE5instances along with their current state (ManagedState) and original registration order for stable sorting._nextDeviceIndex:uint8_t- Index used for the round-robin starting point.ManagedState: Enum (UNKNOWN,IDLE,REQUESTING_HEAT,HEATING) - Tracks the state of each managed device from the budget manager's perspective.
3.3 Key Methods
AmperageBudgetManager(uint32_t wattBudget): Constructor, sets the budget.addManagedDevice(OmronE5* device): Registers anOmronE5instance to be managed.setup(): Initializes the manager, sets initial states.loop(): The core logic, executed repeatedly. Checks device status, calculates needs, allocates budget, and sendsrun()/stop()commands.info(): Prints debugging information about managed devices and budget status._allocateBudget(): Internal helper encapsulating the budget allocation logic described in section 4.
4. Scheduling Logic (Round-Robin with Preemption)
The loop() method executes the following logic periodically:
-
Identify Needs & Stop Unneeded:
- Iterate through all
_managedDevices. - For each device, get its current PV, SP, and
isRunning()status. - If
PV >= SP(wants to stop) and its state isHEATINGorREQUESTING_HEAT:- Call
device->stop(). - Set its state to
IDLE.
- Call
- If
PV < SP(wants to run):- If its state is
IDLE, change state toREQUESTING_HEAT.
- If its state is
- Keep track of devices currently in
HEATINGstate and those inREQUESTING_HEAT.
- Iterate through all
-
Prioritize and Allocate Budget (
_allocateBudget):- Create a list
potentialRunnerscontaining all devices currently in theREQUESTING_HEATorHEATINGstate. - Sort
potentialRunnersbased on their original registration order, but starting the comparison cycle from_nextDeviceIndexto implement round-robin priority. (Effectively, devices closer to_nextDeviceIndexin the circular list get higher priority for this cycle). - Initialize
currentWattage = 0. - Create an empty list
willRun. - Iterate through the prioritized
potentialRunners:- Get the device's consumption:
device->getConsumption(). - If
currentWattage + device->getConsumption() <= _wattBudget:- Add the device to the
willRunlist. currentWattage += device->getConsumption().
- Add the device to the
- Get the device's consumption:
- Create a list
-
Apply Changes:
- Iterate through all
_managedDevicesagain:- If the device is in the
willRunlist:- If its state was
REQUESTING_HEAT, calldevice->run(). - Set its state to
HEATING.
- If its state was
- Else (device is not in
willRunlist):- If its state was
HEATING, calldevice->stop(). - Set its state to
REQUESTING_HEAT(if PV < SP) orIDLE(if PV >= SP - handled in step 1).
- If its state was
- If the device is in the
- Iterate through all
-
Update Robin Index:
- Increment
_nextDeviceIndex(wrapping around) to ensure the priority shifts in the next cycle:_nextDeviceIndex = (_nextDeviceIndex + 1) % _numDevices.
- Increment
4.1 State Diagram (for a single managed OmronE5)
stateDiagram-v2
[*] --> IDLE : Initialized / PV >= SP
IDLE --> REQUESTING_HEAT : PV < SP detected
IDLE --> IDLE : PV >= SP
REQUESTING_HEAT --> HEATING : Budget allocated & run() called
REQUESTING_HEAT --> IDLE : PV >= SP detected & stop() called
REQUESTING_HEAT --> REQUESTING_HEAT : Budget not available
HEATING --> IDLE : PV >= SP detected & stop() called
HEATING --> REQUESTING_HEAT : Budget revoked & stop() called
HEATING --> HEATING : Budget remains allocated & PV < SP
4.2 Sequence Diagram (Simplified loop cycle)
sequenceDiagram
participant L as loop()
participant BM as AmperageBudgetManager
participant O1 as OmronE5_1
participant O2 as OmronE5_2
participant O3 as OmronE5_3
loop over Managed Devices
L->>O1: getPV(), getSP(), isRunning()
L->>BM: Update O1 State (e.g., IDLE to REQUESTING_HEAT)
L->>O2: getPV(), getSP(), isRunning()
L->>BM: Update O2 State (e.g., HEATING to IDLE)
L->>O2: stop()
L->>O3: getPV(), getSP(), isRunning()
L->>BM: Update O3 State (e.g., HEATING remains HEATING)
end
L->>BM: _allocateBudget()
BM->>O1: getConsumption()
BM->>O3: getConsumption()
BM-->>L: Return willRun = [O1, O3]
loop over Managed Devices
L->>BM: Check if O1 in willRun -> Yes
BM->>O1: run() (if state was REQUESTING_HEAT)
BM->>BM: Set O1 state = HEATING
L->>BM: Check if O2 in willRun -> No
BM->>BM: Set O2 state = IDLE (already stopped)
L->>BM: Check if O3 in willRun -> Yes
BM->>BM: Set O3 state = HEATING (no change needed)
end
L->>BM: Increment _nextDeviceIndex
5. Configuration and Integration
- Instantiation: An instance of
AmperageBudgetManagershould be created inPHApp.// In PHApp.h (example) #include "components/AmperageBudgetManager.h" // ... AmperageBudgetManager* budgetManager; // In PHApp::setup() (example) _amperageBudget = new AmperageBudgetManager(10000); // Example: 10kW budget _amperageBudget->addManagedDevice(_omron1); // Assuming _omron1 is an OmronE5* _amperageBudget->addManagedDevice(_omron2); // ... add other Omrons _amperageBudget->setup(); - Budget Value: The power budget (Watts) is passed to the constructor. This could be made configurable via Modbus or REST API by adding setter methods and potentially exposing them through
PHApp. - Device Consumption: The manager relies on
OmronE5::getConsumption(). This method needs to be added to theOmronE5class, returning the_consumptionvalue (which might also need to be made configurable perOmronE5instance). - PHApp Loop:
AmperageBudgetManager::loop()must be called fromPHApp::loop().// In PHApp::loop() if (_amperageBudget) { _amperageBudget->loop(); }
6. Potential Issues and Refinements
- SP Reachability: If the budget is too low relative to the heat loss and the number/power of partitions, the system might struggle or fail to reach the Set Point (SP) on all devices. This requires careful tuning of the budget.
- Rapid Cycling: If the budget forces frequent starting/stopping, it might cause wear on relays (if used by the Omron output). Hysteresis could be added (e.g., require PV to be
SP + deltabefore stopping, orSP - deltabefore wanting heat). - Consumption Accuracy: The accuracy depends on the configured
_consumptionvalue in eachOmronE5. If actual consumption varies significantly, the budget management might be inaccurate. - Error Handling: The design assumes
OmronE5methods succeed. Robust error handling (checking return values ofrun(),stop(),getPV, etc.) should be added. - Dynamic Budget: The budget could be adjusted dynamically based on external factors (e.g., total system load).
- Advanced Scheduling: More complex scheduling (e.g., prioritizing devices further from SP) could be implemented if simple round-robin proves insufficient.
7. Mathematical Modeling and Optimization Considerations
This section explores the mathematical underpinnings and potential optimizations for the budget management system.
7.1 Core Budget Constraint
Let N be the number of managed OmronE5 devices. Let P_i be the power consumption (Watts) of device i when heating, obtained via device[i]->getConsumption(). Let S_i(t) be the state of device i at time t, where S_i(t) = 1 if the device is actively heating (state HEATING) and S_i(t) = 0 otherwise (IDLE, REQUESTING_HEAT, or stopped by the manager).
The fundamental constraint enforced by the AmperageBudgetManager at any given time t is:
Sum(S_i(t) * P_i for i=1 to N) <= W_budget
where W_budget is the configured maximum power budget.
The scheduling algorithm (Section 4, Step 2) effectively selects the subset of devices H(t) = { i | S_i(t) = 1 } such that this inequality holds, prioritizing devices based on need (PV_i < SP_i) and the round-robin index.
7.2 Error Sources
Potential sources of error can affect the system's ability to precisely meet the budget or achieve optimal heating:
- Consumption Estimation Error (epsilon_P): The actual power P_i_actual drawn by a heater might differ from the configured P_i. The total actual power is
Sum(P_i_actual for i in H(t)). The budget calculation error isSum(P_i - P_i_actual for i in H(t)).- Mitigation: Calibration, using more accurate power measurement if available, adding a safety margin to W_budget.
- Measurement Delay (tau_m): Time lag between a temperature change, its measurement by the
OmronE5(PV update), and theAmperageBudgetManagerreading it. - Control Delay (tau_c): Time lag between the manager deciding to change a device's state (
run()/stop()) and the command being executed and having an effect (e.g., RS485 communication delay, relay actuation time). - Sampling Rate: The
loop()frequency of theAmperageBudgetManagerdetermines how quickly changes are detected and acted upon. A slower rate increases the effective delay.
7.3 Thermal Inertia and Dynamics
Each heating partition i has thermal characteristics. A simplified model might be a first-order system:
C_i * d(PV_i)/dt = Q_heat_i(t) - Q_loss_i(t)
where:
- C_i is the thermal capacitance of partition i.
- PV_i is the process variable (temperature) of partition i.
- Q_heat_i(t) is the heat input rate. Q_heat_i(t) = P_i if S_i(t) = 1, and 0 otherwise (assuming idealized heater).
- Q_loss_i(t) is the heat loss rate, often modeled as
U_i * (PV_i(t) - T_ambient), where U_i is a heat transfer coefficient and T_ambient is the ambient temperature.
Implications:
- Lag: Due to C_i, the temperature PV_i changes gradually in response to Q_heat_i(t). Stopping heat doesn't instantly stop the temperature rise, potentially leading to overshoot, especially if the Omron's internal PID isn't tuned for intermittent operation.
- Interdependence: Heat loss Q_loss_i(t) might depend not only on T_ambient but also on the temperatures of adjacent partitions (PV_j), creating thermal coupling.
- SP Reachability: If the average heat input allowed by the budget over time (Avg_Q_heat_i) is less than the heat loss at the setpoint (Q_loss_i(SP_i)), the partition may never reach SP_i.
Avg_Q_heat_i = P_i * (Average Duty Cycle for i) < Q_loss_i(SP_i) - Minimum Effective On-Time (tau_min_on_i): Due to thermal capacitance (C_i) and initial heat loss rate (Q_loss_i(t)), there might be a minimum duration tau_min_on_i for which a heater i must be active (S_i(t) = 1) to produce a significant or useful increase in PV_i. Activating a heater for durations less than tau_min_on_i could be inefficient, consuming power without meaningfully contributing to reaching the setpoint. The scheduling algorithm should ideally ensure that when a device is granted budget, it runs for at least this duration, or consider this constraint when deciding which devices to activate.
7.4 Optimization Objectives
Beyond simply staying within budget, optimization could target:
- Minimize Time-to-Setpoint: Reduce the total time for all devices requesting heat to reach their respective SP_i.
- Maximize Fairness: Ensure all devices requesting heat receive a proportional amount of heating time over a longer window, preventing starvation.
- Minimize Overshoot: Reduce the amount PV_i exceeds SP_i after heating stops.
- Minimize Control Effort: Reduce the frequency of
run()/stop()commands to minimize wear on physical components (like relays). - Maximize Weighted Priority: Allow certain devices to have higher priority in budget allocation.
7.5 Potential Control Improvements
- Hysteresis: Introduce a deadband around SP. Only request heat if
PV_i < SP_i - delta_lowerand only stop requesting heat ifPV_i > SP_i + delta_upper. This reduces rapid cycling near the setpoint. - Priority Scheduling: Instead of pure round-robin, prioritize devices based on:
- Error magnitude:
SP_i - PV_i - Time since last heated
- Configured static priority
- Error magnitude:
- Predictive Control: Model the thermal dynamics (C_i, U_i) and predict future PV_i to make more informed decisions about when to start/stop heating, potentially anticipating overshoot or allocating budget more effectively.
- Duty Cycle Modulation: If the heating elements support it (unlikely with simple
run/stop), modulate the power P_i rather than just on/off state, allowing finer budget control. - Adaptive Budget: Adjust W_budget based on overall system state or external inputs.
8. Required Changes to OmronE5
- Add a public method
uint32_t getConsumption() const;toOmronE5.h. - Implement
OmronE5::getConsumption()inOmronE5.cppto return the value of the_consumptionmember variable.
// In OmronE5.h
public:
// ... other methods ...
uint32_t getConsumption() const; // Add this
// In OmronE5.cpp
uint32_t OmronE5::getConsumption() const {
return _consumption; // Assuming _consumption holds Watts
}