🧠 Auto TPI: In-Depth Technical Guide

NOTE

This document is intended for advanced users who want to understand the Auto TPI algorithm in detail. For a more accessible introduction, see the Auto TPI User Guide.


Table of Contents

  1. The TPI Algorithm
  2. Detailed Learning Cycle
  3. Thermal Capacity Calibration
  4. Coefficient Calculation Algorithms
  5. Automatic Correction Mechanisms
  6. Advanced Parameters and Constants
  7. Services and API
  8. Advanced Diagnostics and Troubleshooting

The TPI Algorithm

Fundamental Principle

The TPI (Time Proportional & Integral) algorithm calculates a power percentage at each cycle. This percentage determines how long the heater will be active during the cycle (e.g., 60% on a 10-minute cycle = 6 minutes of heating).

Basic Formula

Power = (Kint × ΔT_indoor) + (Kext × ΔT_outdoor)

Where:

  • Kint (tpi_coef_int): Indoor coefficient, reacts to the setpoint gap
  • Kext (tpi_coef_ext): Outdoor coefficient, compensates for thermal losses
  • ΔT_indoor = Setpoint − Indoor Temperature
  • ΔT_outdoor = Setpoint − Outdoor Temperature
graph LR
    subgraph Inputs
        A[Indoor Temperature]
        B[Outdoor Temperature]
        C[Setpoint]
    end
    
    subgraph TPI Calculation
        D["ΔT_int = Setpoint - T_int"]
        E["ΔT_ext = Setpoint - T_ext"]
        F["Power = Kint×ΔT_int + Kext×ΔT_ext"]
    end
    
    subgraph Output
        G["Power % (0-100%)"]
        H["ON/OFF Time"]
    end
    
    A --> D
    C --> D
    B --> E
    C --> E
    D --> F
    E --> F
    F --> G
    G --> H

Role of Coefficients

CoefficientRoleLearning Situation
KintControls reactivity: the higher it is, the faster heating reacts to gapsDuring temperature rise (gap > 0.05°C, power < 99%)
KextCompensates for thermal losses: the higher it is, the more heating anticipates coolingDuring stabilization around the setpoint (gap < 0.5°C)

Detailed Learning Cycle

Flow Overview

flowchart TD
    subgraph Initialization
        A[Start Session] --> B{Heating Rate = 0?}
        B -->|Yes| C[Historical Pre-calibration]
        B -->|No| G[Active Learning]
        
        C --> D{Reliability >= 20%?}
        D -->|Yes| G
        D -->|No| E[Bootstrap Mode]
        E -->|3 aggressive cycles| F[Estimated Capacity]
        F --> G
    end
    
    subgraph "Learning Loop"
        G --> H[Start TPI Cycle]
        H --> I[Snapshot Initial State]
        I --> J[Execute ON/OFF Heating]
        J --> K[End Cycle: Measure ΔT]
        K --> L{Valid Conditions?}
        
        L -->|No| M[Skip Learning]
        L -->|Yes| N{Analyze Situation}
        
        N -.->|Overshoot| O[🔸 Kext Correction<br/>optional]
        N -.->|Stagnation| P[🔸 Kint Boost<br/>optional]
        N -->|T° Rising| Q[Kint Learning]
        N -->|Stabilization| R[Kext Learning]
        
        O -.-> S[Update Coefficients]
        P -.-> S
        Q --> S
        R --> S
        M --> H
        S --> H
    end
    
    subgraph Finalization
        S --> T{50 cycles Kint AND Kext?}
        T -->|No| H
        T -->|Yes| U[Save to Config]
        U --> V[End Notification]
    end
    
    style O fill:#fff3cd,stroke:#ffc107,stroke-width:2px
    style P fill:#fff3cd,stroke:#ffc107,stroke-width:2px

NOTE

Yellow boxes with dashed lines (🔸) represent optional correction mechanisms. They must be explicitly enabled via set_auto_tpi_mode service parameters.

Cycle Snapshot Details

At each cycle start, the algorithm captures the current state:

Captured DataUsage
last_temp_inIndoor temperature at cycle start
last_temp_outOutdoor temperature at cycle start
last_orderSetpoint at cycle start
last_powerCalculated power for this cycle (0.0 to 1.0)
last_stateHVAC mode (heat/cool)

At cycle end, these values are compared with current measurements to calculate progression.

Cycle Validation Conditions

A cycle is ignored for learning if:

ConditionReason
Power = 0% or 100%Saturation: no exploitable efficiency information
Setpoint modifiedTarget changed mid-cycle
Power shedding activeHeating was forced OFF by Power Manager
Failure detectedAnomaly detected (ineffective heating)
Central boiler OFFThermostat requests but boiler doesn't respond
First cycle after restartNo valid reference data

Thermal Capacity Calibration

Definition

Thermal capacity (or heating rate) represents the maximum temperature rise speed of your system, expressed in °C per hour (°C/h).

Example: A capacity of 2.0 °C/h means your radiator can raise the temperature by 2°C in one hour at full power (under ideal adiabatic conditions).

Determination Methods

graph TD
    A[Heating Rate = 0?] -->|Yes| B[Pre-calibration]
    A -->|No| C[Use Configured Value]
    
    B --> D{History Available?}
    D -->|Yes| E[History Analysis]
    D -->|No| F[Bootstrap Mode]
    
    E --> G{Reliability >= 20%?}
    G -->|Yes| H[Calibrated Capacity]
    G -->|No| F
    
    F --> I[3 aggressive cycles Kint=1.0 Kext=0.1]
    I --> J[Measure Actual Rise]
    J --> K[Estimated Capacity]
    
    H --> L[Kint/Kext Learning]
    K --> L
    C --> L

Pre-calibration via History Analysis

The auto_tpi_calibrate_capacity service analyzes sensor history:

  1. Retrieval of temperature_slope and power_percent data over 30 days
  2. Filtering: only keeps points where power >= 95%
  3. Outlier elimination using IQR (Interquartile Range) method
  4. 75th percentile calculation of slopes (more representative than median)
  5. Adiabatic correction: Capacity = P75 + Kext × ΔT
  6. Safety margin application: 20% by default

Bootstrap Mode

If history is insufficient (reliability < 20%), the system enters bootstrap mode:

  • Aggressive coefficients: Kint = 1.0, Kext = 0.1
  • Duration: minimum 3 cycles
  • Goal: Trigger significant temperature rise to measure actual capacity
  • Timeout safety: If failure after 5 cycles, default capacity = 0.3 °C/h (slow systems)

Coefficient Calculation Algorithms

Kint Learning (Indoor Coefficient)

The algorithm adjusts Kint when temperature rises toward the setpoint.

Detailed Formula

flowchart LR
    subgraph "1. Effective Capacity"
        A["C_eff = C_ref × (1 - Kext × ΔT_ext)"]
    end
    
    subgraph "2. Max Possible Rise"
        B["max_rise = C_eff × cycle_duration × efficiency"]
    end
    
    subgraph "3. Adjusted Target"
        C["target = min(setpoint_gap, max_rise)"]
    end
    
    subgraph "4. Ratio"
        D["ratio = (target / actual_rise) × aggressiveness"]
    end
    
    subgraph "5. New Kint"
        E["Kint_new = Kint_old × ratio"]
    end
    
    A --> B --> C --> D --> E

Variables Used

VariableDescriptionTypical Value
C_refCalibrated reference capacity1.5 °C/h
KextCurrent outdoor coefficient0.02
ΔT_extIndoor/outdoor temp difference15°C
cycle_durationIn hours0.167 (10 min)
efficiencyPower percentage used0.70
aggressivenessModeration factor0.9

Kext Learning (Outdoor Coefficient)

The algorithm adjusts Kext when temperature is close to setpoint (|gap| < 0.5°C).

Formula

Correction = Kint × (indoor_gap / outdoor_gap)
Kext_new = Kext_old + Correction
  • If indoor_gap negative (overshoot) → Negative correction → Kext decreases
  • If indoor_gap positive (undershoot) → Positive correction → Kext increases

Smoothing Methods

Two methods are available to smooth new values:

Weighted Average ("Discovery" mode)

Kint_final = (Kint_old × count + Kint_new) / (count + 1)
CycleOld WeightNew WeightNew Value Impact
11150%
101019%
505012%

The counter is capped at 50 to maintain minimum reactivity.

EWMA ("Fine Tuning" mode)

Kint_final = (1 - α) × Kint_old + α × Kint_new
α(n) = α₀ / (1 + decay_rate × n)
ParameterDefaultDescription
α₀ (initial alpha)0.08Initial weight of new values
decay_rate0.12Alpha decrease speed

Continuous Kext Learning

This mechanism allows for long-term adaptation of KextK_{ext} without an active learning session.

Eligibility Conditions

A cycle is used for continuous learning only if:

  1. Feature enabled: auto_tpi_continuous_kext is set to true.
  2. Bootstrapped: At least one outdoor learning cycle has been completed previously for the current mode.
  3. Non-saturated power: 0<Preal<Psaturation0 < P_{real} < P_{saturation}.
  4. Stable system: No cycle interruptions, no boiler off, no heating failure, and no excessive consecutive failures.
  5. Significant outdoor delta: SetpointToutdoor1.0°C|Setpoint - T_{outdoor}| \ge 1.0°C.
  6. No setpoint change: The target temperature did not change during the cycle.

Continuous Learning Formula

The correction is calculated similarly to the standard KextK_{ext} learning: Kexttarget=Kextold+Kint×ΔTindoorΔToutdoorK_{ext}^{target} = K_{ext}^{old} + K_{int} \times \frac{\Delta T_{indoor}}{\Delta T_{outdoor}}

Then, it is applied using an EWMA with a specific alpha: Kextnew=(1αcont)×Kextold+αcont×KexttargetK_{ext}^{new} = (1 - \alpha_{cont}) \times K_{ext}^{old} + \alpha_{cont} \times K_{ext}^{target}

By default, αcont=0.04\alpha_{cont} = 0.04.


Automatic Correction Mechanisms

Overshoot Correction (Kext Deboost)

Activation: allow_kext_compensation_on_overshoot parameter in set_auto_tpi_mode service

Detects and corrects when temperature exceeds the setpoint without coming back down.

flowchart TD
    A{T° > Setpoint + 0.2°C?} -->|Yes| B{Power > 5%?}
    B -->|Yes| C{T° not falling?}
    C -->|Yes| D[Kext Correction]
    
    A -->|No| E[No Correction]
    B -->|No| E
    C -->|No| E
    
    D --> F["reduction = overshoot × Kint / ΔT_ext"]
    F --> G["Kext_target = max(0.001, Kext - reduction)"]
    G --> H[Apply with alpha boost ×2]

Stagnation Correction (Kint Boost)

Activation: allow_kint_boost_on_stagnation parameter in set_auto_tpi_mode service

Detects and corrects when temperature stagnates despite significant gap.

flowchart TD
    A{Gap > 0.5°C?} -->|Yes| B{Progress < 0.02°C?}
    B -->|Yes| C{Power < 99%?}
    C -->|Yes| D{Consecutive boosts < 5?}
    D -->|Yes| E[Kint Boost]
    
    A -->|No| F[No Correction]
    B -->|No| F
    C -->|No| F
    D -->|No| G[Undersized Heating Alert]
    
    E --> H["boost = 8% × min(gap/0.3, 2.0)"]
    H --> I["Kint_target = Kint × (1 + boost)"]

Advanced Parameters and Constants

Internal Constants (Non-configurable)

ConstantValueDescription
MIN_KINT0.01Kint floor to maintain reactivity
OVERSHOOT_THRESHOLD0.2°COvershoot threshold to trigger correction
OVERSHOOT_POWER_THRESHOLD5%Min power to consider overshoot as Kext error
OVERSHOOT_CORRECTION_BOOST2.0Alpha multiplier during correction
NATURAL_RECOVERY_POWER_THRESHOLD20%Max power to skip learning in natural recovery
INSUFFICIENT_RISE_GAP_THRESHOLD0.5°CMin gap to trigger Kint boost
MAX_CONSECUTIVE_KINT_BOOSTS5Limit before undersizing alert
MIN_PRE_BOOTSTRAP_CALIBRATION_RELIABILITY20%Min reliability to skip bootstrap

Configurable Parameters

ParameterTypeDefaultRange
AggressivenessSlider1.00.5 - 1.0
Heating TimeMinutes51 - 30
Cooling TimeMinutes71 - 60
Heating Rate°C/h0 (auto)0 - 5.0
Initial Weight (Discovery)Integer11 - 50
Alpha (Fine Tuning)Float0.080.01 - 0.3
Decay RateFloat0.120.0 - 0.5

Services and API

versatile_thermostat.set_auto_tpi_mode

Controls learning start/stop.

service: versatile_thermostat.set_auto_tpi_mode
target:
  entity_id: climate.my_thermostat
data:
  auto_tpi_mode: true                    # true = start, false = stop
  reinitialise: true                     # true = full reset, false = resume
  allow_kint_boost_on_stagnation: false  # Boost Kint on stagnation
  allow_kext_compensation_on_overshoot: false  # Kext correction on overshoot

versatile_thermostat.auto_tpi_calibrate_capacity

Calibrates thermal capacity from history.

service: versatile_thermostat.auto_tpi_calibrate_capacity
target:
  entity_id: climate.my_thermostat
data:
  start_date: "2024-01-01T00:00:00+00:00"  # Optional
  end_date: "2024-02-01T00:00:00+00:00"    # Optional
  min_power_threshold: 95                   # Min power %
  capacity_safety_margin: 20                # Safety margin %
  save_to_config: true                      # Save to config

Service Returns:

KeyDescription
max_capacityCalculated raw capacity (°C/h)
recommended_capacityCapacity after margin (°C/h)
reliabilityReliability index (%)
samples_usedNumber of samples
outliers_removedNumber of outliers removed

Advanced Diagnostics and Troubleshooting

Diagnostic Sensor

Entity: sensor.<name>_auto_tpi_learning_state

AttributeDescription
activeLearning in progress
heating_cycles_countTotal observed cycles
coeff_int_cyclesValidated Kint cycles
coeff_ext_cyclesValidated Kext cycles
model_confidenceConfidence 0.0 - 1.0
calculated_coef_intCurrent Kint
calculated_coef_extCurrent Kext
last_learning_statusLast cycle reason
capacity_heat_statuslearning or learned
capacity_heat_valueCurrent capacity (°C/h)

Common Learning Statuses

StatusMeaningSuggested Action
learned_indoor_heatKint updated successfullyNormal
learned_outdoor_heatKext updated successfullyNormal
power_out_of_rangePower at 0% or 100%Wait for non-saturated cycle
real_rise_too_smallRise < 0.01°CCheck sensor or cycle duration
setpoint_changed_during_cycleSetpoint modifiedAvoid touching setpoint
no_capacity_definedNo calibrated capacityWait for calibration/bootstrap
corrected_kext_overshootOvershoot correction appliedNormal if Kext too high
corrected_kint_insufficient_riseKint boost appliedNormal if Kint too low
max_kint_boosts_reached5 consecutive boostsUndersized heating

Diagnostic Decision Tree

flowchart TD
    A[Problem Detected] --> B{Kint or Kext?}
    
    B -->|Kint too low| C[T° rises slowly]
    C --> D{After 10 cycles?}
    D -->|Yes| E[Check heating/cooling times]
    D -->|No| F[Wait for convergence]
    
    B -->|Kint too high| G[T° oscillations]
    G --> H[Reduce aggressiveness]
    
    B -->|Kext too low| I[T° drops below setpoint]
    I --> J[Check outdoor T° sensor]
    
    B -->|Kext too high| K[Persistent overshoot]
    K --> L[Enable allow_kext_compensation]
    
    A --> M{No learning?}
    M -->|power_out_of_range| N[Saturated heating]
    N --> O[Wait for favorable conditions]
    M -->|no_capacity_defined| P[No calibration]
    P --> Q[Check history or force value]

Persistence File

Location: .storage/versatile_thermostat_{unique_id}_auto_tpi_v2.json

This file contains the complete learning state and is restored on Home Assistant restart. It can be deleted to force a complete reset (not recommended).

Startup Synchronization

At each startup, if Continuous Kext Learning is enabled, the system performs an Alignment between the stored data (JSON) and the Home Assistant configuration (ConfigEntry):

  1. Clamping: Loaded coefficients are immediately capped to the max_coef_int limit (standard safety).
  2. Kext and Capacity Configuration Sync: If the KextK_{ext} or the heating/cooling capacity in the configuration differs from the learned value in storage (captured through background adaptation without integration reload), the system performs an atomic update of the configuration. This ensures that the user interface and the YAML/UI configuration remain synchronized with the most accurate building model.

Appendices

Heating TypeHeating TimeCooling TimeTypical Capacity
Electric convector2-5 min3-7 min2.0-3.0 °C/h
Inertia radiator5-10 min10-20 min1.0-2.0 °C/h
Underfloor heating15-30 min30-60 min0.3-0.8 °C/h
Central boiler5-15 min10-30 min1.0-2.5 °C/h

Complete Mathematical Formulas

Effective Capacity: Ceff=Cref×(1Kext×ΔText)C_{eff} = C_{ref} \times (1 - K_{ext} \times \Delta T_{ext})

Adaptive Alpha (EWMA): α(n)=α01+k×n\alpha(n) = \frac{\alpha_0}{1 + k \times n}

Calibration Reliability: reliability=100×min(samples20,1)×max(0,1CV2)reliability = 100 \times \min\left(\frac{samples}{20}, 1\right) \times \max\left(0, 1 - \frac{CV}{2}\right)

Where CV = Coefficient of Variation (standard deviation / mean)