diff --git a/ha-thermostat-calibration.yaml b/ha-thermostat-calibration.yaml index edb469f..18b57ff 100644 --- a/ha-thermostat-calibration.yaml +++ b/ha-thermostat-calibration.yaml @@ -105,50 +105,57 @@ action: climate_device: "{{ device_id(climate_entity) }}" external_temp: "{{ states(external_sensor) | float(0) }}" valve_temp: "{{ state_attr(climate_entity, 'current_temperature') | float(0) }}" - - # Skip if climate entity is unavailable or has no valid temperature + hvac_action: "{{ state_attr(climate_entity, 'hvac_action') }}" # Get valve state (heating/idle) + target_temp: "{{ state_attr(climate_entity, 'temperature') | float(0) }}" # Get setpoint + + # 1. Standard Skip Conditions - condition: template value_template: > {{ states(climate_entity) not in ['unknown', 'unavailable'] and valve_temp > 0 and external_temp > 0 }} - - # Find offset entity for this device + + # 2. **CRITICAL STABILITY CONDITION** + # Only calibrate if the valve is not actively heating and the setpoint is close to external temp + - condition: template + value_template: > + {{ hvac_action != 'heating' and (target_temp - external_temp) < 0.5 }} + + # Find offset entity for this device (unchanged) - variables: offset_entity: > {% set entities = device_entities(climate_device) if climate_device else [] %} {% set offset_entities = entities | select('match', '^number\.') | select('search', offset_entity_suffix) | list %} {{ offset_entities[0] if offset_entities else '' }} - # Skip if no offset entity found or is unavailable + # 3. Skip if no offset entity found (unchanged) - condition: template value_template: "{{ offset_entity != '' and states(offset_entity) not in ['unknown', 'unavailable'] }}" - # Calculate and set new offset + # 4. Calculate New Offset using Differential Method - variables: current_offset: "{{ states(offset_entity) | float(0) }}" - # 1. Calculate the required offset based on the difference - # Required Offset = External Temp - (Valve Internal Temp - Current Offset) + Manual Correction - raw_offset_required: "{{ external_temp - (valve_temp - current_offset) + manual_correction }}" + # The new required offset is the difference between the external (ground truth) + # and the uncompensated internal sensor reading. + raw_offset_required: "{{ external_temp - valve_temp + manual_correction }}" - # 2. Round the raw offset to the nearest rounding_step - # Safely scale, round, and scale back to minimize floating point errors + # Round the raw offset to the nearest rounding_step (unchanged rounding logic) new_offset: > {% set step = rounding_step | float(1.0) %} {% set scale = (1 / step) | round(0) %} {{ (raw_offset_required * scale) | round(0) / scale }} - # Only update if the calculated value is different from the current value + # 5. Only update if the calculated value is different from the current value (unchanged) - condition: template value_template: "{{ new_offset | round(3) != current_offset | round(3) }}" - # Set new offset + # 6. Set new offset (unchanged) - service: number.set_value target: entity_id: "{{ offset_entity }}" data: value: "{{ new_offset }}" - # Small delay between updates to avoid overwhelming the system + # Small delay between updates (unchanged) - delay: milliseconds: 100