Opening a window adds heat exchange that the envelope model mistook for
poor insulation or a constant cooling bias.
windows.py adds windows as config subentries ("Add window"): a contact
sensor (optionally inverted; unknown/unavailable stays unknown, never
"closed"), its zone, the direction its outside faces (default 345°,
editable) and an opening factor. Each poll computes the share of the
interval each window was open (one change located by its timestamp, a
round trip is unknown), the wind exposure with proper circular angles
(meteorological "from" direction vs the window's facing) and a recovery
weight after closing that decays towards 0, pulling the air back
towards its pre-ventilation temperature. A bounded event log records
opening/closing, duration, temperatures before/lowest/at close/30 min
after, weather and other open windows.
While a zone's window is open, unknown or recovering, the base and
extended models skip it; a separate ventilation learner fits the
closed-window model's residual with natural exchange, wind speed, a
directional term bounded by the wind term (a leeward window still
ventilates), recovery, and optional gust excess and cross-ventilation
terms. Forecasts assume an open window closes after a configurable time
(default 15 min), then recovers. New poll outcome "ventilation".
Weather gains optional wind direction (degrees, 360 -> 0) and wind gust
sources with the same priorities and fallback; knots and ft/s accepted.
On a synthetic week with ventilation events every four hours and a
daily outdoor swing, the configured zone keeps loss/heat/bias close to
the truth and recovers the combined natural+wind effect within 15 %,
recovery within 15 % and direction within 30 %. The same data without
the window configured produces a -0.013 °C/5 min cooling bias and an
11 % inflated boiler effect.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
319 lines
17 KiB
JSON
319 lines
17 KiB
JSON
{
|
||
"title": "Heating MPC",
|
||
"config": {
|
||
"step": {
|
||
"user": {
|
||
"title": "Heating MPC — room inputs",
|
||
"description": "Choose thermostat, read-only relay and room sensors. This integration NEVER commands the heater.",
|
||
"data": {
|
||
"climate_entity": "Existing Generic Thermostat",
|
||
"heater_entity": "Physical heating switch (read-only)",
|
||
"room_entities": "Individual room temperature sensors",
|
||
"room_max_age_minutes": "Maximum room sensor report age (minutes)",
|
||
"weather_max_age_minutes": "Maximum weather sensor report age (minutes)",
|
||
"room_stale_use_last_value": "Keep using the last value of a room sensor that stopped reporting"
|
||
},
|
||
"data_description": {
|
||
"room_stale_use_last_value": "Off: a room sensor older than its report-age limit is left out and the other rooms are re-weighted. On: its last value is still used, for up to 24 hours without a report; for sensors that report only when the value changes. Unavailable or invalid room sensors are always left out. Nothing pauses unless no room is usable."
|
||
}
|
||
},
|
||
"weather": {
|
||
"title": "Primary weather sources",
|
||
"description": "Choose as many sensors per metric as needed and arrange them in priority order. If the preferred source is unavailable, the next one is used automatically. Outdoor temperature is mandatory; wind and cloud coverage are diagnostic-only in this version.",
|
||
"data": {
|
||
"outside_temperature_sources": "Outdoor temperature sources (ordered: preferred first)",
|
||
"outside_wind_sources": "Wind speed sources (optional)",
|
||
"outside_clouds_sources": "Cloud coverage sources (optional)",
|
||
"outside_wind_direction_sources": "Wind direction sources (optional; degrees the wind comes from)",
|
||
"outside_wind_gust_sources": "Wind gust sources (optional)"
|
||
}
|
||
},
|
||
"weather_extra": {
|
||
"title": "Additional weather sources",
|
||
"description": "Additional weather inputs are optional and used only for comparison/diagnostics in v0.2; they do not yet change MPC predictions.",
|
||
"data": {
|
||
"outside_humidity_sources": "Outdoor humidity sources (optional)",
|
||
"outside_feelslike_sources": "Feels-like temperature sources (optional)",
|
||
"outside_pressure_sources": "Atmospheric pressure sources (optional)",
|
||
"outside_visibility_sources": "Visibility sources (optional)",
|
||
"outside_rain_sources": "Rain sources (optional)",
|
||
"outside_snow_sources": "Snow sources (optional)"
|
||
}
|
||
},
|
||
"weights": {
|
||
"title": "Room priorities",
|
||
"description": "Each selected sensor becomes its own zone with this comfort weight. Later, group the sensors of one room into one zone with Add zone or by editing a zone."
|
||
}
|
||
},
|
||
"error": {
|
||
"invalid_rooms": "Select 1–12 distinct room sensors.",
|
||
"invalid_weather": "Select at least one outdoor temperature sensor, distinct from room sensors."
|
||
},
|
||
"abort": {
|
||
"already_configured": "Heating MPC is already configured."
|
||
}
|
||
},
|
||
"options": {
|
||
"step": {
|
||
"init": {
|
||
"title": "Heating MPC options",
|
||
"description": "Change room sensors and controller settings. This integration remains shadow-only.",
|
||
"data": {
|
||
"climate_entity": "Existing Generic Thermostat",
|
||
"heater_entity": "Physical heating switch (read-only)",
|
||
"horizon_minutes": "Prediction horizon (minutes)",
|
||
"minimum_on_minutes": "Minimum ON time (minutes)",
|
||
"minimum_off_minutes": "Minimum OFF time (minutes)",
|
||
"switching_cost": "Switching cost",
|
||
"heating_cost": "Heating runtime cost",
|
||
"comfort_band": "Comfort tolerance (°C)",
|
||
"room_max_age_minutes": "Maximum room sensor report age (minutes)",
|
||
"weather_max_age_minutes": "Maximum weather sensor report age (minutes)",
|
||
"room_stale_use_last_value": "Keep using the last value of a room sensor that stopped reporting",
|
||
"internal_heat_half_life_minutes": "Extra electrical heat: forecast half-life (minutes)",
|
||
"window_open_minutes": "Open window: assumed remaining open time (minutes)",
|
||
"recovery_minutes": "Recovery after closing a window (minutes)",
|
||
"ventilation_wind": "Ventilation: use wind speed and direction",
|
||
"ventilation_gust": "Ventilation: use wind gusts",
|
||
"ventilation_cross": "Ventilation: cross-ventilation between zones"
|
||
},
|
||
"data_description": {
|
||
"room_stale_use_last_value": "Off: a room sensor older than its report-age limit is left out and the other rooms are re-weighted. On: its last value is still used, for up to 24 hours without a report; for sensors that report only when the value changes. Unavailable or invalid room sensors are always left out. Nothing pauses unless no room is usable.",
|
||
"internal_heat_half_life_minutes": "Forecasts assume heat above the baseline fades with this half-life, for example when a gaming session ends. 0 assumes it stops now.",
|
||
"window_open_minutes": "Forecasts assume an open window closes after this many more minutes, then the room recovers.",
|
||
"recovery_minutes": "Time constant of the air warming back towards the walls' temperature.",
|
||
"ventilation_wind": "Needs wind sources; direction also needs wind direction sources.",
|
||
"ventilation_gust": "Adds a term for gusts above the mean wind. Keep it only if Forecast Error improves.",
|
||
"ventilation_cross": "Adds a term for windows open in several zones at once. Needs windows in two or more zones."
|
||
}
|
||
},
|
||
"weather": {
|
||
"title": "Weather source priority",
|
||
"description": "Choose as many sensors per metric as needed and arrange them in priority order. If the preferred source is unavailable, the next one is used automatically. Outdoor temperature is mandatory; wind and cloud coverage are diagnostic-only in this version.",
|
||
"data": {
|
||
"outside_temperature_sources": "Outdoor temperature sources (ordered: preferred first)",
|
||
"outside_wind_sources": "Wind speed sources (optional)",
|
||
"outside_clouds_sources": "Cloud coverage sources (optional)",
|
||
"outside_wind_direction_sources": "Wind direction sources (optional; degrees the wind comes from)",
|
||
"outside_wind_gust_sources": "Wind gust sources (optional)"
|
||
}
|
||
},
|
||
"weather_extra": {
|
||
"title": "Additional weather sensors",
|
||
"description": "Additional weather inputs are optional and used only for comparison/diagnostics in v0.2; they do not yet change MPC predictions.",
|
||
"data": {
|
||
"outside_humidity_sources": "Outdoor humidity sources (optional)",
|
||
"outside_feelslike_sources": "Feels-like temperature sources (optional)",
|
||
"outside_pressure_sources": "Atmospheric pressure sources (optional)",
|
||
"outside_visibility_sources": "Visibility sources (optional)",
|
||
"outside_rain_sources": "Rain sources (optional)",
|
||
"outside_snow_sources": "Snow sources (optional)"
|
||
}
|
||
}
|
||
},
|
||
"error": {
|
||
"invalid_weather": "Select at least one outdoor temperature sensor, distinct from room sensors."
|
||
}
|
||
},
|
||
"entity": {
|
||
"sensor": {
|
||
"last_sample": {
|
||
"state": {
|
||
"waiting_for_inputs": "Waiting for inputs",
|
||
"first_sample": "First sample",
|
||
"sensors_changed": "Zone sensors changed",
|
||
"learned": "Learned",
|
||
"ventilation": "Window open or recovering",
|
||
"partly_rejected": "Partly rejected (outlier)",
|
||
"interval_out_of_range": "Irregular interval",
|
||
"relay_changed": "Relay switched",
|
||
"outside_source_changed": "Outdoor source changed",
|
||
"lag_uncertain": "Waiting for radiators to settle"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"config_subentries": {
|
||
"zone": {
|
||
"entry_type": "Zone",
|
||
"initiate_flow": {
|
||
"user": "Add zone",
|
||
"reconfigure": "Edit zone"
|
||
},
|
||
"step": {
|
||
"user": {
|
||
"title": "Thermal zone",
|
||
"description": "A room or area of the apartment. Sensor weights combine several sensors in one room; the comfort weight says how much the zone matters to the MPC. Neither changes the zone's learned thermal properties.",
|
||
"data": {
|
||
"name": "Name",
|
||
"sensors": "Temperature sensors",
|
||
"comfort_weight": "Comfort weight",
|
||
"target_offset": "Target offset (°C)",
|
||
"neighbors": "Adjacent zones (heat exchange)"
|
||
},
|
||
"data_description": {
|
||
"comfort_weight": "Relative importance for the MPC (0.1–10).",
|
||
"target_offset": "Added to the thermostat's target temperature for this zone.",
|
||
"neighbors": "Zones this one exchanges heat with, for example through a wall or an open door. The coefficient is learned only when the data supports it."
|
||
}
|
||
},
|
||
"sensor_weights": {
|
||
"title": "Sensor weights",
|
||
"description": "How much each sensor counts in the zone temperature (0.1–10)."
|
||
},
|
||
"reconfigure": {
|
||
"title": "Thermal zone",
|
||
"description": "A room or area of the apartment. Sensor weights combine several sensors in one room; the comfort weight says how much the zone matters to the MPC. Neither changes the zone's learned thermal properties.",
|
||
"data": {
|
||
"name": "Name",
|
||
"sensors": "Temperature sensors",
|
||
"comfort_weight": "Comfort weight",
|
||
"target_offset": "Target offset (°C)",
|
||
"neighbors": "Adjacent zones (heat exchange)"
|
||
},
|
||
"data_description": {
|
||
"comfort_weight": "Relative importance for the MPC (0.1–10).",
|
||
"target_offset": "Added to the thermostat's target temperature for this zone.",
|
||
"neighbors": "Zones this one exchanges heat with, for example through a wall or an open door. The coefficient is learned only when the data supports it."
|
||
}
|
||
}
|
||
},
|
||
"error": {
|
||
"zone_name_required": "Enter a name.",
|
||
"zone_sensors": "Choose 1–6 different sensors.",
|
||
"sensor_in_other_zone": "A chosen sensor already belongs to another zone.",
|
||
"sensor_is_weather_source": "A chosen sensor is used as an outdoor temperature source.",
|
||
"too_many_zones": "At most 12 zones are supported."
|
||
},
|
||
"abort": {
|
||
"reconfigure_successful": "The zone was updated."
|
||
}
|
||
},
|
||
"heat_source": {
|
||
"entry_type": "Heat source",
|
||
"initiate_flow": {
|
||
"user": "Add heat source",
|
||
"reconfigure": "Edit heat source"
|
||
},
|
||
"step": {
|
||
"user": {
|
||
"title": "Electrical heat source",
|
||
"description": "Equipment whose electrical power ends up as heat in a zone, such as a UPS with computers. Only the variation above a persistent baseline is modeled: the constant part is already part of the zone's learned behavior. Use active power (W or kW), not VA or load %.",
|
||
"data": {
|
||
"name": "Name",
|
||
"sensor": "Active power sensor",
|
||
"zone": "Zone",
|
||
"heat_fraction": "Share of power that becomes heat in the zone",
|
||
"baseline_mode": "Baseline",
|
||
"baseline_w": "Manual baseline (W)",
|
||
"included_in": "Already included in the measurement of",
|
||
"response_minutes": "Thermal response time (minutes)",
|
||
"enabled": "Enabled"
|
||
},
|
||
"data_description": {
|
||
"heat_fraction": "1.0 when the equipment is in this zone. Lower it if part of the heat goes elsewhere, for example out of a vent.",
|
||
"baseline_mode": "Automatic uses the low end of the last 24 hours (the persistent load).",
|
||
"included_in": "Choose the UPS or meter whose reading already contains this device, so its power is not counted twice.",
|
||
"response_minutes": "How quickly the zone feels a change in power: room air, furniture and equipment take time to warm."
|
||
}
|
||
},
|
||
"reconfigure": {
|
||
"title": "Electrical heat source",
|
||
"description": "Equipment whose electrical power ends up as heat in a zone, such as a UPS with computers. Only the variation above a persistent baseline is modeled: the constant part is already part of the zone's learned behavior. Use active power (W or kW), not VA or load %.",
|
||
"data": {
|
||
"name": "Name",
|
||
"sensor": "Active power sensor",
|
||
"zone": "Zone",
|
||
"heat_fraction": "Share of power that becomes heat in the zone",
|
||
"baseline_mode": "Baseline",
|
||
"baseline_w": "Manual baseline (W)",
|
||
"included_in": "Already included in the measurement of",
|
||
"response_minutes": "Thermal response time (minutes)",
|
||
"enabled": "Enabled"
|
||
},
|
||
"data_description": {
|
||
"heat_fraction": "1.0 when the equipment is in this zone. Lower it if part of the heat goes elsewhere, for example out of a vent.",
|
||
"baseline_mode": "Automatic uses the low end of the last 24 hours (the persistent load).",
|
||
"included_in": "Choose the UPS or meter whose reading already contains this device, so its power is not counted twice.",
|
||
"response_minutes": "How quickly the zone feels a change in power: room air, furniture and equipment take time to warm."
|
||
}
|
||
}
|
||
},
|
||
"error": {
|
||
"source_name_required": "Enter a name.",
|
||
"sensor_in_other_source": "This power sensor is already used by another heat source.",
|
||
"invalid_parent_source": "The parent must be an existing source that is not itself included in another.",
|
||
"source_has_children": "Other sources are included in this one, so it cannot be included in another.",
|
||
"baseline_required": "Enter the manual baseline in W.",
|
||
"too_many_sources": "At most 16 heat sources are supported."
|
||
},
|
||
"abort": {
|
||
"reconfigure_successful": "The heat source was updated.",
|
||
"no_zones": "Add a zone first."
|
||
}
|
||
},
|
||
"window": {
|
||
"entry_type": "Window",
|
||
"initiate_flow": {
|
||
"user": "Add window",
|
||
"reconfigure": "Edit window"
|
||
},
|
||
"step": {
|
||
"user": {
|
||
"title": "Window",
|
||
"description": "A window contact sensor. Open-window periods are kept out of the zone's normal heat-loss learning and modeled as ventilation instead. Unknown or unavailable states count as unknown, never as closed.",
|
||
"data": {
|
||
"name": "Name",
|
||
"sensor": "Contact sensor",
|
||
"zone": "Zone",
|
||
"inverted": "Sensor reports on when closed",
|
||
"azimuth": "Facing direction (°)",
|
||
"opening_factor": "Opening factor",
|
||
"enabled": "Enabled"
|
||
},
|
||
"data_description": {
|
||
"inverted": "Turn on for sensors whose polarity is the reverse of a standard window sensor.",
|
||
"azimuth": "Direction the outside of the window faces, clockwise from north (0 = N, 90 = E, 180 = S, 270 = W). Used with the wind direction.",
|
||
"opening_factor": "Relative ventilation when open: 1.0 for a normal window, lower for tilted, higher for a large or double window."
|
||
}
|
||
},
|
||
"reconfigure": {
|
||
"title": "Window",
|
||
"description": "A window contact sensor. Open-window periods are kept out of the zone's normal heat-loss learning and modeled as ventilation instead. Unknown or unavailable states count as unknown, never as closed.",
|
||
"data": {
|
||
"name": "Name",
|
||
"sensor": "Contact sensor",
|
||
"zone": "Zone",
|
||
"inverted": "Sensor reports on when closed",
|
||
"azimuth": "Facing direction (°)",
|
||
"opening_factor": "Opening factor",
|
||
"enabled": "Enabled"
|
||
},
|
||
"data_description": {
|
||
"inverted": "Turn on for sensors whose polarity is the reverse of a standard window sensor.",
|
||
"azimuth": "Direction the outside of the window faces, clockwise from north (0 = N, 90 = E, 180 = S, 270 = W). Used with the wind direction.",
|
||
"opening_factor": "Relative ventilation when open: 1.0 for a normal window, lower for tilted, higher for a large or double window."
|
||
}
|
||
}
|
||
},
|
||
"error": {
|
||
"window_name_required": "Enter a name.",
|
||
"sensor_in_other_window": "This sensor is already used by another window.",
|
||
"too_many_windows": "At most 24 windows are supported."
|
||
},
|
||
"abort": {
|
||
"reconfigure_successful": "The window was updated.",
|
||
"no_zones": "Add a zone first."
|
||
}
|
||
}
|
||
},
|
||
"selector": {
|
||
"baseline_mode": {
|
||
"options": {
|
||
"auto": "Automatic (24-hour low)",
|
||
"manual": "Manual value"
|
||
}
|
||
}
|
||
}
|
||
}
|