firmware-base/docs/amp-budget.md

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 OmronE5 devices.
  • 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 PHApp and OmronE5 components.
  • Control OmronE5 devices using their existing run() and stop() 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 the HEATING state.
  • _managedDevices: An array or similar structure holding pointers to the managed OmronE5 instances 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 an OmronE5 instance to be managed.
  • setup(): Initializes the manager, sets initial states.
  • loop(): The core logic, executed repeatedly. Checks device status, calculates needs, allocates budget, and sends run()/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:

  1. 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 is HEATING or REQUESTING_HEAT:
      • Call device->stop().
      • Set its state to IDLE.
    • If PV < SP (wants to run):
      • If its state is IDLE, change state to REQUESTING_HEAT.
    • Keep track of devices currently in HEATING state and those in REQUESTING_HEAT.
  2. Prioritize and Allocate Budget (_allocateBudget):

    • Create a list potentialRunners containing all devices currently in the REQUESTING_HEAT or HEATING state.
    • Sort potentialRunners based on their original registration order, but starting the comparison cycle from _nextDeviceIndex to implement round-robin priority. (Effectively, devices closer to _nextDeviceIndex in 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 willRun list.
        • currentWattage += device->getConsumption().
  3. Apply Changes:

    • Iterate through all _managedDevices again:
      • If the device is in the willRun list:
        • If its state was REQUESTING_HEAT, call device->run().
        • Set its state to HEATING.
      • Else (device is not in willRun list):
        • If its state was HEATING, call device->stop().
        • Set its state to REQUESTING_HEAT (if PV < SP) or IDLE (if PV >= SP - handled in step 1).
  4. Update Robin Index:

    • Increment _nextDeviceIndex (wrapping around) to ensure the priority shifts in the next cycle: _nextDeviceIndex = (_nextDeviceIndex + 1) % _numDevices.

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 AmperageBudgetManager should be created in PHApp.
    // 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 the OmronE5 class, returning the _consumption value (which might also need to be made configurable per OmronE5 instance).
  • PHApp Loop: AmperageBudgetManager::loop() must be called from PHApp::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 + delta before stopping, or SP - delta before wanting heat).
  • Consumption Accuracy: The accuracy depends on the configured _consumption value in each OmronE5. If actual consumption varies significantly, the budget management might be inaccurate.
  • Error Handling: The design assumes OmronE5 methods succeed. Robust error handling (checking return values of run(), 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:

  1. 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 is Sum(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.
  2. Measurement Delay (tau_m): Time lag between a temperature change, its measurement by the OmronE5 (PV update), and the AmperageBudgetManager reading it.
  3. 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).
  4. Sampling Rate: The loop() frequency of the AmperageBudgetManager determines 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:

  1. Minimize Time-to-Setpoint: Reduce the total time for all devices requesting heat to reach their respective SP_i.
  2. Maximize Fairness: Ensure all devices requesting heat receive a proportional amount of heating time over a longer window, preventing starvation.
  3. Minimize Overshoot: Reduce the amount PV_i exceeds SP_i after heating stops.
  4. Minimize Control Effort: Reduce the frequency of run()/stop() commands to minimize wear on physical components (like relays).
  5. Maximize Weighted Priority: Allow certain devices to have higher priority in budget allocation.

7.5 Potential Control Improvements

  1. Hysteresis: Introduce a deadband around SP. Only request heat if PV_i < SP_i - delta_lower and only stop requesting heat if PV_i > SP_i + delta_upper. This reduces rapid cycling near the setpoint.
  2. Priority Scheduling: Instead of pure round-robin, prioritize devices based on:
    • Error magnitude: SP_i - PV_i
    • Time since last heated
    • Configured static priority
  3. 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.
  4. 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.
  5. 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; to OmronE5.h.
  • Implement OmronE5::getConsumption() in OmronE5.cpp to return the value of the _consumption member 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
}