diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 00000000..ee933310
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,17 @@
+name: Release
+
+on:
+ release:
+ types: [ published ]
+
+jobs:
+ release:
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+ - uses: vimtor/action-zip@v1.2
+ with:
+ files: custom_components/
+ dest: sat.zip
\ No newline at end of file
diff --git a/.pylintrc b/.pylintrc
new file mode 100644
index 00000000..ee63ef26
--- /dev/null
+++ b/.pylintrc
@@ -0,0 +1,19 @@
+[MASTER]
+# Specify a configuration file.
+rcfile=
+
+[REPORTS]
+# Set the output format. Available formats: text, parseable, colorized, msvs (visual studio).
+output-format=text
+
+[FORMAT]
+# Maximum number of characters on a single line.
+max-line-length=200
+
+[DESIGN]
+# Maximum number of arguments for function / method.
+max-args=5
+
+[TYPECHECK]
+# Tells whether missing members accessed in mixin class should be ignored.
+ignore-mixin-members=yes
diff --git a/README.md b/README.md
index 38d2e042..e899a348 100644
--- a/README.md
+++ b/README.md
@@ -5,6 +5,11 @@
![build][build-badge]
[![discord][discord-badge]][discord-url]
+**Please :star: this repo if you find it useful.**
+
+**Your support for the countless hours we've dedicated to this development is greatly appreciated, though not required**
+
+

@@ -13,10 +18,16 @@
## What is the Smart Autotune Thermostat?
-The Smart Autotune Thermostat, or SAT for short, is a custom component for Home Assistant that works with an [OpenTherm Gateway (OTGW)](https://otgw.tclcode.com/) (MQTT or Serial). It can also function as a PID ON/OFF thermostat, providing advanced temperature control based on Outside Temperature compensation and the Proportional-Integral-Derivative (PID) algorithm. Unlike other thermostat components, SAT supports automatic gain tuning and heating curve coefficients. This capability allows it to determine the optimal setpoint for your boiler without any manual intervention.
+The Smart Autotune Thermostat, or SAT for short, is a custom component for Home Assistant that seamlessly integrates with the following devices:
+- [OpenTherm Gateway (OTGW)](https://otgw.tclcode.com/) (MQTT or Serial)
+- [DIYLess](https://diyless.com/) Master OpenTherm Shield
+- [Ihor Melnyk's](http://ihormelnyk.com/opentherm_adapter) OpenTherm adapter
+- [Jiří Praus'](https://www.tindie.com/products/jiripraus/opentherm-gateway-arduino-shield/) OpenTherm Gateway Arduino Shield
+
+It can also function as a PID ON/OFF thermostat, providing advanced temperature control based on Outside Temperature compensation and the Proportional-Integral-Derivative (PID) algorithm. Unlike other thermostat components, SAT supports automatic gain tuning and heating curve coefficients. This capability allows it to determine the optimal setpoint for your boiler without any manual intervention.
## Features
-OpenTherm ( MQTT / Serial ):
+OpenTherm ( MQTT / Serial / ESPHome ):
- Multi-room temperature control with support for temperature synchronization for main climates
- Overshoot protection value automatic calculation mechanism
- Adjustable heating curve coefficients to fine-tune your heating system
@@ -43,43 +54,137 @@ PID ON/OFF thermostat:
- Open Window detection
## Installation
+### HACS
+
+Smart Autotune Thermostat ( SAT ) is available in [HACS][hacs] (Home Assistant Community Store).
+
+Use this link to directly go to the repository in HACS
+
+[](https://my.home-assistant.io/redirect/hacs_repository/?owner=Alexwijn&repository=SAT)
+
+_or_
+
+1. Install HACS if you don't have it already
+2. Open HACS in Home Assistant
+3. Search for "Smart Autotune Thermostat"
+4. Click the download button. ⬇️
+
### Manual
1. Download the latest release of the SAT custom component from the GitHub repository.
2. Copy the sat directory to the custom_components directory in your Home Assistant configuration directory. If the custom_components directory doesn't exist, create it.
3. Restart Home Assistant to load the SAT custom component.
4. After installing the SAT custom component, you can configure it via the Home Assistant Config Flow interface.
-### HACS
-1. Install HACS if you haven't already.
-2. Open the HACS web interface in Home Assistant and navigate to the Integrations section.
-3. Click the three dots in the top-right corner and select "Custom repositories."
-4. Enter the URL of the SAT custom component GitHub repository (https://github.com/Alexwijn/SAT) and select "Integration" as the category. Click "Add."
-5. Once the SAT custom component appears in the list of available integrations, click "Install" to install it.
-6. Restart Home Assistant to load the SAT custom component.
-7. After installing the SAT custom component, you can configure it via the Home Assistant Config Flow interface.
-
# Configuration
SAT is configured using a config flow. After installation, go to the Integrations page in Home Assistant, click on the Add Integration button, and search for SAT if the autodiscovery feature fails.
## OpenTherm
1. OpenTherm Connection
- - MQTT
+ - OpenTherm Gateway MQTT:
- Name of the thermostat
- Top Topic ( *MQTT Top Topic* found in OTGW-firmware Settings )
- Device
- - Serial:
+ - OpenTherm Gateway Serial:
- Name of the thermostat
- URL
+ - ESPHome Opentherm:
+ - Name of the thermostat
+ - Device
+
+> [!Important]
+> The ESPHome yaml needs to follow the exact naming of the following entities, otherwise SAT will not be able to find them in Home Assistant.
+
+ESPHome minimal yaml configuration
+
+```yaml
+# Insert usual esphome configuration (board, api, ota, etc.)
+
+opentherm:
+ in_pin: # insert in pin
+ out_pin: # insert out pin
+ ch_enable: true
+ dhw_enable: true
+
+number:
+ - platform: opentherm
+ t_dhw_set:
+ name: t_dhw_set
+ step: 1
+ restore_value: true
+ t_set:
+ name: t_set
+ restore_value: true
+ max_t_set:
+ name: max_t_set
+ step: 1
+ restore_value: true
+ max_rel_mod_level:
+ name: max_rel_mod_level
+ min_value: 0
+ max_value: 100
+ step: 1
+ initial_value: 100
+ restore_value: true
+
+sensor:
+ - platform: opentherm
+ rel_mod_level:
+ name: rel_mod_level
+ device_id:
+ name: device_id
+ t_boiler:
+ name: t_boiler
+ t_ret:
+ name: t_ret
+ max_capacity:
+ name: max_capacity
+ min_mod_level:
+ name: min_mod_level
+ t_dhw_set_lb:
+ name: t_dhw_set_lb
+ t_dhw_set_ub:
+ name: t_dhw_set_ub
+
+binary_sensor:
+ - platform: opentherm
+ flame_on:
+ name: flame_on
+
+switch:
+ - platform: opentherm
+ dhw_enable:
+ name: dhw_enable
+ ch_enable:
+ name: ch_enable
+```
+
+For more information about which other entities are available for OpenTherm please visit the [ESPHome OpenTherm documentation](https://esphome.io/components/opentherm.html)
+
+
+
2. Configure sensors:
- - Inside Sensor Entity ( Your Room Temperature sensor )
- - Outside temperature sensor ( Your Outside Temperature sensor )
+ - Inside Temperature sensor ( Your Room Temperature sensor )
+ - Outside Temperature sensor ( Your Outside Temperature sensor )
+ - Inside Humidity Sensor ( Your Room Humidity sensor )
+
+> [!NOTE]
+> For better results use an Inside Temperature sensor that reports two decimals and has a refresh rate of 30 seconds.
3. Heating System: Selecting the correct heating system type is important for SAT to accurately control the temperature and optimize performance. Choose the option that matches your setup to ensure proper temperature regulation throughout your home.
-4. Calibrate System: Optimize your heating system by automatically determining the optimal PID values for your setup. When selecting Automatic Gains, please note that the system will go through a calibration process that may take approximately 20 minutes to complete.
+4. Areas:
+ - Primary: Users can add their physical thermostat. SAT will syncronize the `hvac_action` of the physical thermostat with the SAT climate entity's `hvac action`, that means if the physical thermostat doesn't require heating then the SAT climate entity `hvac_action` will remain at idle. Also the physical thermostat's room setpoint stays in sync with SAT climate entity. Moreover the physical thermostat will act as a back up if any failure to HA occurs.
+ - Rooms: Users can add their TRV climate entities. So when any of the rooms will ask for heating, SAT will start the boiler.
+> [!Note]
+> If SAT is the only climate entity, skip this step.
+
+> [!TIP]
+> Look at the Heating Mode setting in General Tab for further customization.
+
+5. Calibrate System: Optimize your heating system by automatically determining the optimal PID values for your setup. When selecting Automatic Gains, please note that the system will go through a calibration process that may take approximately 20 minutes to complete.
If you already know this value, then use the "Manually enter the overshoot protection value" option and fill the value.
@@ -94,27 +199,91 @@ To be completed
# Configure
## General tab:
+*Heating Curve Version*: Represents the 3 formulas of calculation. The available options are:
+
+Radiators:
+- [Classic Curve](https://www.desmos.com/calculator/cy8gjiciny)
+- [Quantum Curve](https://www.desmos.com/calculator/hmrlrapnxz)
+- [Precision Curve](https://www.desmos.com/calculator/spfvsid4ds) ( Recommented )
+
+Underfloor:
+- [Classic Curve](https://www.desmos.com/calculator/exjth5qsoe)
+- [Quantum Curve](https://www.desmos.com/calculator/ke69ywalcz)
+- [Precision Curve](https://www.desmos.com/calculator/i7f7uuyaoz) ( Recommented )
+
+> [!NOTE]
+> Graph parameters:
+> - a: Heating Curve Value
+> - b: Room Setpoint
+
+> [!TIP]
+> You can add the graph as an `iframe` card in HA.
+>
+> Example:
+> ```yaml
+> type: iframe
+> url: https://www.desmos.com/calculator/spfvsid4ds
+> allow_open_top_navigation: true
+> allow: fullscreen
+> aspect_ratio: 130%
+
+*PID Controller Version*:
+- Classic Controller
+- Improved Controller
+
+*Heating Mode*:
+
+> [!NOTE]
+>Available only for multiroom installations
+
+- Comfort ( SAT monitors the climates in other rooms to determine the error. It selects the highest error value as the PID error value for the current room )
+- Eco ( SAT monitors **only** the Main thermostat's error and it is used as the PID error )
+
*Maximum Setpoint*:
You can choose the max water setpoint for your system.
For radiator installations, it is recommended to choose a value between 55-75 °C.
For underfloor installations, the recommended max water setpoint is 50 °C.
-Note for Radiators: Higher Max water setpoint values will cause a more aggressive warm-up.
+> [!NOTE]
+> Radiators: Higher Max water setpoint values will cause a more aggressive warm-up.
*Heating Curve Coefficient*:
The heating curve coefficient is a configurable parameter in SAT that allows you to adjust the relationship between the outdoor temperature and the heating system output. This is useful for optimizing the heating system's performance in different weather conditions, as it allows you to adjust how much heat the system delivers as the outdoor temperature changes. By tweaking this parameter, you can achieve a more efficient and comfortable heating system.
-## Areas tab:
-*Multi-room setup*:
-In multi-room mode, SAT monitors the climates in other rooms to determine the error and calculates how much heat is needed. It selects the highest error value as the error value for the current room, instead of using the average temperature across all rooms. This ensures that the temperature in each room is maintained at its desired level.
+*Automatic Gains Value*: Automatically tweaking the aggressiveness of the Kp, Ki and Kd gains.
+
+> [!TIP]
+> Best results when the user uses the same value as the Heating Curve Coefficient value.
-Note that SAT assumes that the climate control systems in the additional rooms are smart and won't exceed their target temperatures, as this can cause inefficiencies in the overall system. Once every climate control system in all rooms is around the target temperature, SAT can operate at its most efficient level.
+*Derivative Time Weight*: Further tweaking of the Kd value.
+
+> [!TIP]
+> Better start with the value `2`.
+
+*Adjustment Factor for Return Temperature*:
+This factor adjusts the heating setpoint based on the boiler's return temperature, affecting heating responsiveness and efficiency. A higher value increases sensitivity to temperature changes, enhancing control over comfort and energy use.
+
+> [!TIP]
+> Recommended starting range is 0.1 to 0.5. Adjust to suit your system and comfort preferences.
*Contact Sensor*: You can add contact sensors to avoid wasting energy when a door/window is open. When the door/window is closed again, SAT restores heating.
## Presets tab:
Predefined temperature settings for different scenarios or activities.
+## Advanced Tab
+*Thermal Comfort*: Uses as temperature sensor the Summer SImmer Index. The Summer Simmer Index refers to the perceived temperature based on the measured air temperature and relative humidity.
+
+ *Dynamic Minimum Setpoint (Experimental)*: The Boiler flow water temperature may exceed the Overshoot Protection Value during Low-Load Control in multiroom installations ( Some valves may be closed ). We developed a mechanishm that monitors the boiler return water temperature and changes accordingly the Control Setpoint that is sent to the boiler. See *Adjustment Factor for Return Temperature*.
+
+*Minimum Consumption*: The user can find this value at the boiler's manual. SAT uses this value in order to calculate the instant gas consumption.
+
+*Maximum Consumption*: The user can find this value at the boiler's manual. SAT uses this value in order to calculate the instant gas consumption.
+
+*Target Temperature Step*: SAT climate entity room setpoint step.
+
+*Maximum Relative Modulation*: The user is able to control the maximum relative modulation that the boiler will operate.
+
# Terminology
*Heating Curve Coefficient*: By adjusting the heating curve coefficient, you can balance the heating loss of your home with the energy generated from your boiler at a given setpoint based on the outside temperature. When this value is properly tuned, the room temperature should hover around the setpoint.
@@ -125,16 +294,20 @@ Predefined temperature settings for different scenarios or activities.
*Overshoot Protection*: This feature should be enabled when the minimum boiler capacity is greater than the control setpoint calculated by SAT. If the boiler overshoots the control setpoint, it may cycle, shortening the life of the burner. The solution is to adjust the boiler's on/off times to maintain the temperature at the setpoint while minimizing cycling.
-Overshoot Protection Value (OPV) Calculation:
+*Overshoot Protection Value (OPV) Calculation*:
The OPV is a crucial value that determines the boiler's on/off times when the Overshoot Protection feature is present (During initial configuration).
+*Automatic Calculation*: To calculate the OPV automatically, choose the "Calibrate and determine your overshoot protection value (approx. 20 min)" option during the initial configuration. SAT will then send the MM=0 and CS=75 commands, attempting to find the highest flow water temperature the boiler can produce while running at 0% modulation. This process takes at least 20 minutes. Once the OPV calculation is complete, SAT will resume normal operation and send a completion notification. The calculated value will be stored as an attribute in the SAT climate entity and used to determine the boiler's on/off times in the low-load control algorithm. If SAT detects that the boiler doesn't respect the 0% Max Modulation command, it will automatically change the calibration algorithm to a more sophisticated one to perform the calibration of the system.
+
*Manual Calculation*: If you know the maximum flow water temperature of the boiler at 0% modulation, you can fill in this value during the initial configuration.
-*Automatic Calculation*: To calculate the OPV automatically, choose the "Calibrate and determine your overshoot protection value (approx. 20 min)" option during the initial configuration. SAT will then send the MM=0 and CS=75 commands, attempting to find the highest flow water temperature the boiler can produce while running at 0% modulation. This process takes at least 20 minutes. Once the OPV calculation is complete, SAT will resume normal operation and send a completion notification. The calculated value will be stored as an attribute in the SAT climate entity and used to determine the boiler's on/off times in the low-load control algorithm. If SAT detects that the boiler doesn't respect the 0% Max Modulation command, it will automatically change the calibration algorithm to a more sophisticated one to perform the calibration of the system.
+> [!Note]
+> If you have any TRVs, open all of them (set them to a high setpoint) to ensure accurate calculation of the OPV. Once the calculation is complete, you can lower the setpoint back to your desired temperature.
-Note: If you have any TRVs, open all of them (set them to a high setpoint) to ensure accurate calculation of the OPV. Once the calculation is complete, you can lower the setpoint back to your desired temperature.
+*Automatic Duty Cycle*: When this option is enabled, SAT calculates the ON and OFF times of the boiler in 15-minute intervals, given that the kW needed to heat the home is less than the minimum boiler capacity. Moreover, using this feature, SAT can efficiently regulate the room temperature even in mild weather by automatically extending the duty cycle up to 30 minutes.
-*Automatic Duty Cycle*: When this option is enabled, SAT calculates the ON and OFF times of the boiler in 15-minute intervals, given that the kW needed to heat the home is less than the minimum boiler capacity. Moreover, using this feature, SAT can efficiently regulate the room temperature even in mild weather by automatically adjusting the duty cycle.
+> [!TIP]
+> For more in depth review of SAT and real time observations you can read this [excellent discussion post](https://github.com/Alexwijn/SAT/discussions/40) from @critictidier.
@@ -150,4 +323,4 @@ Note: If you have any TRVs, open all of them (set them to a high setpoint) to en
[hacs]: https://hacs.xyz
[home-assistant]: https://www.home-assistant.io/
[release-url]: https://github.com/Alexwijn/SAT/releases
-[discord-url]: https://discord.gg/jnVXpzqGEq
\ No newline at end of file
+[discord-url]: https://discord.gg/jnVXpzqGEq
diff --git a/configuration.yaml b/configuration.yaml
index 1709f622..e63ee222 100644
--- a/configuration.yaml
+++ b/configuration.yaml
@@ -4,6 +4,7 @@ logger:
default: info
logs:
custom_components.sat: debug
+ custom_components.interpolated_sensor: debug
homeassistant:
customize:
@@ -39,18 +40,22 @@ template:
- unit_of_measurement: °C
name: Heater Temperature
device_class: 'temperature'
+ unique_id: heater_temperature
state: "{{ states('input_number.heater_temperature_raw') }}"
- unit_of_measurement: °C
name: Current Temperature
device_class: 'temperature'
+ unique_id: current_temperature
state: "{{ states('input_number.current_temperature_raw') }}"
- unit_of_measurement: °C
name: Outside Temperature
device_class: 'temperature'
+ unique_id: outside_temperature
state: "{{ states('input_number.outside_temperature_raw') }}"
- unit_of_measurement: "%"
name: Current Humidity
device_class: 'humidity'
+ unique_id: current_humidity
state: "{{ states('input_number.humidity_raw') }}"
input_number:
diff --git a/custom_components/sat/__init__.py b/custom_components/sat/__init__.py
index f491a5f2..76589991 100644
--- a/custom_components/sat/__init__.py
+++ b/custom_components/sat/__init__.py
@@ -1,5 +1,6 @@
import asyncio
import logging
+import traceback
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN
@@ -7,145 +8,220 @@
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
+from homeassistant.helpers import device_registry
from homeassistant.helpers.storage import Store
-
-from . import mqtt, serial, switch
-from .const import *
+from sentry_sdk import Client, Hub
+
+from .const import (
+ DOMAIN,
+ CLIMATE,
+ SENTRY,
+ COORDINATOR,
+ CONF_MODE,
+ CONF_DEVICE,
+ CONF_ERROR_MONITORING, OPTIONS_DEFAULTS,
+)
from .coordinator import SatDataUpdateCoordinatorFactory
_LOGGER: logging.Logger = logging.getLogger(__name__)
+PLATFORMS = [CLIMATE_DOMAIN, SENSOR_DOMAIN, NUMBER_DOMAIN, BINARY_SENSOR_DOMAIN]
-async def async_setup_entry(_hass: HomeAssistant, _entry: ConfigEntry):
+async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
"""
Set up this integration using the UI.
This function is called by Home Assistant when the integration is set up with the UI.
"""
# Make sure we have our default domain property
- _hass.data.setdefault(DOMAIN, {})
+ hass.data.setdefault(DOMAIN, {})
# Create a new dictionary for this entry
- _hass.data[DOMAIN][_entry.entry_id] = {}
+ hass.data[DOMAIN][entry.entry_id] = {}
+
+ # Setup error monitoring (if enabled)
+ if entry.options.get(CONF_ERROR_MONITORING, OPTIONS_DEFAULTS[CONF_ERROR_MONITORING]):
+ await hass.async_add_executor_job(initialize_sentry, hass)
# Resolve the coordinator by using the factory according to the mode
- _hass.data[DOMAIN][_entry.entry_id][COORDINATOR] = await SatDataUpdateCoordinatorFactory().resolve(
- hass=_hass, data=_entry.data, options=_entry.options, mode=_entry.data.get(CONF_MODE), device=_entry.data.get(CONF_DEVICE)
+ hass.data[DOMAIN][entry.entry_id][COORDINATOR] = SatDataUpdateCoordinatorFactory().resolve(
+ hass=hass, data=entry.data, options=entry.options, mode=entry.data.get(CONF_MODE), device=entry.data.get(CONF_DEVICE)
)
- # Forward entry setup for climate and other platforms
- await _hass.async_add_job(_hass.config_entries.async_forward_entry_setup(_entry, CLIMATE_DOMAIN))
- await _hass.async_add_job(_hass.config_entries.async_forward_entry_setups(_entry, [SENSOR_DOMAIN, NUMBER_DOMAIN, BINARY_SENSOR_DOMAIN]))
+ # Making sure everything is loaded
+ await hass.data[DOMAIN][entry.entry_id][COORDINATOR].async_setup()
+
+ # Forward entry setup for used platforms
+ await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
# Add an update listener for this entry
- _entry.async_on_unload(_entry.add_update_listener(async_reload_entry))
+ entry.async_on_unload(entry.add_update_listener(async_reload_entry))
return True
-async def async_unload_entry(_hass: HomeAssistant, _entry: ConfigEntry) -> bool:
+async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""
Handle removal of an entry.
This function is called by Home Assistant when the integration is being removed.
"""
- climate = _hass.data[DOMAIN][_entry.entry_id][CLIMATE]
- await _hass.data[DOMAIN][_entry.entry_id][COORDINATOR].async_will_remove_from_hass(climate)
+ _climate = hass.data[DOMAIN][entry.entry_id][CLIMATE]
+ _coordinator = hass.data[DOMAIN][entry.entry_id][COORDINATOR]
+
+ await _coordinator.async_will_remove_from_hass()
unloaded = all(
- await asyncio.gather(
- _hass.config_entries.async_unload_platforms(_entry, [CLIMATE_DOMAIN, SENSOR_DOMAIN, NUMBER_DOMAIN, BINARY_SENSOR_DOMAIN]),
- )
+ # Forward entry unload for used platforms
+ await asyncio.gather(hass.config_entries.async_unload_platforms(entry, PLATFORMS))
)
+ try:
+ if SENTRY in hass.data[DOMAIN]:
+ hass.data[DOMAIN][SENTRY].flush()
+ hass.data[DOMAIN][SENTRY].close()
+ hass.data[DOMAIN].pop(SENTRY, None)
+ except Exception as ex:
+ _LOGGER.error("Error during Sentry cleanup: %s", str(ex))
+
# Remove the entry from the data dictionary if all components are unloaded successfully
if unloaded:
- _hass.data[DOMAIN].pop(_entry.entry_id)
+ hass.data[DOMAIN].pop(entry.entry_id)
return unloaded
-async def async_reload_entry(_hass: HomeAssistant, _entry: ConfigEntry) -> None:
+async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""
Reload config entry.
This function is called by Home Assistant when the integration configuration is updated.
"""
# Unload the entry and its dependent components
- await async_unload_entry(_hass, _entry)
+ await async_unload_entry(hass, entry)
# Set up the entry again
- await async_setup_entry(_hass, _entry)
+ await async_setup_entry(hass, entry)
-async def async_migrate_entry(_hass: HomeAssistant, _entry: ConfigEntry) -> bool:
+async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Migrate old entry."""
- from custom_components.sat.config_flow import SatFlowHandler
- _LOGGER.debug("Migrating from version %s", _entry.version)
+ from .config_flow import SatFlowHandler
+ _LOGGER.debug("Migrating from version %s", entry.version)
- if _entry.version < SatFlowHandler.VERSION:
- new_data = {**_entry.data}
- new_options = {**_entry.options}
+ if entry.version < SatFlowHandler.VERSION:
+ new_data = {**entry.data}
+ new_options = {**entry.options}
- if _entry.version < 2:
- if not _entry.data.get(CONF_MINIMUM_SETPOINT):
+ if entry.version < 2:
+ if not entry.data.get("minimum_setpoint"):
# Legacy Store
- store = Store(_hass, 1, DOMAIN)
- new_data[CONF_MINIMUM_SETPOINT] = MINIMUM_SETPOINT
+ store = Store(hass, 1, DOMAIN)
+ new_data["minimum_setpoint"] = 10
if (data := await store.async_load()) and (overshoot_protection_value := data.get("overshoot_protection_value")):
- new_data[CONF_MINIMUM_SETPOINT] = overshoot_protection_value
+ new_data["minimum_setpoint"] = overshoot_protection_value
- if _entry.options.get("heating_system") == "underfloor":
- new_data[CONF_HEATING_SYSTEM] = HEATING_SYSTEM_UNDERFLOOR
+ if entry.options.get("heating_system") == "underfloor":
+ new_data["heating_system"] = "underfloor"
else:
- new_data[CONF_HEATING_SYSTEM] = HEATING_SYSTEM_RADIATORS
+ new_data["heating_system"] = "radiators"
- if not _entry.data.get(CONF_MAXIMUM_SETPOINT):
- new_data[CONF_MAXIMUM_SETPOINT] = 55
+ if not entry.data.get("maximum_setpoint"):
+ new_data["maximum_setpoint"] = 55
- if _entry.options.get("heating_system") == "underfloor":
- new_data[CONF_MAXIMUM_SETPOINT] = 50
+ if entry.options.get("heating_system") == "underfloor":
+ new_data["maximum_setpoint"] = 50
- if _entry.options.get("heating_system") == "radiator_low_temperatures":
- new_data[CONF_MAXIMUM_SETPOINT] = 55
+ if entry.options.get("heating_system") == "radiator_low_temperatures":
+ new_data["maximum_setpoint"] = 55
- if _entry.options.get("heating_system") == "radiator_medium_temperatures":
- new_data[CONF_MAXIMUM_SETPOINT] = 65
+ if entry.options.get("heating_system") == "radiator_medium_temperatures":
+ new_data["maximum_setpoint"] = 65
- if _entry.options.get("heating_system") == "radiator_high_temperatures":
- new_data[CONF_MAXIMUM_SETPOINT] = 75
+ if entry.options.get("heating_system") == "radiator_high_temperatures":
+ new_data["maximum_setpoint"] = 75
- if _entry.version < 3:
- if main_climates := _entry.options.get("main_climates"):
- new_data[CONF_MAIN_CLIMATES] = main_climates
+ if entry.version < 3:
+ if main_climates := entry.options.get("main_climates"):
+ new_data["main_climates"] = main_climates
new_options.pop("main_climates")
- if secondary_climates := _entry.options.get("climates"):
- new_data[CONF_SECONDARY_CLIMATES] = secondary_climates
+ if secondary_climates := entry.options.get("climates"):
+ new_data["secondary_climates"] = secondary_climates
new_options.pop("climates")
- if sync_with_thermostat := _entry.options.get("sync_with_thermostat"):
- new_data[CONF_SYNC_WITH_THERMOSTAT] = sync_with_thermostat
+ if sync_with_thermostat := entry.options.get("sync_with_thermostat"):
+ new_data["sync_with_thermostat"] = sync_with_thermostat
new_options.pop("sync_with_thermostat")
- if _entry.version < 4:
- if _entry.data.get("window_sensor") is not None:
- new_data[CONF_WINDOW_SENSORS] = [_entry.data.get("window_sensor")]
+ if entry.version < 4:
+ if entry.data.get("window_sensor") is not None:
+ new_data["window_sensors"] = [entry.data.get("window_sensor")]
del new_options["window_sensor"]
- if _entry.version < 5:
- if _entry.options.get("overshoot_protection") is not None:
- new_data[CONF_OVERSHOOT_PROTECTION] = _entry.options.get("overshoot_protection")
+ if entry.version < 5:
+ if entry.options.get("overshoot_protection") is not None:
+ new_data["overshoot_protection"] = entry.options.get("overshoot_protection")
del new_options["overshoot_protection"]
- if _entry.version < 6:
- new_options[CONF_HEATING_CURVE_VERSION] = 1
+ if entry.version < 7:
+ new_options["pid_controller_version"] = 1
+
+ if entry.version < 8:
+ if entry.options.get("heating_curve_version") is not None and int(entry.options.get("heating_curve_version")) < 2:
+ new_options["heating_curve_version"] = 3
+
+ if entry.version < 9:
+ if entry.data.get("heating_system") == "heat_pump":
+ new_options["cycles_per_hour"] = 2
+
+ if entry.data.get("heating_system") == "radiators":
+ new_options["cycles_per_hour"] = 3
+
+ if entry.version < 10:
+ if entry.data.get("mode") == "mqtt":
+ device = device_registry.async_get(hass).async_get(entry.data.get("device"))
+
+ new_data["mode"] = "mqtt_opentherm"
+ new_data["device"] = list(device.identifiers)[0][1]
- _entry.version = SatFlowHandler.VERSION
- _hass.config_entries.async_update_entry(_entry, data=new_data, options=new_options)
+ hass.config_entries.async_update_entry(entry, version=SatFlowHandler.VERSION, data=new_data, options=new_options)
- _LOGGER.info("Migration to version %s successful", _entry.version)
+ _LOGGER.info("Migration to version %s successful", entry.version)
return True
+
+
+def initialize_sentry(hass: HomeAssistant):
+ """Initialize Sentry synchronously in an offloaded executor job."""
+
+ def exception_filter(event, hint):
+ """Filter events to send only SAT-related exceptions to Sentry."""
+ exc_info = hint.get("exc_info")
+
+ if exc_info:
+ _, _, exc_traceback = exc_info
+ stack = traceback.extract_tb(exc_traceback)
+
+ # Check if the exception originates from the SAT custom component
+ if any("custom_components/sat/" in frame.filename for frame in stack):
+ return event
+
+ # Ignore exceptions not related to SAT
+ return None
+
+ # Configure the Sentry client
+ client = Client(
+ traces_sample_rate=1.0,
+ before_send=exception_filter,
+ dsn="https://216fc0a74c488abdb79f9839fb7da33e@o4508432869621760.ingest.de.sentry.io/4508432872898640",
+ )
+
+ # Bind the Sentry client to the Sentry hub
+ hub = Hub(client)
+ hub.bind_client(client)
+
+ # Store the hub in Home Assistant's data for later use
+ hass.data[DOMAIN][SENTRY] = client
diff --git a/custom_components/sat/area.py b/custom_components/sat/area.py
new file mode 100644
index 00000000..b94925bf
--- /dev/null
+++ b/custom_components/sat/area.py
@@ -0,0 +1,138 @@
+from types import MappingProxyType
+from typing import Any, List
+
+from homeassistant.components.climate import HVACMode
+from homeassistant.const import STATE_UNKNOWN, STATE_UNAVAILABLE
+from homeassistant.core import HomeAssistant, State
+
+from .util import (
+ create_pwm_controller,
+ create_pid_controller,
+ create_heating_curve_controller, float_value,
+)
+
+SENSOR_TEMPERATURE_ID = "sensor_temperature_id"
+
+
+class Area:
+ def __init__(self, config_data: MappingProxyType[str, Any], config_options: MappingProxyType[str, Any], entity_id: str):
+ self._hass = None
+ self._entity_id = entity_id
+
+ # Create controllers with the given configuration options
+ self.pid = create_pid_controller(config_options)
+ self.heating_curve = create_heating_curve_controller(config_data, config_options)
+ self.pwm = create_pwm_controller(self.heating_curve, config_data, config_options)
+
+ @property
+ def id(self) -> str:
+ return self._entity_id
+
+ @property
+ def state(self) -> State | None:
+ """Retrieve the current state of the climate entity."""
+ if (self._hass is None) or (state := self._hass.states.get(self._entity_id)) is None:
+ return None
+
+ return state if state.state not in [STATE_UNKNOWN, STATE_UNAVAILABLE] else None
+
+ @property
+ def target_temperature(self) -> float | None:
+ """Retrieve the target temperature from the climate entity."""
+ if (self._hass is None) or (state := self.state) is None:
+ return None
+
+ return float_value(state.attributes.get("temperature"))
+
+ @property
+ def current_temperature(self) -> float | None:
+ """Retrieve the current temperature, overridden by a sensor if set."""
+ if (self._hass is None) or (state := self.state) is None:
+ return None
+
+ # Check if there is an overridden sensor temperature
+ if sensor_temperature_id := state.attributes.get(SENSOR_TEMPERATURE_ID):
+ sensor_state = self._hass.states.get(sensor_temperature_id)
+ if sensor_state and sensor_state.state not in [STATE_UNKNOWN, STATE_UNAVAILABLE, HVACMode.OFF]:
+ return float_value(sensor_state.state)
+
+ return float_value(state.attributes.get("current_temperature") or self.target_temperature)
+
+ @property
+ def error(self) -> float | None:
+ """Calculate the temperature error."""
+ target_temperature = self.target_temperature
+ current_temperature = self.current_temperature
+
+ if target_temperature is None or current_temperature is None:
+ return None
+
+ return round(target_temperature - current_temperature, 2)
+
+ async def async_added_to_hass(self, hass: HomeAssistant):
+ self._hass = hass
+
+ async def async_control_heating_loop(self, _time=None) -> None:
+ """Asynchronously control the heating loop."""
+ if (temperature_error := self.error) is not None:
+ # Control the integral (if exceeded the time limit)
+ self.pid.update_integral(temperature_error, self.heating_curve.value)
+
+
+class Areas:
+ def __init__(self, config_data: MappingProxyType[str, Any], config_options: MappingProxyType[str, Any], entity_ids: list):
+ """Initialize Areas with multiple Area instances using shared config data and options."""
+ self._entity_ids = entity_ids
+ self._config_data = config_data
+ self._config_options = config_options
+ self._areas = [Area(config_data, config_options, entity_id) for entity_id in entity_ids]
+
+ @property
+ def errors(self) -> List[float]:
+ """Return a list of all the error values for all areas."""
+ return [area.error for area in self._areas if area.error is not None]
+
+ @property
+ def heating_curves(self):
+ """Return an interface to update heating curves for all areas."""
+ return Areas._HeatingCurves(self._areas)
+
+ @property
+ def pids(self):
+ """Return an interface to reset PID controllers for all areas."""
+ return Areas._PIDs(self._areas)
+
+ async def async_added_to_hass(self, hass: HomeAssistant):
+ for area in self._areas:
+ await area.async_added_to_hass(hass)
+
+ async def async_control_heating_loops(self, _time=None) -> None:
+ """Asynchronously control heating loop for all areas."""
+ for area in self._areas:
+ await area.async_control_heating_loop(_time)
+
+ class _HeatingCurves:
+ def __init__(self, areas: list[Area]):
+ self.areas = areas
+
+ def update(self, current_outside_temperature: float) -> None:
+ """Update the heating curve for all areas based on current outside temperature."""
+ for area in self.areas:
+ if area.target_temperature is None:
+ continue
+
+ area.heating_curve.update(area.target_temperature, current_outside_temperature)
+
+ class _PIDs:
+ def __init__(self, areas: list[Area]):
+ self.areas = areas
+
+ def update(self, boiler_temperature: float) -> None:
+ for area in self.areas:
+ if area.error is not None:
+ area.pid.update(area.error, area.heating_curve.value, boiler_temperature)
+
+ def reset(self) -> None:
+ """Reset PID controllers for all areas."""
+ for area in self.areas:
+ area.pid.reset()
diff --git a/custom_components/sat/binary_sensor.py b/custom_components/sat/binary_sensor.py
index e17010cf..4f938c87 100644
--- a/custom_components/sat/binary_sensor.py
+++ b/custom_components/sat/binary_sensor.py
@@ -1,5 +1,6 @@
from __future__ import annotations
+import asyncio
import logging
from homeassistant.components.binary_sensor import BinarySensorEntity, BinarySensorDeviceClass
@@ -30,18 +31,49 @@ async def async_setup_entry(_hass: HomeAssistant, _config_entry: ConfigEntry, _a
await serial_binary_sensor.async_setup_entry(_hass, _config_entry, _async_add_entities)
if coordinator.supports_setpoint_management:
- _async_add_entities([SatControlSetpointSynchroSensor(coordinator, climate, _config_entry)])
+ _async_add_entities([SatControlSetpointSynchroSensor(coordinator, _config_entry, climate)])
if coordinator.supports_relative_modulation_management:
- _async_add_entities([SatRelativeModulationSynchroSensor(coordinator, climate, _config_entry)])
+ _async_add_entities([SatRelativeModulationSynchroSensor(coordinator, _config_entry, climate)])
if len(_config_entry.options.get(CONF_WINDOW_SENSORS, [])) > 0:
- _async_add_entities([SatWindowSensor(coordinator, climate, _config_entry)])
+ _async_add_entities([SatWindowSensor(coordinator, _config_entry, climate)])
- _async_add_entities([SatCentralHeatingSynchroSensor(coordinator, climate, _config_entry)])
+ _async_add_entities([SatCentralHeatingSynchroSensor(coordinator, _config_entry, climate)])
-class SatControlSetpointSynchroSensor(SatClimateEntity, BinarySensorEntity):
+class SatSynchroSensor:
+ """Mixin to add delayed state change for binary sensors."""
+
+ def __init__(self, delay: int = 30):
+ """Initialize the mixin with a delay."""
+ self._delay = delay
+ self._last_mismatch = None
+
+ def state_delayed(self, condition: bool) -> bool:
+ """Determine the delayed state based on a condition."""
+ if not condition:
+ self._last_mismatch = None
+ return False
+
+ if self._last_mismatch is None:
+ self._last_mismatch = self._get_current_time()
+
+ if self._get_current_time() - self._last_mismatch >= self._delay:
+ return True
+
+ return False
+
+ @staticmethod
+ def _get_current_time() -> float:
+ """Get the current time in seconds since epoch."""
+ return asyncio.get_event_loop().time()
+
+
+class SatControlSetpointSynchroSensor(SatSynchroSensor, SatClimateEntity, BinarySensorEntity):
+ def __init__(self, coordinator, _config_entry, climate):
+ SatSynchroSensor.__init__(self)
+ SatClimateEntity.__init__(self, coordinator, _config_entry, climate)
@property
def name(self):
@@ -61,7 +93,7 @@ def available(self):
@property
def is_on(self):
"""Return the state of the sensor."""
- return round(self._climate.setpoint, 1) != round(self._coordinator.setpoint, 1)
+ return self.state_delayed(round(self._climate.setpoint, 1) != round(self._coordinator.setpoint, 1))
@property
def unique_id(self):
@@ -69,7 +101,10 @@ def unique_id(self):
return f"{self._config_entry.data.get(CONF_NAME).lower()}-control-setpoint-synchro"
-class SatRelativeModulationSynchroSensor(SatClimateEntity, BinarySensorEntity):
+class SatRelativeModulationSynchroSensor(SatSynchroSensor, SatClimateEntity, BinarySensorEntity):
+ def __init__(self, coordinator, _config_entry, climate):
+ SatSynchroSensor.__init__(self)
+ SatClimateEntity.__init__(self, coordinator, _config_entry, climate)
@property
def name(self):
@@ -89,7 +124,7 @@ def available(self):
@property
def is_on(self):
"""Return the state of the sensor."""
- return int(self._climate.relative_modulation_value) != int(self._coordinator.maximum_relative_modulation_value)
+ return self.state_delayed(int(self._climate.relative_modulation_value) != int(self._coordinator.maximum_relative_modulation_value))
@property
def unique_id(self):
@@ -97,7 +132,10 @@ def unique_id(self):
return f"{self._config_entry.data.get(CONF_NAME).lower()}-relative-modulation-synchro"
-class SatCentralHeatingSynchroSensor(SatClimateEntity, BinarySensorEntity):
+class SatCentralHeatingSynchroSensor(SatSynchroSensor, SatClimateEntity, BinarySensorEntity):
+ def __init__(self, coordinator, _config_entry, climate):
+ SatSynchroSensor.__init__(self)
+ SatClimateEntity.__init__(self, coordinator, _config_entry, climate)
@property
def name(self) -> str:
@@ -120,11 +158,11 @@ def is_on(self) -> bool:
device_active = self._coordinator.device_active
climate_hvac_action = self._climate.state_attributes.get("hvac_action")
- return not (
+ return self.state_delayed(not (
(climate_hvac_action == HVACAction.OFF and not device_active) or
(climate_hvac_action == HVACAction.IDLE and not device_active) or
(climate_hvac_action == HVACAction.HEATING and device_active)
- )
+ ))
@property
def unique_id(self) -> str:
@@ -133,8 +171,8 @@ def unique_id(self) -> str:
class SatWindowSensor(SatClimateEntity, BinarySensorGroup):
- def __init__(self, coordinator, climate: SatClimate, config_entry: ConfigEntry):
- super().__init__(coordinator, climate, config_entry)
+ def __init__(self, coordinator, config_entry: ConfigEntry, climate: SatClimate):
+ super().__init__(coordinator, config_entry, climate)
self.mode = any
self._entity_ids = self._config_entry.options.get(CONF_WINDOW_SENSORS)
diff --git a/custom_components/sat/boiler_state.py b/custom_components/sat/boiler_state.py
new file mode 100644
index 00000000..23ef367a
--- /dev/null
+++ b/custom_components/sat/boiler_state.py
@@ -0,0 +1,52 @@
+class BoilerState:
+ """
+ Represents the operational state of a boiler, including activity, flame status,
+ hot water usage, and current temperature.
+ """
+ def __init__(
+ self,
+ device_active: bool,
+ flame_active: bool,
+ hot_water_active: bool,
+ temperature: float
+ ):
+ """
+ Initialize with the boiler's state parameters.
+
+ :param device_active: Whether the boiler is currently operational.
+ :param flame_active: Whether the boiler's flame is ignited.
+ :param hot_water_active: Whether the boiler is heating water.
+ :param temperature: The current boiler temperature in Celsius.
+ """
+ self._device_active = device_active
+ self._flame_active = flame_active
+ self._hot_water_active = hot_water_active
+ self._temperature = temperature
+
+ @property
+ def device_active(self) -> bool:
+ """
+ Indicates whether the boiler is running.
+ """
+ return self._device_active
+
+ @property
+ def flame_active(self) -> bool:
+ """
+ Indicates whether the flame is ignited.
+ """
+ return self._flame_active
+
+ @property
+ def hot_water_active(self) -> bool:
+ """
+ Indicates whether the boiler is heating water.
+ """
+ return self._hot_water_active
+
+ @property
+ def temperature(self) -> float:
+ """
+ The boiler's current temperature.
+ """
+ return self._temperature
diff --git a/custom_components/sat/climate.py b/custom_components/sat/climate.py
index 855452d6..b837e718 100644
--- a/custom_components/sat/climate.py
+++ b/custom_components/sat/climate.py
@@ -5,7 +5,6 @@
import logging
from datetime import timedelta
from time import monotonic, time
-from typing import List
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
from homeassistant.components.climate import (
@@ -36,15 +35,16 @@
from homeassistant.helpers.event import async_track_state_change_event, async_track_time_interval
from homeassistant.helpers.restore_state import RestoreEntity
+from .area import Areas, SENSOR_TEMPERATURE_ID
+from .boiler_state import BoilerState
from .const import *
from .coordinator import SatDataUpdateCoordinator, DeviceState
from .entity import SatEntity
-from .minimum_setpoint import MinimumSetpoint
from .pwm import PWMState
from .relative_modulation import RelativeModulation, RelativeModulationState
from .summer_simmer import SummerSimmer
from .util import create_pid_controller, create_heating_curve_controller, create_pwm_controller, convert_time_str_to_seconds, \
- calculate_derivative_per_hour
+ calculate_derivative_per_hour, create_minimum_setpoint_controller
ATTR_ROOMS = "rooms"
ATTR_WARMING_UP = "warming_up_data"
@@ -55,8 +55,6 @@
ATTR_PRE_ACTIVITY_TEMPERATURE = "pre_activity_temperature"
ATTR_ADJUSTED_MINIMUM_SETPOINTS = "adjusted_minimum_setpoints"
-SENSOR_TEMPERATURE_ID = "sensor_temperature_id"
-
_LOGGER = logging.getLogger(__name__)
@@ -81,6 +79,8 @@ def elapsed(self):
class SatClimate(SatEntity, ClimateEntity, RestoreEntity):
+ _enable_turn_on_off_backwards_compatibility = False
+
def __init__(self, coordinator: SatDataUpdateCoordinator, config_entry: ConfigEntry, unit: str):
super().__init__(coordinator, config_entry)
@@ -139,15 +139,21 @@ def __init__(self, coordinator: SatDataUpdateCoordinator, config_entry: ConfigEn
self._attr_preset_mode = PRESET_NONE
self._attr_hvac_modes = [HVACMode.OFF, HVACMode.HEAT]
self._attr_preset_modes = [PRESET_NONE] + list(self._presets.keys())
+
+ # Add features based on compatibility
self._attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE
+ # Conditionally add TURN_OFF if it exists
+ if hasattr(ClimateEntityFeature, 'TURN_OFF'):
+ self._attr_supported_features |= ClimateEntityFeature.TURN_OFF
+
# System Configuration
self._attr_name = str(config_entry.data.get(CONF_NAME))
self._attr_id = str(config_entry.data.get(CONF_NAME)).lower()
self._climates = config_entry.data.get(CONF_SECONDARY_CLIMATES) or []
self._main_climates = config_entry.data.get(CONF_MAIN_CLIMATES) or []
- self._window_sensors = config_entry.data.get(CONF_WINDOW_SENSORS) or []
+ self._window_sensors = config_entry.options.get(CONF_WINDOW_SENSORS) or []
self._simulation = bool(config_entry.data.get(CONF_SIMULATION))
self._heating_system = str(config_entry.data.get(CONF_HEATING_SYSTEM))
@@ -155,10 +161,12 @@ def __init__(self, coordinator: SatDataUpdateCoordinator, config_entry: ConfigEn
self._overshoot_protection = bool(config_entry.data.get(CONF_OVERSHOOT_PROTECTION))
# User Configuration
+ self._heating_mode = str(config_entry.options.get(CONF_HEATING_MODE))
self._thermal_comfort = bool(config_options.get(CONF_THERMAL_COMFORT))
self._climate_valve_offset = float(config_options.get(CONF_CLIMATE_VALVE_OFFSET))
self._target_temperature_step = float(config_options.get(CONF_TARGET_TEMPERATURE_STEP))
self._dynamic_minimum_setpoint = bool(config_options.get(CONF_DYNAMIC_MINIMUM_SETPOINT))
+ self._sync_climates_with_mode = bool(config_options.get(CONF_SYNC_CLIMATES_WITH_MODE))
self._sync_climates_with_preset = bool(config_options.get(CONF_SYNC_CLIMATES_WITH_PRESET))
self._maximum_relative_modulation = int(config_options.get(CONF_MAXIMUM_RELATIVE_MODULATION))
self._force_pulse_width_modulation = bool(config_options.get(CONF_FORCE_PULSE_WIDTH_MODULATION))
@@ -175,11 +183,14 @@ def __init__(self, coordinator: SatDataUpdateCoordinator, config_entry: ConfigEn
self.pwm = create_pwm_controller(self.heating_curve, config_entry.data, config_options)
# Create the Minimum Setpoint controller
- self._minimum_setpoint = MinimumSetpoint(coordinator)
+ self._minimum_setpoint = create_minimum_setpoint_controller(config_entry.data, config_options)
# Create Relative Modulation controller
self._relative_modulation = RelativeModulation(coordinator, self._heating_system)
+ # Create Area controllers
+ self._areas = Areas(config_entry.data, config_options, self._climates)
+
if self._simulation:
_LOGGER.warning("Simulation mode!")
@@ -195,6 +206,7 @@ async def async_added_to_hass(self) -> None:
# Update a heating curve if outside temperature is available
if self.current_outside_temperature is not None:
+ self._areas.heating_curves.update(self.current_outside_temperature)
self.heating_curve.update(self.target_temperature, self.current_outside_temperature)
# Start control loop
@@ -206,8 +218,11 @@ async def async_added_to_hass(self) -> None:
# Initialize minimum setpoint system
await self._minimum_setpoint.async_initialize(self.hass)
+ # Initialize the area system
+ await self._areas.async_added_to_hass(self.hass)
+
# Let the coordinator know we are ready
- await self._coordinator.async_added_to_hass(self)
+ await self._coordinator.async_added_to_hass()
async def _register_event_listeners(self):
"""Register event listeners."""
@@ -250,11 +265,12 @@ async def _register_event_listeners(self):
if len(self._window_sensors) > 0:
entities = entity_registry.async_get(self.hass)
- unique_id = f"{self._config_entry.data.get(CONF_NAME).lower()}-window-sensor"
+ device_name = self._config_entry.data.get(CONF_NAME)
+ window_id = entities.async_get_entity_id(BINARY_SENSOR_DOMAIN, DOMAIN, f"{device_name.lower()}-window-sensor")
self.async_on_remove(
async_track_state_change_event(
- self.hass, [entities.async_get_entity_id(BINARY_SENSOR_DOMAIN, DOMAIN, unique_id)], self._async_window_sensor_changed
+ self.hass, [window_id], self._async_window_sensor_changed
)
)
@@ -324,6 +340,7 @@ async def _register_services(self):
async def reset_integral(_call: ServiceCall):
"""Service to reset the integral part of the PID controller."""
self.pid.reset()
+ self._areas.pids.reset()
self.hass.services.async_register(DOMAIN, SERVICE_RESET_INTEGRAL, reset_integral)
@@ -371,6 +388,7 @@ def extra_state_attributes(self):
"minimum_setpoint": self.minimum_setpoint,
"requested_setpoint": self.requested_setpoint,
"adjusted_minimum_setpoint": self.adjusted_minimum_setpoint,
+ "base_return_temperature": self._minimum_setpoint.base_return_temperature,
"outside_temperature": self.current_outside_temperature,
"optimal_coefficient": self.heating_curve.optimal_coefficient,
"coefficient_derivative": self.heating_curve.coefficient_derivative,
@@ -451,7 +469,10 @@ def hvac_action(self):
@property
def max_error(self) -> float:
- return max([self.error] + self.climate_errors)
+ if self._heating_mode == HEATING_MODE_ECO:
+ return self.error
+
+ return max([self.error] + self._areas.errors)
@property
def setpoint(self) -> float | None:
@@ -465,34 +486,6 @@ def requested_setpoint(self) -> float:
return round(max(self.heating_curve.value + self.pid.output, MINIMUM_SETPOINT), 1)
- @property
- def climate_errors(self) -> List[float]:
- """Calculate the temperature difference between the current temperature and target temperature for all connected climates."""
- errors = []
- for climate in self._climates:
- # Skip if climate state is unavailable or HVAC mode is off
- state = self.hass.states.get(climate)
- if state is None or state.state in [STATE_UNKNOWN, STATE_UNAVAILABLE, HVACMode.OFF]:
- continue
-
- # Calculate temperature difference for this climate
- target_temperature = float(state.attributes.get("temperature"))
- current_temperature = float(state.attributes.get("current_temperature") or target_temperature)
-
- # Retrieve the overridden sensor temperature if set
- if sensor_temperature_id := state.attributes.get(SENSOR_TEMPERATURE_ID):
- sensor_state = self.hass.states.get(sensor_temperature_id)
- if sensor_state is not None and sensor_state.state not in [STATE_UNKNOWN, STATE_UNAVAILABLE, HVACMode.OFF]:
- current_temperature = float(sensor_state.state)
-
- # Calculate the error value
- error = round(target_temperature - current_temperature, 2)
-
- # Add to the list, so we calculate the max. later
- errors.append(error)
-
- return errors
-
@property
def valves_open(self) -> bool:
"""Determine if any of the controlled thermostats have open valves."""
@@ -550,7 +543,7 @@ def relative_modulation_state(self) -> RelativeModulationState:
return self._relative_modulation.state
@property
- def warming_up(self):
+ def warming_up(self) -> bool:
"""Return True if we are warming up, False otherwise."""
return self._warming_up_data is not None and self._warming_up_data.elapsed < HEATER_STARTUP_TIMEFRAME
@@ -559,11 +552,11 @@ def minimum_setpoint(self) -> float:
if not self._dynamic_minimum_setpoint:
return self._coordinator.minimum_setpoint
- return self.adjusted_minimum_setpoint
+ return min(self.adjusted_minimum_setpoint, self._coordinator.maximum_setpoint)
@property
def adjusted_minimum_setpoint(self) -> float:
- return self._minimum_setpoint.current([self.error] + self.climate_errors)
+ return self._minimum_setpoint.current()
def _calculate_control_setpoint(self) -> float:
"""Calculate the control setpoint based on the heating curve and PID output."""
@@ -573,10 +566,6 @@ def _calculate_control_setpoint(self) -> float:
# Combine the heating curve value and the calculated output from the pid controller
requested_setpoint = self.requested_setpoint
- # Make sure we are above the base setpoint when we are below the target temperature
- if self.max_error > 0:
- requested_setpoint = max(requested_setpoint, self.heating_curve.value)
-
# Ensure setpoint is limited to our max
return min(requested_setpoint, self._coordinator.maximum_setpoint)
@@ -664,9 +653,8 @@ async def _async_climate_changed(self, event: Event) -> None:
await self._async_control_pid(True)
# If the current temperature has changed, update the PID controller
- elif not hasattr(new_state.attributes, SENSOR_TEMPERATURE_ID) and new_attrs.get("current_temperature") != old_attrs.get(
- "current_temperature"):
- await self._async_control_pid(False)
+ elif SENSOR_TEMPERATURE_ID not in new_state.attributes and new_attrs.get("current_temperature") != old_attrs.get("current_temperature"):
+ await self._async_control_pid()
if (self._rooms is not None and new_state.entity_id not in self._rooms) or self.preset_mode in [PRESET_HOME, PRESET_COMFORT]:
if target_temperature := new_state.attributes.get("temperature"):
@@ -685,7 +673,7 @@ async def _async_temperature_change(self, event: Event) -> None:
return
_LOGGER.debug(f"Climate Sensor Changed ({new_state.entity_id}).")
- await self._async_control_pid(False)
+ await self._async_control_pid()
await self.async_control_heating_loop()
async def _async_window_sensor_changed(self, event: Event) -> None:
@@ -731,15 +719,14 @@ async def _async_control_pid(self, reset: bool = False) -> None:
# Reset the PID controller if the sensor data is too old
if self._sensor_max_value_age != 0 and monotonic() - self.pid.last_updated > self._sensor_max_value_age:
self.pid.reset()
+ self._areas.pids.reset()
# Calculate the maximum error between the current temperature and the target temperature of all climates
max_error = self.max_error
# Make sure we use the latest heating curve value
- self.heating_curve.update(
- target_temperature=self.target_temperature,
- outside_temperature=self.current_outside_temperature,
- )
+ self.heating_curve.update(self.target_temperature, self.current_outside_temperature)
+ self._areas.heating_curves.update(self.current_outside_temperature)
# Update the PID controller with the maximum error
if not reset:
@@ -765,11 +752,8 @@ async def _async_control_pid(self, reset: bool = False) -> None:
_LOGGER.info("Reached deadband, turning off warming up.")
self._warming_up_data = None
- self.pid.update(
- error=max_error,
- heating_curve_value=self.heating_curve.value,
- boiler_temperature=self._coordinator.filtered_boiler_temperature
- )
+ self._areas.pids.update(self._coordinator.filtered_boiler_temperature)
+ self.pid.update(max_error, self.heating_curve.value, self._coordinator.filtered_boiler_temperature)
elif max_error != self.pid.last_error:
_LOGGER.info(f"Updating error value to {max_error} (Reset: True)")
@@ -785,25 +769,51 @@ async def _async_control_pid(self, reset: bool = False) -> None:
self.async_write_ha_state()
async def _async_control_setpoint(self, pwm_state: PWMState) -> None:
- """Control the setpoint of the heating system."""
- if self.hvac_mode == HVACMode.HEAT:
- if not self.pulse_width_modulation_enabled or pwm_state == pwm_state.IDLE:
- _LOGGER.info("Running Normal cycle")
- self._setpoint = self._calculated_setpoint
- else:
- _LOGGER.info(f"Running PWM cycle: {pwm_state}")
- self._setpoint = self.minimum_setpoint if pwm_state == pwm_state.ON else MINIMUM_SETPOINT
- else:
+ """Control the setpoint of the heating system based on the current mode and PWM state."""
+
+ # Check if the system is in HEAT mode
+ if self.hvac_mode != HVACMode.HEAT:
+ # If not in HEAT mode, set to minimum setpoint
self._calculated_setpoint = None
self._setpoint = MINIMUM_SETPOINT
+ _LOGGER.info("HVAC mode is not HEAT. Setting setpoint to minimum: %.1f°C", MINIMUM_SETPOINT)
+
+ elif not self.pulse_width_modulation_enabled or pwm_state == PWMState.IDLE:
+ # Normal cycle without PWM
+ _LOGGER.info("Pulse Width Modulation is disabled or in IDLE state. Running normal heating cycle.")
+ _LOGGER.debug("Calculated setpoint for normal cycle: %.1f°C", self._calculated_setpoint)
+ self._setpoint = self._calculated_setpoint
+
+ else:
+ # PWM is enabled and actively controlling the cycle
+ _LOGGER.info("Running PWM cycle with state: %s", pwm_state)
+
+ if pwm_state == PWMState.ON:
+ self._setpoint = self.minimum_setpoint
+ _LOGGER.debug("Setting setpoint to minimum: %.1f°C", self.minimum_setpoint)
+ else:
+ self._setpoint = MINIMUM_SETPOINT
+ _LOGGER.debug("Setting setpoint to absolute minimum: %.1f°C", MINIMUM_SETPOINT)
+ # Apply the setpoint using the coordinator
await self._coordinator.async_set_control_setpoint(self._setpoint)
+ _LOGGER.info("Control setpoint has been updated to: %.1f°C", self._setpoint)
async def _async_control_relative_modulation(self) -> None:
- """Control the relative modulation value based on the conditions"""
- if self._coordinator.supports_relative_modulation_management:
- await self._relative_modulation.update(self.warming_up, self.pwm.state)
- await self._coordinator.async_set_control_max_relative_modulation(self.relative_modulation_value)
+ """Control the relative modulation value based on the conditions."""
+ if not self._coordinator.supports_relative_modulation_management:
+ _LOGGER.debug("Relative modulation management is not supported. Skipping control.")
+ return
+
+ # Update relative modulation state
+ await self._relative_modulation.update(self.pulse_width_modulation_enabled)
+
+ # Determine if the value needs to be updated
+ if self._coordinator.maximum_relative_modulation_value == self.relative_modulation_value:
+ _LOGGER.debug("Relative modulation value unchanged (%d%%). No update necessary.", self.relative_modulation_value)
+ return
+
+ await self._coordinator.async_set_control_max_relative_modulation(self.relative_modulation_value)
async def _async_update_rooms_from_climates(self) -> None:
"""Update the temperature setpoint for each room based on their associated climate entity."""
@@ -851,6 +861,9 @@ async def async_control_heating_loop(self, _time=None) -> None:
if self.current_temperature is None or self.target_temperature is None or self.current_outside_temperature is None:
return
+ if self.hvac_mode != HVACMode.HEAT:
+ return
+
# Control the heating through the coordinator
await self._coordinator.async_control_heating_loop(self)
@@ -863,7 +876,14 @@ async def async_control_heating_loop(self, _time=None) -> None:
# Pulse Width Modulation
if self.pulse_width_modulation_enabled:
- await self.pwm.update(self._calculated_setpoint, self._coordinator.boiler_temperature)
+ boiler_state = BoilerState(
+ flame_active=self._coordinator.flame_active,
+ device_active=self._coordinator.device_active,
+ hot_water_active=self._coordinator.hot_water_active,
+ temperature=self._coordinator.boiler_temperature
+ )
+
+ await self.pwm.update(self._calculated_setpoint, boiler_state)
else:
self.pwm.reset()
@@ -876,23 +896,42 @@ async def async_control_heating_loop(self, _time=None) -> None:
# Control the integral (if exceeded the time limit)
self.pid.update_integral(self.max_error, self.heating_curve.value)
- # Calculate the minimum setpoint
- self._minimum_setpoint.calculate(self._coordinator.setpoint, [self.error] + self.climate_errors)
+ # Control our areas
+ await self._areas.async_control_heating_loops()
+
+ # Control our dynamic minimum setpoint
+ if not self._coordinator.hot_water_active and self._coordinator.flame_active:
+ # Calculate the base return temperature
+ if self.warming_up:
+ self._minimum_setpoint.warming_up(self._coordinator.return_temperature)
+
+ # Calculate the dynamic minimum setpoint
+ self._minimum_setpoint.calculate(self._coordinator.return_temperature)
# If the setpoint is high and the HVAC is not off, turn on the heater
- if self._setpoint > MINIMUM_SETPOINT and self.hvac_mode != HVACMode.OFF:
- await self.async_set_heater_state(DeviceState.ON)
- else:
- await self.async_set_heater_state(DeviceState.OFF)
+ await self.async_set_heater_state(DeviceState.ON if self._setpoint > MINIMUM_SETPOINT else DeviceState.OFF)
self.async_write_ha_state()
async def async_set_heater_state(self, state: DeviceState):
- if state == DeviceState.ON and not self.valves_open:
- _LOGGER.warning('No valves are open at the moment.')
- return await self._coordinator.async_set_heater_state(DeviceState.OFF)
+ """Set the heater state, ensuring proper conditions are met."""
+ _LOGGER.debug("Attempting to set heater state to: %s", state)
+
+ if state == DeviceState.ON:
+ if self._coordinator.device_active:
+ _LOGGER.info("Heater is already active. No action taken.")
+ return
+
+ if not self.valves_open:
+ _LOGGER.warning("Cannot turn on heater: no valves are open.")
+ return
- return await self._coordinator.async_set_heater_state(state)
+ elif state == DeviceState.OFF:
+ if not self._coordinator.device_active:
+ _LOGGER.info("Heater is already off. No action taken.")
+ return
+
+ await self._coordinator.async_set_heater_state(state)
async def async_set_temperature(self, **kwargs) -> None:
"""Set the target temperature."""
@@ -914,6 +953,7 @@ async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
self._hvac_mode = HVACMode.HEAT
elif hvac_mode == HVACMode.OFF:
self._hvac_mode = HVACMode.OFF
+ await self.async_set_heater_state(DeviceState.OFF)
else:
# If an unsupported mode is passed, log an error message
_LOGGER.error("Unrecognized hvac mode: %s", hvac_mode)
@@ -922,8 +962,13 @@ async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
# Reset the PID controller
await self._async_control_pid(True)
- # Set the hvac mode for all climate devices
- for entity_id in (self._climates + self._main_climates):
+ # Collect which climates to control
+ climates = self._main_climates[:]
+ if self._sync_climates_with_mode:
+ climates += self._climates
+
+ # Set the hvac mode for those climate devices
+ for entity_id in climates:
data = {ATTR_ENTITY_ID: entity_id, ATTR_HVAC_MODE: hvac_mode}
await self.hass.services.async_call(CLIMATE_DOMAIN, SERVICE_SET_HVAC_MODE, data, blocking=True)
@@ -946,10 +991,6 @@ async def async_set_preset_mode(self, preset_mode: str) -> None:
self._attr_preset_mode = PRESET_NONE
await self.async_set_target_temperature(self._pre_custom_temperature)
else:
- # Set the HVAC mode to `HEAT` if it is currently `OFF`
- if self.hvac_mode == HVACMode.OFF:
- await self.async_set_hvac_mode(HVACMode.HEAT)
-
# Save the current target temperature if the preset mode is being set for the first time
if self._attr_preset_mode == PRESET_NONE:
self._pre_custom_temperature = self._target_temperature
@@ -980,9 +1021,6 @@ async def async_set_target_temperature(self, temperature: float) -> None:
# Set the new target temperature
self._target_temperature = temperature
- # Reset the PID controller
- await self._async_control_pid(True)
-
# Set the target temperature for each main climate
for entity_id in self._main_climates:
data = {ATTR_ENTITY_ID: entity_id, ATTR_TEMPERATURE: temperature}
@@ -992,6 +1030,9 @@ async def async_set_target_temperature(self, temperature: float) -> None:
# Set the target temperature for the connected boiler
await self._coordinator.async_set_control_thermostat_setpoint(temperature)
+ # Reset the PID controller
+ await self._async_control_pid(True)
+
# Write the state to Home Assistant
self.async_write_ha_state()
diff --git a/custom_components/sat/config_flow.py b/custom_components/sat/config_flow.py
index ff89bd80..4bf9979e 100644
--- a/custom_components/sat/config_flow.py
+++ b/custom_components/sat/config_flow.py
@@ -1,26 +1,26 @@
"""Adds config flow for SAT."""
import asyncio
import logging
+from typing import Any
import voluptuous as vol
from homeassistant import config_entries
-from homeassistant.components import mqtt
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN, BinarySensorDeviceClass
-from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN
+from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN, ATTR_HVAC_MODE, HVACMode, SERVICE_SET_HVAC_MODE
from homeassistant.components.dhcp import DhcpServiceInfo
from homeassistant.components.input_boolean import DOMAIN as INPUT_BOOLEAN_DOMAIN
-from homeassistant.components.mqtt import DOMAIN as MQTT_DOMAIN
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN, SensorDeviceClass
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
+from homeassistant.components.valve import DOMAIN as VALVE_DOMAIN
from homeassistant.components.weather import DOMAIN as WEATHER_DOMAIN
from homeassistant.config_entries import ConfigEntry
-from homeassistant.const import MAJOR_VERSION, MINOR_VERSION
+from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.core import callback
-from homeassistant.data_entry_flow import FlowResult
-from homeassistant.helpers import selector, device_registry, entity_registry
+from homeassistant.helpers import selector, entity_registry
from homeassistant.helpers.selector import SelectSelectorMode
from homeassistant.helpers.service_info.mqtt import MqttServiceInfo
from pyotgw import OpenThermGateway
+from voluptuous import UNDEFINED
from . import SatDataUpdateCoordinatorFactory
from .const import *
@@ -35,16 +35,18 @@
class SatFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
"""Config flow for SAT."""
- VERSION = 6
+ VERSION = 10
MINOR_VERSION = 0
calibration = None
+ previous_hvac_mode = None
overshoot_protection_value = None
def __init__(self):
"""Initialize."""
self.data = {}
self.errors = {}
+ self.config_entry = None
@staticmethod
@callback
@@ -56,23 +58,21 @@ def async_remove(self) -> None:
if self.calibration is not None:
self.calibration.cancel()
- async def async_step_user(self, _user_input=None) -> FlowResult:
+ async def async_step_user(self, _user_input: dict[str, Any] | None = None):
"""Handle user flow."""
- menu_options = []
-
- # Since we rely on the availability logic in 2023.5, we do not support below it.
- if MAJOR_VERSION >= 2023 and (MINOR_VERSION >= 5 or MAJOR_VERSION > 2023):
- menu_options.append("mosquitto")
-
- menu_options.append("serial")
- menu_options.append("switch")
+ menu_options = [
+ "mosquitto",
+ "esphome",
+ "serial",
+ "switch"
+ ]
if self.show_advanced_options:
menu_options.append("simulator")
return self.async_show_menu(step_id="user", menu_options=menu_options)
- async def async_step_dhcp(self, discovery_info: DhcpServiceInfo) -> FlowResult:
+ async def async_step_dhcp(self, discovery_info: DhcpServiceInfo):
"""Handle dhcp discovery."""
_LOGGER.debug("Discovered OTGW at [socket://%s]", discovery_info.hostname)
self.data[CONF_DEVICE] = f"socket://{discovery_info.hostname}:25238"
@@ -80,53 +80,104 @@ async def async_step_dhcp(self, discovery_info: DhcpServiceInfo) -> FlowResult:
# abort if we already have exactly this gateway id/host
# reload the integration if the host got updated
await self.async_set_unique_id(discovery_info.hostname)
- self._abort_if_unique_id_configured(updates=self.data, reload_on_update=True)
+ self._abort_if_unique_id_configured(updates=self.data)
return await self.async_step_serial()
async def async_step_mqtt(self, discovery_info: MqttServiceInfo):
- """Handle dhcp discovery."""
- device = device_registry.async_get(self.hass).async_get_device(
- {(MQTT_DOMAIN, discovery_info.topic[11:])}
- )
+ """Handle mqtt discovery."""
+ _LOGGER.debug("Discovered MQTT at [mqtt://%s]", discovery_info.topic)
- _LOGGER.debug("Discovered OTGW at [mqtt://%s]", discovery_info.topic)
- self.data[CONF_DEVICE] = device.id
+ # Mapping topic prefixes to handler methods and device IDs
+ topic_mapping = {
+ "ems-esp/": (MODE_MQTT_EMS, "ems-esp", self.async_step_mosquitto_ems),
+ "OTGW/": (MODE_MQTT_OPENTHERM, discovery_info.topic[11:], self.async_step_mosquitto_opentherm),
+ }
- # abort if we already have exactly this gateway id/host
- # reload the integration if the host got updated
- await self.async_set_unique_id(device.id)
- self._abort_if_unique_id_configured(updates=self.data, reload_on_update=True)
+ # Check for matching prefix and handle appropriately
+ for prefix, (mode, device_id, step_method) in topic_mapping.items():
+ if discovery_info.topic.startswith(prefix):
+ _LOGGER.debug("Identified gateway type %s: %s", mode[5:], device_id)
+ self.data[CONF_MODE] = mode
+ self.data[CONF_DEVICE] = device_id
+
+ # Abort if the gateway is already registered, reload if necessary
+ await self.async_set_unique_id(device_id)
+ self._abort_if_unique_id_configured(updates=self.data)
+
+ return await step_method()
- return await self.async_step_mosquitto()
+ _LOGGER.error("Unsupported MQTT topic format: %s", discovery_info.topic)
+ return self.async_abort(reason="unsupported_gateway")
- async def async_step_mosquitto(self, _user_input=None):
+ async def async_step_mosquitto(self, _user_input: dict[str, Any] | None = None):
+ """Entry step to select the MQTT mode and branch to specific setup."""
self.errors = {}
if _user_input is not None:
self.data.update(_user_input)
- self.data[CONF_MODE] = MODE_MQTT
- if not await mqtt.async_wait_for_mqtt_client(self.hass):
- self.errors["base"] = "mqtt_component"
- return await self.async_step_mosquitto()
+ if self.data[CONF_MODE] == MODE_MQTT_OPENTHERM:
+ return await self.async_step_mosquitto_opentherm()
- return await self.async_step_sensors()
+ if self.data[CONF_MODE] == MODE_MQTT_EMS:
+ return await self.async_step_mosquitto_ems()
return self.async_show_form(
step_id="mosquitto",
last_step=False,
errors=self.errors,
+ data_schema=vol.Schema({
+ vol.Required(CONF_MODE): selector.SelectSelector(
+ selector.SelectSelectorConfig(
+ mode=SelectSelectorMode.DROPDOWN,
+ options=[
+ selector.SelectOptionDict(value=MODE_MQTT_OPENTHERM, label="OpenTherm Gateway (For advanced boiler control)"),
+ selector.SelectOptionDict(value=MODE_MQTT_EMS, label="EMS-ESP (For Bosch, Junkers, Buderus systems)"),
+ ]
+ )
+ ),
+ }),
+ )
+
+ async def async_step_mosquitto_opentherm(self, _user_input: dict[str, Any] | None = None):
+ """Setup specific to OpenTherm Gateway."""
+ if _user_input is not None:
+ self.data.update(_user_input)
+
+ return await self.async_step_sensors()
+
+ return self._create_mqtt_form("mosquitto_opentherm", "OTGW", "otgw-XXXXXXXXXXXX")
+
+ async def async_step_mosquitto_ems(self, _user_input: dict[str, Any] | None = None):
+ """Setup specific to EMS-ESP."""
+ if _user_input is not None:
+ self.data.update(_user_input)
+ self.data[CONF_DEVICE] = "ems-esp"
+
+ return await self.async_step_sensors()
+
+ return self._create_mqtt_form("mosquitto_ems", "ems-esp")
+
+ async def async_step_esphome(self, _user_input: dict[str, Any] | None = None):
+ if _user_input is not None:
+ self.data.update(_user_input)
+ self.data[CONF_MODE] = MODE_ESPHOME
+
+ return await self.async_step_sensors()
+
+ return self.async_show_form(
+ step_id="esphome",
+ last_step=False,
data_schema=vol.Schema({
vol.Required(CONF_NAME, default=DEFAULT_NAME): str,
- vol.Required(CONF_MQTT_TOPIC, default=OPTIONS_DEFAULTS[CONF_MQTT_TOPIC]): str,
vol.Required(CONF_DEVICE, default=self.data.get(CONF_DEVICE)): selector.DeviceSelector(
- selector.DeviceSelectorConfig(model="otgw-nodo")
+ selector.DeviceSelectorConfig(integration="esphome")
),
}),
)
- async def async_step_serial(self, _user_input=None):
+ async def async_step_serial(self, _user_input: dict[str, Any] | None = None):
self.errors = {}
if _user_input is not None:
@@ -151,7 +202,7 @@ async def async_step_serial(self, _user_input=None):
}),
)
- async def async_step_switch(self, _user_input=None):
+ async def async_step_switch(self, _user_input: dict[str, Any] | None = None):
if _user_input is not None:
self.data.update(_user_input)
self.data[CONF_MODE] = MODE_SWITCH
@@ -165,7 +216,7 @@ async def async_step_switch(self, _user_input=None):
data_schema=vol.Schema({
vol.Required(CONF_NAME, default=DEFAULT_NAME): str,
vol.Required(CONF_DEVICE): selector.EntitySelector(
- selector.EntitySelectorConfig(domain=[SWITCH_DOMAIN, INPUT_BOOLEAN_DOMAIN])
+ selector.EntitySelectorConfig(domain=[SWITCH_DOMAIN, VALVE_DOMAIN, INPUT_BOOLEAN_DOMAIN])
),
vol.Required(CONF_MINIMUM_SETPOINT, default=50): selector.NumberSelector(
selector.NumberSelectorConfig(min=10, max=100, step=1)
@@ -173,7 +224,7 @@ async def async_step_switch(self, _user_input=None):
}),
)
- async def async_step_simulator(self, _user_input=None):
+ async def async_step_simulator(self, _user_input: dict[str, Any] | None = None):
if _user_input is not None:
self.data.update(_user_input)
self.data[CONF_MODE] = MODE_SIMULATOR
@@ -200,14 +251,21 @@ async def async_step_simulator(self, _user_input=None):
}),
)
- async def async_step_sensors(self, _user_input=None):
- await self.async_set_unique_id(self.data[CONF_DEVICE], raise_on_progress=False)
- self._abort_if_unique_id_configured()
+ async def async_step_reconfigure(self, _user_input: dict[str, Any] | None = None):
+ self.config_entry = self.hass.config_entries.async_get_entry(self.context["entry_id"])
+ self.data = self.config_entry.data.copy()
+
+ return await self.async_step_sensors()
+
+ async def async_step_sensors(self, _user_input: dict[str, Any] | None = None):
+ if self.config_entry is None:
+ await self.async_set_unique_id(self.data[CONF_DEVICE], raise_on_progress=False)
+ self._abort_if_unique_id_configured()
if _user_input is not None:
self.data.update(_user_input)
- if self.data[CONF_MODE] in [MODE_MQTT, MODE_SERIAL, MODE_SIMULATOR]:
+ if self.data[CONF_MODE] in [MODE_ESPHOME, MODE_MQTT_OPENTHERM, MODE_MQTT_EMS, MODE_SERIAL, MODE_SIMULATOR]:
return await self.async_step_heating_system()
return await self.async_step_areas()
@@ -216,19 +274,19 @@ async def async_step_sensors(self, _user_input=None):
last_step=False,
step_id="sensors",
data_schema=vol.Schema({
- vol.Required(CONF_INSIDE_SENSOR_ENTITY_ID): selector.EntitySelector(
+ vol.Required(CONF_INSIDE_SENSOR_ENTITY_ID, default=self.data.get(CONF_INSIDE_SENSOR_ENTITY_ID)): selector.EntitySelector(
selector.EntitySelectorConfig(
domain=SENSOR_DOMAIN,
device_class=[SensorDeviceClass.TEMPERATURE]
)
),
- vol.Required(CONF_OUTSIDE_SENSOR_ENTITY_ID): selector.EntitySelector(
+ vol.Required(CONF_OUTSIDE_SENSOR_ENTITY_ID, default=self.data.get(CONF_OUTSIDE_SENSOR_ENTITY_ID)): selector.EntitySelector(
selector.EntitySelectorConfig(
multiple=True,
domain=[SENSOR_DOMAIN, WEATHER_DOMAIN]
)
),
- vol.Optional(CONF_HUMIDITY_SENSOR_ENTITY_ID): selector.EntitySelector(
+ vol.Optional(CONF_HUMIDITY_SENSOR_ENTITY_ID, default=self.data.get(CONF_HUMIDITY_SENSOR_ENTITY_ID, UNDEFINED)): selector.EntitySelector(
selector.EntitySelectorConfig(
domain=SENSOR_DOMAIN,
device_class=[SensorDeviceClass.HUMIDITY]
@@ -237,7 +295,7 @@ async def async_step_sensors(self, _user_input=None):
}),
)
- async def async_step_heating_system(self, _user_input=None):
+ async def async_step_heating_system(self, _user_input: dict[str, Any] | None = None):
if _user_input is not None:
self.data.update(_user_input)
@@ -247,7 +305,7 @@ async def async_step_heating_system(self, _user_input=None):
last_step=False,
step_id="heating_system",
data_schema=vol.Schema({
- vol.Required(CONF_HEATING_SYSTEM, default=OPTIONS_DEFAULTS[CONF_HEATING_SYSTEM]): selector.SelectSelector(
+ vol.Required(CONF_HEATING_SYSTEM, default=self.data.get(CONF_HEATING_SYSTEM, OPTIONS_DEFAULTS[CONF_HEATING_SYSTEM])): selector.SelectSelector(
selector.SelectSelectorConfig(options=[
{"value": HEATING_SYSTEM_RADIATORS, "label": "Radiators"},
{"value": HEATING_SYSTEM_HEAT_PUMP, "label": "Heat Pump"},
@@ -257,7 +315,7 @@ async def async_step_heating_system(self, _user_input=None):
})
)
- async def async_step_areas(self, _user_input=None):
+ async def async_step_areas(self, _user_input: dict[str, Any] | None = None):
if _user_input is not None:
self.data.update(_user_input)
@@ -271,14 +329,15 @@ async def async_step_areas(self, _user_input=None):
))
return self.async_show_form(
+ last_step=False,
step_id="areas",
data_schema=vol.Schema({
- vol.Optional(CONF_MAIN_CLIMATES): climate_selector,
- vol.Optional(CONF_SECONDARY_CLIMATES): climate_selector,
+ vol.Optional(CONF_MAIN_CLIMATES, default=self.data.get(CONF_MAIN_CLIMATES, [])): climate_selector,
+ vol.Optional(CONF_SECONDARY_CLIMATES, default=self.data.get(CONF_SECONDARY_CLIMATES, [])): climate_selector,
})
)
- async def async_step_automatic_gains(self, _user_input=None):
+ async def async_step_automatic_gains(self, _user_input: dict[str, Any] | None = None):
if _user_input is not None:
self.data.update(_user_input)
@@ -293,64 +352,81 @@ async def async_step_automatic_gains(self, _user_input=None):
data_schema=vol.Schema({vol.Required(CONF_AUTOMATIC_GAINS, default=True): bool})
)
- async def async_step_calibrate_system(self, _user_input=None):
+ async def async_step_calibrate_system(self, _user_input: dict[str, Any] | None = None):
return self.async_show_menu(
step_id="calibrate_system",
menu_options=["calibrate", "overshoot_protection", "pid_controller"]
)
- async def async_step_calibrate(self, _user_input=None):
- coordinator = await self.async_create_coordinator()
+ async def async_step_calibrate(self, _user_input: dict[str, Any] | None = None):
+ # Let's see if we have already been configured before
+ device_name = self.data[CONF_NAME]
+ entities = entity_registry.async_get(self.hass)
+ climate_id = entities.async_get_entity_id(CLIMATE_DOMAIN, DOMAIN, device_name.lower())
async def start_calibration():
try:
- overshoot_protection = OvershootProtection(coordinator)
+ coordinator = await self.async_create_coordinator()
+ await coordinator.async_added_to_hass()
+
+ overshoot_protection = OvershootProtection(coordinator, self.data.get(CONF_HEATING_SYSTEM))
self.overshoot_protection_value = await overshoot_protection.calculate()
+
+ await coordinator.async_will_remove_from_hass()
except asyncio.TimeoutError:
- _LOGGER.warning("Calibration time-out.")
- return False
+ _LOGGER.warning("Timed out during overshoot protection calculation.")
except asyncio.CancelledError:
- _LOGGER.warning("Cancelled calibration.")
- return False
-
- self.hass.async_create_task(
- self.hass.config_entries.flow.async_configure(flow_id=self.flow_id)
- )
-
- return True
+ _LOGGER.warning("Cancelled overshoot protection calculation.")
if not self.calibration:
self.calibration = self.hass.async_create_task(
start_calibration()
)
+ # Make sure to turn off the existing climate if we found one
+ if climate_id is not None:
+ self.previous_hvac_mode = self.hass.states.get(climate_id).state
+ data = {ATTR_ENTITY_ID: climate_id, ATTR_HVAC_MODE: HVACMode.OFF}
+ await self.hass.services.async_call(CLIMATE_DOMAIN, SERVICE_SET_HVAC_MODE, data, blocking=True)
+
+ # Make sure all climate valves are open
+ for entity_id in self.data.get(CONF_MAIN_CLIMATES, []) + self.data.get(CONF_SECONDARY_CLIMATES, []):
+ data = {ATTR_ENTITY_ID: entity_id, ATTR_HVAC_MODE: HVACMode.HEAT}
+ await self.hass.services.async_call(CLIMATE_DOMAIN, SERVICE_SET_HVAC_MODE, data, blocking=True)
+
return self.async_show_progress(
step_id="calibrate",
+ progress_task=self.calibration,
progress_action="calibration",
)
if self.overshoot_protection_value is None:
return self.async_abort(reason="unable_to_calibrate")
- await self._enable_overshoot_protection(
+ self._enable_overshoot_protection(
self.overshoot_protection_value
)
self.calibration = None
self.overshoot_protection_value = None
+ # Make sure to restore the mode after we are done
+ if climate_id is not None:
+ data = {ATTR_ENTITY_ID: climate_id, ATTR_HVAC_MODE: self.previous_hvac_mode}
+ await self.hass.services.async_call(CLIMATE_DOMAIN, SERVICE_SET_HVAC_MODE, data, blocking=True)
+
return self.async_show_progress_done(next_step_id="calibrated")
- async def async_step_calibrated(self, _user_input=None):
+ async def async_step_calibrated(self, _user_input: dict[str, Any] | None = None):
return self.async_show_menu(
step_id="calibrated",
description_placeholders=self.data,
menu_options=["calibrate", "finish"],
)
- async def async_step_overshoot_protection(self, _user_input=None):
+ async def async_step_overshoot_protection(self, _user_input: dict[str, Any] | None = None):
if _user_input is not None:
- await self._enable_overshoot_protection(
+ self._enable_overshoot_protection(
_user_input[CONF_MINIMUM_SETPOINT]
)
@@ -359,13 +435,13 @@ async def async_step_overshoot_protection(self, _user_input=None):
return self.async_show_form(
step_id="overshoot_protection",
data_schema=vol.Schema({
- vol.Required(CONF_MINIMUM_SETPOINT, default=OPTIONS_DEFAULTS[CONF_MINIMUM_SETPOINT]): selector.NumberSelector(
- selector.NumberSelectorConfig(min=MINIMUM_SETPOINT, max=OVERSHOOT_PROTECTION_SETPOINT, step=1, unit_of_measurement="°C")
+ vol.Required(CONF_MINIMUM_SETPOINT, default=self.data.get(CONF_MINIMUM_SETPOINT, OPTIONS_DEFAULTS[CONF_MINIMUM_SETPOINT])): selector.NumberSelector(
+ selector.NumberSelectorConfig(min=MINIMUM_SETPOINT, max=MAXIMUM_SETPOINT, step=1, unit_of_measurement="°C")
),
})
)
- async def async_step_pid_controller(self, _user_input=None):
+ async def async_step_pid_controller(self, _user_input: dict[str, Any] | None = None):
self.data[CONF_AUTOMATIC_GAINS] = False
if _user_input is not None:
@@ -375,22 +451,50 @@ async def async_step_pid_controller(self, _user_input=None):
return self.async_show_form(
step_id="pid_controller",
data_schema=vol.Schema({
- vol.Required(CONF_PROPORTIONAL, default=OPTIONS_DEFAULTS[CONF_PROPORTIONAL]): str,
- vol.Required(CONF_INTEGRAL, default=OPTIONS_DEFAULTS[CONF_INTEGRAL]): str,
- vol.Required(CONF_DERIVATIVE, default=OPTIONS_DEFAULTS[CONF_DERIVATIVE]): str
+ vol.Required(CONF_PROPORTIONAL, default=self.data.get(CONF_PROPORTIONAL, OPTIONS_DEFAULTS[CONF_PROPORTIONAL])): str,
+ vol.Required(CONF_INTEGRAL, default=self.data.get(CONF_INTEGRAL, OPTIONS_DEFAULTS[CONF_INTEGRAL])): str,
+ vol.Required(CONF_DERIVATIVE, default=self.data.get(CONF_DERIVATIVE, OPTIONS_DEFAULTS[CONF_DERIVATIVE])): str
})
)
- async def async_step_finish(self, _user_input=None):
- return self.async_create_entry(title=self.data[CONF_NAME], data=self.data)
+ async def async_step_finish(self, _user_input: dict[str, Any] | None = None):
+ if self.config_entry is not None:
+ return self.async_update_reload_and_abort(
+ data=self.data,
+ entry=self.config_entry,
+ title=self.data[CONF_NAME],
+ reason="reconfigure_successful",
+ )
+
+ return self.async_create_entry(
+ title=self.data[CONF_NAME],
+ data=self.data
+ )
async def async_create_coordinator(self) -> SatDataUpdateCoordinator:
- # Resolve the coordinator by using the factory according to the mode
- return await SatDataUpdateCoordinatorFactory().resolve(
+ """Resolve the coordinator by using the factory according to the mode"""
+ return SatDataUpdateCoordinatorFactory().resolve(
hass=self.hass, data=self.data, mode=self.data[CONF_MODE], device=self.data[CONF_DEVICE]
)
- async def _enable_overshoot_protection(self, overshoot_protection_value: float):
+ def _create_mqtt_form(self, step_id: str, default_topic: str, default_device: str = None):
+ """Create a common MQTT configuration form."""
+ schema = {
+ vol.Required(CONF_NAME, default=DEFAULT_NAME): str,
+ vol.Required(CONF_MQTT_TOPIC, default=default_topic): str,
+ }
+
+ if default_device:
+ schema[vol.Required(CONF_DEVICE, default=default_device)] = str
+
+ return self.async_show_form(
+ step_id=step_id,
+ last_step=False,
+ data_schema=vol.Schema(schema),
+ )
+
+ def _enable_overshoot_protection(self, overshoot_protection_value: float):
+ """Store the value and enable overshoot protection."""
self.data[CONF_OVERSHOOT_PROTECTION] = True
self.data[CONF_MINIMUM_SETPOINT] = overshoot_protection_value
@@ -402,7 +506,7 @@ def __init__(self, config_entry: ConfigEntry):
self._config_entry = config_entry
self._options = dict(config_entry.options)
- async def async_step_init(self, _user_input=None):
+ async def async_step_init(self, _user_input: dict[str, Any] | None = None):
menu_options = ["general", "presets", "system_configuration"]
if self.show_advanced_options:
@@ -413,7 +517,7 @@ async def async_step_init(self, _user_input=None):
menu_options=menu_options
)
- async def async_step_general(self, _user_input=None) -> FlowResult:
+ async def async_step_general(self, _user_input: dict[str, Any] | None = None):
if _user_input is not None:
return await self.update_options(_user_input)
@@ -425,11 +529,27 @@ async def async_step_general(self, _user_input=None) -> FlowResult:
schema[vol.Required(CONF_HEATING_CURVE_VERSION, default=str(options[CONF_HEATING_CURVE_VERSION]))] = selector.SelectSelector(
selector.SelectSelectorConfig(mode=SelectSelectorMode.DROPDOWN, options=[
- selector.SelectOptionDict(value="1", label="Classic Curve"),
- selector.SelectOptionDict(value="2", label="Quantum Curve")
+ selector.SelectOptionDict(value="2", label="Quantum Curve"),
+ selector.SelectOptionDict(value="3", label="Precision Curve"),
])
)
+ schema[vol.Required(CONF_PID_CONTROLLER_VERSION, default=str(options[CONF_PID_CONTROLLER_VERSION]))] = selector.SelectSelector(
+ selector.SelectSelectorConfig(mode=SelectSelectorMode.DROPDOWN, options=[
+ selector.SelectOptionDict(value="1", label="Classic Controller"),
+ selector.SelectOptionDict(value="2", label="Improved Controller"),
+ selector.SelectOptionDict(value="3", label="Adaptive Controller")
+ ])
+ )
+
+ if len(self._config_entry.data.get(CONF_SECONDARY_CLIMATES, [])) > 0:
+ schema[vol.Required(CONF_HEATING_MODE, default=str(options[CONF_HEATING_MODE]))] = selector.SelectSelector(
+ selector.SelectSelectorConfig(mode=SelectSelectorMode.DROPDOWN, options=[
+ selector.SelectOptionDict(value=HEATING_MODE_COMFORT, label="Comfort"),
+ selector.SelectOptionDict(value=HEATING_MODE_ECO, label="Eco"),
+ ])
+ )
+
schema[vol.Required(CONF_MAXIMUM_SETPOINT, default=maximum_setpoint)] = selector.NumberSelector(
selector.NumberSelectorConfig(min=10, max=100, step=1, unit_of_measurement="°C")
)
@@ -439,17 +559,23 @@ async def async_step_general(self, _user_input=None) -> FlowResult:
)
if options[CONF_AUTOMATIC_GAINS]:
- schema[vol.Required(CONF_AUTOMATIC_GAINS_VALUE, default=options[CONF_AUTOMATIC_GAINS_VALUE])] = selector.NumberSelector(
- selector.NumberSelectorConfig(min=1, max=5, step=1)
- )
- schema[vol.Required(CONF_DERIVATIVE_TIME_WEIGHT, default=options[CONF_DERIVATIVE_TIME_WEIGHT])] = selector.NumberSelector(
- selector.NumberSelectorConfig(min=1, max=6, step=1)
- )
+ if int(options[CONF_PID_CONTROLLER_VERSION]) < 3:
+ schema[vol.Required(CONF_AUTOMATIC_GAINS_VALUE, default=options[CONF_AUTOMATIC_GAINS_VALUE])] = selector.NumberSelector(
+ selector.NumberSelectorConfig(min=1, max=5, step=0.1)
+ )
+ schema[vol.Required(CONF_DERIVATIVE_TIME_WEIGHT, default=options[CONF_DERIVATIVE_TIME_WEIGHT])] = selector.NumberSelector(
+ selector.NumberSelectorConfig(min=1, max=6, step=0.1)
+ )
else:
schema[vol.Required(CONF_PROPORTIONAL, default=options[CONF_PROPORTIONAL])] = str
schema[vol.Required(CONF_INTEGRAL, default=options[CONF_INTEGRAL])] = str
schema[vol.Required(CONF_DERIVATIVE, default=options[CONF_DERIVATIVE])] = str
+ if options[CONF_DYNAMIC_MINIMUM_SETPOINT]:
+ schema[vol.Required(CONF_MINIMUM_SETPOINT_ADJUSTMENT_FACTOR, default=options[CONF_MINIMUM_SETPOINT_ADJUSTMENT_FACTOR])] = selector.NumberSelector(
+ selector.NumberSelectorConfig(min=0.1, max=0.5, step=0.1)
+ )
+
if not options[CONF_AUTOMATIC_DUTY_CYCLE]:
schema[vol.Required(CONF_DUTY_CYCLE, default=options[CONF_DUTY_CYCLE])] = selector.TimeSelector()
@@ -468,7 +594,7 @@ async def async_step_general(self, _user_input=None) -> FlowResult:
return self.async_show_form(step_id="general", data_schema=vol.Schema(schema))
- async def async_step_presets(self, _user_input=None) -> FlowResult:
+ async def async_step_presets(self, _user_input: dict[str, Any] | None = None):
if _user_input is not None:
return await self.update_options(_user_input)
@@ -491,26 +617,47 @@ async def async_step_presets(self, _user_input=None) -> FlowResult:
vol.Required(CONF_COMFORT_TEMPERATURE, default=options[CONF_COMFORT_TEMPERATURE]): selector.NumberSelector(
selector.NumberSelectorConfig(min=5, max=35, step=0.5, unit_of_measurement="°C")
),
+ vol.Required(CONF_SYNC_WITH_THERMOSTAT, default=options[CONF_SYNC_WITH_THERMOSTAT]): bool,
vol.Required(CONF_SYNC_CLIMATES_WITH_PRESET, default=options[CONF_SYNC_CLIMATES_WITH_PRESET]): bool,
})
)
- async def async_step_system_configuration(self, _user_input=None) -> FlowResult:
+ async def async_step_system_configuration(self, _user_input: dict[str, Any] | None = None):
if _user_input is not None:
return await self.update_options(_user_input)
options = await self.get_options()
+ schema = {
+ vol.Required(CONF_AUTOMATIC_DUTY_CYCLE, default=options[CONF_AUTOMATIC_DUTY_CYCLE]): bool,
+ vol.Required(CONF_SYNC_CLIMATES_WITH_MODE, default=options[CONF_SYNC_CLIMATES_WITH_MODE]): bool,
+ }
+
+ if options.get(CONF_HEATING_SYSTEM) == HEATING_SYSTEM_HEAT_PUMP:
+ schema[vol.Required(CONF_CYCLES_PER_HOUR, default=str(options[CONF_CYCLES_PER_HOUR]))] = selector.SelectSelector(
+ selector.SelectSelectorConfig(mode=SelectSelectorMode.DROPDOWN, options=[
+ selector.SelectOptionDict(value="2", label="Normal (2x per hour)"),
+ selector.SelectOptionDict(value="3", label="High (3x per hour)"),
+ ])
+ )
+
+ if options.get(CONF_HEATING_SYSTEM) == HEATING_SYSTEM_RADIATORS:
+ schema[vol.Required(CONF_CYCLES_PER_HOUR, default=str(options[CONF_CYCLES_PER_HOUR]))] = selector.SelectSelector(
+ selector.SelectSelectorConfig(mode=SelectSelectorMode.DROPDOWN, options=[
+ selector.SelectOptionDict(value="3", label="Normal (3x per hour)"),
+ selector.SelectOptionDict(value="4", label="High (4x per hour)"),
+ ])
+ )
+
+ schema[vol.Required(CONF_SENSOR_MAX_VALUE_AGE, default=options[CONF_SENSOR_MAX_VALUE_AGE])] = selector.TimeSelector()
+ schema[vol.Required(CONF_WINDOW_MINIMUM_OPEN_TIME, default=options[CONF_WINDOW_MINIMUM_OPEN_TIME])] = selector.TimeSelector()
+
return self.async_show_form(
step_id="system_configuration",
- data_schema=vol.Schema({
- vol.Required(CONF_AUTOMATIC_DUTY_CYCLE, default=options[CONF_AUTOMATIC_DUTY_CYCLE]): bool,
- vol.Required(CONF_SENSOR_MAX_VALUE_AGE, default=options[CONF_SENSOR_MAX_VALUE_AGE]): selector.TimeSelector(),
- vol.Required(CONF_WINDOW_MINIMUM_OPEN_TIME, default=options[CONF_WINDOW_MINIMUM_OPEN_TIME]): selector.TimeSelector(),
- })
+ data_schema=vol.Schema(schema)
)
- async def async_step_advanced(self, _user_input=None) -> FlowResult:
+ async def async_step_advanced(self, _user_input: dict[str, Any] | None = None):
if _user_input is not None:
return await self.update_options(_user_input)
@@ -519,10 +666,11 @@ async def async_step_advanced(self, _user_input=None) -> FlowResult:
schema = {
vol.Required(CONF_SIMULATION, default=options[CONF_SIMULATION]): bool,
vol.Required(CONF_THERMAL_COMFORT, default=options[CONF_THERMAL_COMFORT]): bool,
- vol.Required(CONF_DYNAMIC_MINIMUM_SETPOINT, default=options[CONF_DYNAMIC_MINIMUM_SETPOINT]): bool
+ vol.Required(CONF_ERROR_MONITORING, default=options[CONF_ERROR_MONITORING]): bool,
+ vol.Required(CONF_DYNAMIC_MINIMUM_SETPOINT, default=options[CONF_DYNAMIC_MINIMUM_SETPOINT]): bool,
}
- if options.get(CONF_MODE) in [MODE_MQTT, MODE_SERIAL, MODE_SIMULATOR]:
+ if self._config_entry.data.get(CONF_MODE) in [MODE_MQTT_OPENTHERM, MODE_SERIAL, MODE_SIMULATOR]:
schema[vol.Required(CONF_FORCE_PULSE_WIDTH_MODULATION, default=options[CONF_FORCE_PULSE_WIDTH_MODULATION])] = bool
schema[vol.Required(CONF_MINIMUM_CONSUMPTION, default=options[CONF_MINIMUM_CONSUMPTION])] = selector.NumberSelector(
@@ -552,7 +700,7 @@ async def async_step_advanced(self, _user_input=None) -> FlowResult:
data_schema=vol.Schema(schema)
)
- async def update_options(self, _user_input) -> FlowResult:
+ async def update_options(self, _user_input):
self._options.update(_user_input)
return self.async_create_entry(title=self._config_entry.data[CONF_NAME], data=self._options)
diff --git a/custom_components/sat/const.py b/custom_components/sat/const.py
index 11748d0d..e6f97610 100644
--- a/custom_components/sat/const.py
+++ b/custom_components/sat/const.py
@@ -1,32 +1,35 @@
# Base component constants
NAME = "Smart Autotune Thermostat"
DOMAIN = "sat"
-VERSION = "3.0.x"
CLIMATE = "climate"
+SENTRY = "sentry"
COORDINATOR = "coordinator"
CONFIG_STORE = "config_store"
MODE_FAKE = "fake"
-MODE_MQTT = "mqtt"
+MODE_MQTT_EMS = "mqtt_ems"
+MODE_MQTT_OPENTHERM = "mqtt_opentherm"
MODE_SWITCH = "switch"
MODE_SERIAL = "serial"
+MODE_ESPHOME = "esphome"
MODE_SIMULATOR = "simulator"
DEADBAND = 0.1
HEATER_STARTUP_TIMEFRAME = 180
MINIMUM_SETPOINT = 10
+MAXIMUM_SETPOINT = 65
MINIMUM_RELATIVE_MOD = 0
MAXIMUM_RELATIVE_MOD = 100
MAX_BOILER_TEMPERATURE_AGE = 60
-OVERSHOOT_PROTECTION_SETPOINT = 75
-OVERSHOOT_PROTECTION_REQUIRED_DATASET = 40
# Configuration and options
CONF_MODE = "mode"
CONF_NAME = "name"
CONF_DEVICE = "device"
+CONF_ERROR_MONITORING = "error_monitoring"
+CONF_CYCLES_PER_HOUR = "cycles_per_hour"
CONF_SIMULATED_HEATING = "simulated_heating"
CONF_SIMULATED_COOLING = "simulated_cooling"
CONF_SIMULATED_WARMING_UP = "simulated_warming_up"
@@ -53,6 +56,7 @@
CONF_CLIMATE_VALVE_OFFSET = "climate_valve_offset"
CONF_SENSOR_MAX_VALUE_AGE = "sensor_max_value_age"
CONF_OVERSHOOT_PROTECTION = "overshoot_protection"
+CONF_SYNC_CLIMATES_WITH_MODE = "sync_climates_with_mode"
CONF_SYNC_CLIMATES_WITH_PRESET = "sync_climates_with_preset"
CONF_FORCE_PULSE_WIDTH_MODULATION = "force_pulse_width_modulation"
CONF_TARGET_TEMPERATURE_STEP = "target_temperature_step"
@@ -60,11 +64,15 @@
CONF_OUTSIDE_SENSOR_ENTITY_ID = "outside_sensor_entity_id"
CONF_HUMIDITY_SENSOR_ENTITY_ID = "humidity_sensor_entity_id"
CONF_DYNAMIC_MINIMUM_SETPOINT = "dynamic_minimum_setpoint"
+CONF_MINIMUM_SETPOINT_ADJUSTMENT_FACTOR = "minimum_setpoint_adjustment_factor"
+CONF_HEATING_MODE = "heating_mode"
CONF_HEATING_SYSTEM = "heating_system"
CONF_HEATING_CURVE_VERSION = "heating_curve_version"
CONF_HEATING_CURVE_COEFFICIENT = "heating_curve_coefficient"
+CONF_PID_CONTROLLER_VERSION = "pid_controller_version"
+
CONF_MINIMUM_CONSUMPTION = "minimum_consumption"
CONF_MAXIMUM_CONSUMPTION = "maximum_consumption"
@@ -79,18 +87,23 @@
HEATING_SYSTEM_RADIATORS = "radiators"
HEATING_SYSTEM_UNDERFLOOR = "underfloor"
+HEATING_MODE_ECO = "eco"
+HEATING_MODE_COMFORT = "comfort"
+
OPTIONS_DEFAULTS = {
- CONF_MODE: MODE_SERIAL,
CONF_PROPORTIONAL: "45",
CONF_INTEGRAL: "0",
CONF_DERIVATIVE: "6000",
+ CONF_ERROR_MONITORING: False,
+ CONF_CYCLES_PER_HOUR: 4,
CONF_AUTOMATIC_GAINS: True,
CONF_AUTOMATIC_DUTY_CYCLE: True,
- CONF_AUTOMATIC_GAINS_VALUE: 5.0,
- CONF_DERIVATIVE_TIME_WEIGHT: 6.0,
+ CONF_AUTOMATIC_GAINS_VALUE: 2.0,
+ CONF_DERIVATIVE_TIME_WEIGHT: 2.5,
CONF_OVERSHOOT_PROTECTION: False,
CONF_DYNAMIC_MINIMUM_SETPOINT: False,
+ CONF_MINIMUM_SETPOINT_ADJUSTMENT_FACTOR: 0.2,
CONF_SECONDARY_CLIMATES: [],
CONF_MAIN_CLIMATES: [],
@@ -99,6 +112,7 @@
CONF_THERMAL_COMFORT: False,
CONF_HUMIDITY_SENSOR_ENTITY_ID: None,
CONF_SYNC_WITH_THERMOSTAT: False,
+ CONF_SYNC_CLIMATES_WITH_MODE: True,
CONF_SYNC_CLIMATES_WITH_PRESET: False,
CONF_SIMULATED_HEATING: 20,
@@ -111,7 +125,6 @@
CONF_MINIMUM_CONSUMPTION: 0,
CONF_MAXIMUM_CONSUMPTION: 0,
- CONF_MQTT_TOPIC: "OTGW",
CONF_DUTY_CYCLE: "00:13:00",
CONF_SAMPLE_TIME: "00:01:00",
CONF_CLIMATE_VALVE_OFFSET: 0,
@@ -126,9 +139,20 @@
CONF_SLEEP_TEMPERATURE: 15,
CONF_COMFORT_TEMPERATURE: 20,
- CONF_HEATING_CURVE_VERSION: 2,
- CONF_HEATING_CURVE_COEFFICIENT: 1.0,
+ CONF_HEATING_CURVE_VERSION: 3,
+ CONF_HEATING_CURVE_COEFFICIENT: 2.0,
+ CONF_HEATING_MODE: HEATING_MODE_COMFORT,
CONF_HEATING_SYSTEM: HEATING_SYSTEM_RADIATORS,
+
+ CONF_PID_CONTROLLER_VERSION: 3,
+}
+
+# Overshoot protection
+OVERSHOOT_PROTECTION_REQUIRED_DATASET = 40
+OVERSHOOT_PROTECTION_SETPOINT = {
+ HEATING_SYSTEM_HEAT_PUMP: 40,
+ HEATING_SYSTEM_RADIATORS: 62,
+ HEATING_SYSTEM_UNDERFLOOR: 45,
}
# Storage
diff --git a/custom_components/sat/coordinator.py b/custom_components/sat/coordinator.py
index 46a9ed56..ac104ccf 100644
--- a/custom_components/sat/coordinator.py
+++ b/custom_components/sat/coordinator.py
@@ -4,13 +4,14 @@
from abc import abstractmethod
from datetime import datetime, timedelta
from enum import Enum
-from typing import TYPE_CHECKING, Mapping, Any
+from typing import TYPE_CHECKING, Mapping, Any, Optional
-from homeassistant.components.climate import HVACMode
+from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .const import *
+from .manufacturer import ManufacturerFactory, Manufacturer
from .util import calculate_default_maximum_setpoint
if TYPE_CHECKING:
@@ -26,7 +27,7 @@ class DeviceState(str, Enum):
class SatDataUpdateCoordinatorFactory:
@staticmethod
- async def resolve(
+ def resolve(
hass: HomeAssistant,
mode: str,
device: str,
@@ -41,17 +42,25 @@ async def resolve(
from .simulator import SatSimulatorCoordinator
return SatSimulatorCoordinator(hass=hass, data=data, options=options)
- if mode == MODE_MQTT:
- from .mqtt import SatMqttCoordinator
- return SatMqttCoordinator(hass=hass, device_id=device, data=data, options=options)
-
if mode == MODE_SWITCH:
from .switch import SatSwitchCoordinator
return SatSwitchCoordinator(hass=hass, entity_id=device, data=data, options=options)
+ if mode == MODE_ESPHOME:
+ from .esphome import SatEspHomeCoordinator
+ return SatEspHomeCoordinator(hass=hass, device_id=device, data=data, options=options)
+
+ if mode == MODE_MQTT_EMS:
+ from .mqtt.ems import SatEmsMqttCoordinator
+ return SatEmsMqttCoordinator(hass=hass, device_id=device, data=data, options=options)
+
+ if mode == MODE_MQTT_OPENTHERM:
+ from .mqtt.opentherm import SatOpenThermMqttCoordinator
+ return SatOpenThermMqttCoordinator(hass=hass, device_id=device, data=data, options=options)
+
if mode == MODE_SERIAL:
from .serial import SatSerialCoordinator
- return await SatSerialCoordinator(hass=hass, port=device, data=data, options=options).async_connect()
+ return SatSerialCoordinator(hass=hass, port=device, data=data, options=options)
raise Exception(f'Invalid mode[{mode}]')
@@ -62,18 +71,39 @@ def __init__(self, hass: HomeAssistant, data: Mapping[str, Any], options: Mappin
self.boiler_temperatures = []
self._data = data
- self._options = options
+ self._manufacturer = None
+ self._options = options or {}
self._device_state = DeviceState.OFF
- self._simulation = bool(data.get(CONF_SIMULATION))
+ self._simulation = bool(self._options.get(CONF_SIMULATION))
self._heating_system = str(data.get(CONF_HEATING_SYSTEM, HEATING_SYSTEM_UNKNOWN))
super().__init__(hass, _LOGGER, name=DOMAIN)
+ @property
+ @abstractmethod
+ def device_id(self) -> str:
+ pass
+
+ @property
+ @abstractmethod
+ def device_type(self) -> str:
+ pass
+
@property
def device_state(self):
"""Return the current state of the device."""
return self._device_state
+ @property
+ def manufacturer(self) -> Manufacturer | None:
+ if self.member_id is None:
+ return None
+
+ if self._manufacturer is None:
+ self._manufacturer = ManufacturerFactory().resolve(self.member_id)
+
+ return self._manufacturer
+
@property
@abstractmethod
def setpoint(self) -> float | None:
@@ -84,6 +114,11 @@ def setpoint(self) -> float | None:
def device_active(self) -> bool:
pass
+ @property
+ @abstractmethod
+ def member_id(self) -> int | None:
+ pass
+
@property
def flame_active(self) -> bool:
return self.device_active
@@ -101,10 +136,14 @@ def boiler_temperature(self) -> float | None:
return None
@property
- def filtered_boiler_temperature(self) -> float | None:
+ def return_temperature(self) -> float | None:
+ return None
+
+ @property
+ def filtered_boiler_temperature(self) -> float:
# Not able to use if we do not have at least two values
if len(self.boiler_temperatures) < 2:
- return None
+ return self.boiler_temperature
# Some noise filtering on the boiler temperature
difference_boiler_temperature_sum = sum(
@@ -214,20 +253,20 @@ def supports_maximum_setpoint_management(self):
"""
return False
- async def async_added_to_hass(self, climate: SatClimate) -> None:
+ async def async_setup(self) -> None:
+ """Perform setup when the integration is about to be added to Home Assistant."""
+ pass
+
+ async def async_added_to_hass(self) -> None:
"""Perform setup when the integration is added to Home Assistant."""
await self.async_set_control_max_setpoint(self.maximum_setpoint)
- async def async_will_remove_from_hass(self, climate: SatClimate) -> None:
+ async def async_will_remove_from_hass(self) -> None:
"""Run when an entity is removed from hass."""
pass
async def async_control_heating_loop(self, climate: SatClimate = None, _time=None) -> None:
"""Control the heating loop for the device."""
- if climate is not None and climate.hvac_mode == HVACMode.OFF and self.device_active:
- # Send out a new command to turn off the device
- await self.async_set_heater_state(DeviceState.OFF)
-
current_time = datetime.now()
# Make sure we have valid value
@@ -246,23 +285,46 @@ async def async_set_heater_state(self, state: DeviceState) -> None:
async def async_set_control_setpoint(self, value: float) -> None:
"""Control the boiler setpoint temperature for the device."""
if self.supports_setpoint_management:
- _LOGGER.info("Set control boiler setpoint to %d", value)
+ _LOGGER.info("Set control boiler setpoint to %d°C", value)
async def async_set_control_hot_water_setpoint(self, value: float) -> None:
"""Control the DHW setpoint temperature for the device."""
if self.supports_hot_water_setpoint_management:
- _LOGGER.info("Set control hot water setpoint to %d", value)
+ _LOGGER.info("Set control hot water setpoint to %d°C", value)
async def async_set_control_max_setpoint(self, value: float) -> None:
"""Control the maximum setpoint temperature for the device."""
if self.supports_maximum_setpoint_management:
- _LOGGER.info("Set maximum setpoint to %d", value)
+ _LOGGER.info("Set maximum setpoint to %d°C", value)
async def async_set_control_max_relative_modulation(self, value: int) -> None:
"""Control the maximum relative modulation for the device."""
if self.supports_relative_modulation_management:
- _LOGGER.info("Set maximum relative modulation to %d", value)
+ _LOGGER.info("Set maximum relative modulation to %d%%", value)
async def async_set_control_thermostat_setpoint(self, value: float) -> None:
"""Control the setpoint temperature for the thermostat."""
pass
+
+
+class SatEntityCoordinator(DataUpdateCoordinator):
+ def get(self, domain: str, key: str) -> Optional[Any]:
+ """Get the value for the given `key` from the boiler data.
+
+ :param domain: Domain of where this value is located.
+ :param key: Key of the value to retrieve from the boiler data.
+ :return: Value for the given key from the boiler data, or None if the boiler data or the value are not available.
+ """
+ entity_id = self._get_entity_id(domain, key)
+ if entity_id is None:
+ return None
+
+ state = self.hass.states.get(self._get_entity_id(domain, key))
+ if state is None or state.state in (STATE_UNAVAILABLE, STATE_UNKNOWN):
+ return None
+
+ return state.state
+
+ @abstractmethod
+ def _get_entity_id(self, domain: str, key: str):
+ pass
diff --git a/custom_components/sat/entity.py b/custom_components/sat/entity.py
index b4cea2f1..0e6a4963 100644
--- a/custom_components/sat/entity.py
+++ b/custom_components/sat/entity.py
@@ -4,9 +4,10 @@
import typing
from homeassistant.config_entries import ConfigEntry
+from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity
-from .const import DOMAIN, NAME, VERSION, CONF_NAME
+from .const import DOMAIN, NAME, CONF_NAME
_LOGGER: logging.Logger = logging.getLogger(__name__)
@@ -24,16 +25,21 @@ def __init__(self, coordinator: SatDataUpdateCoordinator, config_entry: ConfigEn
@property
def device_info(self):
- return {
- "name": NAME,
- "model": VERSION,
- "manufacturer": NAME,
- "identifiers": {(DOMAIN, self._config_entry.data.get(CONF_NAME))},
- }
+ manufacturer = "Unknown"
+ if self._coordinator.manufacturer is not None:
+ manufacturer = self._coordinator.manufacturer.name
+
+ return DeviceInfo(
+ name=NAME,
+ manufacturer=manufacturer,
+ suggested_area="Living Room",
+ model=self._coordinator.device_type,
+ identifiers={(DOMAIN, self._config_entry.data.get(CONF_NAME))}
+ )
class SatClimateEntity(SatEntity):
- def __init__(self, coordinator, climate: SatClimate, config_entry: ConfigEntry):
+ def __init__(self, coordinator, config_entry: ConfigEntry, climate: SatClimate):
super().__init__(coordinator, config_entry)
self._climate = climate
diff --git a/custom_components/sat/esphome/__init__.py b/custom_components/sat/esphome/__init__.py
new file mode 100644
index 00000000..2a8585f6
--- /dev/null
+++ b/custom_components/sat/esphome/__init__.py
@@ -0,0 +1,250 @@
+from __future__ import annotations, annotations
+
+import logging
+from typing import Mapping, Any
+
+from homeassistant.components import mqtt
+from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
+from homeassistant.components.esphome import DOMAIN as ESPHOME_DOMAIN
+from homeassistant.components.number import DOMAIN as NUMBER_DOMAIN
+from homeassistant.components.number.const import SERVICE_SET_VALUE
+from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
+from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN, SERVICE_TURN_ON, SERVICE_TURN_OFF
+from homeassistant.core import HomeAssistant, Event
+from homeassistant.helpers import device_registry, entity_registry
+from homeassistant.helpers.event import async_track_state_change_event
+
+from ..coordinator import DeviceState, SatDataUpdateCoordinator, SatEntityCoordinator
+
+# Sensors
+DATA_FLAME_ACTIVE = "flame_on"
+DATA_REL_MOD_LEVEL = "rel_mod_level"
+DATA_SLAVE_MEMBERID = "device_id"
+DATA_BOILER_TEMPERATURE = "t_boiler"
+DATA_RETURN_TEMPERATURE = "t_ret"
+DATA_BOILER_CAPACITY = "max_capacity"
+DATA_REL_MIN_MOD_LEVEL = "min_mod_level"
+
+DATA_DHW_SETPOINT_MINIMUM = "t_dhw_set_lb"
+DATA_DHW_SETPOINT_MAXIMUM = "t_dhw_set_ub"
+
+# Switch
+DATA_DHW_ENABLE = "dhw_enable"
+DATA_CENTRAL_HEATING = "ch_enable"
+
+# Number
+DATA_DHW_SETPOINT = "t_dhw_set"
+DATA_CONTROL_SETPOINT = "t_set"
+DATA_MAX_CH_SETPOINT = "max_t_set"
+DATA_MAX_REL_MOD_LEVEL_SETTING = "max_rel_mod_level"
+
+_LOGGER: logging.Logger = logging.getLogger(__name__)
+
+
+class SatEspHomeCoordinator(SatDataUpdateCoordinator, SatEntityCoordinator):
+ """Class to manage to fetch data from the OTGW Gateway using esphome."""
+
+ def __init__(self, hass: HomeAssistant, device_id: str, data: Mapping[str, Any], options: Mapping[str, Any] | None = None) -> None:
+ super().__init__(hass, data, options)
+
+ self.data = {}
+
+ self._device = device_registry.async_get(hass).async_get(device_id)
+ self._mac_address = list(self._device.connections)[0][1]
+
+ self._entity_registry = entity_registry.async_get(hass)
+ self._entities = entity_registry.async_entries_for_device(self._entity_registry, self._device.id)
+
+ @property
+ def device_id(self) -> str:
+ return self._mac_address
+
+ @property
+ def device_type(self) -> str:
+ return "ESPHome"
+
+ @property
+ def supports_setpoint_management(self):
+ return True
+
+ @property
+ def supports_hot_water_setpoint_management(self):
+ return True
+
+ @property
+ def supports_maximum_setpoint_management(self):
+ return True
+
+ @property
+ def supports_relative_modulation_management(self):
+ return True
+
+ @property
+ def device_active(self) -> bool:
+ return self.get(SWITCH_DOMAIN, DATA_CENTRAL_HEATING) == DeviceState.ON
+
+ @property
+ def flame_active(self) -> bool:
+ return self.get(BINARY_SENSOR_DOMAIN, DATA_FLAME_ACTIVE) == DeviceState.ON
+
+ @property
+ def hot_water_active(self) -> bool:
+ return self.get(BINARY_SENSOR_DOMAIN, DATA_DHW_ENABLE) == DeviceState.ON
+
+ @property
+ def setpoint(self) -> float | None:
+ if (setpoint := self.get(NUMBER_DOMAIN, DATA_CONTROL_SETPOINT)) is not None:
+ return float(setpoint)
+
+ return None
+
+ @property
+ def hot_water_setpoint(self) -> float | None:
+ if (setpoint := self.get(NUMBER_DOMAIN, DATA_DHW_SETPOINT)) is not None:
+ return float(setpoint)
+
+ return super().hot_water_setpoint
+
+ @property
+ def minimum_hot_water_setpoint(self) -> float:
+ if (setpoint := self.get(SENSOR_DOMAIN, DATA_DHW_SETPOINT_MINIMUM)) is not None:
+ return float(setpoint)
+
+ return super().minimum_hot_water_setpoint
+
+ @property
+ def maximum_hot_water_setpoint(self) -> float | None:
+ if (setpoint := self.get(SENSOR_DOMAIN, DATA_DHW_SETPOINT_MAXIMUM)) is not None:
+ return float(setpoint)
+
+ return super().maximum_hot_water_setpoint
+
+ @property
+ def boiler_temperature(self) -> float | None:
+ if (value := self.get(SENSOR_DOMAIN, DATA_BOILER_TEMPERATURE)) is not None:
+ return float(value)
+
+ return super().boiler_temperature
+
+ @property
+ def return_temperature(self) -> float | None:
+ if (value := self.get(SENSOR_DOMAIN, DATA_RETURN_TEMPERATURE)) is not None:
+ return float(value)
+
+ return super().return_temperature
+
+ @property
+ def relative_modulation_value(self) -> float | None:
+ if (value := self.get(SENSOR_DOMAIN, DATA_REL_MOD_LEVEL)) is not None:
+ return float(value)
+
+ return super().relative_modulation_value
+
+ @property
+ def boiler_capacity(self) -> float | None:
+ if (value := self.get(SENSOR_DOMAIN, DATA_BOILER_CAPACITY)) is not None:
+ return float(value)
+
+ return super().boiler_capacity
+
+ @property
+ def minimum_relative_modulation_value(self) -> float | None:
+ if (value := self.get(SENSOR_DOMAIN, DATA_REL_MIN_MOD_LEVEL)) is not None:
+ return float(value)
+
+ return super().minimum_relative_modulation_value
+
+ @property
+ def maximum_relative_modulation_value(self) -> float | None:
+ if (value := self.get(NUMBER_DOMAIN, DATA_MAX_REL_MOD_LEVEL_SETTING)) is not None:
+ return float(value)
+
+ return super().maximum_relative_modulation_value
+
+ @property
+ def member_id(self) -> int | None:
+ if (value := self.get(SENSOR_DOMAIN, DATA_SLAVE_MEMBERID)) is not None:
+ return int(value)
+
+ return None
+
+ async def async_added_to_hass(self) -> None:
+ await mqtt.async_wait_for_mqtt_client(self.hass)
+
+ # Create a list of entities that we track
+ entities = list(filter(lambda entity: entity is not None, [
+ self._get_entity_id(SENSOR_DOMAIN, DATA_FLAME_ACTIVE),
+ self._get_entity_id(SENSOR_DOMAIN, DATA_REL_MOD_LEVEL),
+ self._get_entity_id(SENSOR_DOMAIN, DATA_SLAVE_MEMBERID),
+ self._get_entity_id(SENSOR_DOMAIN, DATA_BOILER_TEMPERATURE),
+ self._get_entity_id(SENSOR_DOMAIN, DATA_RETURN_TEMPERATURE),
+
+ self._get_entity_id(SENSOR_DOMAIN, DATA_DHW_SETPOINT_MINIMUM),
+ self._get_entity_id(SENSOR_DOMAIN, DATA_DHW_SETPOINT_MAXIMUM),
+
+ self._get_entity_id(SWITCH_DOMAIN, DATA_DHW_ENABLE),
+ self._get_entity_id(SWITCH_DOMAIN, DATA_CENTRAL_HEATING),
+
+ self._get_entity_id(NUMBER_DOMAIN, DATA_DHW_SETPOINT),
+ self._get_entity_id(NUMBER_DOMAIN, DATA_CONTROL_SETPOINT),
+ self._get_entity_id(NUMBER_DOMAIN, DATA_MAX_REL_MOD_LEVEL_SETTING),
+ ]))
+
+ # Track those entities so the coordinator can be updated when something changes
+ async_track_state_change_event(self.hass, entities, self.async_state_change_event)
+
+ await super().async_added_to_hass()
+
+ async def async_state_change_event(self, _event: Event):
+ if self._listeners:
+ self._schedule_refresh()
+
+ self.async_update_listeners()
+
+ async def async_set_control_setpoint(self, value: float) -> None:
+ await self._send_command_value(DATA_CONTROL_SETPOINT, value)
+
+ await super().async_set_control_setpoint(value)
+
+ async def async_set_control_hot_water_setpoint(self, value: float) -> None:
+ await self._send_command_value(DATA_DHW_SETPOINT, value)
+
+ await super().async_set_control_hot_water_setpoint(value)
+
+ async def async_set_heater_state(self, state: DeviceState) -> None:
+ await self._send_command_state(DATA_CENTRAL_HEATING, state == DeviceState.ON)
+
+ await super().async_set_heater_state(state)
+
+ async def async_set_control_max_relative_modulation(self, value: int) -> None:
+ await self._send_command_value(DATA_MAX_REL_MOD_LEVEL_SETTING, value)
+
+ await super().async_set_control_max_relative_modulation(value)
+
+ async def async_set_control_max_setpoint(self, value: float) -> None:
+ await self._send_command_value(DATA_MAX_CH_SETPOINT, value)
+
+ await super().async_set_control_max_setpoint(value)
+
+ def _get_entity_id(self, domain: str, key: str):
+ unique_id = f"{self._mac_address.upper()}-{domain}-{key}"
+ _LOGGER.debug(f"Attempting to find the unique_id of {unique_id}")
+ return self._entity_registry.async_get_entity_id(domain, ESPHOME_DOMAIN, unique_id)
+
+ async def _send_command(self, domain: str, service: str, _key: str, payload: dict):
+ """Helper method to send a command to a specified domain and service."""
+ if not self._simulation:
+ await self.hass.services.async_call(domain, service, payload, blocking=True)
+
+ _LOGGER.debug(f"Sending '{payload}' to {service} in {domain}.")
+
+ async def _send_command_state(self, key: str, value: bool):
+ """Send a command to turn a switch on or off."""
+ service = SERVICE_TURN_ON if value else SERVICE_TURN_OFF
+ payload = {"entity_id": self._get_entity_id(SWITCH_DOMAIN, key)}
+ await self._send_command(SWITCH_DOMAIN, service, key, payload)
+
+ async def _send_command_value(self, key: str, value: float):
+ """Send a command to set a numerical value."""
+ payload = {"entity_id": self._get_entity_id(NUMBER_DOMAIN, key), "value": value}
+ await self._send_command(NUMBER_DOMAIN, SERVICE_SET_VALUE, key, payload)
diff --git a/custom_components/sat/fake/__init__.py b/custom_components/sat/fake/__init__.py
index d95c73a5..cbdf8372 100644
--- a/custom_components/sat/fake/__init__.py
+++ b/custom_components/sat/fake/__init__.py
@@ -25,7 +25,17 @@ def __init__(
class SatFakeCoordinator(SatDataUpdateCoordinator):
- """Class to manage to fetch data from the OTGW Gateway using mqtt."""
+ @property
+ def device_id(self) -> str:
+ return "Fake"
+
+ @property
+ def device_type(self) -> str:
+ return "Fake"
+
+ @property
+ def member_id(self) -> int | None:
+ return -1
def __init__(self, hass: HomeAssistant, data: Mapping[str, Any], options: Mapping[str, Any] | None = None) -> None:
self.data = {}
@@ -65,6 +75,7 @@ def supports_hot_water_setpoint_management(self):
return self.config.supports_hot_water_setpoint_management
+ @property
def supports_maximum_setpoint_management(self):
if self.config is None:
return super().supports_maximum_setpoint_management
diff --git a/custom_components/sat/heating_curve.py b/custom_components/sat/heating_curve.py
index 77a7c08f..3197a95a 100644
--- a/custom_components/sat/heating_curve.py
+++ b/custom_components/sat/heating_curve.py
@@ -8,7 +8,7 @@
class HeatingCurve:
- def __init__(self, heating_system: str, coefficient: float, version: int = 2):
+ def __init__(self, heating_system: str, coefficient: float, version: int = 3):
"""
:param heating_system: The type of heating system, either "underfloor" or "radiator"
:param coefficient: The coefficient used to adjust the heating curve
@@ -72,10 +72,13 @@ def restore_autotune(self, coefficient: float, derivative: float):
def _get_heating_curve_value(self, target_temperature: float, outside_temperature: float) -> float:
"""Calculate the heating curve value based on the current outside temperature"""
- if self._version <= 1:
- return target_temperature - (0.01 * outside_temperature ** 2) - (0.8 * outside_temperature)
+ if self._version == 2:
+ return 2.72 * (target_temperature - 20) + 0.03 * (outside_temperature - 20) ** 2 - 1.2 * (outside_temperature - 20)
- return 2.72 * (target_temperature - 20) + 0.03 * (outside_temperature - 20) ** 2 - 1.2 * (outside_temperature - 20)
+ if self._version == 3:
+ return 4 * (target_temperature - 20) + 0.03 * (outside_temperature - 20) ** 2 - 0.4 * (outside_temperature - 20)
+
+ raise Exception("Invalid version")
@property
def base_offset(self) -> float:
diff --git a/custom_components/sat/manifest.json b/custom_components/sat/manifest.json
index 2f1207e5..d93de40e 100644
--- a/custom_components/sat/manifest.json
+++ b/custom_components/sat/manifest.json
@@ -6,7 +6,8 @@
],
"config_flow": true,
"dependencies": [
- "mqtt"
+ "mqtt",
+ "dhcp"
],
"dhcp": [
{
@@ -17,10 +18,12 @@
"iot_class": "local_push",
"issue_tracker": "https://github.com/Alexwijn/SAT/issues",
"mqtt": [
+ "ems-esp/#",
"OTGW/value/+"
],
"requirements": [
- "pyotgw==2.1.3"
+ "pyotgw==2.2.2",
+ "sentry-sdk==2.19.2"
],
- "version": "3.0.1"
-}
+ "version": "4.0.0"
+}
\ No newline at end of file
diff --git a/custom_components/sat/manufacturer.py b/custom_components/sat/manufacturer.py
new file mode 100644
index 00000000..077ddd9a
--- /dev/null
+++ b/custom_components/sat/manufacturer.py
@@ -0,0 +1,46 @@
+from abc import abstractmethod
+
+
+class Manufacturer:
+ @property
+ @abstractmethod
+ def name(self) -> str:
+ pass
+
+
+class ManufacturerFactory:
+ @abstractmethod
+ def resolve(self, member_id: int) -> Manufacturer | None:
+ if member_id == -1:
+ from .manufacturers.simulator import Simulator
+ return Simulator()
+
+ if member_id == 4:
+ from .manufacturers.geminox import Geminox
+ return Geminox()
+
+ if member_id == 6:
+ from .manufacturers.ideal import Ideal
+ return Ideal()
+
+ if member_id == 9:
+ from .manufacturers.ferroli import Ferroli
+ return Ferroli()
+
+ if member_id == 11:
+ from .manufacturers.dedietrich import DeDietrich
+ return DeDietrich()
+
+ if member_id == 27:
+ from .manufacturers.immergas import Immergas
+ return Immergas()
+
+ if member_id == 131:
+ from .manufacturers.nefit import Nefit
+ return Nefit()
+
+ if member_id == 173:
+ from .manufacturers.intergas import Intergas
+ return Intergas()
+
+ return None
diff --git a/custom_components/sat/manufacturers/dedietrich.py b/custom_components/sat/manufacturers/dedietrich.py
new file mode 100644
index 00000000..42e4b3b2
--- /dev/null
+++ b/custom_components/sat/manufacturers/dedietrich.py
@@ -0,0 +1,7 @@
+from ..manufacturer import Manufacturer
+
+
+class DeDietrich(Manufacturer):
+ @property
+ def name(self) -> str:
+ return 'De Dietrich'
diff --git a/custom_components/sat/manufacturers/ferroli.py b/custom_components/sat/manufacturers/ferroli.py
new file mode 100644
index 00000000..05ad5ce6
--- /dev/null
+++ b/custom_components/sat/manufacturers/ferroli.py
@@ -0,0 +1,7 @@
+from ..manufacturer import Manufacturer
+
+
+class Ferroli(Manufacturer):
+ @property
+ def name(self) -> str:
+ return 'Ferroli'
diff --git a/custom_components/sat/manufacturers/geminox.py b/custom_components/sat/manufacturers/geminox.py
new file mode 100644
index 00000000..358e83a4
--- /dev/null
+++ b/custom_components/sat/manufacturers/geminox.py
@@ -0,0 +1,7 @@
+from ..manufacturer import Manufacturer
+
+
+class Geminox(Manufacturer):
+ @property
+ def name(self) -> str:
+ return 'Geminox'
diff --git a/custom_components/sat/manufacturers/ideal.py b/custom_components/sat/manufacturers/ideal.py
new file mode 100644
index 00000000..18ebf5d4
--- /dev/null
+++ b/custom_components/sat/manufacturers/ideal.py
@@ -0,0 +1,7 @@
+from ..manufacturer import Manufacturer
+
+
+class Ideal(Manufacturer):
+ @property
+ def name(self) -> str:
+ return 'Ideal'
diff --git a/custom_components/sat/manufacturers/immergas.py b/custom_components/sat/manufacturers/immergas.py
new file mode 100644
index 00000000..08207733
--- /dev/null
+++ b/custom_components/sat/manufacturers/immergas.py
@@ -0,0 +1,7 @@
+from ..manufacturer import Manufacturer
+
+
+class Immergas(Manufacturer):
+ @property
+ def name(self) -> str:
+ return 'Immergas'
diff --git a/custom_components/sat/manufacturers/intergas.py b/custom_components/sat/manufacturers/intergas.py
new file mode 100644
index 00000000..a6c98760
--- /dev/null
+++ b/custom_components/sat/manufacturers/intergas.py
@@ -0,0 +1,7 @@
+from ..manufacturer import Manufacturer
+
+
+class Intergas(Manufacturer):
+ @property
+ def name(self) -> str:
+ return 'Intergas'
diff --git a/custom_components/sat/manufacturers/nefit.py b/custom_components/sat/manufacturers/nefit.py
new file mode 100644
index 00000000..4f2b9669
--- /dev/null
+++ b/custom_components/sat/manufacturers/nefit.py
@@ -0,0 +1,7 @@
+from ..manufacturer import Manufacturer
+
+
+class Nefit(Manufacturer):
+ @property
+ def name(self) -> str:
+ return 'Nefit'
diff --git a/custom_components/sat/manufacturers/simulator.py b/custom_components/sat/manufacturers/simulator.py
new file mode 100644
index 00000000..b4ae0293
--- /dev/null
+++ b/custom_components/sat/manufacturers/simulator.py
@@ -0,0 +1,7 @@
+from ..manufacturer import Manufacturer
+
+
+class Simulator(Manufacturer):
+ @property
+ def name(self) -> str:
+ return 'Simulator'
diff --git a/custom_components/sat/minimum_setpoint.py b/custom_components/sat/minimum_setpoint.py
index 388989a0..d73dc41b 100644
--- a/custom_components/sat/minimum_setpoint.py
+++ b/custom_components/sat/minimum_setpoint.py
@@ -1,118 +1,55 @@
-import hashlib
import logging
-import time
-from typing import List
from homeassistant.core import HomeAssistant
from homeassistant.helpers.storage import Store
-from custom_components.sat.coordinator import SatDataUpdateCoordinator
-
_LOGGER = logging.getLogger(__name__)
-def _is_valid(data):
- if not isinstance(data, dict):
- return False
-
- if not 'value' in data or not isinstance(data['value'], float):
- return False
-
- if not 'timestamp' in data or not isinstance(data['timestamp'], int):
- return False
-
- return True
-
-
class MinimumSetpoint:
_STORAGE_VERSION = 1
_STORAGE_KEY = "minimum_setpoint"
- def __init__(self, coordinator: SatDataUpdateCoordinator):
- self._alpha = 0.2
+ def __init__(self, adjustment_factor: float, configured_minimum_setpoint: float):
self._store = None
- self._adjusted_setpoints = {}
- self._coordinator = coordinator
- self._previous_adjusted_setpoint = None
+ self.base_return_temperature = None
+ self.current_minimum_setpoint = None
- @staticmethod
- def _get_cache_key(errors: List[float]) -> str:
- errors_str = ','.join(map(str, errors))
- cache_hash = hashlib.sha256(errors_str.encode('utf-8'))
- return cache_hash.hexdigest()
+ self.adjustment_factor = adjustment_factor
+ self.configured_minimum_setpoint = configured_minimum_setpoint
async def async_initialize(self, hass: HomeAssistant) -> None:
self._store = Store(hass, self._STORAGE_VERSION, self._STORAGE_KEY)
- if (adjusted_setpoints := await self._store.async_load()) is None:
- adjusted_setpoints = {}
-
- self._adjusted_setpoints = adjusted_setpoints
+ data = await self._store.async_load()
+ if data and "base_return_temperature" in data:
+ self.base_return_temperature = data["base_return_temperature"]
+ _LOGGER.debug("Loaded base return temperature from storage.")
- def calculate(self, setpoint: float, errors: List[float], adjustment_percentage=10):
- # Check for a valid setpoint
- if setpoint is None:
+ def warming_up(self, return_temperature: float) -> None:
+ if self.base_return_temperature is not None and self.base_return_temperature > return_temperature:
return
- # Calculate a cache key for adjusted setpoints
- hash_key = self._get_cache_key(errors)
+ # Use the new value if it's higher or none is set
+ self.base_return_temperature = return_temperature
+ _LOGGER.debug(f"Higher temperature set to: {return_temperature}.")
- # Extract relevant values from the coordinator for clarity
- boiler_temperature = self._coordinator.boiler_temperature
- target_setpoint_temperature = self._coordinator.setpoint
- is_flame_active = self._coordinator.flame_active
-
- # Check for None values
- if boiler_temperature is None or target_setpoint_temperature is None:
- return
+ # Make sure to remember this value
+ if self._store:
+ self._store.async_delay_save(self._data_to_save)
+ _LOGGER.debug("Stored base return temperature changes.")
- # Check for flame activity and if we are stable
- if not is_flame_active or abs(target_setpoint_temperature - boiler_temperature) <= 1:
+ def calculate(self, return_temperature: float) -> None:
+ if self.base_return_temperature is None:
return
- # Check if we are above configured minimum setpoint, does not make sense if we are below it
- if boiler_temperature <= self._coordinator.minimum_setpoint:
- return
-
- # Dynamically adjust the minimum setpoint
- adjustment_value = (adjustment_percentage / 100) * (target_setpoint_temperature - boiler_temperature)
- raw_adjusted_setpoint = max(boiler_temperature, target_setpoint_temperature - adjustment_value)
-
- adjusted_setpoint = raw_adjusted_setpoint
- if hash_key in self._adjusted_setpoints:
- # Determine some defaults
- previous_adjusted_setpoint = self._previous_adjusted_setpoint
- if setpoint in self._adjusted_setpoints[hash_key]:
- previous_adjusted_setpoint = self._adjusted_setpoints[hash_key][setpoint]['value']
-
- # Use the moving average to adjust the calculated setpoint
- if previous_adjusted_setpoint is not None:
- adjusted_setpoint = self._alpha * raw_adjusted_setpoint + (1 - self._alpha) * previous_adjusted_setpoint
- else:
- self._adjusted_setpoints[hash_key] = {}
-
- # Keep track of the adjusted setpoint and update the timestamp
- self._adjusted_setpoints[hash_key][setpoint] = {
- 'errors': errors,
- 'timestamp': int(time.time()),
- 'value': round(adjusted_setpoint, 1)
- }
-
- # Store previous value, so we have a moving value
- self._previous_adjusted_setpoint = round(adjusted_setpoint, 1)
-
- # Store the change calibration
- if self._store is not None:
- self._store.async_delay_save(lambda: self._adjusted_setpoints)
-
- def current(self, errors: List[float]) -> float:
- cache_key = self._get_cache_key(errors)
+ adjustment = (return_temperature - self.base_return_temperature) * self.adjustment_factor
+ self.current_minimum_setpoint = self.configured_minimum_setpoint + adjustment
- if (data := self._adjusted_setpoints.get(cache_key)) is None:
- return self._coordinator.minimum_setpoint + 2
+ _LOGGER.debug("Calculated new minimum setpoint: %d°C", self.current_minimum_setpoint)
- return min(data.values(), key=lambda x: x['value'])['value']
+ def current(self) -> float:
+ return self.current_minimum_setpoint if self.current_minimum_setpoint is not None else self.configured_minimum_setpoint
- @property
- def cache(self) -> dict[str, float]:
- return self._adjusted_setpoints
+ def _data_to_save(self) -> dict:
+ return {"base_return_temperature": self.base_return_temperature}
diff --git a/custom_components/sat/mqtt/__init__.py b/custom_components/sat/mqtt/__init__.py
index b6d0ff55..52a8e03e 100644
--- a/custom_components/sat/mqtt/__init__.py
+++ b/custom_components/sat/mqtt/__init__.py
@@ -1,228 +1,130 @@
-from __future__ import annotations, annotations
-
+import asyncio
import logging
-from typing import TYPE_CHECKING, Mapping, Any
+from abc import ABC, abstractmethod
+from typing import Mapping, Any
from homeassistant.components import mqtt
-from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
-from homeassistant.components.mqtt import DOMAIN as MQTT_DOMAIN
-from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
-from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN
-from homeassistant.core import HomeAssistant, Event
-from homeassistant.helpers import device_registry, entity_registry
-from homeassistant.helpers.event import async_track_state_change_event
-
-from ..const import *
-from ..coordinator import DeviceState, SatDataUpdateCoordinator
-
-DATA_FLAME_ACTIVE = "flame"
-DATA_DHW_SETPOINT = "TdhwSet"
-DATA_CONTROL_SETPOINT = "TSet"
-DATA_REL_MOD_LEVEL = "RelModLevel"
-DATA_BOILER_TEMPERATURE = "Tboiler"
-DATA_DHW_ENABLE = "domestichotwater"
-DATA_CENTRAL_HEATING = "centralheating"
-DATA_BOILER_CAPACITY = "MaxCapacityMinModLevel_hb_u8"
-DATA_REL_MIN_MOD_LEVEL = "MaxCapacityMinModLevel_lb_u8"
-DATA_REL_MIN_MOD_LEVELL = "MaxCapacityMinModLevell_lb_u8"
-DATA_MAX_REL_MOD_LEVEL_SETTING = "MaxRelModLevelSetting"
-DATA_DHW_SETPOINT_MINIMUM = "TdhwSetUBTdhwSetLB_value_lb"
-DATA_DHW_SETPOINT_MAXIMUM = "TdhwSetUBTdhwSetLB_value_hb"
-
-if TYPE_CHECKING:
- from ..climate import SatClimate
+from homeassistant.core import HomeAssistant, callback
+from homeassistant.helpers.storage import Store
+
+from ..const import CONF_MQTT_TOPIC
+from ..coordinator import SatDataUpdateCoordinator
+from ..util import snake_case
_LOGGER: logging.Logger = logging.getLogger(__name__)
+STORAGE_VERSION = 1
+
-class SatMqttCoordinator(SatDataUpdateCoordinator):
- """Class to manage to fetch data from the OTGW Gateway using mqtt."""
+class SatMqttCoordinator(ABC, SatDataUpdateCoordinator):
+ """Base class to manage fetching data using MQTT."""
def __init__(self, hass: HomeAssistant, device_id: str, data: Mapping[str, Any], options: Mapping[str, Any] | None = None) -> None:
super().__init__(hass, data, options)
self.data = {}
-
- self._device = device_registry.async_get(hass).async_get(device_id)
- self._node_id = list(self._device.identifiers)[0][1]
+ self._device_id = device_id
self._topic = data.get(CONF_MQTT_TOPIC)
-
- self._entity_registry = entity_registry.async_get(hass)
- self._entities = entity_registry.async_entries_for_device(self._entity_registry, self._device.id)
-
- @property
- def supports_setpoint_management(self):
- return True
-
- @property
- def supports_hot_water_setpoint_management(self):
- return True
-
- def supports_maximum_setpoint_management(self):
- return True
-
- @property
- def supports_relative_modulation_management(self):
- return True
-
- @property
- def device_active(self) -> bool:
- return self._get_entity_state(BINARY_SENSOR_DOMAIN, DATA_CENTRAL_HEATING) == DeviceState.ON
-
- @property
- def flame_active(self) -> bool:
- return self._get_entity_state(BINARY_SENSOR_DOMAIN, DATA_FLAME_ACTIVE) == DeviceState.ON
-
- @property
- def hot_water_active(self) -> bool:
- return self._get_entity_state(BINARY_SENSOR_DOMAIN, DATA_DHW_ENABLE) == DeviceState.ON
-
- @property
- def setpoint(self) -> float | None:
- if (setpoint := self._get_entity_state(SENSOR_DOMAIN, DATA_CONTROL_SETPOINT)) is not None:
- return float(setpoint)
-
- return None
+ self._store = Store(hass, STORAGE_VERSION, snake_case(f"{self.__class__.__name__}_{device_id}"))
@property
- def hot_water_setpoint(self) -> float | None:
- if (setpoint := self._get_entity_state(SENSOR_DOMAIN, DATA_DHW_SETPOINT)) is not None:
- return float(setpoint)
+ def device_id(self) -> str:
+ return self._device_id
- return super().hot_water_setpoint
+ async def async_setup(self):
+ await self._load_stored_data()
- @property
- def minimum_hot_water_setpoint(self) -> float:
- if (setpoint := self._get_entity_state(SENSOR_DOMAIN, DATA_DHW_SETPOINT_MINIMUM)) is not None:
- return float(setpoint)
-
- return super().minimum_hot_water_setpoint
-
- @property
- def maximum_hot_water_setpoint(self) -> float | None:
- if (setpoint := self._get_entity_state(SENSOR_DOMAIN, DATA_DHW_SETPOINT_MAXIMUM)) is not None:
- return float(setpoint)
-
- return super().maximum_hot_water_setpoint
-
- @property
- def boiler_temperature(self) -> float | None:
- if (value := self._get_entity_state(SENSOR_DOMAIN, DATA_BOILER_TEMPERATURE)) is not None:
- return float(value)
-
- return super().boiler_temperature
-
- @property
- def relative_modulation_value(self) -> float | None:
- if (value := self._get_entity_state(SENSOR_DOMAIN, DATA_REL_MOD_LEVEL)) is not None:
- return float(value)
-
- return super().relative_modulation_value
-
- @property
- def boiler_capacity(self) -> float | None:
- if (value := self._get_entity_state(SENSOR_DOMAIN, DATA_BOILER_CAPACITY)) is not None:
- return float(value)
-
- return super().boiler_capacity
-
- @property
- def minimum_relative_modulation_value(self) -> float | None:
- if (value := self._get_entity_state(SENSOR_DOMAIN, DATA_REL_MIN_MOD_LEVEL)) is not None:
- return float(value)
+ async def async_added_to_hass(self) -> None:
+ await mqtt.async_wait_for_mqtt_client(self.hass)
- # Legacy
- if (value := self._get_entity_state(SENSOR_DOMAIN, DATA_REL_MIN_MOD_LEVELL)) is not None:
- return float(value)
+ for key in self.get_tracked_entities():
+ await mqtt.async_subscribe(
+ self.hass,
+ self._get_topic_for_subscription(key),
+ self._create_message_handler(key)
+ )
- return super().minimum_relative_modulation_value
+ await self.boot()
- @property
- def maximum_relative_modulation_value(self) -> float | None:
- if (value := self._get_entity_state(SENSOR_DOMAIN, DATA_MAX_REL_MOD_LEVEL_SETTING)) is not None:
- return float(value)
+ await super().async_added_to_hass()
- return super().maximum_relative_modulation_value
+ async def async_will_remove_from_hass(self) -> None:
+ # Save the updated data to persistent storage
+ await self._save_data()
- async def async_added_to_hass(self, climate: SatClimate) -> None:
- await mqtt.async_wait_for_mqtt_client(self.hass)
-
- # Create a list of entities that we track
- entities = list(filter(lambda entity: entity is not None, [
- self._get_entity_id(BINARY_SENSOR_DOMAIN, DATA_CENTRAL_HEATING),
- self._get_entity_id(BINARY_SENSOR_DOMAIN, DATA_FLAME_ACTIVE),
- self._get_entity_id(BINARY_SENSOR_DOMAIN, DATA_DHW_ENABLE),
-
- self._get_entity_id(SENSOR_DOMAIN, DATA_DHW_SETPOINT),
- self._get_entity_id(SENSOR_DOMAIN, DATA_CONTROL_SETPOINT),
- self._get_entity_id(SENSOR_DOMAIN, DATA_REL_MOD_LEVEL),
- self._get_entity_id(SENSOR_DOMAIN, DATA_BOILER_TEMPERATURE),
- self._get_entity_id(SENSOR_DOMAIN, DATA_BOILER_CAPACITY),
- self._get_entity_id(SENSOR_DOMAIN, DATA_REL_MIN_MOD_LEVEL),
- self._get_entity_id(SENSOR_DOMAIN, DATA_REL_MIN_MOD_LEVELL),
- self._get_entity_id(SENSOR_DOMAIN, DATA_MAX_REL_MOD_LEVEL_SETTING),
- self._get_entity_id(SENSOR_DOMAIN, DATA_DHW_SETPOINT_MINIMUM),
- self._get_entity_id(SENSOR_DOMAIN, DATA_DHW_SETPOINT_MAXIMUM),
- ]))
-
- # Track those entities so the coordinator can be updated when something changes
- async_track_state_change_event(self.hass, entities, self.async_state_change_event)
-
- await self._send_command("PM=48")
- await super().async_added_to_hass(climate)
-
- async def async_state_change_event(self, event: Event):
- if self._listeners:
- self._schedule_refresh()
+ async def async_notify_listeners(self):
+ """Notify listeners of an update asynchronously."""
+ # Make sure we do not spam
+ self._async_unsub_refresh()
+ self._debounced_refresh.async_cancel()
+ # Inform the listeners that we are updated
self.async_update_listeners()
- async def async_set_control_setpoint(self, value: float) -> None:
- await self._send_command(f"CS={value}")
-
- await super().async_set_control_setpoint(value)
+ async def _load_stored_data(self) -> None:
+ """Load the data from persistent storage."""
+ if stored_data := await self._store.async_load():
+ self.data.update({key: value for key, value in stored_data.items() if value not in (None, "")})
- async def async_set_control_hot_water_setpoint(self, value: float) -> None:
- await self._send_command(f"SW={value}")
+ async def _save_data(self) -> None:
+ """Save the data to persistent storage."""
+ await self._store.async_save(self.data)
- await super().async_set_control_hot_water_setpoint(value)
+ @abstractmethod
+ def get_tracked_entities(self) -> list[str]:
+ """Method to be overridden in subclasses to provide specific entities to track."""
+ pass
- async def async_set_control_thermostat_setpoint(self, value: float) -> None:
- await self._send_command(f"TC={value}")
+ @abstractmethod
+ def _get_topic_for_subscription(self, key: str) -> str:
+ """Method to be overridden in subclasses to provide a specific topic for subscribing."""
+ pass
- await super().async_set_control_thermostat_setpoint(value)
+ @abstractmethod
+ def _get_topic_for_publishing(self) -> str:
+ """Method to be overridden in subclasses to provide a specific topic for publishing."""
+ pass
- async def async_set_heater_state(self, state: DeviceState) -> None:
- await self._send_command(f"CH={1 if state == DeviceState.ON else 0}")
+ @abstractmethod
+ async def boot(self) -> None:
+ """Method to be overridden in subclasses to provide specific boot functionality."""
+ pass
- await super().async_set_heater_state(state)
+ def _create_message_handler(self, key: str):
+ """Create a message handler to process incoming MQTT messages."""
- async def async_set_control_max_relative_modulation(self, value: int) -> None:
- await self._send_command(f"MM={value}")
+ @callback
+ def message_handler(message):
+ """Handle an incoming MQTT message and schedule an update."""
- await super().async_set_control_max_relative_modulation(value)
+ try:
+ # Process the payload and update the data property
+ self._process_message_payload(key, message.payload)
+ except Exception as e:
+ _LOGGER.error("Failed to process message for key '%s': %s", key, str(e))
- async def async_set_control_max_setpoint(self, value: float) -> None:
- await self._send_command(f"SH={value}")
+ # Notify listeners to ensure the entities are updated
+ self.hass.async_create_task(self.async_notify_listeners())
- await super().async_set_control_max_setpoint(value)
+ return message_handler
- def _get_entity_state(self, domain: str, key: str):
- entity_id = self._get_entity_id(domain, key)
- if entity_id is None:
- return None
+ def _process_message_payload(self, key: str, payload):
+ """Process and store the payload of a received MQTT message."""
+ self.data[key] = payload
- state = self.hass.states.get(self._get_entity_id(domain, key))
- if state is None or state.state in (STATE_UNAVAILABLE, STATE_UNKNOWN):
- return None
+ async def _publish_command(self, payload: str, wait_time: float = 1.0):
+ """Publish a command to the MQTT topic."""
+ topic = self._get_topic_for_publishing()
- return state.state
+ _LOGGER.debug("Publishing MQTT command: payload='%s', topic='%s', simulation='%s'", payload, topic, self._simulation)
- def _get_entity_id(self, domain: str, key: str):
- return self._entity_registry.async_get_entity_id(domain, MQTT_DOMAIN, f"{self._node_id}-{key}")
+ if self._simulation:
+ return
- async def _send_command(self, payload: str):
- if not self._simulation:
- await mqtt.async_publish(self.hass, f"{self._topic}/set/{self._node_id}/command", payload)
+ try:
+ await mqtt.async_publish(hass=self.hass, topic=topic, payload=payload, qos=1)
- _LOGGER.debug(f"Publishing '{payload}' to MQTT.")
+ # Add a small delay to allow processing of the message
+ await asyncio.sleep(wait_time)
+ except Exception as error:
+ _LOGGER.error("Failed to publish MQTT command. Error: %s", error)
diff --git a/custom_components/sat/mqtt/ems.py b/custom_components/sat/mqtt/ems.py
new file mode 100644
index 00000000..c7ad1f3d
--- /dev/null
+++ b/custom_components/sat/mqtt/ems.py
@@ -0,0 +1,149 @@
+from __future__ import annotations
+
+import json
+import logging
+
+from . import SatMqttCoordinator
+from ..coordinator import DeviceState
+from ..util import float_value
+
+DATA_ON = "on"
+DATA_OFF = "off"
+
+DATA_BOILER_DATA = "boiler_data"
+DATA_FLAME_ACTIVE = "burngas"
+DATA_DHW_SETPOINT = "dhw/seltemp"
+DATA_CONTROL_SETPOINT = "selflowtemp"
+DATA_REL_MOD_LEVEL = "curburnpow"
+DATA_BOILER_TEMPERATURE = "curflowtemp"
+DATA_RETURN_TEMPERATURE = "rettemp"
+
+DATA_DHW_ENABLE = "tapactivated"
+DATA_CENTRAL_HEATING = "heatingactive"
+DATA_BOILER_CAPACITY = "nompower"
+
+DATA_REL_MIN_MOD_LEVEL = "burnminpower"
+DATA_MAX_REL_MOD_LEVEL_SETTING = "burnmaxpower"
+
+_LOGGER: logging.Logger = logging.getLogger(__name__)
+
+
+class SatEmsMqttCoordinator(SatMqttCoordinator):
+ """Class to manage fetching data from the OTGW Gateway using MQTT."""
+
+ @property
+ def device_type(self) -> str:
+ return "Energy Management System (via mqtt)"
+
+ @property
+ def supports_setpoint_management(self) -> bool:
+ return True
+
+ @property
+ def supports_hot_water_setpoint_management(self) -> bool:
+ return True
+
+ @property
+ def supports_maximum_setpoint_management(self) -> bool:
+ return True
+
+ @property
+ def supports_relative_modulation_management(self) -> bool:
+ return True
+
+ @property
+ def device_active(self) -> bool:
+ return self.data.get(DATA_CENTRAL_HEATING) == DATA_ON
+
+ @property
+ def flame_active(self) -> bool:
+ return self.data.get(DATA_FLAME_ACTIVE) == DATA_ON
+
+ @property
+ def hot_water_active(self) -> bool:
+ return self.data.get(DATA_DHW_ENABLE) == DATA_ON
+
+ @property
+ def setpoint(self) -> float | None:
+ return float_value(self.data.get(DATA_CONTROL_SETPOINT))
+
+ @property
+ def hot_water_setpoint(self) -> float | None:
+ return float_value(self.data.get(DATA_DHW_SETPOINT))
+
+ @property
+ def boiler_temperature(self) -> float | None:
+ return float_value(self.data.get(DATA_BOILER_TEMPERATURE))
+
+ @property
+ def return_temperature(self) -> float | None:
+ return float_value(self.data.get(DATA_RETURN_TEMPERATURE))
+
+ @property
+ def relative_modulation_value(self) -> float | None:
+ return float_value(self.data.get(DATA_REL_MOD_LEVEL))
+
+ @property
+ def boiler_capacity(self) -> float | None:
+ return float_value(self.data.get(DATA_BOILER_CAPACITY))
+
+ @property
+ def minimum_relative_modulation_value(self) -> float | None:
+ return float_value(self.data.get(DATA_REL_MIN_MOD_LEVEL))
+
+ @property
+ def maximum_relative_modulation_value(self) -> float | None:
+ return float_value(self.data.get(DATA_MAX_REL_MOD_LEVEL_SETTING))
+
+ @property
+ def member_id(self) -> int | None:
+ # Not supported (yet)
+ return None
+
+ async def boot(self) -> SatMqttCoordinator:
+ # Nothing needs to be booted (yet)
+ return self
+
+ def get_tracked_entities(self) -> list[str]:
+ return [DATA_BOILER_DATA]
+
+ async def async_set_control_setpoint(self, value: float) -> None:
+ await self._publish_command(f'{{"cmd": "selflowtemp", "value": {0 if value == 10 else value}}}')
+
+ await super().async_set_control_setpoint(value)
+
+ async def async_set_control_hot_water_setpoint(self, value: float) -> None:
+ await self._publish_command(f'{{"cmd": "dhw/seltemp", "value": {value}}}')
+
+ await super().async_set_control_hot_water_setpoint(value)
+
+ async def async_set_control_thermostat_setpoint(self, value: float) -> None:
+ # Not supported (yet)
+ await super().async_set_control_thermostat_setpoint(value)
+
+ async def async_set_heater_state(self, state: DeviceState) -> None:
+ await self._publish_command(f'{{"cmd": "heatingoff", "value": "{DATA_OFF if state == DeviceState.ON else DATA_ON}"}}')
+
+ await super().async_set_heater_state(state)
+
+ async def async_set_control_max_relative_modulation(self, value: int) -> None:
+ await self._publish_command(f'{{"cmd": "burnmaxpower", "value": {max(value, 20)}}}')
+
+ await super().async_set_control_max_relative_modulation(value)
+
+ async def async_set_control_max_setpoint(self, value: float) -> None:
+ await self._publish_command(f'{{"cmd": "heatingtemp", "value": {value}}}')
+
+ await super().async_set_control_max_setpoint(value)
+
+ def _get_topic_for_subscription(self, key: str) -> str:
+ return f"{self._topic}/{key}"
+
+ def _get_topic_for_publishing(self) -> str:
+ return f"{self._topic}/boiler"
+
+ def _process_message_payload(self, key: str, payload):
+ try:
+ self.data = json.loads(payload)
+ except json.JSONDecodeError as error:
+ _LOGGER.error("Failed to decode JSON payload: %s. Error: %s", payload, error)
diff --git a/custom_components/sat/mqtt/opentherm.py b/custom_components/sat/mqtt/opentherm.py
new file mode 100644
index 00000000..8185eaec
--- /dev/null
+++ b/custom_components/sat/mqtt/opentherm.py
@@ -0,0 +1,206 @@
+from __future__ import annotations
+
+import logging
+
+from . import SatMqttCoordinator
+from ..coordinator import DeviceState
+from ..manufacturers.immergas import Immergas
+
+STATE_ON = "ON"
+
+DATA_FLAME_ACTIVE = "flame"
+DATA_DHW_SETPOINT = "TdhwSet"
+DATA_CONTROL_SETPOINT = "TSet"
+DATA_REL_MOD_LEVEL = "RelModLevel"
+DATA_BOILER_TEMPERATURE = "Tboiler"
+DATA_RETURN_TEMPERATURE = "Tret"
+DATA_DHW_ENABLE = "domestichotwater"
+DATA_CENTRAL_HEATING = "centralheating"
+DATA_SLAVE_MEMBERID = "slave_memberid_code"
+DATA_BOILER_CAPACITY = "MaxCapacityMinModLevel_hb_u8"
+DATA_REL_MIN_MOD_LEVEL = "MaxCapacityMinModLevel_lb_u8"
+DATA_REL_MIN_MOD_LEVEL_LEGACY = "MaxCapacityMinModLevell_lb_u8"
+DATA_MAX_REL_MOD_LEVEL_SETTING = "MaxRelModLevelSetting"
+DATA_DHW_SETPOINT_MINIMUM = "TdhwSetUBTdhwSetLB_value_lb"
+DATA_DHW_SETPOINT_MAXIMUM = "TdhwSetUBTdhwSetLB_value_hb"
+
+_LOGGER: logging.Logger = logging.getLogger(__name__)
+
+
+class SatOpenThermMqttCoordinator(SatMqttCoordinator):
+ """Class to manage to fetch data from the OTGW Gateway using mqtt."""
+
+ @property
+ def device_type(self) -> str:
+ return "OpenThermGateway (via mqtt)"
+
+ @property
+ def supports_setpoint_management(self):
+ return True
+
+ @property
+ def supports_hot_water_setpoint_management(self):
+ return True
+
+ @property
+ def supports_maximum_setpoint_management(self):
+ return True
+
+ @property
+ def supports_relative_modulation_management(self):
+ return True
+
+ @property
+ def device_active(self) -> bool:
+ return self.data.get(DATA_CENTRAL_HEATING) == STATE_ON
+
+ @property
+ def flame_active(self) -> bool:
+ return self.data.get(DATA_FLAME_ACTIVE) == STATE_ON
+
+ @property
+ def hot_water_active(self) -> bool:
+ return self.data.get(DATA_DHW_ENABLE) == STATE_ON
+
+ @property
+ def setpoint(self) -> float | None:
+ if (setpoint := self.data.get(DATA_CONTROL_SETPOINT)) is not None:
+ return float(setpoint)
+
+ return None
+
+ @property
+ def hot_water_setpoint(self) -> float | None:
+ if (setpoint := self.data.get(DATA_DHW_SETPOINT)) is not None:
+ return float(setpoint)
+
+ return super().hot_water_setpoint
+
+ @property
+ def minimum_hot_water_setpoint(self) -> float:
+ if (setpoint := self.data.get(DATA_DHW_SETPOINT_MINIMUM)) is not None:
+ return float(setpoint)
+
+ return super().minimum_hot_water_setpoint
+
+ @property
+ def maximum_hot_water_setpoint(self) -> float | None:
+ if (setpoint := self.data.get(DATA_DHW_SETPOINT_MAXIMUM)) is not None:
+ return float(setpoint)
+
+ return super().maximum_hot_water_setpoint
+
+ @property
+ def boiler_temperature(self) -> float | None:
+ if (value := self.data.get(DATA_BOILER_TEMPERATURE)) is not None:
+ return float(value)
+
+ return super().boiler_temperature
+
+ @property
+ def return_temperature(self) -> float | None:
+ if (value := self.data.get(DATA_RETURN_TEMPERATURE)) is not None:
+ return float(value)
+
+ return super().return_temperature
+
+ @property
+ def relative_modulation_value(self) -> float | None:
+ if (value := self.data.get(DATA_REL_MOD_LEVEL)) is not None:
+ return float(value)
+
+ return super().relative_modulation_value
+
+ @property
+ def boiler_capacity(self) -> float | None:
+ if (value := self.data.get(DATA_BOILER_CAPACITY)) is not None:
+ return float(value)
+
+ return super().boiler_capacity
+
+ @property
+ def minimum_relative_modulation_value(self) -> float | None:
+ if (value := self.data.get(DATA_REL_MIN_MOD_LEVEL)) is not None:
+ return float(value)
+
+ # Legacy
+ if (value := self.data.get(DATA_REL_MIN_MOD_LEVEL_LEGACY)) is not None:
+ return float(value)
+
+ return super().minimum_relative_modulation_value
+
+ @property
+ def maximum_relative_modulation_value(self) -> float | None:
+ if (value := self.data.get(DATA_MAX_REL_MOD_LEVEL_SETTING)) is not None:
+ return float(value)
+
+ return super().maximum_relative_modulation_value
+
+ @property
+ def member_id(self) -> int | None:
+ if (value := self.data.get(DATA_SLAVE_MEMBERID)) is not None:
+ return int(value)
+
+ return None
+
+ async def boot(self) -> None:
+ await self._publish_command("PM=3")
+ await self._publish_command("PM=48")
+
+ def get_tracked_entities(self) -> list[str]:
+ return [
+ DATA_SLAVE_MEMBERID,
+ DATA_CENTRAL_HEATING,
+ DATA_FLAME_ACTIVE,
+ DATA_DHW_ENABLE,
+ DATA_DHW_SETPOINT,
+ DATA_CONTROL_SETPOINT,
+ DATA_REL_MOD_LEVEL,
+ DATA_BOILER_TEMPERATURE,
+ DATA_RETURN_TEMPERATURE,
+ DATA_BOILER_CAPACITY,
+ DATA_REL_MIN_MOD_LEVEL,
+ DATA_REL_MIN_MOD_LEVEL_LEGACY,
+ DATA_MAX_REL_MOD_LEVEL_SETTING,
+ DATA_DHW_SETPOINT_MINIMUM,
+ DATA_DHW_SETPOINT_MAXIMUM,
+ ]
+
+ async def async_set_control_setpoint(self, value: float) -> None:
+ await self._publish_command(f"CS={value}")
+
+ await super().async_set_control_setpoint(value)
+
+ async def async_set_control_hot_water_setpoint(self, value: float) -> None:
+ await self._publish_command(f"SW={value}")
+
+ await super().async_set_control_hot_water_setpoint(value)
+
+ async def async_set_control_thermostat_setpoint(self, value: float) -> None:
+ await self._publish_command(f"TC={value}")
+
+ await super().async_set_control_thermostat_setpoint(value)
+
+ async def async_set_heater_state(self, state: DeviceState) -> None:
+ await self._publish_command(f"CH={1 if state == DeviceState.ON else 0}")
+
+ await super().async_set_heater_state(state)
+
+ async def async_set_control_max_relative_modulation(self, value: int) -> None:
+ if isinstance(self.manufacturer, Immergas):
+ await self._publish_command(f"TP=11:12={min(value, 80)}")
+
+ await self._publish_command(f"MM={value}")
+
+ await super().async_set_control_max_relative_modulation(value)
+
+ async def async_set_control_max_setpoint(self, value: float) -> None:
+ await self._publish_command(f"SH={value}")
+
+ await super().async_set_control_max_setpoint(value)
+
+ def _get_topic_for_subscription(self, key: str) -> str:
+ return f"{self._topic}/value/{self._device_id}/{key}"
+
+ def _get_topic_for_publishing(self) -> str:
+ return f"{self._topic}/set/{self._device_id}/command"
diff --git a/custom_components/sat/overshoot_protection.py b/custom_components/sat/overshoot_protection.py
index a1ab1238..4fd7a38b 100644
--- a/custom_components/sat/overshoot_protection.py
+++ b/custom_components/sat/overshoot_protection.py
@@ -1,97 +1,118 @@
import asyncio
import logging
+import time
-from custom_components.sat.const import *
-from custom_components.sat.coordinator import DeviceState, SatDataUpdateCoordinator
+from .const import OVERSHOOT_PROTECTION_SETPOINT, MINIMUM_SETPOINT, DEADBAND, MAXIMUM_RELATIVE_MOD
+from .coordinator import DeviceState, SatDataUpdateCoordinator
_LOGGER = logging.getLogger(__name__)
-OVERSHOOT_PROTECTION_TIMEOUT = 7200 # Two hours in seconds
-OVERSHOOT_PROTECTION_INITIAL_WAIT = 180 # Three minutes in seconds
+# Constants for timeouts and intervals
+OVERSHOOT_PROTECTION_INITIAL_WAIT = 300 # Five minutes in seconds
+OVERSHOOT_PROTECTION_STABLE_WAIT = 900 # Fifteen minutes in seconds
+OVERSHOOT_PROTECTION_RELATIVE_MODULATION_WAIT = 300 # Five minutes in seconds
+SLEEP_INTERVAL = 15 # Sleep interval in seconds
class OvershootProtection:
- def __init__(self, coordinator: SatDataUpdateCoordinator):
- self._alpha = 0.2
+ def __init__(self, coordinator: SatDataUpdateCoordinator, heating_system: str):
+ """Initialize OvershootProtection with a coordinator and heating system configuration."""
+ self._alpha = 0.5
self._coordinator = coordinator
+ self._stable_temperature = None
+ self._setpoint = OVERSHOOT_PROTECTION_SETPOINT.get(heating_system)
- async def calculate(self) -> float | None:
- _LOGGER.info("Starting calculation")
-
- await self._coordinator.async_set_heater_state(DeviceState.ON)
+ if self._setpoint is None:
+ raise ValueError(f"Invalid heating system: {heating_system}")
+ async def calculate(self) -> float | None:
+ """Calculate the overshoot protection value."""
try:
- # First wait for a flame
+ _LOGGER.info("Starting overshoot protection calculation")
+
+ # Sequentially ensure the system is ready
await asyncio.wait_for(self._wait_for_flame(), timeout=OVERSHOOT_PROTECTION_INITIAL_WAIT)
- # Since the coordinator doesn't support modulation management, so we need to fall back to find it with modulation
- if not self._coordinator.supports_relative_modulation_management:
- return await self._calculate_with_no_modulation_management()
+ # Wait for a stable temperature
+ await asyncio.wait_for(self._wait_for_stable_temperature(), timeout=OVERSHOOT_PROTECTION_STABLE_WAIT)
- # Run with maximum power of the boiler, zero modulation.
- return await self._calculate_with_zero_modulation()
- except asyncio.TimeoutError:
- _LOGGER.warning("Timed out waiting for stable temperature")
- return None
+ # Wait a bit before calculating the overshoot value, if required
+ if self._coordinator.relative_modulation_value > 0:
+ await self._wait_a_moment(OVERSHOOT_PROTECTION_RELATIVE_MODULATION_WAIT)
+
+ return self._calculate_overshoot_value()
except asyncio.CancelledError as exception:
await self._coordinator.async_set_heater_state(DeviceState.OFF)
await self._coordinator.async_set_control_setpoint(MINIMUM_SETPOINT)
- await self._coordinator.async_set_control_max_relative_modulation(MAXIMUM_RELATIVE_MOD)
raise exception
- async def _calculate_with_zero_modulation(self) -> float:
- _LOGGER.info("Running calculation with zero modulation")
- await self._coordinator.async_set_control_max_relative_modulation(MINIMUM_RELATIVE_MOD)
+ async def _wait_for_flame(self) -> None:
+ """Wait until the heating system flame is active."""
+ while not self._coordinator.flame_active:
+ _LOGGER.warning("Waiting for heating system to start")
+ await self._trigger_heating_cycle(is_ready=False)
- try:
- return await asyncio.wait_for(
- self._wait_for_stable_temperature(0),
- timeout=OVERSHOOT_PROTECTION_TIMEOUT,
- )
- except asyncio.TimeoutError:
- _LOGGER.warning("Timed out waiting for stable temperature")
+ _LOGGER.info("Heating system has started")
- async def _calculate_with_no_modulation_management(self) -> float:
- _LOGGER.info("Running calculation with no modulation management")
+ async def _wait_a_moment(self, wait_time: int) -> None:
+ """Wait until the relative modulation stabilizes."""
- try:
- return await asyncio.wait_for(
- self._wait_for_stable_temperature(100),
- timeout=OVERSHOOT_PROTECTION_TIMEOUT,
- )
- except asyncio.TimeoutError:
- _LOGGER.warning("Timed out waiting for stable temperature")
-
- async def _wait_for_flame(self):
- while True:
- if bool(self._coordinator.flame_active):
- _LOGGER.info("Heating system has started to run")
- break
+ start_time = time.time()
+ while time.time() - start_time < wait_time:
+ await self._trigger_heating_cycle(True)
+ await asyncio.sleep(SLEEP_INTERVAL)
- _LOGGER.warning("Heating system is not running yet")
- await self._coordinator.async_set_control_setpoint(OVERSHOOT_PROTECTION_SETPOINT)
+ async def _wait_for_stable_temperature(self) -> None:
+ """Wait until the boiler temperature stabilizes, influenced by relative modulation."""
+ while not self._coordinator.boiler_temperature:
+ _LOGGER.warning("Waiting for boiler temperature")
- await asyncio.sleep(5)
- await self._coordinator.async_control_heating_loop()
-
- async def _wait_for_stable_temperature(self, max_modulation: float) -> float:
- previous_average_temperature = float(self._coordinator.boiler_temperature)
+ starting_temperature = self._coordinator.boiler_temperature
+ previous_average_temperature = self._coordinator.boiler_temperature
while True:
- actual_temperature = float(self._coordinator.boiler_temperature)
- average_temperature = self._alpha * actual_temperature + (1 - self._alpha) * previous_average_temperature
+ current_temperature = float(self._coordinator.boiler_temperature)
+ average_temperature, error_value = self._calculate_exponential_moving_average(previous_average_temperature, current_temperature)
+
+ if current_temperature > starting_temperature and error_value <= DEADBAND:
+ self._stable_temperature = current_temperature
+ _LOGGER.info("Stable temperature reached: %.2f°C", current_temperature)
+ return
- if previous_average_temperature is not None and abs(actual_temperature - previous_average_temperature) <= DEADBAND:
- _LOGGER.info("Stable temperature reached: %s", actual_temperature)
- return actual_temperature
+ await self._trigger_heating_cycle(is_ready=True)
previous_average_temperature = average_temperature
+ _LOGGER.warning("Waiting for a stable temperature")
+ _LOGGER.debug("Temperature: %s°C, Error: %s°C", current_temperature, error_value)
+
+ def _calculate_overshoot_value(self) -> float:
+ """Calculate and log the overshoot value."""
+ if self._coordinator.relative_modulation_value == 0:
+ return self._stable_temperature
+
+ return (100 - self._coordinator.relative_modulation_value) / 100 * self._setpoint
+
+ def _calculate_exponential_moving_average(self, previous_average: float, current_value: float) -> tuple[float, float]:
+ """Calculate the exponential moving average and error."""
+ average_value = self._alpha * current_value + (1 - self._alpha) * previous_average
+ error_value = abs(current_value - previous_average)
+ return average_value, error_value
+
+ async def _trigger_heating_cycle(self, is_ready: bool) -> None:
+ """Trigger a heating cycle with the coordinator."""
+ await self._coordinator.async_set_heater_state(DeviceState.ON)
+ await self._coordinator.async_set_control_setpoint(await self._get_setpoint(is_ready))
+ await self._coordinator.async_set_control_max_relative_modulation(MAXIMUM_RELATIVE_MOD)
+
+ await asyncio.sleep(SLEEP_INTERVAL)
+ await self._coordinator.async_control_heating_loop()
- if max_modulation > 0:
- await self._coordinator.async_set_control_setpoint(actual_temperature)
- else:
- await self._coordinator.async_set_control_setpoint(OVERSHOOT_PROTECTION_SETPOINT)
+ async def _get_setpoint(self, is_ready) -> float:
+ """Get the setpoint for the heating cycle."""
+ return self._setpoint if not is_ready or self._coordinator.relative_modulation_value > 0 else self._coordinator.boiler_temperature
- await asyncio.sleep(2)
- await self._coordinator.async_control_heating_loop()
+ async def _reset_heater_state(self) -> None:
+ """Reset the heater state to default settings."""
+ await self._coordinator.async_set_heater_state(DeviceState.OFF)
+ await self._coordinator.async_set_control_setpoint(MINIMUM_SETPOINT)
diff --git a/custom_components/sat/pid.py b/custom_components/sat/pid.py
index 05664bc4..eca7dbe2 100644
--- a/custom_components/sat/pid.py
+++ b/custom_components/sat/pid.py
@@ -18,19 +18,21 @@ class PID:
def __init__(self,
heating_system: str,
automatic_gain_value: float,
+ heating_curve_coefficient: float,
derivative_time_weight: float,
kp: float, ki: float, kd: float,
max_history: int = 2,
deadband: float = DEADBAND,
automatic_gains: bool = False,
integral_time_limit: float = 300,
- sample_time_limit: Optional[float] = 10):
+ sample_time_limit: Optional[float] = 10,
+ version: int = 3):
"""
Initialize the PID controller.
:param heating_system: The type of heating system, either "underfloor" or "radiator"
- :param automatic_gain_value: The value to finetune the aggression value.
- :param derivative_time_weight: The weight to finetune the derivative.
+ :param automatic_gain_value: The value to fine-tune the aggression value.
+ :param derivative_time_weight: The weight to fine-tune the derivative.
:param kp: The proportional gain of the PID controller.
:param ki: The integral gain of the PID controller.
:param kd: The derivative gain of the PID controller.
@@ -38,16 +40,19 @@ def __init__(self,
:param deadband: The deadband of the PID controller. The range of error values where the controller will not make adjustments.
:param integral_time_limit: The minimum time interval between integral updates to the PID controller, in seconds.
:param sample_time_limit: The minimum time interval between updates to the PID controller, in seconds.
+ :param version: The version of math calculation for the controller.
"""
self._kp = kp
self._ki = ki
self._kd = kd
+ self._version = version
self._deadband = deadband
self._history_size = max_history
self._heating_system = heating_system
self._automatic_gains = automatic_gains
self._automatic_gains_value = automatic_gain_value
self._derivative_time_weight = derivative_time_weight
+ self._heating_curve_coefficient = heating_curve_coefficient
self._last_interval_updated = monotonic()
self._sample_time_limit = max(sample_time_limit, 1)
@@ -123,6 +128,10 @@ def update_integral(self, error: float, heating_curve_value: float, force: bool
:param heating_curve_value: The current value of the heating curve.
:param force: Boolean flag indicating whether to force an update even if the integral time limit has not been reached.
"""
+ # Make sure we reset the time if we just entered deadband
+ if abs(self.last_error) > self._deadband >= abs(error):
+ self._last_interval_updated = monotonic()
+
# Make sure the integral term is enabled
if not self.integral_enabled:
self._integral = 0
@@ -134,7 +143,6 @@ def update_integral(self, error: float, heating_curve_value: float, force: bool
return
current_time = monotonic()
- limit = heating_curve_value / 10
time_elapsed = current_time - self._last_interval_updated
# Check if the integral gain `ki` is set
@@ -145,8 +153,8 @@ def update_integral(self, error: float, heating_curve_value: float, force: bool
self._integral += self.ki * error * time_elapsed
# Clamp the integral value within the limit
- self._integral = min(self._integral, float(+limit))
- self._integral = max(self._integral, float(-limit))
+ self._integral = min(self._integral, float(+heating_curve_value))
+ self._integral = max(self._integral, float(-heating_curve_value))
# Record the time of the latest update
self._last_interval_updated = current_time
@@ -178,6 +186,10 @@ def update_derivative(self, error: float, alpha1: float = 0.8, alpha2: float = 0
if len(self._errors) < 2:
return
+ # If "derivative" is disabled, we freeze it
+ if not self.derivative_enabled:
+ return
+
# Calculate the derivative using the errors and times in the error history.
num_of_errors = len(self._errors)
time_elapsed = self._times[num_of_errors - 1] - self._times[0]
@@ -242,6 +254,18 @@ def restore(self, state: State) -> None:
if last_heating_curve := state.attributes.get("heating_curve"):
self._last_heating_curve_value = last_heating_curve
+ def _get_aggression_value(self):
+ if self._version == 1:
+ return 73 if self._heating_system == HEATING_SYSTEM_UNDERFLOOR else 99
+
+ if self._version == 2:
+ return 73 if self._heating_system == HEATING_SYSTEM_UNDERFLOOR else 81.5
+
+ if self._version == 3:
+ return 8400
+
+ raise Exception("Invalid version")
+
@property
def last_error(self) -> float:
"""Return the last error value used by the PID controller."""
@@ -264,6 +288,10 @@ def last_updated(self) -> float:
def kp(self) -> float | None:
"""Return the value of kp based on the current configuration."""
if self._automatic_gains:
+ if self._version == 3:
+ automatic_gain_value = 4 if self._heating_system == HEATING_SYSTEM_UNDERFLOOR else 3
+ return round((self._heating_curve_coefficient * self._last_heating_curve_value) / automatic_gain_value, 6)
+
automatic_gain_value = 0.243 if self._heating_system == HEATING_SYSTEM_UNDERFLOOR else 0.33
return round(self._automatic_gains_value * automatic_gain_value * self._last_heating_curve_value, 6)
@@ -276,22 +304,30 @@ def ki(self) -> float | None:
if self._last_heating_curve_value is None:
return 0
- return round(self._last_heating_curve_value / 73900, 6)
+ if self._version == 1:
+ return round(self._last_heating_curve_value / 73900, 6)
+
+ if self._version == 2:
+ return round(self._automatic_gains_value * (self._last_heating_curve_value / 7200), 6)
+
+ if self._version == 3:
+ return round(self.kp / self._get_aggression_value(), 6)
+
+ raise Exception("Invalid version")
return float(self._ki)
@property
def kd(self) -> float | None:
"""Return the value of kd based on the current configuration."""
- if not self.derivative_enabled:
- return 0
-
if self._automatic_gains:
if self._last_heating_curve_value is None:
return 0
- aggression_value = 73 if self._heating_system == HEATING_SYSTEM_UNDERFLOOR else 99
- return round(self._automatic_gains_value * aggression_value * self._derivative_time_weight * self._last_heating_curve_value, 6)
+ if self._version == 3:
+ return round(0.07 * self._get_aggression_value() * self.kp, 6)
+
+ return round(self._automatic_gains_value * self._get_aggression_value() * self._derivative_time_weight * self._last_heating_curve_value, 6)
return float(self._kd)
@@ -308,17 +344,7 @@ def integral(self) -> float:
@property
def derivative(self) -> float:
"""Return the derivative value."""
- derivative = self.kd * self._raw_derivative
- output = self._last_heating_curve_value + self.proportional + self.integral
-
- if self._last_boiler_temperature is not None:
- if abs(self._last_error) > 0.1 and abs(self._last_boiler_temperature - output) < 3:
- return 0
-
- if abs(self._last_error) <= 0.1 and abs(self._last_boiler_temperature - output) < 7:
- return 0
-
- return round(derivative, 3)
+ return round(self.kd * self._raw_derivative, 3)
@property
def raw_derivative(self) -> float:
@@ -338,7 +364,7 @@ def integral_enabled(self) -> bool:
@property
def derivative_enabled(self) -> bool:
"""Return whether the updates of the derivative are enabled."""
- return abs(self.last_error) > self._deadband or abs(self.previous_error) > self._deadband
+ return abs(self._last_error) > self._deadband
@property
def num_errors(self) -> int:
diff --git a/custom_components/sat/pwm.py b/custom_components/sat/pwm.py
index e867a94b..161f7d51 100644
--- a/custom_components/sat/pwm.py
+++ b/custom_components/sat/pwm.py
@@ -3,19 +3,12 @@
from time import monotonic
from typing import Optional, Tuple
+from .boiler_state import BoilerState
from .const import HEATER_STARTUP_TIMEFRAME
from .heating_curve import HeatingCurve
_LOGGER = logging.getLogger(__name__)
-DUTY_CYCLE_20_PERCENT = 0.2
-DUTY_CYCLE_80_PERCENT = 0.8
-MIN_DUTY_CYCLE_PERCENTAGE = 0.1
-MAX_DUTY_CYCLE_PERCENTAGE = 0.9
-
-ON_TIME_20_PERCENT = 180
-ON_TIME_80_PERCENT = 900
-
class PWMState(str, Enum):
ON = "on"
@@ -24,130 +17,204 @@ class PWMState(str, Enum):
class PWM:
- """A class for implementing Pulse Width Modulation (PWM) control."""
+ """Implements Pulse Width Modulation (PWM) control for managing boiler operations."""
- def __init__(self, heating_curve: HeatingCurve, max_cycle_time: int, automatic_duty_cycle: bool, force: bool = False):
+ def __init__(self, heating_curve: HeatingCurve, max_cycle_time: int, automatic_duty_cycle: bool, max_cycles: int, force: bool = False):
"""Initialize the PWM control."""
self._alpha = 0.2
self._force = force
self._last_boiler_temperature = None
- self._last_duty_cycle_percentage = None
+ self._max_cycles = max_cycles
self._heating_curve = heating_curve
self._max_cycle_time = max_cycle_time
self._automatic_duty_cycle = automatic_duty_cycle
+ # Timing thresholds for duty cycle management
+ self._on_time_lower_threshold = 180
+ self._on_time_upper_threshold = 3600 / self._max_cycles
+ self._on_time_max_threshold = self._on_time_upper_threshold * 2
+
+ # Duty cycle percentage thresholds
+ self._duty_cycle_lower_threshold = self._on_time_lower_threshold / self._on_time_upper_threshold
+ self._duty_cycle_upper_threshold = 1 - self._duty_cycle_lower_threshold
+ self._min_duty_cycle_percentage = self._duty_cycle_lower_threshold / 2
+ self._max_duty_cycle_percentage = 1 - self._min_duty_cycle_percentage
+
+ _LOGGER.debug(
+ "Initialized PWM control with duty cycle thresholds - Lower: %.2f%%, Upper: %.2f%%",
+ self._duty_cycle_lower_threshold * 100, self._duty_cycle_upper_threshold * 100
+ )
+
self.reset()
def reset(self) -> None:
"""Reset the PWM control."""
+ self._cycles = 0
self._duty_cycle = None
self._state = PWMState.IDLE
self._last_update = monotonic()
- async def update(self, requested_setpoint: float, boiler_temperature: float) -> None:
+ self._first_duty_cycle_start = None
+ self._last_duty_cycle_percentage = None
+
+ _LOGGER.info("PWM control reset to initial state.")
+
+ async def update(self, requested_setpoint: float, boiler: BoilerState) -> None:
"""Update the PWM state based on the output of a PID controller."""
- if not self._heating_curve.value:
+ if not self._heating_curve.value or requested_setpoint is None or boiler.temperature is None:
self._state = PWMState.IDLE
self._last_update = monotonic()
- _LOGGER.warning("Turned off PWM due since we do not have a valid heating curve value.")
- return
+ self._last_boiler_temperature = boiler.temperature
+
+ _LOGGER.warning("PWM turned off due missing values.")
- if requested_setpoint is None:
- self._state = PWMState.IDLE
- self._last_update = monotonic()
- self._last_boiler_temperature = boiler_temperature
- _LOGGER.debug("Turned off PWM due since we do not have a valid requested setpoint.")
return
- if boiler_temperature is not None and self._last_boiler_temperature is None:
- self._last_boiler_temperature = boiler_temperature
+ if self._last_boiler_temperature is None:
+ self._last_boiler_temperature = boiler.temperature
+ _LOGGER.debug("Initialized last boiler temperature to %.1f°C", boiler.temperature)
- elapsed = monotonic() - self._last_update
- self._duty_cycle = self._calculate_duty_cycle(requested_setpoint)
+ if self._first_duty_cycle_start is None or (monotonic() - self._first_duty_cycle_start) > 3600:
+ self._cycles = 0
+ self._first_duty_cycle_start = monotonic()
+ _LOGGER.info("CYCLES count reset for the rolling hour.")
- _LOGGER.debug("Calculated duty cycle %.0f seconds ON", self._duty_cycle[0])
- _LOGGER.debug("Calculated duty cycle %.0f seconds OFF", self._duty_cycle[1])
+ elapsed = monotonic() - self._last_update
+ self._duty_cycle = self._calculate_duty_cycle(requested_setpoint, boiler)
- if self._state == PWMState.ON and boiler_temperature is not None:
+ # Update boiler temperature if heater has just started up
+ if self._state == PWMState.ON:
if elapsed <= HEATER_STARTUP_TIMEFRAME:
- self._last_boiler_temperature = self._alpha * boiler_temperature + (1 - self._alpha) * self._last_boiler_temperature
+ self._last_boiler_temperature = (
+ self._alpha * boiler.temperature + (1 - self._alpha) * self._last_boiler_temperature
+ )
+
+ _LOGGER.debug("Updated last boiler temperature with weighted average during startup phase.")
else:
- self._last_boiler_temperature = boiler_temperature
+ self._last_boiler_temperature = boiler.temperature
+ _LOGGER.debug("Updated last boiler temperature to %.1f°C", boiler.temperature)
- if self._state != PWMState.ON and self._duty_cycle[0] >= HEATER_STARTUP_TIMEFRAME and (elapsed >= self._duty_cycle[1] or self._state == PWMState.IDLE):
+ # State transitions for PWM
+ if self._state != PWMState.ON and self._duty_cycle[0] >= HEATER_STARTUP_TIMEFRAME and (
+ elapsed >= self._duty_cycle[1] or self._state == PWMState.IDLE):
+ if self._cycles >= self._max_cycles:
+ _LOGGER.info("Reached max cycles per hour, preventing new duty cycle.")
+ return
+
+ self._cycles += 1
self._state = PWMState.ON
self._last_update = monotonic()
- self._last_boiler_temperature = boiler_temperature or 0
- _LOGGER.debug("Starting duty cycle.")
+ self._last_boiler_temperature = boiler.temperature
+ _LOGGER.info("Starting new duty cycle (ON state). Current CYCLES count: %d", self._cycles)
return
- if self._state != PWMState.OFF and (self._duty_cycle[0] < HEATER_STARTUP_TIMEFRAME or elapsed >= self._duty_cycle[0] or self._state == PWMState.IDLE):
+ if self._state != PWMState.OFF and (
+ self._duty_cycle[0] < HEATER_STARTUP_TIMEFRAME or elapsed >= self._duty_cycle[0] or self._state == PWMState.IDLE):
self._state = PWMState.OFF
self._last_update = monotonic()
- _LOGGER.debug("Finished duty cycle.")
+ _LOGGER.info("Duty cycle completed. Switching to OFF state.")
return
- _LOGGER.debug("Cycle time elapsed %.0f seconds in %s", elapsed, self._state)
+ _LOGGER.debug("Cycle time elapsed: %.0f seconds in state: %s", elapsed, self._state)
- def _calculate_duty_cycle(self, requested_setpoint: float) -> Optional[Tuple[int, int]]:
- """Calculates the duty cycle in seconds based on the output of a PID controller and a heating curve value."""
- boiler_temperature = self._last_boiler_temperature or requested_setpoint
+ def _calculate_duty_cycle(self, requested_setpoint: float, boiler: BoilerState) -> Tuple[int, int]:
+ """Calculate the duty cycle in seconds based on the output of a PID controller and a heating curve value."""
+ boiler_temperature = self._last_boiler_temperature
base_offset = self._heating_curve.base_offset
- if boiler_temperature < base_offset:
- boiler_temperature = base_offset + 1
+ # Ensure boiler temperature is above the base offset
+ boiler_temperature = max(boiler_temperature, base_offset + 1)
+ # Calculate duty cycle percentage
self._last_duty_cycle_percentage = (requested_setpoint - base_offset) / (boiler_temperature - base_offset)
- self._last_duty_cycle_percentage = min(self._last_duty_cycle_percentage, 1)
- self._last_duty_cycle_percentage = max(self._last_duty_cycle_percentage, 0)
+ self._last_duty_cycle_percentage = min(max(self._last_duty_cycle_percentage, 0), 1)
- _LOGGER.debug("Requested setpoint %.1f", requested_setpoint)
- _LOGGER.debug("Boiler Temperature %.1f", boiler_temperature)
- _LOGGER.debug("Calculated duty cycle %.2f%%", self._last_duty_cycle_percentage * 100)
+ _LOGGER.debug(
+ "Duty cycle calculation - Requested setpoint: %.1f°C, Boiler temperature: %.1f°C, Duty cycle percentage: %.2f%%",
+ requested_setpoint, boiler_temperature, self._last_duty_cycle_percentage * 100
+ )
+ # If automatic duty cycle control is disabled
if not self._automatic_duty_cycle:
- return int(self._last_duty_cycle_percentage * self._max_cycle_time), int((1 - self._last_duty_cycle_percentage) * self._max_cycle_time)
+ on_time = self._last_duty_cycle_percentage * self._max_cycle_time
+ off_time = (1 - self._last_duty_cycle_percentage) * self._max_cycle_time
+
+ _LOGGER.debug(
+ "Calculated on_time: %.0f seconds, off_time: %.0f seconds.",
+ on_time, off_time
+ )
+
+ return int(on_time), int(off_time)
+
+ # Handle special low-duty cycle cases
+ if self._last_duty_cycle_percentage < self._min_duty_cycle_percentage:
+ if boiler.flame_active and not boiler.hot_water_active:
+ on_time = self._on_time_lower_threshold
+ off_time = self._on_time_max_threshold - self._on_time_lower_threshold
- if self._last_duty_cycle_percentage < MIN_DUTY_CYCLE_PERCENTAGE:
- return 0, 1800
+ _LOGGER.debug(
+ "Special low-duty case with flame active. Setting on_time: %d seconds, off_time: %d seconds.",
+ on_time, off_time
+ )
- if self._last_duty_cycle_percentage <= DUTY_CYCLE_20_PERCENT:
- on_time = ON_TIME_20_PERCENT
- off_time = (ON_TIME_20_PERCENT / self._last_duty_cycle_percentage) - ON_TIME_20_PERCENT
+ return int(on_time), int(off_time)
+
+ _LOGGER.debug("Special low-duty case without flame. Setting on_time: 0 seconds, off_time: %d seconds.", self._on_time_max_threshold)
+ return 0, int(self._on_time_max_threshold)
+
+ # Mapping duty cycle ranges to on/off times
+ if self._last_duty_cycle_percentage <= self._duty_cycle_lower_threshold:
+ on_time = self._on_time_lower_threshold
+ off_time = (self._on_time_lower_threshold / self._last_duty_cycle_percentage) - self._on_time_lower_threshold
+
+ _LOGGER.debug(
+ "Low duty cycle range, cycles this hour: %d. Calculated on_time: %d seconds, off_time: %d seconds.",
+ self._cycles, on_time, off_time
+ )
return int(on_time), int(off_time)
- if self._last_duty_cycle_percentage <= DUTY_CYCLE_80_PERCENT:
- on_time = ON_TIME_80_PERCENT * self._last_duty_cycle_percentage
- off_time = ON_TIME_80_PERCENT * (1 - self._last_duty_cycle_percentage)
+ if self._last_duty_cycle_percentage <= self._duty_cycle_upper_threshold:
+ on_time = self._on_time_upper_threshold * self._last_duty_cycle_percentage
+ off_time = self._on_time_upper_threshold * (1 - self._last_duty_cycle_percentage)
+
+ _LOGGER.debug(
+ "Mid-range duty cycle, cycles this hour: %d. Calculated on_time: %d seconds, off_time: %d seconds.",
+ self._cycles, on_time, off_time
+ )
return int(on_time), int(off_time)
- if self._last_duty_cycle_percentage <= MAX_DUTY_CYCLE_PERCENTAGE:
- on_time = ON_TIME_20_PERCENT / (1 - self._last_duty_cycle_percentage) - ON_TIME_20_PERCENT
- off_time = ON_TIME_20_PERCENT
+ if self._last_duty_cycle_percentage <= self._max_duty_cycle_percentage:
+ on_time = self._on_time_lower_threshold / (1 - self._last_duty_cycle_percentage) - self._on_time_lower_threshold
+ off_time = self._on_time_lower_threshold
+
+ _LOGGER.debug(
+ "High duty cycle range, cycles this hour: %d. Calculated on_time: %d seconds, off_time: %d seconds.",
+ self._cycles, on_time, off_time
+ )
return int(on_time), int(off_time)
- if self._last_duty_cycle_percentage > MAX_DUTY_CYCLE_PERCENTAGE:
- return 1800, 0
+ # Handle cases where the duty cycle exceeds the maximum allowed percentage
+ on_time = self._on_time_max_threshold
+ off_time = 0
+
+ _LOGGER.debug("Maximum duty cycle exceeded. Setting on_time: %d seconds, off_time: %d seconds.", on_time, off_time)
+ return int(on_time), int(off_time)
@property
def state(self) -> PWMState:
- """Returns the current state of the PWM control."""
+ """Current PWM state."""
return self._state
@property
- def duty_cycle(self) -> None | tuple[int, int]:
- """
- Returns the current duty cycle of the PWM control.
-
- If the PWM control is not currently active, None is returned.
- Otherwise, a tuple is returned with the on and off times of the duty cycle in seconds.
- """
+ def duty_cycle(self) -> Optional[Tuple[int, int]]:
+ """Current duty cycle as a tuple of (on_time, off_time) in seconds, or None if inactive."""
return self._duty_cycle
@property
- def last_duty_cycle_percentage(self):
- return round(self._last_duty_cycle_percentage * 100, 2)
+ def last_duty_cycle_percentage(self) -> Optional[float]:
+ """Returns the last calculated duty cycle percentage."""
+ return round(self._last_duty_cycle_percentage * 100, 2) if self._last_duty_cycle_percentage is not None else None
diff --git a/custom_components/sat/relative_modulation.py b/custom_components/sat/relative_modulation.py
index e35062ff..1b2d2656 100644
--- a/custom_components/sat/relative_modulation.py
+++ b/custom_components/sat/relative_modulation.py
@@ -1,8 +1,10 @@
+import logging
from enum import Enum
-from custom_components.sat import MINIMUM_SETPOINT, HEATING_SYSTEM_HEAT_PUMP
-from custom_components.sat.coordinator import SatDataUpdateCoordinator
-from custom_components.sat.pwm import PWMState
+from .const import MINIMUM_SETPOINT
+from .coordinator import SatDataUpdateCoordinator
+
+_LOGGER = logging.getLogger(__name__)
# Enum to represent different states of relative modulation
@@ -10,47 +12,37 @@ class RelativeModulationState(str, Enum):
OFF = "off"
COLD = "cold"
HOT_WATER = "hot_water"
- WARMING_UP = "warming_up"
PULSE_WIDTH_MODULATION_OFF = "pulse_width_modulation_off"
class RelativeModulation:
def __init__(self, coordinator: SatDataUpdateCoordinator, heating_system: str):
"""Initialize instance variables"""
- self._heating_system = heating_system # The heating system that is being controlled
- self._pwm_state = None # Tracks the current state of the PWM (Pulse Width Modulation) system
- self._warming_up = False # Stores data related to the warming up state of the heating system
- self._coordinator = coordinator # Reference to the data coordinator responsible for system-wide information
+ self._coordinator = coordinator
+ self._heating_system = heating_system
+ self._pulse_width_modulation_enabled = None
+
+ _LOGGER.debug("Relative Modulation initialized for heating system: %s", heating_system)
- async def update(self, warming_up: bool, state: PWMState) -> None:
- """Update internal state with new data received from the coordinator"""
- self._pwm_state = state
- self._warming_up = warming_up
+ async def update(self, pulse_width_modulation_enabled: bool) -> None:
+ """Update internal state with new internal data"""
+ self._pulse_width_modulation_enabled = pulse_width_modulation_enabled
@property
def state(self) -> RelativeModulationState:
"""Determine the current state of relative modulation based on coordinator and internal data"""
- # If setpoint is not available or below the minimum threshold, it's considered COLD
- if self._coordinator.setpoint is None or self._coordinator.setpoint <= MINIMUM_SETPOINT:
- return RelativeModulationState.COLD
-
- # If hot water is actively being used, it's considered HOT_WATER
if self._coordinator.hot_water_active:
return RelativeModulationState.HOT_WATER
- # If the heating system is currently in the process of warming up, it's considered WARMING_UP
- if self._warming_up and self._heating_system != HEATING_SYSTEM_HEAT_PUMP:
- return RelativeModulationState.WARMING_UP
+ if not self._pulse_width_modulation_enabled:
+ if self._coordinator.setpoint is None or self._coordinator.setpoint <= MINIMUM_SETPOINT:
+ return RelativeModulationState.COLD
- # If the PWM state is in the ON state, it's considered PULSE_WIDTH_MODULATION_OFF
- if self._pwm_state != PWMState.ON:
return RelativeModulationState.PULSE_WIDTH_MODULATION_OFF
- # Default case, when none of the above conditions are met, it's considered OFF
return RelativeModulationState.OFF
@property
def enabled(self) -> bool:
"""Check if the relative modulation is enabled based on its current state"""
- # Relative modulation is considered enabled if it's not in the OFF state or in the WARMING_UP state
- return self.state != RelativeModulationState.OFF and self.state != RelativeModulationState.WARMING_UP
+ return self.state != RelativeModulationState.OFF
\ No newline at end of file
diff --git a/custom_components/sat/sensor.py b/custom_components/sat/sensor.py
index cc0536c1..095c3fec 100644
--- a/custom_components/sat/sensor.py
+++ b/custom_components/sat/sensor.py
@@ -12,12 +12,12 @@
from .const import CONF_MODE, MODE_SERIAL, CONF_NAME, DOMAIN, COORDINATOR, CLIMATE, MODE_SIMULATOR, CONF_MINIMUM_CONSUMPTION, CONF_MAXIMUM_CONSUMPTION
from .coordinator import SatDataUpdateCoordinator
-from .entity import SatEntity
+from .entity import SatEntity, SatClimateEntity
from .serial import sensor as serial_sensor
from .simulator import sensor as simulator_sensor
if typing.TYPE_CHECKING:
- from .climate import SatClimate
+ pass
_LOGGER: logging.Logger = logging.getLogger(__name__)
@@ -26,6 +26,10 @@ async def async_setup_entry(_hass: HomeAssistant, _config_entry: ConfigEntry, _a
"""
Add sensors for the serial protocol if the integration is set to use it.
"""
+ # Some sanity checks before we continue
+ if any(key not in _hass.data[DOMAIN][_config_entry.entry_id] for key in (CLIMATE, COORDINATOR)):
+ return
+
climate = _hass.data[DOMAIN][_config_entry.entry_id][CLIMATE]
coordinator = _hass.data[DOMAIN][_config_entry.entry_id][COORDINATOR]
@@ -38,6 +42,7 @@ async def async_setup_entry(_hass: HomeAssistant, _config_entry: ConfigEntry, _a
await simulator_sensor.async_setup_entry(_hass, _config_entry, _async_add_entities)
_async_add_entities([
+ SatManufacturerSensor(coordinator, _config_entry),
SatErrorValueSensor(coordinator, _config_entry, climate),
SatHeatingCurveSensor(coordinator, _config_entry, climate),
])
@@ -53,7 +58,7 @@ class SatCurrentPowerSensor(SatEntity, SensorEntity):
@property
def name(self) -> str:
- return f"Boiler Current Power {self._config_entry.data.get(CONF_NAME)} (Boiler)"
+ return f"Current Power {self._config_entry.data.get(CONF_NAME)} (Boiler)"
@property
def device_class(self):
@@ -94,7 +99,7 @@ def __init__(self, coordinator: SatDataUpdateCoordinator, config_entry: ConfigEn
@property
def name(self) -> str:
- return f"Boiler Current Consumption {self._config_entry.data.get(CONF_NAME)} (Boiler)"
+ return f"Current Consumption {self._config_entry.data.get(CONF_NAME)} (Boiler)"
@property
def device_class(self):
@@ -135,12 +140,7 @@ def unique_id(self) -> str:
return f"{self._config_entry.data.get(CONF_NAME).lower()}-boiler-current-consumption"
-class SatHeatingCurveSensor(SatEntity, SensorEntity):
-
- def __init__(self, coordinator: SatDataUpdateCoordinator, config_entry: ConfigEntry, climate: SatClimate):
- super().__init__(coordinator, config_entry)
-
- self._climate = climate
+class SatHeatingCurveSensor(SatClimateEntity, SensorEntity):
async def async_added_to_hass(self) -> None:
async def on_state_change(_event: Event):
@@ -185,12 +185,7 @@ def unique_id(self) -> str:
return f"{self._config_entry.data.get(CONF_NAME).lower()}-heating-curve"
-class SatErrorValueSensor(SatEntity, SensorEntity):
-
- def __init__(self, coordinator: SatDataUpdateCoordinator, config_entry: ConfigEntry, climate: SatClimate):
- super().__init__(coordinator, config_entry)
-
- self._climate = climate
+class SatErrorValueSensor(SatClimateEntity, SensorEntity):
async def async_added_to_hass(self) -> None:
async def on_state_change(_event: Event):
@@ -233,3 +228,20 @@ def native_value(self) -> float:
def unique_id(self) -> str:
"""Return a unique ID to use for this entity."""
return f"{self._config_entry.data.get(CONF_NAME).lower()}-error-value"
+
+
+class SatManufacturerSensor(SatEntity, SensorEntity):
+ @property
+ def name(self) -> str:
+ return f"Boiler Manufacturer"
+
+ @property
+ def native_value(self) -> str:
+ if not (manufacturer := self._coordinator.manufacturer):
+ return "Unknown"
+
+ return manufacturer.name
+
+ @property
+ def unique_id(self) -> str:
+ return f"{self._config_entry.data.get(CONF_NAME).lower()}-manufacturer"
diff --git a/custom_components/sat/serial/__init__.py b/custom_components/sat/serial/__init__.py
index 772eaae4..856875ea 100644
--- a/custom_components/sat/serial/__init__.py
+++ b/custom_components/sat/serial/__init__.py
@@ -2,7 +2,7 @@
import asyncio
import logging
-from typing import Optional, Any, TYPE_CHECKING, Mapping
+from typing import Optional, Any, Mapping
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
@@ -12,9 +12,6 @@
from ..coordinator import DeviceState, SatDataUpdateCoordinator
-if TYPE_CHECKING:
- from ..climate import SatClimate
-
_LOGGER: logging.Logger = logging.getLogger(__name__)
# Sensors
@@ -41,6 +38,14 @@ async def async_coroutine(event):
self._api = OpenThermGateway()
self._api.subscribe(async_coroutine)
+ @property
+ def device_id(self) -> str:
+ return self._port
+
+ @property
+ def device_type(self) -> str:
+ return "OpenThermGateway (via serial)"
+
@property
def device_active(self) -> bool:
return bool(self.get(DATA_MASTER_CH_ENABLED) or False)
@@ -86,6 +91,13 @@ def boiler_temperature(self) -> float | None:
return super().boiler_temperature
+ @property
+ def return_temperature(self) -> float | None:
+ if (value := self.get(DATA_RETURN_WATER_TEMP)) is not None:
+ return float(value)
+
+ return super().return_temperature
+
@property
def minimum_hot_water_setpoint(self) -> float:
if (setpoint := self.get(DATA_SLAVE_DHW_MIN_SETP)) is not None:
@@ -128,6 +140,13 @@ def maximum_relative_modulation_value(self) -> float | None:
return super().maximum_relative_modulation_value
+ @property
+ def member_id(self) -> int | None:
+ if (value := self.get(DATA_SLAVE_MEMBERID)) is not None:
+ return int(value)
+
+ return None
+
@property
def flame_active(self) -> bool:
return bool(self.get(DATA_SLAVE_FLAME_ON))
@@ -148,7 +167,10 @@ async def async_connect(self) -> SatSerialCoordinator:
return self
- async def async_will_remove_from_hass(self, climate: SatClimate) -> None:
+ async def async_setup(self) -> None:
+ await self.async_connect()
+
+ async def async_will_remove_from_hass(self) -> None:
self._api.unsubscribe(self.async_set_updated_data)
await self._api.set_control_setpoint(0)
diff --git a/custom_components/sat/simulator/__init__.py b/custom_components/sat/simulator/__init__.py
index 62b0d046..db8e7fd9 100644
--- a/custom_components/sat/simulator/__init__.py
+++ b/custom_components/sat/simulator/__init__.py
@@ -5,7 +5,7 @@
from homeassistant.core import HomeAssistant
-from .. import CONF_SIMULATED_HEATING, CONF_SIMULATED_COOLING, MINIMUM_SETPOINT, CONF_SIMULATED_WARMING_UP, CONF_MAXIMUM_SETPOINT
+from ..const import CONF_SIMULATED_HEATING, CONF_SIMULATED_COOLING, MINIMUM_SETPOINT, CONF_SIMULATED_WARMING_UP, CONF_MAXIMUM_SETPOINT
from ..coordinator import DeviceState, SatDataUpdateCoordinator
from ..util import convert_time_str_to_seconds
@@ -14,8 +14,6 @@
class SatSimulatorCoordinator(SatDataUpdateCoordinator):
- """Class to manage the Switch."""
-
def __init__(self, hass: HomeAssistant, data: Mapping[str, Any], options: Mapping[str, Any] | None = None) -> None:
"""Initialize."""
super().__init__(hass, data, options)
@@ -29,6 +27,14 @@ def __init__(self, hass: HomeAssistant, data: Mapping[str, Any], options: Mappin
self._maximum_setpoint = data.get(CONF_MAXIMUM_SETPOINT)
self._warming_up = convert_time_str_to_seconds(data.get(CONF_SIMULATED_WARMING_UP))
+ @property
+ def device_id(self) -> str:
+ return 'Simulator'
+
+ @property
+ def device_type(self) -> str:
+ return "Simulator"
+
@property
def supports_setpoint_management(self) -> bool:
return True
@@ -61,6 +67,10 @@ def flame_active(self) -> bool:
def relative_modulation_value(self) -> float | None:
return 100 if self.flame_active else 0
+ @property
+ def member_id(self) -> int | None:
+ return -1
+
async def async_set_heater_state(self, state: DeviceState) -> None:
self._started_on = monotonic() if state == DeviceState.ON else None
diff --git a/custom_components/sat/switch/__init__.py b/custom_components/sat/switch/__init__.py
index 4412f836..df8bcc8b 100644
--- a/custom_components/sat/switch/__init__.py
+++ b/custom_components/sat/switch/__init__.py
@@ -17,14 +17,20 @@
class SatSwitchCoordinator(SatDataUpdateCoordinator):
- """Class to manage the Switch."""
-
def __init__(self, hass: HomeAssistant, entity_id: str, data: Mapping[str, Any], options: Mapping[str, Any] | None = None) -> None:
"""Initialize."""
super().__init__(hass, data, options)
self._entity = entity_registry.async_get(hass).async_get(entity_id)
+ @property
+ def device_id(self) -> str:
+ return self._entity.name
+
+ @property
+ def device_type(self) -> str:
+ return "Switch"
+
@property
def setpoint(self) -> float:
return self.minimum_setpoint
@@ -40,6 +46,10 @@ def device_active(self) -> bool:
return state.state == STATE_ON
+ @property
+ def member_id(self) -> int | None:
+ return -1
+
async def async_set_heater_state(self, state: DeviceState) -> None:
if not self._simulation:
domain_service = DOMAIN_SERVICE.get(self._entity.domain)
diff --git a/custom_components/sat/translations/de.json b/custom_components/sat/translations/de.json
index f9643559..261158b5 100644
--- a/custom_components/sat/translations/de.json
+++ b/custom_components/sat/translations/de.json
@@ -1,222 +1,246 @@
{
"config": {
+ "abort": {
+ "already_configured": "Gateway ist bereits konfiguriert.",
+ "reconfigure_successful": "Gateway wurde neu konfiguriert."
+ },
+ "error": {
+ "connection": "Verbindung zum Gateway konnte nicht hergestellt werden.",
+ "mqtt_component": "Die MQTT-Komponente ist nicht verfügbar.",
+ "unable_to_calibrate": "Der Kalibrierungsprozess ist auf ein Problem gestoßen und konnte nicht erfolgreich abgeschlossen werden. Bitte stellen Sie sicher, dass Ihr Heizsystem ordnungsgemäß funktioniert und dass alle erforderlichen Sensoren angeschlossen und funktionsfähig sind.\n\nWenn Sie weiterhin Probleme mit der Kalibrierung haben, ziehen Sie in Erwägung, uns für weitere Unterstützung zu kontaktieren. Wir entschuldigen uns für etwaige Unannehmlichkeiten."
+ },
+ "progress": {
+ "calibration": "Kalibrierung und Ermittlung des Überschwingungsschutzwertes...\n\nBitte warten Sie, während wir Ihr Heizsystem optimieren. Dieser Prozess kann etwa 20 Minuten dauern."
+ },
"step": {
- "user": {
- "title": "Smart Autotune Thermostat (SAT)",
- "description": "SAT ist ein intelligentes Thermostat, das in der Lage ist, sich selbst automatisch zu justieren, um die Temperaturregelung zu optimieren. Wählen Sie den passenden Modus, der zu Ihrem Heizsystem passt.",
- "menu_options": {
- "mosquitto": "OpenTherm Gateway ( MQTT )",
- "serial": "OpenTherm Gateway ( SERIELL )",
- "switch": "PID-Thermostat mit PWM ( EIN/AUS )",
- "simulator": "Simuliertes Gateway ( FORTGESCHRITTEN )"
- }
- },
- "mosquitto": {
- "title": "OpenTherm Gateway ( MQTT )",
- "description": "Bitte geben Sie die folgenden Details an, um das OpenTherm Gateway einzurichten. Geben Sie im Feld Name einen Namen für das Gateway ein, der Ihnen hilft, es in Ihrem System zu identifizieren.\n\nGeben Sie die Klimaentität an, die für das OpenTherm Gateway verwendet wird. Diese Entität wird vom OpenTherm Gateway bereitgestellt und repräsentiert Ihr Heizsystem.\n\nGeben Sie außerdem das Top-Thema an, das für das Veröffentlichen und Abonnieren von MQTT-Nachrichten im Zusammenhang mit dem OpenTherm Gateway verwendet wird.\n\nDiese Einstellungen sind wesentlich, um die Kommunikation und Integration mit Ihrem OpenTherm Gateway über MQTT herzustellen. Sie ermöglichen einen nahtlosen Datenaustausch und die Steuerung Ihres Heizsystems. Stellen Sie sicher, dass die angegebenen Details korrekt sind, um eine ordnungsgemäße Funktionalität zu gewährleisten.",
- "data": {
- "name": "Name",
- "device": "Gerät",
- "mqtt_topic": "Top-Thema"
- }
- },
- "serial": {
- "title": "OpenTherm Gateway ( SERIELL )",
- "description": "Um eine Verbindung mit dem OpenTherm Gateway über eine Socket-Verbindung herzustellen, geben Sie bitte die folgenden Details an. Geben Sie im Feld Name einen Namen für das Gateway ein, der Ihnen hilft, es in Ihrem System zu identifizieren.\n\nGeben Sie die Netzwerkadresse des OpenTherm Gateways im Feld Gerät an. Dies könnte im Format \"socket://otgw.local:25238\" sein, wobei \"otgw.local\" der Hostname oder die IP-Adresse des Gateways ist und \"25238\" die Portnummer.\n\nDiese Einstellungen sind wesentlich, um die Kommunikation und Integration mit Ihrem OpenTherm Gateway über die Socket-Verbindung herzustellen. Stellen Sie sicher, dass die angegebenen Details korrekt sind, um eine ordnungsgemäße Funktionalität zu gewährleisten.",
- "data": {
- "name": "Name",
- "device": "URL"
- }
- },
- "switch": {
- "title": "PID-Thermostat mit PWM ( EIN/AUS )",
- "description": "Bitte füllen Sie die folgenden Details aus, um den Schalter einzurichten. Geben Sie einen Namen für den Schalter im Feld Name ein, der Ihnen hilft, ihn in Ihrem System zu identifizieren. Wählen Sie die passende Entität für Ihren Schalter aus den angebotenen Optionen aus.\n\nGeben Sie im Feld Temperatureinstellung die gewünschte Zieltemperatur für Ihr Heizsystem an. Wenn Sie einen Warmwasserboiler verwenden, geben Sie die Boilertemperatureinstellung mit dem entsprechenden Wert an. Für elektrische Heizsysteme geben Sie den Wert 100 ein.\n\nDiese Einstellungen sind wesentlich für eine präzise Temperaturregelung und gewährleisten eine optimale Leistung Ihres Heizsystems. Die Angabe der korrekten Temperatureinstellung ermöglicht eine genaue Regelung und hilft dabei, eine komfortable und energieeffiziente Umgebung in Ihrem Zuhause zu erreichen.",
- "data": {
- "name": "Name",
- "device": "Entität",
- "minimum_setpoint": "Temperatureinstellung"
- }
- },
- "simulator": {
- "title": "Simuliertes Gateway ( FORTGESCHRITTEN )",
- "description": "Dieses Gateway ermöglicht es Ihnen, einen Boiler zu simulieren, zu Test- und Demonstrationszwecken. Bitte geben Sie die folgenden Informationen an, um den Simulator zu konfigurieren.\n\nHinweis: Das Simulator Gateway ist nur zu Test- und Demonstrationszwecken gedacht und sollte nicht in Produktionsumgebungen verwendet werden.",
- "data": {
- "name": "Name",
- "minimum_setpoint": "Mindesteinstellung",
- "maximum_setpoint": "Maximale Einstellung",
- "simulated_heating": "Simulierte Heizung",
- "simulated_cooling": "Simulierte Kühlung",
- "simulated_warming_up": "Simuliertes Aufwärmen"
- }
- },
- "sensors": {
- "title": "Sensoren konfigurieren",
- "description": "Bitte wählen Sie die Sensoren aus, die zur Überwachung der Temperatur verwendet werden sollen.",
- "data": {
- "inside_sensor_entity_id": "Innensensor-Entität",
- "outside_sensor_entity_id": "Außensensor-Entität",
- "humidity_sensor_entity_id": "Feuchtigkeitssensor-Entität"
- }
- },
- "heating_system": {
- "title": "Heizsystem",
- "description": "Die Auswahl des richtigen Heizsystemtyps ist wichtig, damit SAT die Temperatur genau steuern und die Leistung optimieren kann. Wählen Sie die Option, die zu Ihrer Einrichtung passt, um eine ordnungsgemäße Temperaturregelung in Ihrem Zuhause zu gewährleisten.",
- "data": {
- "heating_system": "System"
- }
- },
"areas": {
- "title": "Bereiche",
- "description": "Einstellungen im Zusammenhang mit Klimazonen, Mehrzimmern und Temperaturregelung. Primäre Klimazonen befinden sich im selben Raum wie der Innensensor, und die Zimmer haben ihre eigenen Zieltemperaturen, getrennt vom System.",
"data": {
"main_climates": "Primär",
"secondary_climates": "Zimmer"
- }
+ },
+ "description": "Einstellungen im Zusammenhang mit Klimazonen, Mehrzimmern und Temperaturregelung. Primäre Klimazonen befinden sich im selben Raum wie der Innensensor, und die Zimmer haben ihre eigenen Zieltemperaturen, getrennt vom System.",
+ "title": "Bereiche"
},
"automatic_gains": {
- "title": "Automatische Verstärkungen",
- "description": "Diese Funktion passt die Steuerungsparameter Ihres Heizsystems dynamisch an, um die Temperaturregelung für mehr Komfort und Energieeffizienz zu optimieren. Die Aktivierung dieser Option ermöglicht es SAT, die Heizeinstellungen kontinuierlich anzupassen und auf der Grundlage der Umgebungsbedingungen fein abzustimmen. Dies hilft, eine stabile und komfortable Umgebung ohne manuelle Eingriffe aufrechtzuerhalten.\n\nHinweis: Wenn Sie sich entscheiden, automatische Verstärkungen nicht zu aktivieren, müssen Sie die PID-Werte manuell eingeben, um eine präzise Temperaturregelung zu gewährleisten. Bitte stellen Sie sicher, dass Sie genaue PID-Werte für Ihr spezifisches Heizsystem haben, um eine optimale Leistung zu erzielen.",
"data": {
"automatic_gains": "Automatische Verstärkungen (empfohlen)"
- }
+ },
+ "description": "Diese Funktion passt die Steuerungsparameter Ihres Heizsystems dynamisch an, um die Temperaturregelung für mehr Komfort und Energieeffizienz zu optimieren. Die Aktivierung dieser Option ermöglicht es SAT, die Heizeinstellungen kontinuierlich anzupassen und auf der Grundlage der Umgebungsbedingungen fein abzustimmen. Dies hilft, eine stabile und komfortable Umgebung ohne manuelle Eingriffe aufrechtzuerhalten.\n\nHinweis: Wenn Sie sich entscheiden, automatische Verstärkungen nicht zu aktivieren, müssen Sie die PID-Werte manuell eingeben, um eine präzise Temperaturregelung zu gewährleisten. Bitte stellen Sie sicher, dass Sie genaue PID-Werte für Ihr spezifisches Heizsystem haben, um eine optimale Leistung zu erzielen.",
+ "title": "Automatische Verstärkungen"
},
"calibrate_system": {
- "title": "System kalibrieren",
"description": "Optimieren Sie Ihr Heizsystem, indem Sie die optimalen PID-Werte für Ihre Einrichtung automatisch bestimmen. Bitte beachten Sie, dass das System bei Auswahl von Automatischen Verstärkungen einen Kalibrierungsprozess durchläuft, der etwa 20 Minuten dauern kann.\n\nAutomatische Verstärkungen werden für die meisten Benutzer empfohlen, da sie den Einrichtungsprozess vereinfachen und eine optimale Leistung gewährleisten. Wenn Sie jedoch mit der PID-Steuerung vertraut sind und die Werte lieber manuell eingeben möchten, können Sie sich entscheiden, Automatische Verstärkungen zu überspringen.\n\nBitte beachten Sie, dass die Entscheidung gegen Automatische Verstärkungen ein gutes Verständnis der PID-Steuerung erfordert und möglicherweise zusätzliche manuelle Anpassungen für eine optimale Leistung notwendig sind.",
"menu_options": {
"calibrate": "Kalibrieren und Ihren Überschwingungsschutzwert bestimmen (ca. 20 Min.).",
"overshoot_protection": "Den Überschwingungsschutzwert manuell eingeben.",
"pid_controller": "PID-Werte manuell eingeben (nicht empfohlen)."
- }
+ },
+ "title": "System kalibrieren"
+ },
+ "calibrated": {
+ "description": "Der Kalibrierungsprozess wurde erfolgreich abgeschlossen.\n\nHerzlichen Glückwunsch! Ihr Smart Autotune Thermostat (SAT) wurde kalibriert, um die Heizleistung Ihres Systems zu optimieren. Während des Kalibrierungsprozesses hat SAT die Heizeigenschaften sorgfältig analysiert und den angemessenen Überschwingungsschutzwert bestimmt, um eine präzise Temperaturregelung zu gewährleisten.\n\nÜberschwingungsschutzwert: {minimum_setpoint} °C\n\nDieser Wert stellt die maximale Menge an Überschwingen dar, die während des Heizprozesses erlaubt ist. SAT wird aktiv die Heizung überwachen und anpassen, um übermäßiges Überschwingen zu verhindern und eine komfortable und effiziente Heizerfahrung in Ihrem Zuhause zu gewährleisten.\n\nBitte beachten Sie, dass der Überschwingungsschutzwert je nach den spezifischen Eigenschaften Ihres Heizsystems und Umweltfaktoren variieren kann. Er wurde fein abgestimmt, um basierend auf den Kalibrierungsergebnissen eine optimale Leistung zu bieten.",
+ "menu_options": {
+ "calibrate": "Kalibrierung erneut versuchen",
+ "finish": "Mit der aktuellen Kalibrierung fortfahren"
+ },
+ "title": "Kalibrierung abgeschlossen"
+ },
+ "heating_system": {
+ "data": {
+ "heating_system": "System"
+ },
+ "description": "Die Auswahl des richtigen Heizsystemtyps ist wichtig, damit SAT die Temperatur genau steuern und die Leistung optimieren kann. Wählen Sie die Option, die zu Ihrer Einrichtung passt, um eine ordnungsgemäße Temperaturregelung in Ihrem Zuhause zu gewährleisten.",
+ "title": "Heizsystem"
+ },
+ "mosquitto": {
+ "description": "Konfigurieren Sie den MQTT-Modus für Ihr Heizsystem. Wählen Sie den Gateway-Modus aus und geben Sie seinen Namen an.",
+ "data": {
+ "mode": "Modus"
+ },
+ "title": "MQTT-Konfiguration"
+ },
+ "mosquitto_opentherm": {
+ "description": "Richten Sie das OpenTherm Gateway ein. Geben Sie das Haupt-MQTT-Thema und die Gerätekennung an.",
+ "data": {
+ "name": "Name",
+ "mqtt_topic": "MQTT-Thema",
+ "device": "Geräte-ID"
+ },
+ "title": "OpenTherm Gateway Einrichtung"
+ },
+ "mosquitto_ems": {
+ "description": "Richten Sie das EMS-ESP Gateway ein. Geben Sie das MQTT-Thema an.",
+ "data": {
+ "name": "Name",
+ "mqtt_topic": "MQTT-Thema"
+ },
+ "title": "EMS-ESP Gateway Einrichtung"
},
"overshoot_protection": {
- "title": "Überschwingungsschutz",
- "description": "Durch Angabe des Überschwingungsschutzwertes wird SAT die Steuerungsparameter entsprechend anpassen, um eine stabile und komfortable Heizumgebung zu erhalten. Diese manuelle Konfiguration ermöglicht es Ihnen, das System basierend auf Ihrer spezifischen Einrichtung fein abzustimmen.\n\nHinweis: Wenn Sie sich nicht sicher über den Überschwingungsschutzwert sind oder den Kalibrierungsprozess nicht durchgeführt haben, wird empfohlen, die Konfiguration abzubrechen und den Kalibrierungsprozess durchzuführen, damit SAT den Wert automatisch für eine optimale Leistung bestimmt.",
"data": {
"minimum_setpoint": "Wert"
- }
+ },
+ "description": "Durch Angabe des Überschwingungsschutzwertes wird SAT die Steuerungsparameter entsprechend anpassen, um eine stabile und komfortable Heizumgebung zu erhalten. Diese manuelle Konfiguration ermöglicht es Ihnen, das System basierend auf Ihrer spezifischen Einrichtung fein abzustimmen.\n\nHinweis: Wenn Sie sich nicht sicher über den Überschwingungsschutzwert sind oder den Kalibrierungsprozess nicht durchgeführt haben, wird empfohlen, die Konfiguration abzubrechen und den Kalibrierungsprozess durchzuführen, damit SAT den Wert automatisch für eine optimale Leistung bestimmt.",
+ "title": "Überschwingungsschutz"
},
"pid_controller": {
- "title": "PID-Regler manuell konfigurieren.",
- "description": "Konfigurieren Sie die proportionalen, integralen und derivativen Verstärkungen manuell, um Ihr Heizsystem fein abzustimmen. Verwenden Sie diese Option, wenn Sie die volle Kontrolle über die PID-Reglerparameter haben möchten. Passen Sie die Verstärkungen basierend auf den spezifischen Eigenschaften und Vorlieben Ihres Heizsystems an.",
"data": {
- "integral": "Integral (kI)",
"derivative": "Derivativ (kD)",
+ "integral": "Integral (kI)",
"proportional": "Proportional (kP)"
- }
+ },
+ "description": "Konfigurieren Sie die proportionalen, integralen und derivativen Verstärkungen manuell, um Ihr Heizsystem fein abzustimmen. Verwenden Sie diese Option, wenn Sie die volle Kontrolle über die PID-Reglerparameter haben möchten. Passen Sie die Verstärkungen basierend auf den spezifischen Eigenschaften und Vorlieben Ihres Heizsystems an.",
+ "title": "PID-Regler manuell konfigurieren."
},
- "calibrated": {
- "title": "Kalibrierung abgeschlossen",
- "description": "Der Kalibrierungsprozess wurde erfolgreich abgeschlossen.\n\nHerzlichen Glückwunsch! Ihr Smart Autotune Thermostat (SAT) wurde kalibriert, um die Heizleistung Ihres Systems zu optimieren. Während des Kalibrierungsprozesses hat SAT die Heizeigenschaften sorgfältig analysiert und den angemessenen Überschwingungsschutzwert bestimmt, um eine präzise Temperaturregelung zu gewährleisten.\n\nÜberschwingungsschutzwert: {minimum_setpoint} °C\n\nDieser Wert stellt die maximale Menge an Überschwingen dar, die während des Heizprozesses erlaubt ist. SAT wird aktiv die Heizung überwachen und anpassen, um übermäßiges Überschwingen zu verhindern und eine komfortable und effiziente Heizerfahrung in Ihrem Zuhause zu gewährleisten.\n\nBitte beachten Sie, dass der Überschwingungsschutzwert je nach den spezifischen Eigenschaften Ihres Heizsystems und Umweltfaktoren variieren kann. Er wurde fein abgestimmt, um basierend auf den Kalibrierungsergebnissen eine optimale Leistung zu bieten.",
+ "sensors": {
+ "data": {
+ "humidity_sensor_entity_id": "Feuchtigkeitssensor-Entität",
+ "inside_sensor_entity_id": "Innensensor-Entität",
+ "outside_sensor_entity_id": "Außensensor-Entität"
+ },
+ "description": "Bitte wählen Sie die Sensoren aus, die zur Überwachung der Temperatur verwendet werden sollen.",
+ "title": "Sensoren konfigurieren"
+ },
+ "serial": {
+ "data": {
+ "device": "URL",
+ "name": "Name"
+ },
+ "description": "Um eine Verbindung mit dem OpenTherm Gateway über eine Socket-Verbindung herzustellen, geben Sie bitte die folgenden Details an. Geben Sie im Feld Name einen Namen für das Gateway ein, der Ihnen hilft, es in Ihrem System zu identifizieren.\n\nGeben Sie die Netzwerkadresse des OpenTherm Gateways im Feld Gerät an. Dies könnte im Format \"socket://otgw.local:25238\" sein, wobei \"otgw.local\" der Hostname oder die IP-Adresse des Gateways ist und \"25238\" die Portnummer.\n\nDiese Einstellungen sind wesentlich, um die Kommunikation und Integration mit Ihrem OpenTherm Gateway über die Socket-Verbindung herzustellen. Stellen Sie sicher, dass die angegebenen Details korrekt sind, um eine ordnungsgemäße Funktionalität zu gewährleisten.",
+ "title": "OpenTherm Gateway ( SERIELL )"
+ },
+ "simulator": {
+ "data": {
+ "maximum_setpoint": "Maximale Einstellung",
+ "minimum_setpoint": "Mindesteinstellung",
+ "name": "Name",
+ "simulated_cooling": "Simulierte Kühlung",
+ "simulated_heating": "Simulierte Heizung",
+ "simulated_warming_up": "Simuliertes Aufwärmen"
+ },
+ "description": "Dieses Gateway ermöglicht es Ihnen, einen Boiler zu simulieren, zu Test- und Demonstrationszwecken. Bitte geben Sie die folgenden Informationen an, um den Simulator zu konfigurieren.\n\nHinweis: Das Simulator Gateway ist nur zu Test- und Demonstrationszwecken gedacht und sollte nicht in Produktionsumgebungen verwendet werden.",
+ "title": "Simuliertes Gateway ( FORTGESCHRITTEN )"
+ },
+ "switch": {
+ "data": {
+ "device": "Entität",
+ "minimum_setpoint": "Temperatureinstellung",
+ "name": "Name"
+ },
+ "description": "Bitte füllen Sie die folgenden Details aus, um den Schalter einzurichten. Geben Sie einen Namen für den Schalter im Feld Name ein, der Ihnen hilft, ihn in Ihrem System zu identifizieren. Wählen Sie die passende Entität für Ihren Schalter aus den angebotenen Optionen aus.\n\nGeben Sie im Feld Temperatureinstellung die gewünschte Zieltemperatur für Ihr Heizsystem an. Wenn Sie einen Warmwasserboiler verwenden, geben Sie die Boilertemperatureinstellung mit dem entsprechenden Wert an. Für elektrische Heizsysteme geben Sie den Wert 100 ein.\n\nDiese Einstellungen sind wesentlich für eine präzise Temperaturregelung und gewährleisten eine optimale Leistung Ihres Heizsystems. Die Angabe der korrekten Temperatureinstellung ermöglicht eine genaue Regelung und hilft dabei, eine komfortable und energieeffiziente Umgebung in Ihrem Zuhause zu erreichen.",
+ "title": "PID-Thermostat mit PWM ( EIN/AUS )"
+ },
+ "user": {
+ "description": "SAT ist ein intelligentes Thermostat, das sich selbst optimieren kann, um die Temperaturregelung zu verbessern. Wählen Sie den Modus, der zu Ihrem Heizsystem passt.",
"menu_options": {
- "calibrate": "Kalibrierung erneut versuchen",
- "finish": "Mit der aktuellen Kalibrierung fortfahren"
- }
+ "mosquitto": "MQTT-Gateway (OpenTherm, EMS-ESP, andere)",
+ "serial": "Serielles Gateway (z. B. OpenTherm)",
+ "esphome": "ESPHome (Systemdienste und Sensoren)",
+ "simulator": "Simuliertes Gateway (nur für Entwickler)",
+ "switch": "PID-Thermostat (PWM Ein/Aus-Modus)"
+ },
+ "title": "Smart Autotune Thermostat (SAT)"
}
- },
- "error": {
- "connection": "Verbindung zum Gateway konnte nicht hergestellt werden.",
- "mqtt_component": "Die MQTT-Komponente ist nicht verfügbar.",
- "unable_to_calibrate": "Der Kalibrierungsprozess ist auf ein Problem gestoßen und konnte nicht erfolgreich abgeschlossen werden. Bitte stellen Sie sicher, dass Ihr Heizsystem ordnungsgemäß funktioniert und dass alle erforderlichen Sensoren angeschlossen und funktionsfähig sind.\n\nWenn Sie weiterhin Probleme mit der Kalibrierung haben, ziehen Sie in Erwägung, uns für weitere Unterstützung zu kontaktieren. Wir entschuldigen uns für etwaige Unannehmlichkeiten."
- },
- "abort": {
- "already_configured": "Gateway ist bereits konfiguriert."
- },
- "progress": {
- "calibration": "Kalibrierung und Ermittlung des Überschwingungsschutzwertes...\n\nBitte warten Sie, während wir Ihr Heizsystem optimieren. Dieser Prozess kann etwa 20 Minuten dauern."
}
},
"options": {
"step": {
- "init": {
- "menu_options": {
- "general": "Allgemein",
- "presets": "Voreinstellungen",
- "advanced": "Erweiterte Optionen",
- "system_configuration": "Systemkonfiguration"
- }
+ "advanced": {
+ "data": {
+ "error_monitoring": "Fehlerüberwachung aktivieren",
+ "climate_valve_offset": "Klimaventil-Offset",
+ "dynamic_minimum_setpoint": "Dynamischer Minimaler Sollwert (Experimentell)",
+ "force_pulse_width_modulation": "Pulsweitenmodulation erzwingen",
+ "maximum_consumption": "Maximalverbrauch",
+ "maximum_relative_modulation": "Maximale relative Modulation",
+ "minimum_consumption": "Mindestverbrauch",
+ "sample_time": "Abtastzeit",
+ "simulation": "Simulation",
+ "target_temperature_step": "Schrittweite der Zieltemperatur",
+ "thermal_comfort": "Thermischer Komfort"
+ },
+ "data_description": {
+ "climate_valve_offset": "Offset zur Anpassung des Öffnungsgrads des Klimaventils.",
+ "dynamic_minimum_setpoint": "Aktiviert die dynamische Sollwertanpassung basierend auf der Rücklauftemperatur des Kessels, was auch hilft zu identifizieren, ob Ventile geschlossen sind.",
+ "maximum_consumption": "Der maximale Gasverbrauch, wenn der Kessel aktiv ist.",
+ "maximum_relative_modulation": "Die höchste Modulationsstufe für ein effizientes Heizsystem darstellen.",
+ "minimum_consumption": "Der minimale Gasverbrauch, wenn der Kessel aktiv ist.",
+ "sample_time": "Das minimale Zeitintervall zwischen Aktualisierungen des PID-Reglers.",
+ "target_temperature_step": "Die Schrittweite der Zieltemperatur zur Feinabstimmung der Komfortstufen anpassen.",
+ "thermal_comfort": "Verwendung des Simmer-Index für die Anpassung des thermischen Komforts aktivieren."
+ },
+ "title": "Erweitert"
},
"general": {
- "title": "Allgemein",
- "description": "Allgemeine Einstellungen und Konfigurationen.",
"data": {
- "integral": "Integral (kI)",
- "derivative": "Ableitung (kD)",
- "proportional": "Proportional (kP)",
- "maximum_setpoint": "Maximaler Sollwert",
- "window_sensors": "Kontaktsensoren",
"automatic_gains_value": "Automatischer Verstärkungswert",
+ "derivative": "Ableitung (kD)",
"derivative_time_weight": "Zeitgewicht der Ableitung",
- "heating_curve_version": "Version der Heizkurve",
- "heating_curve_coefficient": "Koeffizient der Heizkurve",
"duty_cycle": "Maximaler Tastgrad für Pulsweitenmodulation",
- "sync_with_thermostat": "Sollwert mit Thermostat synchronisieren"
+ "heating_curve_coefficient": "Koeffizient der Heizkurve",
+ "heating_curve_version": "Version der Heizkurve",
+ "integral": "Integral (kI)",
+ "maximum_setpoint": "Maximaler Sollwert",
+ "minimum_setpoint_adjustment_factor": "Anpassungsfaktor für die Rücklauftemperatur",
+ "pid_controller_version": "PID-Reglerversion",
+ "proportional": "Proportional (kP)",
+ "sync_with_thermostat": "Sollwert mit Thermostat synchronisieren",
+ "window_sensors": "Kontaktsensoren",
+ "heating_mode": "Heizbetrieb"
},
"data_description": {
- "integral": "Der Integralterm (kI) im PID-Regler, verantwortlich für die Reduzierung des stationären Fehlers.",
- "derivative": "Der Ableitungsterm (kD) im PID-Regler, verantwortlich für die Minderung von Überschwingen.",
- "proportional": "Der proportionale Term (kP) im PID-Regler, verantwortlich für die sofortige Reaktion auf Fehler.",
- "maximum_setpoint": "Die optimale Temperatur für einen effizienten Kesselbetrieb.",
- "window_sensors": "Kontaktsensoren, die das System auslösen, wenn ein Fenster oder eine Tür für eine bestimmte Zeit geöffnet ist.",
"automatic_gains_value": "Der Wert, der für automatische Verstärkungen im PID-Regler verwendet wird.",
- "heating_curve_coefficient": "Der Koeffizient, der zur Anpassung der Heizkurve verwendet wird.",
+ "derivative": "Der Ableitungsterm (kD) im PID-Regler, verantwortlich für die Minderung von Überschwingen.",
+ "derivative_time_weight": "Ein Parameter zur Anpassung des Einflusses des Ableitungsterms über die Zeit, besonders nützlich, um Unterschwingen während der Aufwärmphase zu reduzieren, wenn der Heizkurvenkoeffizient korrekt eingestellt ist.",
"duty_cycle": "Der maximale Tastgrad für Pulsweitenmodulation (PWM), der die Ein-Aus-Zyklen des Kessels steuert.",
+ "heating_curve_coefficient": "Der Koeffizient, der zur Anpassung der Heizkurve verwendet wird.",
+ "integral": "Der Integralterm (kI) im PID-Regler, verantwortlich für die Reduzierung des stationären Fehlers.",
+ "maximum_setpoint": "Die optimale Temperatur für einen effizienten Kesselbetrieb.",
+ "minimum_setpoint_adjustment_factor": "Dieser Faktor passt den Heizungssollwert basierend auf der Rücklauftemperatur des Kessels an, was die Heizungsreaktivität und -effizienz beeinflusst. Ein höherer Wert erhöht die Sensibilität gegenüber Temperaturänderungen und verbessert die Kontrolle über Komfort und Energieverbrauch. Empfohlener Startbereich liegt zwischen 0,1 und 0,5. Anpassen, um Ihrem System und Ihren Komfortpräferenzen zu entsprechen.",
+ "proportional": "Der proportionale Term (kP) im PID-Regler, verantwortlich für die sofortige Reaktion auf Fehler.",
"sync_with_thermostat": "Sollwert mit Thermostat synchronisieren, um eine koordinierte Temperaturregelung zu gewährleisten.",
- "derivative_time_weight": "Ein Parameter zur Anpassung des Einflusses des Ableitungsterms über die Zeit, besonders nützlich, um Unterschwingen während der Aufwärmphase zu reduzieren, wenn der Heizkurvenkoeffizient korrekt eingestellt ist."
+ "window_sensors": "Kontaktsensoren, die das System auslösen, wenn ein Fenster oder eine Tür für eine bestimmte Zeit geöffnet ist."
+ },
+ "description": "Allgemeine Einstellungen und Konfigurationen.",
+ "title": "Allgemein"
+ },
+ "init": {
+ "menu_options": {
+ "advanced": "Erweiterte Optionen",
+ "general": "Allgemein",
+ "presets": "Voreinstellungen",
+ "system_configuration": "Systemkonfiguration"
}
},
"presets": {
- "title": "Voreinstellungen",
- "description": "Vordefinierte Temperatureinstellungen für verschiedene Szenarien oder Aktivitäten.",
"data": {
+ "activity_temperature": "Aktivitätstemperatur",
"away_temperature": "Abwesenheitstemperatur",
+ "comfort_temperature": "Komforttemperatur",
"home_temperature": "Heimtemperatur",
"sleep_temperature": "Schlafzimmertemperatur",
- "comfort_temperature": "Komforttemperatur",
- "activity_temperature": "Aktivitätstemperatur",
"sync_climates_with_preset": "Klimazonen mit Voreinstellung synchronisieren (schlafen/abwesend/aktiv)"
- }
+ },
+ "description": "Vordefinierte Temperatureinstellungen für verschiedene Szenarien oder Aktivitäten.",
+ "title": "Voreinstellungen"
},
"system_configuration": {
- "title": "Systemkonfiguration",
- "description": "Für Feinabstimmung und Anpassung.",
"data": {
"automatic_duty_cycle": "Automatischer Tastgrad",
"overshoot_protection": "Überschwingungsschutz (mit PWM)",
- "window_minimum_open_time": "Mindestzeit für geöffnetes Fenster",
- "sensor_max_value_age": "Maximales Alter des Temperatursensorwerts"
+ "sync_climates_with_mode": "Klimazonen mit Modus synchronisieren",
+ "sensor_max_value_age": "Maximales Alter des Temperatursensorwerts",
+ "window_minimum_open_time": "Mindestzeit für geöffnetes Fenster"
},
"data_description": {
"automatic_duty_cycle": "Automatischen Tastgrad für Pulsweitenmodulation (PWM) aktivieren oder deaktivieren.",
"overshoot_protection": "Überschwingungsschutz mit Pulsweitenmodulation (PWM) aktivieren, um die Temperaturüberschreitung des Kessels zu verhindern.",
- "window_minimum_open_time": "Die Mindestzeit, die ein Fenster geöffnet sein muss, bevor das System reagiert.",
- "sensor_max_value_age": "Das maximale Alter des Temperatursensorwerts, bevor er als gestoppt betrachtet wird."
- }
- },
- "advanced": {
- "title": "Erweitert",
- "data": {
- "simulation": "Simulation",
- "sample_time": "Abtastzeit",
- "thermal_comfort": "Thermischer Komfort",
- "minimum_consumption": "Mindestverbrauch",
- "maximum_consumption": "Maximalverbrauch",
- "climate_valve_offset": "Klimaventil-Offset",
- "target_temperature_step": "Schrittweite der Zieltemperatur",
- "maximum_relative_modulation": "Maximale relative Modulation",
- "force_pulse_width_modulation": "Pulsweitenmodulation erzwingen",
- "dynamic_minimum_setpoint": "Dynamischer Minimaler Sollwert (Experimentell)"
+ "sensor_max_value_age": "Das maximale Alter des Temperatursensorwerts, bevor er als gestoppt betrachtet wird.",
+ "window_minimum_open_time": "Die Mindestzeit, die ein Fenster geöffnet sein muss, bevor das System reagiert."
},
- "data_description": {
- "thermal_comfort": "Verwendung des Simmer-Index für die Anpassung des thermischen Komforts aktivieren.",
- "minimum_consumption": "Der minimale Gasverbrauch, wenn der Kessel aktiv ist.",
- "maximum_consumption": "Der maximale Gasverbrauch, wenn der Kessel aktiv ist.",
- "climate_valve_offset": "Offset zur Anpassung des Öffnungsgrads des Klimaventils.",
- "target_temperature_step": "Die Schrittweite der Zieltemperatur zur Feinabstimmung der Komfortstufen anpassen.",
- "sample_time": "Das minimale Zeitintervall zwischen Aktualisierungen des PID-Reglers.",
- "maximum_relative_modulation": "Die höchste Modulationsstufe für ein effizientes Heizsystem darstellen."
- }
+ "description": "Für Feinabstimmung und Anpassung.",
+ "title": "Systemkonfiguration"
}
}
}
-}
+}
\ No newline at end of file
diff --git a/custom_components/sat/translations/en.json b/custom_components/sat/translations/en.json
index 311a0857..a6e6b2dc 100644
--- a/custom_components/sat/translations/en.json
+++ b/custom_components/sat/translations/en.json
@@ -1,221 +1,248 @@
{
"config": {
+ "abort": {
+ "already_configured": "Gateway is already configured.",
+ "reconfigure_successful": "Gateway has been re-configured."
+ },
+ "error": {
+ "connection": "Unable to connect to the gateway.",
+ "mqtt_component": "The MQTT component is unavailable.",
+ "unable_to_calibrate": "The calibration process has encountered an issue and could not be completed successfully. Please ensure that your heating system is functioning properly and that all required sensors are connected and working correctly.\n\nIf you continue to experience issues with calibration, consider contacting us for further assistance. We apologize for any inconvenience caused."
+ },
+ "progress": {
+ "calibration": "Calibrating and finding the overshoot protection value...\n\nPlease wait while we optimize your heating system. This process may take approximately 20 minutes."
+ },
"step": {
- "user": {
- "title": "Smart Autotune Thermostat (SAT)",
- "description": "SAT is a smart thermostat that is capable of auto-tuning itself to optimize temperature control. Select the appropriate mode that matches your heating system.",
- "menu_options": {
- "mosquitto": "OpenTherm Gateway ( MQTT )",
- "serial": "OpenTherm Gateway ( SERIAL )",
- "switch": "PID Thermostat with PWM ( ON/OFF )",
- "simulator": "Simulated Gateway ( ADVANCED )"
- }
- },
- "mosquitto": {
- "title": "OpenTherm Gateway ( MQTT )",
- "description": "Please provide the following details to set up the OpenTherm Gateway. In the Name field, enter a name for the gateway that will help you identify it within your system.\n\nSpecify the Climate entity to use for the OpenTherm Gateway. This entity is provided by the OpenTherm Gateway and represents your heating system.\n\nAdditionally, enter the Top Topic that will be used for publishing and subscribing to MQTT messages related to the OpenTherm Gateway.\n\nThese settings are essential for establishing communication and integration with your OpenTherm Gateway through MQTT. They allow for seamless data exchange and control of your heating system. Ensure that the provided details are accurate to ensure proper functionality.",
- "data": {
- "name": "Name",
- "device": "Device",
- "mqtt_topic": "Top Topic"
- }
- },
- "serial": {
- "title": "OpenTherm Gateway ( SERIAL )",
- "description": "To establish a connection with the OpenTherm Gateway using a socket connection, please provide the following details. In the Name field, enter a name for the gateway that will help you identify it within your system.\n\nSpecify the network address of the OpenTherm Gateway in the Device field. This could be in the format of \"socket://otgw.local:25238\", where \"otgw.local\" is the hostname or IP address of the gateway and \"25238\" is the port number.\n\nThese settings are essential for establishing communication and integration with your OpenTherm Gateway through the socket connection. Ensure that the provided details are accurate to ensure proper functionality.",
- "data": {
- "name": "Name",
- "device": "URL"
- }
- },
- "switch": {
- "title": "PID Thermostat with PWM ( ON/OFF )",
- "description": "Please fill in the following details to set up the switch. Enter a name for the switch in the Name field, which will help you identify it within your system. Choose the appropriate entity to use for your switch from the provided options.\n\nIn the Temperature Setting field, specify the desired target temperature for your heating system. If you are using a hot water boiler, fill in the Boiler Temperature Setting with the appropriate value. For electric heating systems, enter the value 100.\n\nThese settings are essential for precise temperature control and ensuring optimal performance of your heating system. Providing the correct Temperature Setting allows for accurate regulation and helps achieve a comfortable and energy-efficient environment in your home.",
- "data": {
- "name": "Name",
- "device": "Entity",
- "minimum_setpoint": "Temperature Setting"
- }
- },
- "simulator": {
- "title": "Simulated Gateway ( ADVANCED )",
- "description": "This gateway allows you to simulate a boiler for testing and demonstration purposes. Please provide the following information to configure the simulator.\n\nNote: The Simulator Gateway is intended for testing and demonstration purposes only and should not be used in production environments.",
- "data": {
- "name": "Name",
- "minimum_setpoint": "Minimum Setpoint",
- "maximum_setpoint": "Maximum Setpoint",
- "simulated_heating": "Simulated Heating",
- "simulated_cooling": "Simulated Cooling",
- "simulated_warming_up": "Simulated Warming Up"
- }
- },
- "sensors": {
- "title": "Configure sensors",
- "description": "Please select the sensors that will be used to track the temperature.",
- "data": {
- "inside_sensor_entity_id": "Inside Sensor Entity",
- "outside_sensor_entity_id": "Outside Sensor Entity",
- "humidity_sensor_entity_id": "Humidity Sensor Entity"
- }
- },
- "heating_system": {
- "title": "Heating System",
- "description": "Selecting the correct heating system type is important for SAT to accurately control the temperature and optimize performance. Choose the option that matches your setup to ensure proper temperature regulation throughout your home.",
- "data": {
- "heating_system": "System"
- }
- },
"areas": {
- "title": "Areas",
- "description": "Settings related to climates, multi-room and temperature control. Primary climates are in the same room as the inside sensor and the rooms have their own target temperatures separate from the system.",
"data": {
"main_climates": "Primary",
"secondary_climates": "Rooms"
- }
+ },
+ "description": "Settings related to climates, multi-room and temperature control. Primary climates are in the same room as the inside sensor and the rooms have their own target temperatures separate from the system.",
+ "title": "Areas"
},
"automatic_gains": {
- "title": "Automatic Gains",
- "description": "This feature adjusts the control parameters of your heating system dynamically, optimizing temperature control for better comfort and energy efficiency. Enabling this option allows SAT to continuously adapt and fine-tune the heating settings based on the environmental conditions. This helps maintain a stable and comfortable environment without manual intervention.\n\nNote: If you choose not to enable automatic gains, you will need to manually enter the PID values for precise temperature control. Please ensure that you have accurate PID values for your specific heating system to achieve optimal performance.",
"data": {
"automatic_gains": "Automatic Gains (recommended)"
- }
+ },
+ "description": "This feature adjusts the control parameters of your heating system dynamically, optimizing temperature control for better comfort and energy efficiency. Enabling this option allows SAT to continuously adapt and fine-tune the heating settings based on the environmental conditions. This helps maintain a stable and comfortable environment without manual intervention.\n\nNote: If you choose not to enable automatic gains, you will need to manually enter the PID values for precise temperature control. Please ensure that you have accurate PID values for your specific heating system to achieve optimal performance.",
+ "title": "Automatic Gains"
},
"calibrate_system": {
- "title": "Calibrate System",
"description": "Optimize your heating system by automatically determining the optimal PID values for your setup. When selecting Automatic Gains, please note that the system will go through a calibration process that may take approximately 20 minutes to complete.\n\nAutomatic Gains is recommended for most users as it simplifies the setup process and ensures optimal performance. However, if you're familiar with PID control and prefer to manually set the values, you can choose to skip Automatic Gains.\n\nPlease note that choosing to skip Automatic Gains requires a good understanding of PID control and may require additional manual adjustments to achieve optimal performance.",
"menu_options": {
"calibrate": "Calibrate and determine your overshoot protection value (approx. 20 min).",
"overshoot_protection": "Manually enter the overshoot protection value.",
"pid_controller": "Manually enter PID values (not recommended)."
- }
+ },
+ "title": "Calibrate System"
+ },
+ "calibrated": {
+ "description": "The calibration process has completed successfully.\n\nCongratulations! Your Smart Autotune Thermostat (SAT) has been calibrated to optimize the heating performance of your system. During the calibration process, SAT carefully analyzed the heating characteristics and determined the appropriate overshoot protection value to ensure precise temperature control.\n\nOvershoot Protection Value: {minimum_setpoint} °C\n\nThis value represents the maximum amount of overshoot allowed during the heating process. SAT will actively monitor and adjust the heating to prevent excessive overshooting, maintaining a comfortable and efficient heating experience in your home.\n\nPlease note that the overshoot protection value may vary depending on the specific characteristics of your heating system and environmental factors. It has been fine-tuned to provide optimal performance based on the calibration results.",
+ "menu_options": {
+ "calibrate": "Retry calibration",
+ "finish": "Continue with current calibration"
+ },
+ "title": "Calibration Completed"
+ },
+ "heating_system": {
+ "data": {
+ "heating_system": "System"
+ },
+ "description": "Selecting the correct heating system type is important for SAT to accurately control the temperature and optimize performance. Choose the option that matches your setup to ensure proper temperature regulation throughout your home.",
+ "title": "Heating System"
+ },
+ "mosquitto": {
+ "description": "Configure the MQTT mode for your heating system. Select the gateway mode and provide its name.",
+ "data": {
+ "mode": "Mode"
+ },
+ "title": "MQTT Configuration"
+ },
+ "mosquitto_opentherm": {
+ "description": "Set up the OpenTherm Gateway. Provide the top MQTT topic and the device identifier.",
+ "data": {
+ "name": "Name",
+ "mqtt_topic": "MQTT Topic",
+ "device": "Device ID"
+ },
+ "title": "OpenTherm Gateway Setup"
+ },
+ "mosquitto_ems": {
+ "description": "Set up the EMS-ESP Gateway. Provide the MQTT topic.",
+ "data": {
+ "name": "Name",
+ "mqtt_topic": "MQTT Topic"
+ },
+ "title": "EMS-ESP Gateway Setup"
},
"overshoot_protection": {
- "title": "Overshoot Protection",
- "description": "By providing the overshoot protection value, SAT will adjust the control parameters accordingly to maintain a stable and comfortable heating environment. This manual configuration allows you to fine-tune the system based on your specific setup.\n\nNote: If you are unsure about the overshoot protection value or have not performed the calibration process, it is recommended to cancel the configuration and go through the calibration process to let SAT automatically determine the value for optimal performance.",
"data": {
"minimum_setpoint": "Value"
- }
+ },
+ "description": "By providing the overshoot protection value, SAT will adjust the control parameters accordingly to maintain a stable and comfortable heating environment. This manual configuration allows you to fine-tune the system based on your specific setup.\n\nNote: If you are unsure about the overshoot protection value or have not performed the calibration process, it is recommended to cancel the configuration and go through the calibration process to let SAT automatically determine the value for optimal performance.",
+ "title": "Overshoot Protection"
},
"pid_controller": {
- "title": "Configure the PID controller manually.",
- "description": "Configure the proportional, integral, and derivative gains manually to fine-tune your heating system. Use this option if you prefer to have full control over the PID controller parameters. Adjust the gains based on your specific heating system characteristics and preferences.",
"data": {
- "integral": "Integral (kI)",
"derivative": "Derivative (kD)",
+ "integral": "Integral (kI)",
"proportional": "Proportional (kP)"
- }
+ },
+ "description": "Configure the proportional, integral, and derivative gains manually to fine-tune your heating system. Use this option if you prefer to have full control over the PID controller parameters. Adjust the gains based on your specific heating system characteristics and preferences.",
+ "title": "Configure the PID controller manually."
},
- "calibrated": {
- "title": "Calibration Completed",
- "description": "The calibration process has completed successfully.\n\nCongratulations! Your Smart Autotune Thermostat (SAT) has been calibrated to optimize the heating performance of your system. During the calibration process, SAT carefully analyzed the heating characteristics and determined the appropriate overshoot protection value to ensure precise temperature control.\n\nOvershoot Protection Value: {minimum_setpoint} °C\n\nThis value represents the maximum amount of overshoot allowed during the heating process. SAT will actively monitor and adjust the heating to prevent excessive overshooting, maintaining a comfortable and efficient heating experience in your home.\n\nPlease note that the overshoot protection value may vary depending on the specific characteristics of your heating system and environmental factors. It has been fine-tuned to provide optimal performance based on the calibration results.",
+ "sensors": {
+ "data": {
+ "humidity_sensor_entity_id": "Humidity Sensor Entity",
+ "inside_sensor_entity_id": "Inside Sensor Entity",
+ "outside_sensor_entity_id": "Outside Sensor Entity"
+ },
+ "description": "Please select the sensors that will be used to track the temperature.",
+ "title": "Configure sensors"
+ },
+ "serial": {
+ "data": {
+ "device": "URL",
+ "name": "Name"
+ },
+ "description": "To establish a connection with the OpenTherm Gateway using a socket connection, please provide the following details. In the Name field, enter a name for the gateway that will help you identify it within your system.\n\nSpecify the network address of the OpenTherm Gateway in the Device field. This could be in the format of \"socket://otgw.local:25238\", where \"otgw.local\" is the hostname or IP address of the gateway and \"25238\" is the port number.\n\nThese settings are essential for establishing communication and integration with your OpenTherm Gateway through the socket connection. Ensure that the provided details are accurate to ensure proper functionality.",
+ "title": "OpenTherm Gateway ( SERIAL )"
+ },
+ "simulator": {
+ "data": {
+ "maximum_setpoint": "Maximum Setpoint",
+ "minimum_setpoint": "Minimum Setpoint",
+ "name": "Name",
+ "simulated_cooling": "Simulated Cooling",
+ "simulated_heating": "Simulated Heating",
+ "simulated_warming_up": "Simulated Warming Up"
+ },
+ "description": "This gateway allows you to simulate a boiler for testing and demonstration purposes. Please provide the following information to configure the simulator.\n\nNote: The Simulator Gateway is intended for testing and demonstration purposes only and should not be used in production environments.",
+ "title": "Simulated Gateway ( ADVANCED )"
+ },
+ "switch": {
+ "data": {
+ "device": "Entity",
+ "minimum_setpoint": "Temperature Setting",
+ "name": "Name"
+ },
+ "description": "Please fill in the following details to set up the switch. Enter a name for the switch in the Name field, which will help you identify it within your system. Choose the appropriate entity to use for your switch from the provided options.\n\nIn the Temperature Setting field, specify the desired target temperature for your heating system. If you are using a hot water boiler, fill in the Boiler Temperature Setting with the appropriate value. For electric heating systems, enter the value 100.\n\nThese settings are essential for precise temperature control and ensuring optimal performance of your heating system. Providing the correct Temperature Setting allows for accurate regulation and helps achieve a comfortable and energy-efficient environment in your home.",
+ "title": "PID Thermostat with PWM ( ON/OFF )"
+ },
+ "user": {
+ "description": "SAT is a smart thermostat capable of auto-tuning itself to optimize temperature control. Select the mode that matches your heating system.",
"menu_options": {
- "calibrate": "Retry calibration",
- "finish": "Continue with current calibration"
- }
+ "mosquitto": "MQTT Gateway (OpenTherm, EMS-ESP, others)",
+ "serial": "Serial Gateway (e.g., OpenTherm)",
+ "esphome": "ESPHome (System services and sensors)",
+ "simulator": "Simulated Gateway (Developer use only)",
+ "switch": "PID Thermostat (PWM On/Off mode)"
+ },
+ "title": "Smart Autotune Thermostat (SAT)"
}
- },
- "error": {
- "connection": "Unable to connect to the gateway.",
- "mqtt_component": "The MQTT component is unavailable.",
- "unable_to_calibrate": "The calibration process has encountered an issue and could not be completed successfully. Please ensure that your heating system is functioning properly and that all required sensors are connected and working correctly.\n\nIf you continue to experience issues with calibration, consider contacting us for further assistance. We apologize for any inconvenience caused."
- },
- "abort": {
- "already_configured": "Gateway is already configured."
- },
- "progress": {
- "calibration": "Calibrating and finding the overshoot protection value...\n\nPlease wait while we optimize your heating system. This process may take approximately 20 minutes."
}
},
"options": {
"step": {
- "init": {
- "menu_options": {
- "general": "General",
- "presets": "Presets",
- "advanced": "Advanced Options",
- "system_configuration": "System Configuration"
- }
+ "advanced": {
+ "data": {
+ "error_monitoring": "Enable error monitoring",
+ "climate_valve_offset": "Climate valve offset",
+ "dynamic_minimum_setpoint": "Dynamic Minimum Setpoint (Experimental)",
+ "force_pulse_width_modulation": "Force Pulse Width Modulation",
+ "maximum_consumption": "Maximum Consumption",
+ "maximum_relative_modulation": "Maximum Relative Modulation",
+ "minimum_consumption": "Minimum Consumption",
+ "sample_time": "Sample Time",
+ "simulation": "Simulation",
+ "target_temperature_step": "Target Temperature Step",
+ "thermal_comfort": "Thermal Comfort"
+ },
+ "data_description": {
+ "climate_valve_offset": "Offset to adjust the opening degree of the climate valve.",
+ "dynamic_minimum_setpoint": "Activates the dynamic setpoint adjustment based on the boiler's return temperature, which also helps identify if any valves are closed.",
+ "maximum_consumption": "The maximum gas consumption when the boiler is active.",
+ "maximum_relative_modulation": "Representing the highest modulation level for an efficient heating system.",
+ "minimum_consumption": "The minimum gas consumption when the boiler is active.",
+ "sample_time": "The minimum time interval between updates to the PID controller.",
+ "target_temperature_step": "Adjust the target temperature step for fine-tuning comfort levels.",
+ "thermal_comfort": "Enable the use of the Simmer Index for thermal comfort adjustment."
+ },
+ "title": "Advanced"
},
"general": {
- "title": "General",
- "description": "General settings and configurations.",
"data": {
- "integral": "Integral (kI)",
- "derivative": "Derivative (kD)",
- "proportional": "Proportional (kP)",
- "maximum_setpoint": "Maximum Setpoint",
- "window_sensors": "Contact Sensors",
"automatic_gains_value": "Automatic Gains Value",
+ "derivative": "Derivative (kD)",
"derivative_time_weight": "Derivative Time Weight",
- "heating_curve_version": "Heating Curve Version",
- "heating_curve_coefficient": "Heating Curve Coefficient",
"duty_cycle": "Maximum Duty Cycle for Pulse Width Modulation",
- "sync_with_thermostat": "Synchronize setpoint with thermostat"
+ "heating_curve_coefficient": "Heating Curve Coefficient",
+ "heating_curve_version": "Heating Curve Version",
+ "integral": "Integral (kI)",
+ "maximum_setpoint": "Maximum Setpoint",
+ "minimum_setpoint_adjustment_factor": "Adjustment Factor for Return Temperature",
+ "pid_controller_version": "PID Controller Version",
+ "proportional": "Proportional (kP)",
+ "sync_with_thermostat": "Synchronize setpoint with thermostat",
+ "window_sensors": "Contact Sensors",
+ "heating_mode": "Heating Mode"
},
"data_description": {
- "integral": "The integral term (kI) in the PID controller, responsible for reducing steady-state error.",
- "derivative": "The derivative term (kD) in the PID controller, responsible for mitigating overshooting.",
- "proportional": "The proportional term (kP) in the PID controller, responsible for the immediate response to errors.",
- "maximum_setpoint": "The optimal temperature for efficient boiler operation.",
- "window_sensors": "Contact Sensors that trigger the system to react when a window or door is open for a period of time.",
"automatic_gains_value": "The value used for automatic gains in the PID controller.",
- "heating_curve_coefficient": "The coefficient used to adjust the heating curve.",
+ "derivative": "The derivative term (kD) in the PID controller, responsible for mitigating overshooting.",
+ "derivative_time_weight": "A parameter to adjust the influence of the derivative term over time, particularly useful for reducing undershoot during the warm-up phase when the heating curve coefficient is correctly set.",
"duty_cycle": "The maximum duty cycle for Pulse Width Modulation (PWM), controlling the boiler's on-off cycles.",
+ "heating_curve_coefficient": "The coefficient used to adjust the heating curve.",
+ "integral": "The integral term (kI) in the PID controller, responsible for reducing steady-state error.",
+ "maximum_setpoint": "The optimal temperature for efficient boiler operation.",
+ "minimum_setpoint_adjustment_factor": "This factor adjusts the heating setpoint based on the boiler's return temperature, affecting heating responsiveness and efficiency. A higher value increases sensitivity to temperature changes, enhancing control over comfort and energy use. Recommended starting range is 0.1 to 0.5. Adjust to suit your system and comfort preferences.",
+ "proportional": "The proportional term (kP) in the PID controller, responsible for the immediate response to errors.",
"sync_with_thermostat": "Synchronize setpoint with thermostat to ensure coordinated temperature control.",
- "derivative_time_weight": "A parameter to adjust the influence of the derivative term over time, particularly useful for reducing undershoot during the warm-up phase when the heating curve coefficient is correctly set."
+ "window_sensors": "Contact Sensors that trigger the system to react when a window or door is open for a period of time."
+ },
+ "description": "General settings and configurations.",
+ "title": "General"
+ },
+ "init": {
+ "menu_options": {
+ "advanced": "Advanced Options",
+ "general": "General",
+ "presets": "Presets",
+ "system_configuration": "System Configuration"
}
},
"presets": {
- "title": "Presets",
- "description": "Predefined temperature settings for different scenarios or activities.",
"data": {
+ "activity_temperature": "Activity Temperature",
"away_temperature": "Away Temperature",
+ "comfort_temperature": "Comfort Temperature",
"home_temperature": "Home Temperature",
"sleep_temperature": "Sleep Temperature",
- "comfort_temperature": "Comfort Temperature",
- "activity_temperature": "Activity Temperature",
+ "sync_with_thermostat": "Synchronize with thermostat attached to the boiler",
"sync_climates_with_preset": "Synchronize climates with preset (sleep / away / activity)"
- }
+ },
+ "description": "Predefined temperature settings for different scenarios or activities.",
+ "title": "Presets"
},
"system_configuration": {
- "title": "System Configuration",
- "description": "For fine-tuning and customization.",
"data": {
+ "cycles_per_hour": "Duty Cycles per hour",
"automatic_duty_cycle": "Automatic duty cycle",
+ "sync_climates_with_mode": "Synchronize climates with mode",
"overshoot_protection": "Overshoot Protection (with PWM)",
- "window_minimum_open_time": "Minimum time for window to be open",
- "sensor_max_value_age": "Temperature Sensor maximum value age"
+ "sensor_max_value_age": "Temperature Sensor maximum value age",
+ "window_minimum_open_time": "Minimum time for window to be open"
},
"data_description": {
+ "cycles_per_hour": "The maximum amount of duty cycles per hour.",
"automatic_duty_cycle": "Enable or disable automatic duty cycle for Pulse Width Modulation (PWM).",
"overshoot_protection": "Enable overshoot protection with Pulse Width Modulation (PWM) to prevent boiler temperature overshooting.",
- "window_minimum_open_time": "The minimum time a window must be open before the system reacts.",
- "sensor_max_value_age": "The maximum age of the temperature sensor value before considering it as a stall."
- }
- },
- "advanced": {
- "title": "Advanced",
- "data": {
- "simulation": "Simulation",
- "sample_time": "Sample Time",
- "thermal_comfort": "Thermal Comfort",
- "minimum_consumption": "Minimum Consumption",
- "maximum_consumption": "Maximum Consumption",
- "climate_valve_offset": "Climate valve offset",
- "target_temperature_step": "Target Temperature Step",
- "maximum_relative_modulation": "Maximum Relative Modulation",
- "force_pulse_width_modulation": "Force Pulse Width Modulation",
- "dynamic_minimum_setpoint": "Dynamic Minimum Setpoint (Experimental)"
+ "sensor_max_value_age": "The maximum age of the temperature sensor value before considering it as a stall.",
+ "window_minimum_open_time": "The minimum time a window must be open before the system reacts."
},
- "data_description": {
- "thermal_comfort": "Enable the use of the Simmer Index for thermal comfort adjustment.",
- "minimum_consumption": "The minimum gas consumption when the boiler is active.",
- "maximum_consumption": "The maximum gas consumption when the boiler is active.",
- "climate_valve_offset": "Offset to adjust the opening degree of the climate valve.",
- "target_temperature_step": "Adjust the target temperature step for fine-tuning comfort levels.",
- "sample_time": "The minimum time interval between updates to the PID controller.",
- "maximum_relative_modulation": "Representing the highest modulation level for an efficient heating system."
- }
+ "description": "For fine-tuning and customization.",
+ "title": "System Configuration"
}
}
}
diff --git a/custom_components/sat/translations/es.json b/custom_components/sat/translations/es.json
index 1be1bb73..3359efea 100644
--- a/custom_components/sat/translations/es.json
+++ b/custom_components/sat/translations/es.json
@@ -1,221 +1,245 @@
{
"config": {
+ "abort": {
+ "already_configured": "La puerta de enlace ya está configurada.",
+ "reconfigure_successful": "La puerta de enlace ha sido reconfigurada."
+ },
+ "error": {
+ "connection": "No se puede conectar a la puerta de enlace.",
+ "mqtt_component": "El componente MQTT no está disponible.",
+ "unable_to_calibrate": "El proceso de calibración ha encontrado un problema y no se pudo completar con éxito. Por favor, asegúrese de que su sistema de calefacción está funcionando correctamente y que todos los sensores necesarios están conectados y funcionando correctamente.\n\nSi continúa experimentando problemas con la calibración, considere contactarnos para obtener más ayuda. Pedimos disculpas por cualquier inconveniente causado."
+ },
+ "progress": {
+ "calibration": "Calibrando y encontrando el valor de protección contra sobrecalentamiento...\n\nPor favor, espere mientras optimizamos su sistema de calefacción. Este proceso puede tomar aproximadamente 20 minutos."
+ },
"step": {
- "user": {
- "title": "Smart Autotune Thermostat (SAT)",
- "description": "SAT es un termostato inteligente capaz de autoajustarse para optimizar el control de la temperatura. Seleccione el modo apropiado que coincida con su sistema de calefacción.",
- "menu_options": {
- "mosquitto": "Puerta de Enlace OpenTherm (MQTT)",
- "serial": "Puerta de Enlace OpenTherm (SERIAL)",
- "switch": "Termostato PID con PWM (ON/OFF)",
- "simulator": "Puerta de Enlace Simulada (AVANZADO)"
- }
- },
- "mosquitto": {
- "title": "Puerta de Enlace OpenTherm (MQTT)",
- "description": "Proporcione los siguientes detalles para configurar la Puerta de Enlace OpenTherm. En el campo Nombre, introduzca un nombre para la puerta de enlace que le ayude a identificarla dentro de su sistema.\n\nEspecifique la entidad Climática que usará para la Puerta de Enlace OpenTherm. Esta entidad es proporcionada por la Puerta de Enlace OpenTherm y representa su sistema de calefacción.\n\nAdicionalmente, introduzca el Tema Principal que se usará para publicar y suscribirse a mensajes MQTT relacionados con la Puerta de Enlace OpenTherm.\n\nEstos ajustes son esenciales para establecer la comunicación e integración con su Puerta de Enlace OpenTherm a través de MQTT. Permiten un intercambio de datos sin problemas y el control de su sistema de calefacción. Asegúrese de que los detalles proporcionados sean precisos para garantizar una funcionalidad adecuada.",
- "data": {
- "name": "Nombre",
- "device": "Dispositivo",
- "mqtt_topic": "Tema Principal"
- }
- },
- "serial": {
- "title": "Puerta de Enlace OpenTherm (SERIAL)",
- "description": "Para establecer una conexión con la Puerta de Enlace OpenTherm usando una conexión de socket, por favor proporcione los siguientes detalles. En el campo Nombre, introduzca un nombre para la puerta de enlace que le ayude a identificarla dentro de su sistema.\n\nEspecifique la dirección de red de la Puerta de Enlace OpenTherm en el campo Dispositivo. Esto podría estar en el formato de \"socket://otgw.local:25238\", donde \"otgw.local\" es el nombre de host o la dirección IP de la puerta de enlace y \"25238\" es el número de puerto.\n\nEstos ajustes son esenciales para establecer la comunicación e integración con su Puerta de Enlace OpenTherm a través de la conexión de socket. Asegúrese de que los detalles proporcionados sean precisos para garantizar una funcionalidad adecuada.",
- "data": {
- "name": "Nombre",
- "device": "URL"
- }
- },
- "switch": {
- "title": "Termostato PID con PWM (ON/OFF)",
- "description": "Por favor, rellene los siguientes detalles para configurar el interruptor. Introduzca un nombre para el interruptor en el campo Nombre, lo que le ayudará a identificarlo dentro de su sistema. Elija la entidad adecuada para usar con su interruptor de las opciones proporcionadas.\n\nEn el campo de Ajuste de Temperatura, especifique la temperatura objetivo deseada para su sistema de calefacción. Si está usando una caldera de agua caliente, rellene el Ajuste de Temperatura de la Caldera con el valor adecuado. Para sistemas de calefacción eléctrica, introduzca el valor 100.\n\nEstos ajustes son esenciales para un control preciso de la temperatura y para asegurar un rendimiento óptimo de su sistema de calefacción. Proporcionar el Ajuste de Temperatura correcto permite una regulación precisa y ayuda a lograr un ambiente cómodo y eficiente en energía en su hogar.",
- "data": {
- "name": "Nombre",
- "device": "Entidad",
- "minimum_setpoint": "Ajuste de Temperatura"
- }
- },
- "simulator": {
- "title": "Puerta de Enlace Simulada (AVANZADO)",
- "description": "Esta puerta de enlace le permite simular una caldera para propósitos de pruebas y demostración. Por favor, proporcione la siguiente información para configurar el simulador.\n\nNota: La Puerta de Enlace Simulada está destinada solo para pruebas y propósitos de demostración y no debería usarse en entornos de producción.",
- "data": {
- "name": "Nombre",
- "minimum_setpoint": "Ajuste Mínimo",
- "maximum_setpoint": "Ajuste Máximo",
- "simulated_heating": "Calefacción Simulada",
- "simulated_cooling": "Enfriamiento Simulado",
- "simulated_warming_up": "Calentamiento Simulado"
- }
- },
- "sensors": {
- "title": "Configurar sensores",
- "description": "Por favor, seleccione los sensores que se usarán para rastrear la temperatura.",
- "data": {
- "inside_sensor_entity_id": "Entidad del Sensor Interior",
- "outside_sensor_entity_id": "Entidad del Sensor Exterior",
- "humidity_sensor_entity_id": "Entidad del Sensor de Humedad"
- }
- },
- "heating_system": {
- "title": "Sistema de Calefacción",
- "description": "Seleccionar el tipo correcto de sistema de calefacción es importante para que SAT controle la temperatura de manera precisa y optimice el rendimiento. Elija la opción que coincida con su configuración para asegurar una regulación adecuada de la temperatura en todo su hogar.",
- "data": {
- "heating_system": "Sistema"
- }
- },
"areas": {
- "title": "Áreas",
- "description": "Configuraciones relacionadas con climas, múltiples habitaciones y control de temperatura. Los climas principales están en la misma habitación que el sensor interior y las habitaciones tienen sus propias temperaturas objetivo separadas del sistema.",
"data": {
"main_climates": "Primarios",
"secondary_climates": "Habitaciones"
- }
+ },
+ "description": "Configuraciones relacionadas con climas, múltiples habitaciones y control de temperatura. Los climas principales están en la misma habitación que el sensor interior y las habitaciones tienen sus propias temperaturas objetivo separadas del sistema.",
+ "title": "Áreas"
},
"automatic_gains": {
- "title": "Ganancias Automáticas",
- "description": "Esta característica ajusta los parámetros de control de su sistema de calefacción de forma dinámica, optimizando el control de la temperatura para mayor comodidad y eficiencia energética. Habilitar esta opción permite que SAT se adapte y ajuste finamente los ajustes de calefacción basándose en las condiciones ambientales. Esto ayuda a mantener un ambiente estable y cómodo sin intervención manual.\n\nNota: Si elige no habilitar las ganancias automáticas, necesitará introducir manualmente los valores PID para un control preciso de la temperatura. Por favor, asegúrese de tener valores PID precisos para su sistema de calefacción específico para lograr un rendimiento óptimo.",
"data": {
"automatic_gains": "Ganancias Automáticas (recomendado)"
- }
+ },
+ "description": "Esta característica ajusta los parámetros de control de su sistema de calefacción de forma dinámica, optimizando el control de la temperatura para mayor comodidad y eficiencia energética. Habilitar esta opción permite que SAT se adapte y ajuste finamente los ajustes de calefacción basándose en las condiciones ambientales. Esto ayuda a mantener un ambiente estable y cómodo sin intervención manual.\n\nNota: Si elige no habilitar las ganancias automáticas, necesitará introducir manualmente los valores PID para un control preciso de la temperatura. Por favor, asegúrese de tener valores PID precisos para su sistema de calefacción específico para lograr un rendimiento óptimo.",
+ "title": "Ganancias Automáticas"
},
"calibrate_system": {
- "title": "Calibrar Sistema",
"description": "Optimice su sistema de calefacción determinando automáticamente los valores PID óptimos para su configuración. Al seleccionar Ganancias Automáticas, tenga en cuenta que el sistema pasará por un proceso de calibración que puede tardar aproximadamente 20 minutos en completarse.\n\nSe recomienda Ganancias Automáticas para la mayoría de los usuarios, ya que simplifica el proceso de configuración y asegura un rendimiento óptimo. Sin embargo, si está familiarizado con el control PID y prefiere configurar los valores manualmente, puede optar por omitir las Ganancias Automáticas.\n\nTenga en cuenta que elegir omitir las Ganancias Automáticas requiere un buen entendimiento del control PID y puede requerir ajustes manuales adicionales para lograr un rendimiento óptimo.",
"menu_options": {
"calibrate": "Calibrar y determinar su valor de protección contra sobrecalentamiento (aprox. 20 min).",
"overshoot_protection": "Introducir manualmente el valor de protección contra sobrecalentamiento.",
"pid_controller": "Introducir manualmente los valores PID (no recomendado)."
- }
+ },
+ "title": "Calibrar Sistema"
+ },
+ "calibrated": {
+ "description": "El proceso de calibración se ha completado con éxito.\n\n¡Felicitaciones! Su Smart Autotune Thermostat (SAT) ha sido calibrado para optimizar el rendimiento de calefacción de su sistema. Durante el proceso de calibración, SAT analizó cuidadosamente las características de calefacción y determinó el valor de protección contra sobrecalentamiento adecuado para asegurar un control preciso de la temperatura.\n\nValor de Protección contra Sobrecalentamiento: {minimum_setpoint} °C\n\nEste valor representa la cantidad máxima de sobrecalentamiento permitido durante el proceso de calefacción. SAT monitorizará activamente y ajustará la calefacción para prevenir un sobrecalentamiento excesivo, manteniendo una experiencia de calefacción cómoda y eficiente en su hogar.\n\nTenga en cuenta que el valor de protección contra sobrecalentamiento puede variar dependiendo de las características específicas de su sistema de calefacción y factores ambientales. Ha sido ajustado finamente para proporcionar un rendimiento óptimo basado en los resultados de calibración.",
+ "menu_options": {
+ "calibrate": "Reintentar calibración",
+ "finish": "Continuar con la calibración actual"
+ },
+ "title": "Calibración Completada"
+ },
+ "heating_system": {
+ "data": {
+ "heating_system": "Sistema"
+ },
+ "description": "Seleccionar el tipo correcto de sistema de calefacción es importante para que SAT controle la temperatura de manera precisa y optimice el rendimiento. Elija la opción que coincida con su configuración para asegurar una regulación adecuada de la temperatura en todo su hogar.",
+ "title": "Sistema de Calefacción"
+ },
+ "mosquitto": {
+ "description": "Configure el modo MQTT para su sistema de calefacción. Seleccione el modo de la puerta de enlace e introduzca su nombre.",
+ "data": {
+ "mode": "Modo"
+ },
+ "title": "Configuración MQTT"
+ },
+ "mosquitto_opentherm": {
+ "description": "Configure la Puerta de Enlace OpenTherm. Proporcione el tema principal de MQTT y el identificador del dispositivo.",
+ "data": {
+ "name": "Nombre",
+ "mqtt_topic": "Tema MQTT",
+ "device": "ID del dispositivo"
+ },
+ "title": "Configuración de la Puerta de Enlace OpenTherm"
+ },
+ "mosquitto_ems": {
+ "description": "Configure la Puerta de Enlace EMS-ESP. Proporcione el tema MQTT.",
+ "data": {
+ "name": "Nombre",
+ "mqtt_topic": "Tema MQTT"
+ },
+ "title": "Configuración de la Puerta de Enlace EMS-ESP"
},
"overshoot_protection": {
- "title": "Protección contra Sobrecalentamiento",
- "description": "Al proporcionar el valor de protección contra sobrecalentamiento, SAT ajustará los parámetros de control en consecuencia para mantener un ambiente de calefacción estable y cómodo. Esta configuración manual le permite ajustar el sistema en base a su configuración específica.\n\nNota: Si no está seguro sobre el valor de protección contra sobrecalentamiento o no ha realizado el proceso de calibración, se recomienda cancelar la configuración y pasar por el proceso de calibración para permitir que SAT determine automáticamente el valor para un rendimiento óptimo.",
"data": {
"minimum_setpoint": "Valor"
- }
+ },
+ "description": "Al proporcionar el valor de protección contra sobrecalentamiento, SAT ajustará los parámetros de control en consecuencia para mantener un ambiente de calefacción estable y cómodo. Esta configuración manual le permite ajustar el sistema en base a su configuración específica.\n\nNota: Si no está seguro sobre el valor de protección contra sobrecalentamiento o no ha realizado el proceso de calibración, se recomienda cancelar la configuración y pasar por el proceso de calibración para permitir que SAT determine automáticamente el valor para un rendimiento óptimo.",
+ "title": "Protección contra Sobrecalentamiento"
},
"pid_controller": {
- "title": "Configurar manualmente el controlador PID.",
- "description": "Configure los parámetros de ganancia proporcional, integral y derivativa manualmente para ajustar finamente su sistema de calefacción. Utilice esta opción si prefiere tener control total sobre los parámetros del controlador PID. Ajuste las ganancias basándose en las características específicas de su sistema de calefacción y preferencias.",
"data": {
- "integral": "Integral (kI)",
"derivative": "Derivativo (kD)",
+ "integral": "Integral (kI)",
"proportional": "Proporcional (kP)"
- }
+ },
+ "description": "Configure los parámetros de ganancia proporcional, integral y derivativa manualmente para ajustar finamente su sistema de calefacción. Utilice esta opción si prefiere tener control total sobre los parámetros del controlador PID. Ajuste las ganancias basándose en las características específicas de su sistema de calefacción y preferencias.",
+ "title": "Configurar manualmente el controlador PID."
},
- "calibrated": {
- "title": "Calibración Completada",
- "description": "El proceso de calibración se ha completado con éxito.\n\n¡Felicitaciones! Su Smart Autotune Thermostat (SAT) ha sido calibrado para optimizar el rendimiento de calefacción de su sistema. Durante el proceso de calibración, SAT analizó cuidadosamente las características de calefacción y determinó el valor de protección contra sobrecalentamiento adecuado para asegurar un control preciso de la temperatura.\n\nValor de Protección contra Sobrecalentamiento: {minimum_setpoint} °C\n\nEste valor representa la cantidad máxima de sobrecalentamiento permitido durante el proceso de calefacción. SAT monitorizará activamente y ajustará la calefacción para prevenir un sobrecalentamiento excesivo, manteniendo una experiencia de calefacción cómoda y eficiente en su hogar.\n\nTenga en cuenta que el valor de protección contra sobrecalentamiento puede variar dependiendo de las características específicas de su sistema de calefacción y factores ambientales. Ha sido ajustado finamente para proporcionar un rendimiento óptimo basado en los resultados de calibración.",
+ "sensors": {
+ "data": {
+ "humidity_sensor_entity_id": "Entidad del Sensor de Humedad",
+ "inside_sensor_entity_id": "Entidad del Sensor Interior",
+ "outside_sensor_entity_id": "Entidad del Sensor Exterior"
+ },
+ "description": "Por favor, seleccione los sensores que se usarán para rastrear la temperatura.",
+ "title": "Configurar sensores"
+ },
+ "serial": {
+ "data": {
+ "device": "URL",
+ "name": "Nombre"
+ },
+ "description": "Para establecer una conexión con la Puerta de Enlace OpenTherm usando una conexión de socket, por favor proporcione los siguientes detalles. En el campo Nombre, introduzca un nombre para la puerta de enlace que le ayude a identificarla dentro de su sistema.\n\nEspecifique la dirección de red de la Puerta de Enlace OpenTherm en el campo Dispositivo. Esto podría estar en el formato de \"socket://otgw.local:25238\", donde \"otgw.local\" es el nombre de host o la dirección IP de la puerta de enlace y \"25238\" es el número de puerto.\n\nEstos ajustes son esenciales para establecer la comunicación e integración con su Puerta de Enlace OpenTherm a través de la conexión de socket. Asegúrese de que los detalles proporcionados sean precisos para garantizar una funcionalidad adecuada.",
+ "title": "Puerta de Enlace OpenTherm (SERIAL)"
+ },
+ "simulator": {
+ "data": {
+ "maximum_setpoint": "Ajuste Máximo",
+ "minimum_setpoint": "Ajuste Mínimo",
+ "name": "Nombre",
+ "simulated_cooling": "Enfriamiento Simulado",
+ "simulated_heating": "Calefacción Simulada",
+ "simulated_warming_up": "Calentamiento Simulado"
+ },
+ "description": "Esta puerta de enlace le permite simular una caldera para propósitos de pruebas y demostración. Por favor, proporcione la siguiente información para configurar el simulador.\n\nNota: La Puerta de Enlace Simulada está destinada solo para pruebas y propósitos de demostración y no debería usarse en entornos de producción.",
+ "title": "Puerta de Enlace Simulada (AVANZADO)"
+ },
+ "switch": {
+ "data": {
+ "device": "Entidad",
+ "minimum_setpoint": "Ajuste de Temperatura",
+ "name": "Nombre"
+ },
+ "description": "Por favor, rellene los siguientes detalles para configurar el interruptor. Introduzca un nombre para el interruptor en el campo Nombre, lo que le ayudará a identificarlo dentro de su sistema. Elija la entidad adecuada para usar con su interruptor de las opciones proporcionadas.\n\nEn el campo de Ajuste de Temperatura, especifique la temperatura objetivo deseada para su sistema de calefacción. Si está usando una caldera de agua caliente, rellene el Ajuste de Temperatura de la Caldera con el valor adecuado. Para sistemas de calefacción eléctrica, introduzca el valor 100.\n\nEstos ajustes son esenciales para un control preciso de la temperatura y para asegurar un rendimiento óptimo de su sistema de calefacción. Proporcionar el Ajuste de Temperatura correcto permite una regulación precisa y ayuda a lograr un ambiente cómodo y eficiente en energía en su hogar.",
+ "title": "Termostato PID con PWM (ON/OFF)"
+ },
+ "user": {
+ "description": "SAT es un termostato inteligente capaz de autoajustarse para optimizar el control de temperatura. Seleccione el modo que se ajuste a su sistema de calefacción.",
"menu_options": {
- "calibrate": "Reintentar calibración",
- "finish": "Continuar con la calibración actual"
- }
+ "mosquitto": "Puerta de enlace MQTT (OpenTherm, EMS-ESP, otros)",
+ "serial": "Puerta de enlace serial (p. ej., OpenTherm)",
+ "esphome": "ESPHome (Servicios y sensores del sistema)",
+ "simulator": "Puerta de enlace simulada (Solo para desarrolladores)",
+ "switch": "Termostato PID (Modo PWM Encendido/Apagado)"
+ },
+ "title": "Smart Autotune Thermostat (SAT)"
}
- },
- "error": {
- "connection": "No se puede conectar a la puerta de enlace.",
- "mqtt_component": "El componente MQTT no está disponible.",
- "unable_to_calibrate": "El proceso de calibración ha encontrado un problema y no se pudo completar con éxito. Por favor, asegúrese de que su sistema de calefacción está funcionando correctamente y que todos los sensores necesarios están conectados y funcionando correctamente.\n\nSi continúa experimentando problemas con la calibración, considere contactarnos para obtener más ayuda. Pedimos disculpas por cualquier inconveniente causado."
- },
- "abort": {
- "already_configured": "La puerta de enlace ya está configurada."
- },
- "progress": {
- "calibration": "Calibrando y encontrando el valor de protección contra sobrecalentamiento...\n\nPor favor, espere mientras optimizamos su sistema de calefacción. Este proceso puede tomar aproximadamente 20 minutos."
}
},
"options": {
"step": {
- "init": {
- "menu_options": {
- "general": "General",
- "presets": "Preajustes",
- "advanced": "Opciones Avanzadas",
- "system_configuration": "Configuración del Sistema"
- }
+ "advanced": {
+ "data": {
+ "error_monitoring": "Habilitar monitoreo de errores",
+ "climate_valve_offset": "Compensación de la Válvula Climática",
+ "dynamic_minimum_setpoint": "Punto de Ajuste Mínimo Dinámico (Experimental)",
+ "force_pulse_width_modulation": "Forzar Modulación de Ancho de Pulso",
+ "maximum_consumption": "Consumo Máximo",
+ "maximum_relative_modulation": "Modulación Relativa Máxima",
+ "minimum_consumption": "Consumo Mínimo",
+ "sample_time": "Tiempo de Muestreo",
+ "simulation": "Simulación",
+ "target_temperature_step": "Paso de Temperatura Objetivo",
+ "thermal_comfort": "Confort Térmico"
+ },
+ "data_description": {
+ "climate_valve_offset": "Compensación para ajustar el grado de apertura de la válvula climática.",
+ "dynamic_minimum_setpoint": "Activa el ajuste dinámico del punto de consigna mínimo basado en la temperatura de retorno de la caldera, lo que también ayuda a identificar si alguna válvula está cerrada.",
+ "maximum_consumption": "El consumo máximo de gas cuando la caldera está activa.",
+ "maximum_relative_modulation": "Representa el nivel más alto de modulación para un sistema de calefacción eficiente.",
+ "minimum_consumption": "El consumo mínimo de gas cuando la caldera está activa.",
+ "sample_time": "El intervalo de tiempo mínimo entre actualizaciones del controlador PID.",
+ "target_temperature_step": "Ajustar el paso de la temperatura objetivo para una afinación precisa de los niveles de confort.",
+ "thermal_comfort": "Habilitar el uso del Índice de Simmer para ajuste de confort térmico."
+ },
+ "title": "Avanzadas"
},
"general": {
- "title": "General",
- "description": "Configuraciones y ajustes generales.",
"data": {
- "integral": "Integral (kI)",
- "derivative": "Derivativa (kD)",
- "proportional": "Proporcional (kP)",
- "maximum_setpoint": "Punto de Ajuste Máximo",
- "window_sensors": "Sensores de Contacto",
"automatic_gains_value": "Valor de Ganancias Automáticas",
+ "derivative": "Derivativa (kD)",
"derivative_time_weight": "Peso Temporal de la Derivativa",
- "heating_curve_version": "Versión de la Curva de Calefacción",
- "heating_curve_coefficient": "Coeficiente de la Curva de Calefacción",
"duty_cycle": "Ciclo de Trabajo Máximo para la Modulación de Ancho de Pulso",
- "sync_with_thermostat": "Sincronizar punto de ajuste con el termostato"
+ "heating_curve_coefficient": "Coeficiente de la Curva de Calefacción",
+ "heating_curve_version": "Versión de la Curva de Calefacción",
+ "integral": "Integral (kI)",
+ "maximum_setpoint": "Punto de Ajuste Máximo",
+ "minimum_setpoint_adjustment_factor": "Factor de ajuste del punto de consigna mínimo",
+ "pid_controller_version": "Versión del controlador PID",
+ "proportional": "Proporcional (kP)",
+ "sync_with_thermostat": "Sincronizar punto de ajuste con el termostato",
+ "window_sensors": "Sensores de Contacto",
+ "heating_mode": "Modo de calefacción"
},
"data_description": {
- "integral": "El término integral (kI) en el controlador PID, responsable de reducir el error en estado estacionario.",
- "derivative": "El término derivativo (kD) en el controlador PID, responsable de mitigar el sobreimpulso.",
- "proportional": "El término proporcional (kP) en el controlador PID, responsable de la respuesta inmediata a errores.",
- "maximum_setpoint": "La temperatura óptima para una operación eficiente de la caldera.",
- "window_sensors": "Sensores de Contacto que activan el sistema cuando una ventana o puerta está abierta durante un período.",
"automatic_gains_value": "El valor utilizado para las ganancias automáticas en el controlador PID.",
- "heating_curve_coefficient": "El coeficiente utilizado para ajustar la curva de calefacción.",
+ "derivative": "El término derivativo (kD) en el controlador PID, responsable de mitigar el sobreimpulso.",
+ "derivative_time_weight": "Un parámetro para ajustar la influencia del término derivativo a lo largo del tiempo, especialmente útil para reducir el infraimpulso durante la fase de calentamiento cuando el coeficiente de la curva de calefacción está correctamente ajustado.",
"duty_cycle": "El ciclo de trabajo máximo para la Modulación de Ancho de Pulso (PWM), controlando los ciclos de encendido/apagado de la caldera.",
+ "heating_curve_coefficient": "El coeficiente utilizado para ajustar la curva de calefacción.",
+ "integral": "El término integral (kI) en el controlador PID, responsable de reducir el error en estado estacionario.",
+ "maximum_setpoint": "La temperatura óptima para una operación eficiente de la caldera.",
+ "minimum_setpoint_adjustment_factor": "Este factor ajusta el punto de ajuste del calefactor basado en la temperatura de retorno de la caldera, afectando la capacidad de respuesta y eficiencia del calefactor. Un valor más alto aumenta la sensibilidad a los cambios de temperatura, mejorando el control sobre el confort y el uso de energía. El rango inicial recomendado es de 0,1 a 0,5. Ajuste para adaptarse a su sistema y preferencias de confort.",
+ "proportional": "El término proporcional (kP) en el controlador PID, responsable de la respuesta inmediata a errores.",
"sync_with_thermostat": "Sincronizar el punto de ajuste con el termostato para asegurar un control coordinado de la temperatura.",
- "derivative_time_weight": "Un parámetro para ajustar la influencia del término derivativo a lo largo del tiempo, especialmente útil para reducir el infraimpulso durante la fase de calentamiento cuando el coeficiente de la curva de calefacción está correctamente ajustado."
+ "window_sensors": "Sensores de Contacto que activan el sistema cuando una ventana o puerta está abierta durante un período."
+ },
+ "description": "Configuraciones y ajustes generales.",
+ "title": "General"
+ },
+ "init": {
+ "menu_options": {
+ "advanced": "Opciones Avanzadas",
+ "general": "General",
+ "presets": "Preajustes",
+ "system_configuration": "Configuración del Sistema"
}
},
"presets": {
- "title": "Preajustes",
- "description": "Configuraciones de temperatura predefinidas para diferentes escenarios o actividades.",
"data": {
+ "activity_temperature": "Temperatura de Actividad",
"away_temperature": "Temperatura de Ausencia",
+ "comfort_temperature": "Temperatura de Confort",
"home_temperature": "Temperatura de Casa",
"sleep_temperature": "Temperatura de Sueño",
- "comfort_temperature": "Temperatura de Confort",
- "activity_temperature": "Temperatura de Actividad",
"sync_climates_with_preset": "Sincronizar climas con preajuste (sueño / ausencia / actividad)"
- }
+ },
+ "description": "Configuraciones de temperatura predefinidas para diferentes escenarios o actividades.",
+ "title": "Preajustes"
},
"system_configuration": {
- "title": "Configuración del Sistema",
- "description": "Para un ajuste fino y personalización.",
"data": {
"automatic_duty_cycle": "Ciclo de trabajo automático",
+ "sync_climates_with_mode": "Sincronizar climas con el modo",
"overshoot_protection": "Protección contra Sobrepasos (con PWM)",
- "window_minimum_open_time": "Tiempo mínimo de apertura de ventana",
- "sensor_max_value_age": "Edad máxima del valor del sensor de temperatura"
+ "sensor_max_value_age": "Edad máxima del valor del sensor de temperatura",
+ "window_minimum_open_time": "Tiempo mínimo de apertura de ventana"
},
"data_description": {
"automatic_duty_cycle": "Habilitar o deshabilitar el ciclo de trabajo automático para la Modulación de Ancho de Pulso (PWM).",
"overshoot_protection": "Habilitar protección contra sobrepasos con Modulación de Ancho de Pulso (PWM) para prevenir excesos de temperatura de la caldera.",
- "window_minimum_open_time": "El tiempo mínimo que una ventana debe estar abierta antes de que el sistema reaccione.",
- "sensor_max_value_age": "La edad máxima del valor del sensor de temperatura antes de considerarlo como obsoleto."
- }
- },
- "advanced": {
- "title": "Avanzadas",
- "data": {
- "simulation": "Simulación",
- "sample_time": "Tiempo de Muestreo",
- "thermal_comfort": "Confort Térmico",
- "minimum_consumption": "Consumo Mínimo",
- "maximum_consumption": "Consumo Máximo",
- "climate_valve_offset": "Compensación de la Válvula Climática",
- "target_temperature_step": "Paso de Temperatura Objetivo",
- "maximum_relative_modulation": "Modulación Relativa Máxima",
- "force_pulse_width_modulation": "Forzar Modulación de Ancho de Pulso",
- "dynamic_minimum_setpoint": "Punto de Ajuste Mínimo Dinámico (Experimental)"
+ "sensor_max_value_age": "La edad máxima del valor del sensor de temperatura antes de considerarlo como obsoleto.",
+ "window_minimum_open_time": "El tiempo mínimo que una ventana debe estar abierta antes de que el sistema reaccione."
},
- "data_description": {
- "thermal_comfort": "Habilitar el uso del Índice de Simmer para ajuste de confort térmico.",
- "minimum_consumption": "El consumo mínimo de gas cuando la caldera está activa.",
- "maximum_consumption": "El consumo máximo de gas cuando la caldera está activa.",
- "climate_valve_offset": "Compensación para ajustar el grado de apertura de la válvula climática.",
- "target_temperature_step": "Ajustar el paso de la temperatura objetivo para una afinación precisa de los niveles de confort.",
- "sample_time": "El intervalo de tiempo mínimo entre actualizaciones del controlador PID.",
- "maximum_relative_modulation": "Representa el nivel más alto de modulación para un sistema de calefacción eficiente."
- }
+ "description": "Para un ajuste fino y personalización.",
+ "title": "Configuración del Sistema"
}
}
}
diff --git a/custom_components/sat/translations/fr.json b/custom_components/sat/translations/fr.json
index f0bc3513..135ab9b0 100644
--- a/custom_components/sat/translations/fr.json
+++ b/custom_components/sat/translations/fr.json
@@ -1,221 +1,245 @@
{
"config": {
+ "abort": {
+ "already_configured": "La passerelle est déjà configurée.",
+ "reconfigure_successful": "La passerelle a été reconfigurée."
+ },
+ "error": {
+ "connection": "Impossible de se connecter à la passerelle.",
+ "mqtt_component": "Le composant MQTT n'est pas disponible.",
+ "unable_to_calibrate": "Le processus de calibration a rencontré un problème et n'a pas pu être complété avec succès. Veuillez vous assurer que votre système de chauffage fonctionne correctement et que tous les capteurs requis sont connectés et fonctionnent correctement.\n\nSi vous continuez à rencontrer des problèmes avec la calibration, envisagez de nous contacter pour obtenir de l'aide supplémentaire. Nous nous excusons pour tout désagrément causé."
+ },
+ "progress": {
+ "calibration": "Calibration et recherche de la valeur de protection contre les dépassements en cours...\n\nVeuillez patienter pendant que nous optimisons votre système de chauffage. Ce processus peut prendre environ 20 minutes."
+ },
"step": {
- "user": {
- "title": "Smart Autotune Thermostat (SAT)",
- "description": "Le SAT est un thermostat intelligent capable de s'auto-ajuster pour optimiser le contrôle de la température. Sélectionnez le mode approprié qui correspond à votre système de chauffage.",
- "menu_options": {
- "mosquitto": "OpenTherm Gateway ( MQTT )",
- "serial": "OpenTherm Gateway ( SERIAL )",
- "switch": "Thermostat PID avec PWM ( ON/OFF )",
- "simulator": "Passerelle simulée ( AVANCÉ )"
- }
- },
- "mosquitto": {
- "title": "OpenTherm Gateway ( MQTT )",
- "description": "Veuillez fournir les détails suivants pour configurer la passerelle OpenTherm. Dans le champ Nom, entrez un nom pour la passerelle qui vous aidera à l'identifier au sein de votre système.\n\nSpécifiez l'entité Climat à utiliser pour la passerelle OpenTherm. Cette entité est fournie par la passerelle OpenTherm et représente votre système de chauffage.\n\nDe plus, entrez le Sujet principal qui sera utilisé pour publier et s'abonner aux messages MQTT liés à la passerelle OpenTherm.\n\nCes paramètres sont essentiels pour établir la communication et l'intégration avec votre passerelle OpenTherm via MQTT. Ils permettent un échange de données et un contrôle fluides de votre système de chauffage. Assurez-vous que les détails fournis sont précis pour garantir une fonctionnalité appropriée.",
- "data": {
- "name": "Nom",
- "device": "Appareil",
- "mqtt_topic": "Sujet Principal"
- }
- },
- "serial": {
- "title": "OpenTherm Gateway ( SERIAL )",
- "description": "Pour établir une connexion avec la passerelle OpenTherm en utilisant une connexion socket, veuillez fournir les détails suivants. Dans le champ Nom, entrez un nom pour la passerelle qui vous aidera à l'identifier au sein de votre système.\n\nSpécifiez l'adresse réseau de la passerelle OpenTherm dans le champ Appareil. Cela pourrait être au format \"socket://otgw.local:25238\", où \"otgw.local\" est le nom d'hôte ou l'adresse IP de la passerelle et \"25238\" est le numéro de port.\n\nCes paramètres sont essentiels pour établir la communication et l'intégration avec votre passerelle OpenTherm via la connexion socket. Assurez-vous que les détails fournis sont précis pour garantir une fonctionnalité appropriée.",
- "data": {
- "name": "Nom",
- "device": "URL"
- }
- },
- "switch": {
- "title": "Thermostat PID avec PWM (ON/OFF)",
- "description": "Veuillez remplir les détails suivants pour configurer l'interrupteur. Entrez un nom pour l'interrupteur dans le champ Nom, ce qui vous aidera à l'identifier au sein de votre système. Choisissez l'entité appropriée à utiliser pour votre interrupteur parmi les options fournies.\n\nDans le champ Réglage de la température, spécifiez la température cible désirée pour votre système de chauffage. Si vous utilisez une chaudière à eau chaude, remplissez le Réglage de la température de la chaudière avec la valeur appropriée. Pour les systèmes de chauffage électrique, entrez la valeur 100.\n\nCes paramètres sont essentiels pour un contrôle précis de la température et pour garantir des performances optimales de votre système de chauffage. Fournir le Réglage de la température correct permet une régulation précise et contribue à créer un environnement confortable et économe en énergie dans votre maison.",
- "data": {
- "name": "Nom",
- "device": "Entité",
- "minimum_setpoint": "Réglage de la température"
- }
- },
- "simulator": {
- "title": "Passerelle simulée ( AVANCÉ )",
- "description": "Cette passerelle vous permet de simuler une chaudière à des fins de test et de démonstration. Veuillez fournir les informations suivantes pour configurer le simulateur.\n\nNote : La Passerelle Simulateur est destinée à des fins de test et de démonstration uniquement et ne doit pas être utilisée dans des environnements de production.",
- "data": {
- "name": "Nom",
- "minimum_setpoint": "Réglage minimal",
- "maximum_setpoint": "Réglage maximal",
- "simulated_heating": "Chauffage simulé",
- "simulated_cooling": "Refroidissement simulé",
- "simulated_warming_up": "Réchauffement simulé"
- }
- },
- "sensors": {
- "title": "Configurer les capteurs",
- "description": "Veuillez sélectionner les capteurs qui seront utilisés pour suivre la température.",
- "data": {
- "inside_sensor_entity_id": "Entité du capteur intérieur",
- "outside_sensor_entity_id": "Entité du capteur extérieur",
- "humidity_sensor_entity_id": "Entité du capteur d'humidité"
- }
- },
- "heating_system": {
- "title": "Système de chauffage",
- "description": "Sélectionner le type de système de chauffage correct est important pour que le SAT contrôle précisément la température et optimise les performances. Choisissez l'option qui correspond à votre configuration pour garantir une régulation appropriée de la température dans votre maison.",
- "data": {
- "heating_system": "Système"
- }
- },
"areas": {
- "title": "Zones",
- "description": "Paramètres liés aux climats, aux pièces multiples et au contrôle de la température. Les climats principaux se trouvent dans la même pièce que le capteur intérieur et les pièces ont leurs propres températures cibles séparées du système.",
"data": {
"main_climates": "Principaux",
"secondary_climates": "Pièces"
- }
+ },
+ "description": "Paramètres liés aux climats, aux pièces multiples et au contrôle de la température. Les climats principaux se trouvent dans la même pièce que le capteur intérieur et les pièces ont leurs propres températures cibles séparées du système.",
+ "title": "Zones"
},
"automatic_gains": {
- "title": "Gains automatiques",
- "description": "Cette fonctionnalité ajuste les paramètres de contrôle de votre système de chauffage de manière dynamique, optimisant le contrôle de la température pour un meilleur confort et une meilleure efficacité énergétique. Activer cette option permet au SAT de s'adapter continuellement et d'affiner les réglages de chauffage en fonction des conditions environnementales. Cela aide à maintenir un environnement stable et confortable sans intervention manuelle.\n\nNote : Si vous choisissez de ne pas activer les gains automatiques, vous devrez entrer manuellement les valeurs PID pour un contrôle précis de la température. Veuillez vous assurer que vous disposez de valeurs PID précises pour votre système de chauffage spécifique afin d'obtenir des performances optimales.",
"data": {
"automatic_gains": "Gains automatiques (recommandé)"
- }
+ },
+ "description": "Cette fonctionnalité ajuste les paramètres de contrôle de votre système de chauffage de manière dynamique, optimisant le contrôle de la température pour un meilleur confort et une meilleure efficacité énergétique. Activer cette option permet au SAT de s'adapter continuellement et d'affiner les réglages de chauffage en fonction des conditions environnementales. Cela aide à maintenir un environnement stable et confortable sans intervention manuelle.\n\nNote : Si vous choisissez de ne pas activer les gains automatiques, vous devrez entrer manuellement les valeurs PID pour un contrôle précis de la température. Veuillez vous assurer que vous disposez de valeurs PID précises pour votre système de chauffage spécifique afin d'obtenir des performances optimales.",
+ "title": "Gains automatiques"
},
"calibrate_system": {
- "title": "Calibrer le système",
"description": "Optimisez votre système de chauffage en déterminant automatiquement les valeurs PID optimales pour votre configuration. Lors de la sélection des Gains automatiques, veuillez noter que le système passera par un processus de calibration qui peut prendre environ 20 minutes à compléter.\n\nLes Gains automatiques sont recommandés pour la plupart des utilisateurs car ils simplifient le processus de configuration et garantissent des performances optimales. Cependant, si vous êtes familier avec le contrôle PID et que vous préférez définir manuellement les valeurs, vous pouvez choisir de ne pas activer les Gains automatiques.\n\nVeuillez noter que choisir de ne pas activer les Gains automatiques nécessite une bonne connaissance du contrôle PID et peut nécessiter des ajustements manuels supplémentaires pour obtenir des performances optimales.",
"menu_options": {
"calibrate": "Calibrer et déterminer votre valeur de protection contre les dépassements (env. 20 min).",
"overshoot_protection": "Entrer manuellement la valeur de protection contre les dépassements.",
"pid_controller": "Entrer manuellement les valeurs PID (non recommandé)."
- }
+ },
+ "title": "Calibrer le système"
+ },
+ "calibrated": {
+ "description": "Le processus de calibration a été complété avec succès.\n\nFélicitations ! Votre Smart Autotune Thermostat (SAT) a été calibré pour optimiser la performance de chauffage de votre système. Au cours du processus de calibration, le SAT a analysé soigneusement les caractéristiques de chauffage et déterminé la valeur de protection contre les dépassements appropriée pour garantir un contrôle précis de la température.\n\nValeur de protection contre les dépassements : {minimum_setpoint} °C\n\nCette valeur représente la quantité maximale de dépassement autorisée pendant le processus de chauffage. Le SAT surveillera activement et ajustera le chauffage pour éviter un dépassement excessif, maintenant ainsi une expérience de chauffage confortable et efficace dans votre maison.\n\nVeuillez noter que la valeur de protection contre les dépassements peut varier en fonction des caractéristiques spécifiques de votre système de chauffage et des facteurs environnementaux. Elle a été affinée pour fournir des performances optimales basées sur les résultats de la calibration.",
+ "menu_options": {
+ "calibrate": "Réessayer la calibration",
+ "finish": "Continuer avec la calibration actuelle"
+ },
+ "title": "Calibration terminée"
+ },
+ "heating_system": {
+ "data": {
+ "heating_system": "Système"
+ },
+ "description": "Sélectionner le type de système de chauffage correct est important pour que le SAT contrôle précisément la température et optimise les performances. Choisissez l'option qui correspond à votre configuration pour garantir une régulation appropriée de la température dans votre maison.",
+ "title": "Système de chauffage"
+ },
+ "mosquitto": {
+ "description": "Configurez le mode MQTT pour votre système de chauffage. Sélectionnez le mode de la passerelle et fournissez son nom.",
+ "data": {
+ "mode": "Mode"
+ },
+ "title": "Configuration MQTT"
+ },
+ "mosquitto_opentherm": {
+ "description": "Configurez la passerelle OpenTherm. Fournissez le sujet principal MQTT et l'identifiant de l'appareil.",
+ "data": {
+ "name": "Nom",
+ "mqtt_topic": "Sujet MQTT",
+ "device": "ID de l'appareil"
+ },
+ "title": "Configuration de la Passerelle OpenTherm"
+ },
+ "mosquitto_ems": {
+ "description": "Configurez la passerelle EMS-ESP. Fournissez le sujet MQTT.",
+ "data": {
+ "name": "Nom",
+ "mqtt_topic": "Sujet MQTT"
+ },
+ "title": "Configuration de la Passerelle EMS-ESP"
},
"overshoot_protection": {
- "title": "Protection contre les dépassements",
- "description": "En fournissant la valeur de protection contre les dépassements, le SAT ajustera les paramètres de contrôle en conséquence pour maintenir un environnement de chauffage stable et confortable. Cette configuration manuelle vous permet d'affiner le système en fonction de votre configuration spécifique.\n\nNote : Si vous n'êtes pas sûr de la valeur de protection contre les dépassements ou si vous n'avez pas effectué le processus de calibration, il est recommandé d'annuler la configuration et de passer par le processus de calibration pour permettre au SAT de déterminer automatiquement la valeur pour des performances optimales.",
"data": {
"minimum_setpoint": "Valeur"
- }
+ },
+ "description": "En fournissant la valeur de protection contre les dépassements, le SAT ajustera les paramètres de contrôle en conséquence pour maintenir un environnement de chauffage stable et confortable. Cette configuration manuelle vous permet d'affiner le système en fonction de votre configuration spécifique.\n\nNote : Si vous n'êtes pas sûr de la valeur de protection contre les dépassements ou si vous n'avez pas effectué le processus de calibration, il est recommandé d'annuler la configuration et de passer par le processus de calibration pour permettre au SAT de déterminer automatiquement la valeur pour des performances optimales.",
+ "title": "Protection contre les dépassements"
},
"pid_controller": {
- "title": "Configurer manuellement le contrôleur PID.",
- "description": "Configurez manuellement les gains proportionnel, intégral et dérivé pour affiner votre système de chauffage. Utilisez cette option si vous préférez avoir un contrôle total sur les paramètres du contrôleur PID. Ajustez les gains en fonction des caractéristiques spécifiques de votre système de chauffage et de vos préférences.",
"data": {
- "integral": "Intégral (kI)",
"derivative": "Dérivé (kD)",
+ "integral": "Intégral (kI)",
"proportional": "Proportionnel (kP)"
- }
+ },
+ "description": "Configurez manuellement les gains proportionnel, intégral et dérivé pour affiner votre système de chauffage. Utilisez cette option si vous préférez avoir un contrôle total sur les paramètres du contrôleur PID. Ajustez les gains en fonction des caractéristiques spécifiques de votre système de chauffage et de vos préférences.",
+ "title": "Configurer manuellement le contrôleur PID."
},
- "calibrated": {
- "title": "Calibration terminée",
- "description": "Le processus de calibration a été complété avec succès.\n\nFélicitations ! Votre Smart Autotune Thermostat (SAT) a été calibré pour optimiser la performance de chauffage de votre système. Au cours du processus de calibration, le SAT a analysé soigneusement les caractéristiques de chauffage et déterminé la valeur de protection contre les dépassements appropriée pour garantir un contrôle précis de la température.\n\nValeur de protection contre les dépassements : {minimum_setpoint} °C\n\nCette valeur représente la quantité maximale de dépassement autorisée pendant le processus de chauffage. Le SAT surveillera activement et ajustera le chauffage pour éviter un dépassement excessif, maintenant ainsi une expérience de chauffage confortable et efficace dans votre maison.\n\nVeuillez noter que la valeur de protection contre les dépassements peut varier en fonction des caractéristiques spécifiques de votre système de chauffage et des facteurs environnementaux. Elle a été affinée pour fournir des performances optimales basées sur les résultats de la calibration.",
+ "sensors": {
+ "data": {
+ "humidity_sensor_entity_id": "Entité du capteur d'humidité",
+ "inside_sensor_entity_id": "Entité du capteur intérieur",
+ "outside_sensor_entity_id": "Entité du capteur extérieur"
+ },
+ "description": "Veuillez sélectionner les capteurs qui seront utilisés pour suivre la température.",
+ "title": "Configurer les capteurs"
+ },
+ "serial": {
+ "data": {
+ "device": "URL",
+ "name": "Nom"
+ },
+ "description": "Pour établir une connexion avec la passerelle OpenTherm en utilisant une connexion socket, veuillez fournir les détails suivants. Dans le champ Nom, entrez un nom pour la passerelle qui vous aidera à l'identifier au sein de votre système.\n\nSpécifiez l'adresse réseau de la passerelle OpenTherm dans le champ Appareil. Cela pourrait être au format \"socket://otgw.local:25238\", où \"otgw.local\" est le nom d'hôte ou l'adresse IP de la passerelle et \"25238\" est le numéro de port.\n\nCes paramètres sont essentiels pour établir la communication et l'intégration avec votre passerelle OpenTherm via la connexion socket. Assurez-vous que les détails fournis sont précis pour garantir une fonctionnalité appropriée.",
+ "title": "OpenTherm Gateway ( SERIAL )"
+ },
+ "simulator": {
+ "data": {
+ "maximum_setpoint": "Réglage maximal",
+ "minimum_setpoint": "Réglage minimal",
+ "name": "Nom",
+ "simulated_cooling": "Refroidissement simulé",
+ "simulated_heating": "Chauffage simulé",
+ "simulated_warming_up": "Réchauffement simulé"
+ },
+ "description": "Cette passerelle vous permet de simuler une chaudière à des fins de test et de démonstration. Veuillez fournir les informations suivantes pour configurer le simulateur.\n\nNote : La Passerelle Simulateur est destinée à des fins de test et de démonstration uniquement et ne doit pas être utilisée dans des environnements de production.",
+ "title": "Passerelle simulée ( AVANCÉ )"
+ },
+ "switch": {
+ "data": {
+ "device": "Entité",
+ "minimum_setpoint": "Réglage de la température",
+ "name": "Nom"
+ },
+ "description": "Veuillez remplir les détails suivants pour configurer l'interrupteur. Entrez un nom pour l'interrupteur dans le champ Nom, ce qui vous aidera à l'identifier au sein de votre système. Choisissez l'entité appropriée à utiliser pour votre interrupteur parmi les options fournies.\n\nDans le champ Réglage de la température, spécifiez la température cible désirée pour votre système de chauffage. Si vous utilisez une chaudière à eau chaude, remplissez le Réglage de la température de la chaudière avec la valeur appropriée. Pour les systèmes de chauffage électrique, entrez la valeur 100.\n\nCes paramètres sont essentiels pour un contrôle précis de la température et pour garantir des performances optimales de votre système de chauffage. Fournir le Réglage de la température correct permet une régulation précise et contribue à créer un environnement confortable et économe en énergie dans votre maison.",
+ "title": "Thermostat PID avec PWM (ON/OFF)"
+ },
+ "user": {
+ "description": "SAT est un thermostat intelligent capable de s'auto-ajuster pour optimiser le contrôle de la température. Sélectionnez le mode correspondant à votre système de chauffage.",
"menu_options": {
- "calibrate": "Réessayer la calibration",
- "finish": "Continuer avec la calibration actuelle"
- }
+ "mosquitto": "Passerelle MQTT (OpenTherm, EMS-ESP, autres)",
+ "serial": "Passerelle série (ex. : OpenTherm)",
+ "esphome": "ESPHome (Services et capteurs système)",
+ "simulator": "Passerelle simulée (Réservé aux développeurs)",
+ "switch": "Thermostat PID (Mode PWM Marche/Arrêt)"
+ },
+ "title": "Smart Autotune Thermostat (SAT)"
}
- },
- "error": {
- "connection": "Impossible de se connecter à la passerelle.",
- "mqtt_component": "Le composant MQTT n'est pas disponible.",
- "unable_to_calibrate": "Le processus de calibration a rencontré un problème et n'a pas pu être complété avec succès. Veuillez vous assurer que votre système de chauffage fonctionne correctement et que tous les capteurs requis sont connectés et fonctionnent correctement.\n\nSi vous continuez à rencontrer des problèmes avec la calibration, envisagez de nous contacter pour obtenir de l'aide supplémentaire. Nous nous excusons pour tout désagrément causé."
- },
- "abort": {
- "already_configured": "La passerelle est déjà configurée."
- },
- "progress": {
- "calibration": "Calibration et recherche de la valeur de protection contre les dépassements en cours...\n\nVeuillez patienter pendant que nous optimisons votre système de chauffage. Ce processus peut prendre environ 20 minutes."
}
},
"options": {
"step": {
- "init": {
- "menu_options": {
- "general": "Général",
- "presets": "Préréglages",
- "advanced": "Options Avancées",
- "system_configuration": "Configuration du Système"
- }
+ "advanced": {
+ "data": {
+ "error_monitoring": "Activer la surveillance des erreurs",
+ "climate_valve_offset": "Décalage de la vanne climatique",
+ "dynamic_minimum_setpoint": "Point de Consigne Minimum Dynamique (Expérimental)",
+ "force_pulse_width_modulation": "Forcer la Modulation de Largeur d'Impulsion",
+ "maximum_consumption": "Consommation Maximale",
+ "maximum_relative_modulation": "Modulation Relative Maximale",
+ "minimum_consumption": "Consommation Minimale",
+ "sample_time": "Temps d'Échantillonnage",
+ "simulation": "Simulation",
+ "target_temperature_step": "Pas de Température Cible",
+ "thermal_comfort": "Confort Thermique"
+ },
+ "data_description": {
+ "climate_valve_offset": "Décalage pour ajuster le degré d'ouverture de la vanne climatique.",
+ "dynamic_minimum_setpoint": "Active l'ajustement dynamique du point de consigne minimal en fonction de la température de retour de la chaudière, ce qui aide également à identifier si des vannes sont fermées.",
+ "maximum_consumption": "La consommation maximale de gaz lorsque la chaudière est active.",
+ "maximum_relative_modulation": "Représentant le niveau de modulation le plus élevé pour un système de chauffage efficace.",
+ "minimum_consumption": "La consommation minimale de gaz lorsque la chaudière est active.",
+ "sample_time": "L'intervalle de temps minimum entre les mises à jour du régulateur PID.",
+ "target_temperature_step": "Ajuster le pas de température cible pour un réglage fin des niveaux de confort.",
+ "thermal_comfort": "Activer l'utilisation de l'Indice de Simmer pour ajuster le confort thermique."
+ },
+ "title": "Avancé"
},
"general": {
- "title": "Général",
- "description": "Paramètres et configurations généraux.",
"data": {
- "integral": "Intégral (kI)",
- "derivative": "Dérivé (kD)",
- "proportional": "Proportionnel (kP)",
- "maximum_setpoint": "Point de consigne maximal",
- "window_sensors": "Capteurs de Contact",
"automatic_gains_value": "Valeur de Gains Automatiques",
+ "derivative": "Dérivé (kD)",
"derivative_time_weight": "Poids Temporel Dérivé",
- "heating_curve_version": "Version de la Courbe de Chauffage",
- "heating_curve_coefficient": "Coefficient de la Courbe de Chauffage",
"duty_cycle": "Cycle de Fonctionnement Maximum pour la Modulation de Largeur d'Impulsion",
- "sync_with_thermostat": "Synchroniser le point de consigne avec le thermostat"
+ "heating_curve_coefficient": "Coefficient de la Courbe de Chauffage",
+ "heating_curve_version": "Version de la Courbe de Chauffage",
+ "integral": "Intégral (kI)",
+ "maximum_setpoint": "Point de consigne maximal",
+ "minimum_setpoint_adjustment_factor": "Facteur d'ajustement du point de consigne minimal",
+ "pid_controller_version": "Version du contrôleur PID",
+ "proportional": "Proportionnel (kP)",
+ "sync_with_thermostat": "Synchroniser le point de consigne avec le thermostat",
+ "window_sensors": "Capteurs de Contact",
+ "heating_mode": "Mode chauffage"
},
"data_description": {
- "integral": "Le terme intégral (kI) dans le régulateur PID, responsable de la réduction de l'erreur en régime permanent.",
- "derivative": "Le terme dérivé (kD) dans le régulateur PID, responsable de l'atténuation des dépassements.",
- "proportional": "Le terme proportionnel (kP) dans le régulateur PID, responsable de la réponse immédiate aux erreurs.",
- "maximum_setpoint": "La température optimale pour un fonctionnement efficace de la chaudière.",
- "window_sensors": "Capteurs de Contact qui déclenchent le système lorsqu'une fenêtre ou une porte est ouverte pendant une période.",
"automatic_gains_value": "La valeur utilisée pour les gains automatiques dans le régulateur PID.",
- "heating_curve_coefficient": "Le coefficient utilisé pour ajuster la courbe de chauffage.",
+ "derivative": "Le terme dérivé (kD) dans le régulateur PID, responsable de l'atténuation des dépassements.",
+ "derivative_time_weight": "Un paramètre pour ajuster l'influence du terme dérivé au fil du temps, particulièrement utile pour réduire le dépassement lors de la phase de montée en température lorsque le coefficient de la courbe de chauffage est correctement réglé.",
"duty_cycle": "Le cycle de fonctionnement maximum pour la Modulation de Largeur d'Impulsion (PWM), contrôlant les cycles de marche/arrêt de la chaudière.",
+ "heating_curve_coefficient": "Le coefficient utilisé pour ajuster la courbe de chauffage.",
+ "integral": "Le terme intégral (kI) dans le régulateur PID, responsable de la réduction de l'erreur en régime permanent.",
+ "maximum_setpoint": "La température optimale pour un fonctionnement efficace de la chaudière.",
+ "minimum_setpoint_adjustment_factor": "Ce facteur ajuste le point de consigne du chauffage en fonction de la température de retour de la chaudière, influençant la réactivité et l'efficacité du chauffage. Une valeur plus élevée augmente la sensibilité aux changements de température, améliorant le contrôle du confort et de l'utilisation de l'énergie. La plage de départ recommandée est de 0,1 à 0,5. Ajustez pour convenir à votre système et à vos préférences de confort.",
+ "proportional": "Le terme proportionnel (kP) dans le régulateur PID, responsable de la réponse immédiate aux erreurs.",
"sync_with_thermostat": "Synchroniser le point de consigne avec le thermostat pour assurer une régulation coordonnée de la température.",
- "derivative_time_weight": "Un paramètre pour ajuster l'influence du terme dérivé au fil du temps, particulièrement utile pour réduire le dépassement lors de la phase de montée en température lorsque le coefficient de la courbe de chauffage est correctement réglé."
+ "window_sensors": "Capteurs de Contact qui déclenchent le système lorsqu'une fenêtre ou une porte est ouverte pendant une période."
+ },
+ "description": "Paramètres et configurations généraux.",
+ "title": "Général"
+ },
+ "init": {
+ "menu_options": {
+ "advanced": "Options Avancées",
+ "general": "Général",
+ "presets": "Préréglages",
+ "system_configuration": "Configuration du Système"
}
},
"presets": {
- "title": "Préréglages",
- "description": "Paramètres de température prédéfinis pour différents scénarios ou activités.",
"data": {
+ "activity_temperature": "Température Activité",
"away_temperature": "Température Absence",
+ "comfort_temperature": "Température Confort",
"home_temperature": "Température Maison",
"sleep_temperature": "Température Sommeil",
- "comfort_temperature": "Température Confort",
- "activity_temperature": "Température Activité",
"sync_climates_with_preset": "Synchroniser les climats avec le préréglage (sommeil / absence / activité)"
- }
+ },
+ "description": "Paramètres de température prédéfinis pour différents scénarios ou activités.",
+ "title": "Préréglages"
},
"system_configuration": {
- "title": "Configuration du Système",
- "description": "Pour un réglage fin et une personnalisation.",
"data": {
"automatic_duty_cycle": "Cycle de fonctionnement automatique",
+ "sync_climates_with_mode": "Synchroniser les climats avec le mode",
"overshoot_protection": "Protection contre le dépassement (avec PWM)",
- "window_minimum_open_time": "Temps minimum d'ouverture de la fenêtre",
- "sensor_max_value_age": "Âge maximal de la valeur du capteur de température"
+ "sensor_max_value_age": "Âge maximal de la valeur du capteur de température",
+ "window_minimum_open_time": "Temps minimum d'ouverture de la fenêtre"
},
"data_description": {
"automatic_duty_cycle": "Activer ou désactiver le cycle de fonctionnement automatique pour la Modulation de Largeur d'Impulsion (PWM).",
"overshoot_protection": "Activer la protection contre le dépassement avec la Modulation de Largeur d'Impulsion (PWM) pour prévenir les dépassements de température de la chaudière.",
- "window_minimum_open_time": "Le temps minimum qu'une fenêtre doit être ouverte avant que le système ne réagisse.",
- "sensor_max_value_age": "L'âge maximum de la valeur du capteur de température avant de la considérer comme stagnante."
- }
- },
- "advanced": {
- "title": "Avancé",
- "data": {
- "simulation": "Simulation",
- "sample_time": "Temps d'Échantillonnage",
- "thermal_comfort": "Confort Thermique",
- "minimum_consumption": "Consommation Minimale",
- "maximum_consumption": "Consommation Maximale",
- "climate_valve_offset": "Décalage de la vanne climatique",
- "target_temperature_step": "Pas de Température Cible",
- "maximum_relative_modulation": "Modulation Relative Maximale",
- "force_pulse_width_modulation": "Forcer la Modulation de Largeur d'Impulsion",
- "dynamic_minimum_setpoint": "Point de Consigne Minimum Dynamique (Expérimental)"
+ "sensor_max_value_age": "L'âge maximum de la valeur du capteur de température avant de la considérer comme stagnante.",
+ "window_minimum_open_time": "Le temps minimum qu'une fenêtre doit être ouverte avant que le système ne réagisse."
},
- "data_description": {
- "thermal_comfort": "Activer l'utilisation de l'Indice de Simmer pour ajuster le confort thermique.",
- "minimum_consumption": "La consommation minimale de gaz lorsque la chaudière est active.",
- "maximum_consumption": "La consommation maximale de gaz lorsque la chaudière est active.",
- "climate_valve_offset": "Décalage pour ajuster le degré d'ouverture de la vanne climatique.",
- "target_temperature_step": "Ajuster le pas de température cible pour un réglage fin des niveaux de confort.",
- "sample_time": "L'intervalle de temps minimum entre les mises à jour du régulateur PID.",
- "maximum_relative_modulation": "Représentant le niveau de modulation le plus élevé pour un système de chauffage efficace."
- }
+ "description": "Pour un réglage fin et une personnalisation.",
+ "title": "Configuration du Système"
}
}
}
diff --git a/custom_components/sat/translations/it.json b/custom_components/sat/translations/it.json
index 2a63f1d0..0d35eb57 100644
--- a/custom_components/sat/translations/it.json
+++ b/custom_components/sat/translations/it.json
@@ -1,221 +1,245 @@
{
"config": {
+ "abort": {
+ "already_configured": "Il gateway è già configurato.",
+ "reconfigure_successful": "Il gateway è stato riconfigurato."
+ },
+ "error": {
+ "connection": "Impossibile connettersi al gateway.",
+ "mqtt_component": "Il componente MQTT non è disponibile.",
+ "unable_to_calibrate": "Il processo di calibrazione ha incontrato un problema e non è stato completato con successo. Si prega di assicurarsi che il sistema di riscaldamento funzioni correttamente e che tutti i sensori richiesti siano connessi e funzionanti correttamente.\n\nSe continui a riscontrare problemi con la calibrazione, considera di contattarci per ulteriore assistenza. Ci scusiamo per eventuali inconvenienti causati."
+ },
+ "progress": {
+ "calibration": "Calibrazione e ricerca del valore di protezione dal superamento in corso...\n\nSi prega di attendere mentre ottimizziamo il tuo sistema di riscaldamento. Questo processo può richiedere circa 20 minuti."
+ },
"step": {
- "user": {
- "title": "Smart Autotune Thermostat (SAT)",
- "description": "Il SAT è un termostato intelligente capace di auto-regolarsi per ottimizzare il controllo della temperatura. Seleziona la modalità appropriata che corrisponde al tuo sistema di riscaldamento.",
- "menu_options": {
- "mosquitto": "Gateway OpenTherm (MQTT)",
- "serial": "Gateway OpenTherm (SERIALE)",
- "switch": "Termostato PID con PWM (ON/OFF)",
- "simulator": "Gateway Simulato (AVANZATO)"
- }
- },
- "mosquitto": {
- "title": "Gateway OpenTherm (MQTT)",
- "description": "Si prega di fornire i seguenti dettagli per configurare il Gateway OpenTherm. Nel campo Nome, inserisci un nome per il gateway che ti aiuterà a identificarlo all'interno del tuo sistema.\n\nSpecifica l'entità Climatica da utilizzare per il Gateway OpenTherm. Questa entità è fornita dal Gateway OpenTherm e rappresenta il tuo sistema di riscaldamento.\n\nInoltre, inserisci l'Argomento Principale che verrà utilizzato per pubblicare e sottoscrivere i messaggi MQTT relativi al Gateway OpenTherm.\n\nQueste impostazioni sono essenziali per stabilire la comunicazione e l'integrazione con il tuo Gateway OpenTherm tramite MQTT. Consentono uno scambio di dati e un controllo fluido del tuo sistema di riscaldamento. Assicurati che i dettagli forniti siano accurati per garantire un corretto funzionamento.",
- "data": {
- "name": "Nome",
- "device": "Dispositivo",
- "mqtt_topic": "Argomento Principale"
- }
- },
- "serial": {
- "title": "Gateway OpenTherm (SERIALE)",
- "description": "Per stabilire una connessione con il Gateway OpenTherm tramite una connessione socket, si prega di fornire i seguenti dettagli. Nel campo Nome, inserisci un nome per il gateway che ti aiuterà a identificarlo all'interno del tuo sistema.\n\nSpecifica l'indirizzo di rete del Gateway OpenTherm nel campo Dispositivo. Questo potrebbe essere nel formato \"socket://otgw.local:25238\", dove \"otgw.local\" è il nome host o l'indirizzo IP del gateway e \"25238\" è il numero della porta.\n\nQueste impostazioni sono essenziali per stabilire la comunicazione e l'integrazione con il tuo Gateway OpenTherm tramite la connessione socket. Assicurati che i dettagli forniti siano accurati per garantire un corretto funzionamento.",
- "data": {
- "name": "Nome",
- "device": "URL"
- }
- },
- "switch": {
- "title": "Termostato PID con PWM (ON/OFF)",
- "description": "Si prega di compilare i seguenti dettagli per configurare l'interruttore. Inserisci un nome per l'interruttore nel campo Nome, che ti aiuterà a identificarlo all'interno del tuo sistema. Scegli l'entità appropriata da utilizzare per il tuo interruttore tra le opzioni fornite.\n\nNel campo Impostazione della Temperatura, specifica la temperatura desiderata per il tuo sistema di riscaldamento. Se stai utilizzando una caldaia per acqua calda, inserisci l'Impostazione della Temperatura della Caldaia con il valore appropriato. Per i sistemi di riscaldamento elettrici, inserisci il valore 100.\n\nQueste impostazioni sono essenziali per un controllo preciso della temperatura e per garantire prestazioni ottimali del tuo sistema di riscaldamento. Fornire l'Impostazione della Temperatura corretta consente una regolazione accurata e contribuisce a creare un ambiente confortevole ed efficiente dal punto di vista energetico nella tua casa.",
- "data": {
- "name": "Nome",
- "device": "Entità",
- "minimum_setpoint": "Impostazione della Temperatura"
- }
- },
- "simulator": {
- "title": "Gateway Simulato (AVANZATO)",
- "description": "Questo gateway ti consente di simulare una caldaia per scopi di test e dimostrazione. Si prega di fornire le seguenti informazioni per configurare il simulatore.\n\nNota: Il Gateway Simulatore è destinato solo a scopi di test e dimostrazione e non dovrebbe essere utilizzato in ambienti di produzione.",
- "data": {
- "name": "Nome",
- "minimum_setpoint": "Setpoint Minimo",
- "maximum_setpoint": "Setpoint Massimo",
- "simulated_heating": "Riscaldamento Simulato",
- "simulated_cooling": "Raffreddamento Simulato",
- "simulated_warming_up": "Riscaldamento Simulato"
- }
- },
- "sensors": {
- "title": "Configura sensori",
- "description": "Si prega di selezionare i sensori che verranno utilizzati per monitorare la temperatura.",
- "data": {
- "inside_sensor_entity_id": "Entità Sensore Interno",
- "outside_sensor_entity_id": "Entità Sensore Esterno",
- "humidity_sensor_entity_id": "Entità Sensore Umidità"
- }
- },
- "heating_system": {
- "title": "Sistema di Riscaldamento",
- "description": "Selezionare il tipo corretto di sistema di riscaldamento è importante affinché il SAT possa controllare accuratamente la temperatura e ottimizzare le prestazioni. Scegli l'opzione che corrisponde alla tua configurazione per garantire una regolazione appropriata della temperatura in tutta la tua casa.",
- "data": {
- "heating_system": "Sistema"
- }
- },
"areas": {
- "title": "Aree",
- "description": "Impostazioni relative ai climi, alle stanze multiple e al controllo della temperatura. I climi primari sono nella stessa stanza del sensore interno e le stanze hanno le loro temperature obiettivo separate dal sistema.",
"data": {
"main_climates": "Primari",
"secondary_climates": "Stanze"
- }
+ },
+ "description": "Impostazioni relative ai climi, alle stanze multiple e al controllo della temperatura. I climi primari sono nella stessa stanza del sensore interno e le stanze hanno le loro temperature obiettivo separate dal sistema.",
+ "title": "Aree"
},
"automatic_gains": {
- "title": "Guadagni Automatici",
- "description": "Questa funzionalità regola dinamicamente i parametri di controllo del tuo sistema di riscaldamento, ottimizzando il controllo della temperatura per un maggiore comfort e efficienza energetica. Attivare questa opzione consente al SAT di adattarsi continuamente e di perfezionare le impostazioni di riscaldamento in base alle condizioni ambientali. Ciò aiuta a mantenere un ambiente stabile e confortevole senza interventi manuali.\n\nNota: Se scegli di non abilitare i guadagni automatici, dovrai inserire manualmente i valori PID per un controllo preciso della temperatura. Assicurati di avere valori PID accurati per il tuo specifico sistema di riscaldamento per ottenere prestazioni ottimali.",
"data": {
"automatic_gains": "Guadagni Automatici (raccomandato)"
- }
+ },
+ "description": "Questa funzionalità regola dinamicamente i parametri di controllo del tuo sistema di riscaldamento, ottimizzando il controllo della temperatura per un maggiore comfort e efficienza energetica. Attivare questa opzione consente al SAT di adattarsi continuamente e di perfezionare le impostazioni di riscaldamento in base alle condizioni ambientali. Ciò aiuta a mantenere un ambiente stabile e confortevole senza interventi manuali.\n\nNota: Se scegli di non abilitare i guadagni automatici, dovrai inserire manualmente i valori PID per un controllo preciso della temperatura. Assicurati di avere valori PID accurati per il tuo specifico sistema di riscaldamento per ottenere prestazioni ottimali.",
+ "title": "Guadagni Automatici"
},
"calibrate_system": {
- "title": "Calibra Sistema",
"description": "Ottimizza il tuo sistema di riscaldamento determinando automaticamente i valori PID ottimali per la tua configurazione. Quando selezioni Guadagni Automatici, si prega di notare che il sistema passerà attraverso un processo di calibrazione che potrebbe richiedere circa 20 minuti per completarsi.\n\nI Guadagni Automatici sono raccomandati per la maggior parte degli utenti in quanto semplificano il processo di configurazione e garantiscono prestazioni ottimali. Tuttavia, se sei familiare con il controllo PID e preferisci impostare manualmente i valori, puoi scegliere di non attivare i Guadagni Automatici.\n\nSi prega di notare che scegliere di non attivare i Guadagni Automatici richiede una buona conoscenza del controllo PID e potrebbe richiedere ulteriori regolazioni manuali per ottenere prestazioni ottimali.",
"menu_options": {
"calibrate": "Calibra e determina il tuo valore di protezione dal superamento (circa 20 min).",
"overshoot_protection": "Inserisci manualmente il valore di protezione dal superamento.",
"pid_controller": "Inserisci manualmente i valori PID (non raccomandato)."
- }
+ },
+ "title": "Calibra Sistema"
+ },
+ "calibrated": {
+ "description": "Il processo di calibrazione è stato completato con successo.\n\nCongratulazioni! Il tuo Smart Autotune Thermostat (SAT) è stato calibrato per ottimizzare le prestazioni di riscaldamento del tuo sistema. Durante il processo di calibrazione, il SAT ha analizzato attentamente le caratteristiche di riscaldamento e determinato il valore appropriato di protezione dal superamento per garantire un controllo preciso della temperatura.\n\nValore di Protezione dal Superamento: {minimum_setpoint} °C\n\nQuesto valore rappresenta la quantità massima di superamento consentita durante il processo di riscaldamento. Il SAT monitorerà attivamente e regolerà il riscaldamento per evitare un superamento eccessivo, mantenendo così un'esperienza di riscaldamento confortevole ed efficiente nella tua casa.\n\nSi prega di notare che il valore di protezione dal superamento può variare a seconda delle caratteristiche specifiche del tuo sistema di riscaldamento e dei fattori ambientali. È stato affinato per fornire prestazioni ottimali in base ai risultati della calibrazione.",
+ "menu_options": {
+ "calibrate": "Riprova la calibrazione",
+ "finish": "Continua con la calibrazione attuale"
+ },
+ "title": "Calibrazione Completata"
+ },
+ "heating_system": {
+ "data": {
+ "heating_system": "Sistema"
+ },
+ "description": "Selezionare il tipo corretto di sistema di riscaldamento è importante affinché il SAT possa controllare accuratamente la temperatura e ottimizzare le prestazioni. Scegli l'opzione che corrisponde alla tua configurazione per garantire una regolazione appropriata della temperatura in tutta la tua casa.",
+ "title": "Sistema di Riscaldamento"
+ },
+ "mosquitto": {
+ "description": "Configura la modalità MQTT per il tuo sistema di riscaldamento. Seleziona la modalità del gateway e fornisci il suo nome.",
+ "data": {
+ "mode": "Modalità"
+ },
+ "title": "Configurazione MQTT"
+ },
+ "mosquitto_opentherm": {
+ "description": "Configura il Gateway OpenTherm. Fornisci l'argomento principale MQTT e l'identificativo del dispositivo.",
+ "data": {
+ "name": "Nome",
+ "mqtt_topic": "Argomento MQTT",
+ "device": "ID del dispositivo"
+ },
+ "title": "Configurazione del Gateway OpenTherm"
+ },
+ "mosquitto_ems": {
+ "description": "Configura il Gateway EMS-ESP. Fornisci l'argomento MQTT.",
+ "data": {
+ "name": "Nome",
+ "mqtt_topic": "Argomento MQTT"
+ },
+ "title": "Configurazione del Gateway EMS-ESP"
},
"overshoot_protection": {
- "title": "Protezione dal Superamento",
- "description": "Fornendo il valore di protezione dal superamento, il SAT regolerà di conseguenza i parametri di controllo per mantenere un ambiente di riscaldamento stabile e confortevole. Questa configurazione manuale ti consente di affinare il sistema in base alla tua configurazione specifica.\n\nNota: Se non sei sicuro del valore di protezione dal superamento o non hai eseguito il processo di calibrazione, si raccomanda di annullare la configurazione e procedere con il processo di calibrazione per consentire al SAT di determinare automaticamente il valore per prestazioni ottimali.",
"data": {
"minimum_setpoint": "Valore"
- }
+ },
+ "description": "Fornendo il valore di protezione dal superamento, il SAT regolerà di conseguenza i parametri di controllo per mantenere un ambiente di riscaldamento stabile e confortevole. Questa configurazione manuale ti consente di affinare il sistema in base alla tua configurazione specifica.\n\nNota: Se non sei sicuro del valore di protezione dal superamento o non hai eseguito il processo di calibrazione, si raccomanda di annullare la configurazione e procedere con il processo di calibrazione per consentire al SAT di determinare automaticamente il valore per prestazioni ottimali.",
+ "title": "Protezione dal Superamento"
},
"pid_controller": {
- "title": "Configura manualmente il controller PID.",
- "description": "Configura manualmente i guadagni proporzionale, integrale e derivativo per affinare il tuo sistema di riscaldamento. Utilizza questa opzione se preferisci avere il pieno controllo sui parametri del controller PID. Regola i guadagni in base alle caratteristiche specifiche del tuo sistema di riscaldamento e alle tue preferenze.",
"data": {
- "integral": "Integrale (kI)",
"derivative": "Derivativo (kD)",
+ "integral": "Integrale (kI)",
"proportional": "Proporzionale (kP)"
- }
+ },
+ "description": "Configura manualmente i guadagni proporzionale, integrale e derivativo per affinare il tuo sistema di riscaldamento. Utilizza questa opzione se preferisci avere il pieno controllo sui parametri del controller PID. Regola i guadagni in base alle caratteristiche specifiche del tuo sistema di riscaldamento e alle tue preferenze.",
+ "title": "Configura manualmente il controller PID."
},
- "calibrated": {
- "title": "Calibrazione Completata",
- "description": "Il processo di calibrazione è stato completato con successo.\n\nCongratulazioni! Il tuo Smart Autotune Thermostat (SAT) è stato calibrato per ottimizzare le prestazioni di riscaldamento del tuo sistema. Durante il processo di calibrazione, il SAT ha analizzato attentamente le caratteristiche di riscaldamento e determinato il valore appropriato di protezione dal superamento per garantire un controllo preciso della temperatura.\n\nValore di Protezione dal Superamento: {minimum_setpoint} °C\n\nQuesto valore rappresenta la quantità massima di superamento consentita durante il processo di riscaldamento. Il SAT monitorerà attivamente e regolerà il riscaldamento per evitare un superamento eccessivo, mantenendo così un'esperienza di riscaldamento confortevole ed efficiente nella tua casa.\n\nSi prega di notare che il valore di protezione dal superamento può variare a seconda delle caratteristiche specifiche del tuo sistema di riscaldamento e dei fattori ambientali. È stato affinato per fornire prestazioni ottimali in base ai risultati della calibrazione.",
+ "sensors": {
+ "data": {
+ "humidity_sensor_entity_id": "Entità Sensore Umidità",
+ "inside_sensor_entity_id": "Entità Sensore Interno",
+ "outside_sensor_entity_id": "Entità Sensore Esterno"
+ },
+ "description": "Si prega di selezionare i sensori che verranno utilizzati per monitorare la temperatura.",
+ "title": "Configura sensori"
+ },
+ "serial": {
+ "data": {
+ "device": "URL",
+ "name": "Nome"
+ },
+ "description": "Per stabilire una connessione con il Gateway OpenTherm tramite una connessione socket, si prega di fornire i seguenti dettagli. Nel campo Nome, inserisci un nome per il gateway che ti aiuterà a identificarlo all'interno del tuo sistema.\n\nSpecifica l'indirizzo di rete del Gateway OpenTherm nel campo Dispositivo. Questo potrebbe essere nel formato \"socket://otgw.local:25238\", dove \"otgw.local\" è il nome host o l'indirizzo IP del gateway e \"25238\" è il numero della porta.\n\nQueste impostazioni sono essenziali per stabilire la comunicazione e l'integrazione con il tuo Gateway OpenTherm tramite la connessione socket. Assicurati che i dettagli forniti siano accurati per garantire un corretto funzionamento.",
+ "title": "Gateway OpenTherm (SERIALE)"
+ },
+ "simulator": {
+ "data": {
+ "maximum_setpoint": "Setpoint Massimo",
+ "minimum_setpoint": "Setpoint Minimo",
+ "name": "Nome",
+ "simulated_cooling": "Raffreddamento Simulato",
+ "simulated_heating": "Riscaldamento Simulato",
+ "simulated_warming_up": "Riscaldamento Simulato"
+ },
+ "description": "Questo gateway ti consente di simulare una caldaia per scopi di test e dimostrazione. Si prega di fornire le seguenti informazioni per configurare il simulatore.\n\nNota: Il Gateway Simulatore è destinato solo a scopi di test e dimostrazione e non dovrebbe essere utilizzato in ambienti di produzione.",
+ "title": "Gateway Simulato (AVANZATO)"
+ },
+ "switch": {
+ "data": {
+ "device": "Entità",
+ "minimum_setpoint": "Impostazione della Temperatura",
+ "name": "Nome"
+ },
+ "description": "Si prega di compilare i seguenti dettagli per configurare l'interruttore. Inserisci un nome per l'interruttore nel campo Nome, che ti aiuterà a identificarlo all'interno del tuo sistema. Scegli l'entità appropriata da utilizzare per il tuo interruttore tra le opzioni fornite.\n\nNel campo Impostazione della Temperatura, specifica la temperatura desiderata per il tuo sistema di riscaldamento. Se stai utilizzando una caldaia per acqua calda, inserisci l'Impostazione della Temperatura della Caldaia con il valore appropriato. Per i sistemi di riscaldamento elettrici, inserisci il valore 100.\n\nQueste impostazioni sono essenziali per un controllo preciso della temperatura e per garantire prestazioni ottimali del tuo sistema di riscaldamento. Fornire l'Impostazione della Temperatura corretta consente una regolazione accurata e contribuisce a creare un ambiente confortevole ed efficiente dal punto di vista energetico nella tua casa.",
+ "title": "Termostato PID con PWM (ON/OFF)"
+ },
+ "user": {
+ "description": "SAT è un termostato intelligente in grado di auto-regolarsi per ottimizzare il controllo della temperatura. Seleziona la modalità che corrisponde al tuo sistema di riscaldamento.",
"menu_options": {
- "calibrate": "Riprova la calibrazione",
- "finish": "Continua con la calibrazione attuale"
- }
+ "mosquitto": "Gateway MQTT (OpenTherm, EMS-ESP, altri)",
+ "serial": "Gateway seriale (es. OpenTherm)",
+ "esphome": "ESPHome (Servizi e sensori di sistema)",
+ "simulator": "Gateway simulato (Solo per sviluppatori)",
+ "switch": "Termostato PID (Modalità PWM Acceso/Spento)"
+ },
+ "title": "Smart Autotune Thermostat (SAT)"
}
- },
- "error": {
- "connection": "Impossibile connettersi al gateway.",
- "mqtt_component": "Il componente MQTT non è disponibile.",
- "unable_to_calibrate": "Il processo di calibrazione ha incontrato un problema e non è stato completato con successo. Si prega di assicurarsi che il sistema di riscaldamento funzioni correttamente e che tutti i sensori richiesti siano connessi e funzionanti correttamente.\n\nSe continui a riscontrare problemi con la calibrazione, considera di contattarci per ulteriore assistenza. Ci scusiamo per eventuali inconvenienti causati."
- },
- "abort": {
- "already_configured": "Il gateway è già configurato."
- },
- "progress": {
- "calibration": "Calibrazione e ricerca del valore di protezione dal superamento in corso...\n\nSi prega di attendere mentre ottimizziamo il tuo sistema di riscaldamento. Questo processo può richiedere circa 20 minuti."
}
},
"options": {
"step": {
- "init": {
- "menu_options": {
- "general": "Generale",
- "presets": "Preimpostazioni",
- "advanced": "Opzioni Avanzate",
- "system_configuration": "Configurazione del Sistema"
- }
+ "advanced": {
+ "data": {
+ "error_monitoring": "Abilita monitoraggio errori",
+ "climate_valve_offset": "Offset della Valvola Climatica",
+ "dynamic_minimum_setpoint": "Setpoint Minimo Dinamico (Sperimentale)",
+ "force_pulse_width_modulation": "Forzare la Modulazione di Larghezza di Impulso",
+ "maximum_consumption": "Consumo Massimo",
+ "maximum_relative_modulation": "Modulazione Relativa Massima",
+ "minimum_consumption": "Consumo Minimo",
+ "sample_time": "Tempo di Campionamento",
+ "simulation": "Simulazione",
+ "target_temperature_step": "Passo della Temperatura Target",
+ "thermal_comfort": "Comfort Termico"
+ },
+ "data_description": {
+ "climate_valve_offset": "Offset per regolare il grado di apertura della valvola climatica.",
+ "dynamic_minimum_setpoint": "Attiva la regolazione dinamica del punto di impostazione minimo in base alla temperatura di ritorno della caldaia, che aiuta anche a identificare se delle valvole sono chiuse.",
+ "maximum_consumption": "Il consumo massimo di gas quando la caldaia è attiva.",
+ "maximum_relative_modulation": "Rappresenta il livello di modulazione più alto per un sistema di riscaldamento efficiente.",
+ "minimum_consumption": "Il consumo minimo di gas quando la caldaia è attiva.",
+ "sample_time": "L'intervallo di tempo minimo tra gli aggiornamenti del controllore PID.",
+ "target_temperature_step": "Regolare il passo della temperatura target per un'accurata regolazione dei livelli di comfort.",
+ "thermal_comfort": "Abilitare l'uso dell'Indice di Simmer per l'aggiustamento del comfort termico."
+ },
+ "title": "Avanzate"
},
"general": {
- "title": "Generale",
- "description": "Impostazioni e configurazioni generali.",
"data": {
- "integral": "Integrale (kI)",
- "derivative": "Derivata (kD)",
- "proportional": "Proporzionale (kP)",
- "maximum_setpoint": "Setpoint Massimo",
- "window_sensors": "Sensori Contatto",
"automatic_gains_value": "Valore dei Guadagni Automatici",
+ "derivative": "Derivata (kD)",
"derivative_time_weight": "Peso Temporale Derivato",
- "heating_curve_version": "Versione della Curva di Riscaldamento",
- "heating_curve_coefficient": "Coefficiente della Curva di Riscaldamento",
"duty_cycle": "Ciclo di Lavoro Massimo per la Modulazione di Larghezza di Impulso",
- "sync_with_thermostat": "Sincronizza setpoint con termostato"
+ "heating_curve_coefficient": "Coefficiente della Curva di Riscaldamento",
+ "heating_curve_version": "Versione della Curva di Riscaldamento",
+ "integral": "Integrale (kI)",
+ "maximum_setpoint": "Setpoint Massimo",
+ "minimum_setpoint_adjustment_factor": "Fattore di regolazione del punto di impostazione minimo",
+ "pid_controller_version": "Versione del controllore PID",
+ "proportional": "Proporzionale (kP)",
+ "sync_with_thermostat": "Sincronizza setpoint con termostato",
+ "window_sensors": "Sensori Contatto",
+ "heating_mode": "Modalità riscaldamento"
},
"data_description": {
- "integral": "Il termine integrale (kI) nel controllore PID, responsabile della riduzione dell'errore in stato stazionario.",
- "derivative": "Il termine derivato (kD) nel controllore PID, responsabile della mitigazione del superamento.",
- "proportional": "Il termine proporzionale (kP) nel controllore PID, responsabile della risposta immediata agli errori.",
- "maximum_setpoint": "La temperatura ottimale per un funzionamento efficiente della caldaia.",
- "window_sensors": "Sensori di Contatto che attivano il sistema quando una finestra o una porta è aperta per un periodo di tempo.",
"automatic_gains_value": "Il valore utilizzato per i guadagni automatici nel controllore PID.",
- "heating_curve_coefficient": "Il coefficiente utilizzato per regolare la curva di riscaldamento.",
+ "derivative": "Il termine derivato (kD) nel controllore PID, responsabile della mitigazione del superamento.",
+ "derivative_time_weight": "Un parametro per regolare l'influenza del termine derivato nel tempo, particolarmente utile per ridurre il sottoscavo durante la fase di riscaldamento quando il coefficiente della curva di riscaldamento è correttamente impostato.",
"duty_cycle": "Il ciclo di lavoro massimo per la Modulazione di Larghezza di Impulso (PWM), controllando i cicli di accensione/spegnimento della caldaia.",
+ "heating_curve_coefficient": "Il coefficiente utilizzato per regolare la curva di riscaldamento.",
+ "integral": "Il termine integrale (kI) nel controllore PID, responsabile della riduzione dell'errore in stato stazionario.",
+ "maximum_setpoint": "La temperatura ottimale per un funzionamento efficiente della caldaia.",
+ "minimum_setpoint_adjustment_factor": "Questo fattore regola il punto di impostazione del riscaldamento in base alla temperatura di ritorno della caldaia, influenzando la reattività e l'efficienza del riscaldamento. Un valore più alto aumenta la sensibilità ai cambiamenti di temperatura, migliorando il controllo del comfort e del consumo energetico. L'intervallo di partenza consigliato è da 0,1 a 0,5. Regolare per adattarsi al proprio sistema e alle preferenze di comfort.",
+ "proportional": "Il termine proporzionale (kP) nel controllore PID, responsabile della risposta immediata agli errori.",
"sync_with_thermostat": "Sincronizza il setpoint con il termostato per garantire un controllo coordinato della temperatura.",
- "derivative_time_weight": "Un parametro per regolare l'influenza del termine derivato nel tempo, particolarmente utile per ridurre il sottoscavo durante la fase di riscaldamento quando il coefficiente della curva di riscaldamento è correttamente impostato."
+ "window_sensors": "Sensori di Contatto che attivano il sistema quando una finestra o una porta è aperta per un periodo di tempo."
+ },
+ "description": "Impostazioni e configurazioni generali.",
+ "title": "Generale"
+ },
+ "init": {
+ "menu_options": {
+ "advanced": "Opzioni Avanzate",
+ "general": "Generale",
+ "presets": "Preimpostazioni",
+ "system_configuration": "Configurazione del Sistema"
}
},
"presets": {
- "title": "Preimpostazioni",
- "description": "Impostazioni di temperatura predefinite per diversi scenari o attività.",
"data": {
+ "activity_temperature": "Temperatura Attività",
"away_temperature": "Temperatura Assente",
+ "comfort_temperature": "Temperatura Comfort",
"home_temperature": "Temperatura Casa",
"sleep_temperature": "Temperatura Sonno",
- "comfort_temperature": "Temperatura Comfort",
- "activity_temperature": "Temperatura Attività",
"sync_climates_with_preset": "Sincronizza climi con preimpostazione (sonno / assente / attività)"
- }
+ },
+ "description": "Impostazioni di temperatura predefinite per diversi scenari o attività.",
+ "title": "Preimpostazioni"
},
"system_configuration": {
- "title": "Configurazione del Sistema",
- "description": "Per una regolazione fine e una personalizzazione.",
"data": {
"automatic_duty_cycle": "Ciclo di lavoro automatico",
+ "sync_climates_with_mode": "Sincronizza climi con la modalità",
"overshoot_protection": "Protezione dal Superamento (con PWM)",
- "window_minimum_open_time": "Tempo minimo di apertura della finestra",
- "sensor_max_value_age": "Età massima del valore del sensore di temperatura"
+ "sensor_max_value_age": "Età massima del valore del sensore di temperatura",
+ "window_minimum_open_time": "Tempo minimo di apertura della finestra"
},
"data_description": {
"automatic_duty_cycle": "Abilitare o disabilitare il ciclo di lavoro automatico per la Modulazione di Larghezza di Impulso (PWM).",
"overshoot_protection": "Abilitare la protezione dal superamento con la Modulazione di Larghezza di Impulso (PWM) per prevenire il superamento della temperatura della caldaia.",
- "window_minimum_open_time": "Il tempo minimo che una finestra deve essere aperta prima che il sistema reagisca.",
- "sensor_max_value_age": "L'età massima del valore del sensore di temperatura prima di considerarlo stazionario."
- }
- },
- "advanced": {
- "title": "Avanzate",
- "data": {
- "simulation": "Simulazione",
- "sample_time": "Tempo di Campionamento",
- "thermal_comfort": "Comfort Termico",
- "minimum_consumption": "Consumo Minimo",
- "maximum_consumption": "Consumo Massimo",
- "climate_valve_offset": "Offset della Valvola Climatica",
- "target_temperature_step": "Passo della Temperatura Target",
- "maximum_relative_modulation": "Modulazione Relativa Massima",
- "force_pulse_width_modulation": "Forzare la Modulazione di Larghezza di Impulso",
- "dynamic_minimum_setpoint": "Setpoint Minimo Dinamico (Sperimentale)"
+ "sensor_max_value_age": "L'età massima del valore del sensore di temperatura prima di considerarlo stazionario.",
+ "window_minimum_open_time": "Il tempo minimo che una finestra deve essere aperta prima che il sistema reagisca."
},
- "data_description": {
- "thermal_comfort": "Abilitare l'uso dell'Indice di Simmer per l'aggiustamento del comfort termico.",
- "minimum_consumption": "Il consumo minimo di gas quando la caldaia è attiva.",
- "maximum_consumption": "Il consumo massimo di gas quando la caldaia è attiva.",
- "climate_valve_offset": "Offset per regolare il grado di apertura della valvola climatica.",
- "target_temperature_step": "Regolare il passo della temperatura target per un'accurata regolazione dei livelli di comfort.",
- "sample_time": "L'intervallo di tempo minimo tra gli aggiornamenti del controllore PID.",
- "maximum_relative_modulation": "Rappresenta il livello di modulazione più alto per un sistema di riscaldamento efficiente."
- }
+ "description": "Per una regolazione fine e una personalizzazione.",
+ "title": "Configurazione del Sistema"
}
}
}
diff --git a/custom_components/sat/translations/nl.json b/custom_components/sat/translations/nl.json
index f86881f2..e877e522 100644
--- a/custom_components/sat/translations/nl.json
+++ b/custom_components/sat/translations/nl.json
@@ -1,221 +1,245 @@
{
"config": {
+ "abort": {
+ "already_configured": "Gateway is al geconfigureerd.",
+ "reconfigure_successful": "Gateway is opnieuw geconfigureerd."
+ },
+ "error": {
+ "connection": "Kan geen verbinding maken met de gateway.",
+ "mqtt_component": "Het MQTT-component is niet beschikbaar.",
+ "unable_to_calibrate": "Het kalibratieproces is op een probleem gestuit en kon niet succesvol worden voltooid. Zorg ervoor dat uw verwarmingssysteem correct functioneert en dat alle vereiste sensoren zijn aangesloten en correct werken.\n\nAls u problemen blijft ondervinden met de kalibratie, overweeg dan om contact met ons op te nemen voor verdere hulp. Onze excuses voor het ongemak."
+ },
+ "progress": {
+ "calibration": "Kalibreren en de waarde van de overshootbeveiliging vinden...\n\nEven geduld a.u.b. terwijl wij uw verwarmingssysteem optimaliseren. Dit proces kan ongeveer 20 minuten duren."
+ },
"step": {
- "user": {
- "title": "Slimme Autotune Thermostaat (SAT)",
- "description": "SAT is een slimme thermostaat die zichzelf kan afstemmen om de temperatuurregeling te optimaliseren. Selecteer de geschikte modus die overeenkomt met uw verwarmingssysteem.",
- "menu_options": {
- "mosquitto": "OpenTherm Gateway ( MQTT )",
- "serial": "OpenTherm Gateway ( SERIEEL )",
- "switch": "PID Thermostaat met PWM ( AAN/UIT )",
- "simulator": "Gesimuleerde Gateway ( GEAVANCEERD )"
- }
- },
- "mosquitto": {
- "title": "OpenTherm Gateway ( MQTT )",
- "description": "Gelieve de volgende gegevens te verstrekken om de OpenTherm Gateway in te stellen. Voer in het veld Naam een naam in voor de gateway die u helpt deze te identificeren binnen uw systeem.\n\nSpecificeer de Climate entity die gebruikt wordt voor de OpenTherm Gateway. Deze entiteit wordt aangeleverd door de OpenTherm Gateway en vertegenwoordigt uw verwarmingssysteem.\n\nVoer daarnaast het Top Topic in dat gebruikt zal worden voor het publiceren en abonneren op MQTT-berichten gerelateerd aan de OpenTherm Gateway.\n\nDeze instellingen zijn essentieel voor het tot stand brengen van communicatie en integratie met uw OpenTherm Gateway via MQTT. Ze zorgen voor een naadloze gegevensuitwisseling en controle over uw verwarmingssysteem. Zorg ervoor dat de verstrekte gegevens nauwkeurig zijn om een correcte werking te garanderen.",
- "data": {
- "name": "Naam",
- "device": "Apparaat",
- "mqtt_topic": "Top Topic"
- }
- },
- "serial": {
- "title": "OpenTherm Gateway ( SERIEEL )",
- "description": "Om een verbinding met de OpenTherm Gateway te maken via een socketverbinding, gelieve de volgende details te verstrekken. Voer in het veld Naam een naam in voor de gateway die u helpt deze te identificeren binnen uw systeem.\n\nSpecificeer het netwerkadres van de OpenTherm Gateway in het veld Apparaat. Dit kan in het formaat zijn van \"socket://otgw.local:25238\", waarbij \"otgw.local\" de hostnaam of het IP-adres van de gateway is en \"25238\" het poortnummer.\n\nDeze instellingen zijn essentieel voor het tot stand brengen van communicatie en integratie met uw OpenTherm Gateway via de socketverbinding. Zorg ervoor dat de verstrekte gegevens nauwkeurig zijn om een correcte werking te garanderen.",
- "data": {
- "name": "Naam",
- "device": "URL"
- }
- },
- "switch": {
- "title": "PID Thermostaat met PWM ( AAN/UIT )",
- "description": "Gelieve de volgende details in te vullen om de schakelaar in te stellen. Voer in het veld Naam een naam in voor de schakelaar, die u helpt deze te identificeren binnen uw systeem. Kies de geschikte entiteit om voor uw schakelaar te gebruiken uit de aangeboden opties.\n\nIn het veld Temperatuurinstelling specificeert u de gewenste doeltemperatuur voor uw verwarmingssysteem. Als u een warmwaterboiler gebruikt, vul dan de Boiler Temperatuurinstelling in met de geschikte waarde. Voor elektrische verwarmingssystemen, voer de waarde 100 in.\n\nDeze instellingen zijn essentieel voor precieze temperatuurregeling en zorgen voor optimale prestaties van uw verwarmingssysteem. Het verstrekken van de juiste Temperatuurinstelling zorgt voor nauwkeurige regulatie en helpt een comfortabele en energie-efficiënte omgeving in uw huis te bereiken.",
- "data": {
- "name": "Naam",
- "device": "Entiteit",
- "minimum_setpoint": "Temperatuurinstelling"
- }
- },
- "simulator": {
- "title": "Gesimuleerde Gateway ( GEAVANCEERD )",
- "description": "Deze gateway stelt u in staat om een ketel te simuleren voor test- en demonstratiedoeleinden. Gelieve de volgende informatie te verstrekken om de simulator te configureren.\n\nLet op: De Simulator Gateway is alleen bedoeld voor test- en demonstratiedoeleinden en mag niet worden gebruikt in productieomgevingen.",
- "data": {
- "name": "Naam",
- "minimum_setpoint": "Minimum Setpoint",
- "maximum_setpoint": "Maximum Setpoint",
- "simulated_heating": "Gesimuleerde Verwarming",
- "simulated_cooling": "Gesimuleerde Koeling",
- "simulated_warming_up": "Gesimuleerde Opwarming"
- }
- },
- "sensors": {
- "title": "Configureer sensoren",
- "description": "Selecteer de sensoren die gebruikt zullen worden om de temperatuur te volgen.",
- "data": {
- "inside_sensor_entity_id": "Binnensensor Entiteit",
- "outside_sensor_entity_id": "Buitensensor Entiteit",
- "humidity_sensor_entity_id": "Vochtigheidssensor Entiteit"
- }
- },
- "heating_system": {
- "title": "Verwarmingssysteem",
- "description": "Het kiezen van het juiste type verwarmingssysteem is belangrijk voor SAT om de temperatuur nauwkeurig te regelen en prestaties te optimaliseren. Kies de optie die overeenkomt met uw opstelling om een correcte temperatuurregeling in uw huis te verzekeren.",
- "data": {
- "heating_system": "Systeem"
- }
- },
"areas": {
- "title": "Gebieden",
- "description": "Instellingen met betrekking tot klimaten, multi-kamer en temperatuurregeling. Primaire klimaten bevinden zich in dezelfde kamer als de binnensensor en de kamers hebben hun eigen doeltemperaturen los van het systeem.",
"data": {
"main_climates": "Primair",
"secondary_climates": "Kamers"
- }
+ },
+ "description": "Instellingen met betrekking tot klimaten, multi-kamer en temperatuurregeling. Primaire klimaten bevinden zich in dezelfde kamer als de binnensensor en de kamers hebben hun eigen doeltemperaturen los van het systeem.",
+ "title": "Gebieden"
},
"automatic_gains": {
- "title": "Automatische Versterkingen",
- "description": "Deze functie past de regelparameters van uw verwarmingssysteem dynamisch aan, waardoor de temperatuurregeling wordt geoptimaliseerd voor meer comfort en energie-efficiëntie. Het inschakelen van deze optie stelt SAT in staat om de verwarmingsinstellingen voortdurend aan te passen en te verfijnen op basis van de omgevingsomstandigheden. Dit helpt een stabiele en comfortabele omgeving te handhaven zonder handmatige tussenkomst.\n\nLet op: Als u ervoor kiest om automatische versterkingen niet in te schakelen, moet u handmatig de PID-waarden invoeren voor nauwkeurige temperatuurregeling. Zorg ervoor dat u nauwkeurige PID-waarden heeft voor uw specifieke verwarmingssysteem om optimale prestaties te bereiken.",
"data": {
"automatic_gains": "Automatische Versterkingen (aanbevolen)"
- }
+ },
+ "description": "Deze functie past de regelparameters van uw verwarmingssysteem dynamisch aan, waardoor de temperatuurregeling wordt geoptimaliseerd voor meer comfort en energie-efficiëntie. Het inschakelen van deze optie stelt SAT in staat om de verwarmingsinstellingen voortdurend aan te passen en te verfijnen op basis van de omgevingsomstandigheden. Dit helpt een stabiele en comfortabele omgeving te handhaven zonder handmatige tussenkomst.\n\nLet op: Als u ervoor kiest om automatische versterkingen niet in te schakelen, moet u handmatig de PID-waarden invoeren voor nauwkeurige temperatuurregeling. Zorg ervoor dat u nauwkeurige PID-waarden heeft voor uw specifieke verwarmingssysteem om optimale prestaties te bereiken.",
+ "title": "Automatische Versterkingen"
},
"calibrate_system": {
- "title": "Systeem Kalibreren",
"description": "Optimaliseer uw verwarmingssysteem door automatisch de optimale PID-waarden voor uw opstelling te bepalen. Let op dat het systeem bij het selecteren van Automatische Versterkingen een kalibratieproces zal ondergaan dat ongeveer 20 minuten kan duren.\n\nAutomatische Versterkingen worden aanbevolen voor de meeste gebruikers omdat het het instelproces vereenvoudigt en optimale prestaties verzekert. Echter, als u bekend bent met PID-regeling en de voorkeur geeft aan het handmatig instellen van de waarden, kunt u ervoor kiezen om Automatische Versterkingen over te slaan.\n\nLet op dat het kiezen om Automatische Versterkingen over te slaan een goede kennis van PID-regeling vereist en mogelijk extra handmatige aanpassingen nodig heeft om optimale prestaties te bereiken.",
"menu_options": {
"calibrate": "Kalibreer en bepaal uw overshoot-beveiligingswaarde (ong. 20 min).",
"overshoot_protection": "Voer handmatig de overshoot-beveiligingswaarde in.",
"pid_controller": "Voer handmatig PID-waarden in (niet aanbevolen)."
- }
+ },
+ "title": "Systeem Kalibreren"
+ },
+ "calibrated": {
+ "description": "Het kalibratieproces is succesvol voltooid.\n\nGefeliciteerd! Uw Slimme Autotune Thermostaat (SAT) is gekalibreerd om de verwarmingsprestaties van uw systeem te optimaliseren. Tijdens het kalibratieproces heeft SAT zorgvuldig de verwarmingskenmerken geanalyseerd en de geschikte overshoot-beveiligingswaarde bepaald om nauwkeurige temperatuurregeling te verzekeren.\n\nOvershoot-beveiligingswaarde: {minimum_setpoint} °C\n\nDeze waarde vertegenwoordigt de maximale hoeveelheid overshoot die is toegestaan tijdens het verwarmingsproces. SAT zal actief de verwarming monitoren en aanpassen om overmatig overshoot te voorkomen, en zo een comfortabele en efficiënte verwarmingservaring in uw huis te handhaven.\n\nLet op dat de overshoot-beveiligingswaarde kan variëren afhankelijk van de specifieke kenmerken van uw verwarmingssysteem en omgevingsfactoren. Het is fijn afgestemd om optimale prestaties te bieden op basis van de kalibratieresultaten.",
+ "menu_options": {
+ "calibrate": "Kalibratie opnieuw proberen",
+ "finish": "Doorgaan met huidige kalibratie"
+ },
+ "title": "Kalibratie Voltooid"
+ },
+ "heating_system": {
+ "data": {
+ "heating_system": "Systeem"
+ },
+ "description": "Het kiezen van het juiste type verwarmingssysteem is belangrijk voor SAT om de temperatuur nauwkeurig te regelen en prestaties te optimaliseren. Kies de optie die overeenkomt met uw opstelling om een correcte temperatuurregeling in uw huis te verzekeren.",
+ "title": "Verwarmingssysteem"
+ },
+ "mosquitto": {
+ "description": "Configureer de MQTT-modus voor uw verwarmingssysteem. Kies de gateway-modus en geef de naam op.",
+ "data": {
+ "mode": "Modus"
+ },
+ "title": "MQTT-configuratie"
+ },
+ "mosquitto_opentherm": {
+ "description": "Stel de OpenTherm Gateway in. Geef het belangrijkste MQTT-onderwerp en het apparaat-ID op.",
+ "data": {
+ "name": "Naam",
+ "mqtt_topic": "MQTT-onderwerp",
+ "device": "Apparaat-ID"
+ },
+ "title": "OpenTherm Gateway Configuratie"
+ },
+ "mosquitto_ems": {
+ "description": "Stel de EMS-ESP Gateway in. Geef het MQTT-onderwerp op.",
+ "data": {
+ "name": "Naam",
+ "mqtt_topic": "MQTT-onderwerp"
+ },
+ "title": "EMS-ESP Gateway Configuratie"
},
"overshoot_protection": {
- "title": "Overshoot-beveiliging",
- "description": "Door het verstrekken van de overshoot-beveiligingswaarde zal SAT de regelparameters dienovereenkomstig aanpassen om een stabiele en comfortabele verwarmingsomgeving te behouden. Deze handmatige configuratie stelt u in staat het systeem fijn af te stemmen op basis van uw specifieke opstelling.\n\nLet op: Als u niet zeker bent over de overshoot-beveiligingswaarde of het kalibratieproces niet heeft uitgevoerd, wordt aanbevolen de configuratie te annuleren en het kalibratieproces te doorlopen zodat SAT automatisch de waarde voor optimale prestaties kan bepalen.",
"data": {
"minimum_setpoint": "Waarde"
- }
+ },
+ "description": "Door het verstrekken van de overshoot-beveiligingswaarde zal SAT de regelparameters dienovereenkomstig aanpassen om een stabiele en comfortabele verwarmingsomgeving te behouden. Deze handmatige configuratie stelt u in staat het systeem fijn af te stemmen op basis van uw specifieke opstelling.\n\nLet op: Als u niet zeker bent over de overshoot-beveiligingswaarde of het kalibratieproces niet heeft uitgevoerd, wordt aanbevolen de configuratie te annuleren en het kalibratieproces te doorlopen zodat SAT automatisch de waarde voor optimale prestaties kan bepalen.",
+ "title": "Overshoot-beveiliging"
},
"pid_controller": {
- "title": "Configureer de PID-regelaar handmatig.",
- "description": "Configureer de proportionele, integrale en afgeleide versterkingen handmatig om uw verwarmingssysteem fijn te stemmen. Gebruik deze optie als u volledige controle wilt hebben over de PID-regelaarparameters. Stel de versterkingen in op basis van de specifieke kenmerken van uw verwarmingssysteem en voorkeuren.",
"data": {
- "integral": "Integraal (kI)",
"derivative": "Afgeleid (kD)",
+ "integral": "Integraal (kI)",
"proportional": "Proportioneel (kP)"
- }
+ },
+ "description": "Configureer de proportionele, integrale en afgeleide versterkingen handmatig om uw verwarmingssysteem fijn te stemmen. Gebruik deze optie als u volledige controle wilt hebben over de PID-regelaarparameters. Stel de versterkingen in op basis van de specifieke kenmerken van uw verwarmingssysteem en voorkeuren.",
+ "title": "Configureer de PID-regelaar handmatig."
},
- "calibrated": {
- "title": "Kalibratie Voltooid",
- "description": "Het kalibratieproces is succesvol voltooid.\n\nGefeliciteerd! Uw Slimme Autotune Thermostaat (SAT) is gekalibreerd om de verwarmingsprestaties van uw systeem te optimaliseren. Tijdens het kalibratieproces heeft SAT zorgvuldig de verwarmingskenmerken geanalyseerd en de geschikte overshoot-beveiligingswaarde bepaald om nauwkeurige temperatuurregeling te verzekeren.\n\nOvershoot-beveiligingswaarde: {minimum_setpoint} °C\n\nDeze waarde vertegenwoordigt de maximale hoeveelheid overshoot die is toegestaan tijdens het verwarmingsproces. SAT zal actief de verwarming monitoren en aanpassen om overmatig overshoot te voorkomen, en zo een comfortabele en efficiënte verwarmingservaring in uw huis te handhaven.\n\nLet op dat de overshoot-beveiligingswaarde kan variëren afhankelijk van de specifieke kenmerken van uw verwarmingssysteem en omgevingsfactoren. Het is fijn afgestemd om optimale prestaties te bieden op basis van de kalibratieresultaten.",
+ "sensors": {
+ "data": {
+ "humidity_sensor_entity_id": "Vochtigheidssensor Entiteit",
+ "inside_sensor_entity_id": "Binnensensor Entiteit",
+ "outside_sensor_entity_id": "Buitensensor Entiteit"
+ },
+ "description": "Selecteer de sensoren die gebruikt zullen worden om de temperatuur te volgen.",
+ "title": "Configureer sensoren"
+ },
+ "serial": {
+ "data": {
+ "device": "URL",
+ "name": "Naam"
+ },
+ "description": "Om een verbinding met de OpenTherm Gateway te maken via een socketverbinding, gelieve de volgende details te verstrekken. Voer in het veld Naam een naam in voor de gateway die u helpt deze te identificeren binnen uw systeem.\n\nSpecificeer het netwerkadres van de OpenTherm Gateway in het veld Apparaat. Dit kan in het formaat zijn van \"socket://otgw.local:25238\", waarbij \"otgw.local\" de hostnaam of het IP-adres van de gateway is en \"25238\" het poortnummer.\n\nDeze instellingen zijn essentieel voor het tot stand brengen van communicatie en integratie met uw OpenTherm Gateway via de socketverbinding. Zorg ervoor dat de verstrekte gegevens nauwkeurig zijn om een correcte werking te garanderen.",
+ "title": "OpenTherm Gateway ( SERIEEL )"
+ },
+ "simulator": {
+ "data": {
+ "maximum_setpoint": "Maximum Setpoint",
+ "minimum_setpoint": "Minimum Setpoint",
+ "name": "Naam",
+ "simulated_cooling": "Gesimuleerde Koeling",
+ "simulated_heating": "Gesimuleerde Verwarming",
+ "simulated_warming_up": "Gesimuleerde Opwarming"
+ },
+ "description": "Deze gateway stelt u in staat om een ketel te simuleren voor test- en demonstratiedoeleinden. Gelieve de volgende informatie te verstrekken om de simulator te configureren.\n\nLet op: De Simulator Gateway is alleen bedoeld voor test- en demonstratiedoeleinden en mag niet worden gebruikt in productieomgevingen.",
+ "title": "Gesimuleerde Gateway ( GEAVANCEERD )"
+ },
+ "switch": {
+ "data": {
+ "device": "Entiteit",
+ "minimum_setpoint": "Temperatuurinstelling",
+ "name": "Naam"
+ },
+ "description": "Gelieve de volgende details in te vullen om de schakelaar in te stellen. Voer in het veld Naam een naam in voor de schakelaar, die u helpt deze te identificeren binnen uw systeem. Kies de geschikte entiteit om voor uw schakelaar te gebruiken uit de aangeboden opties.\n\nIn het veld Temperatuurinstelling specificeert u de gewenste doeltemperatuur voor uw verwarmingssysteem. Als u een warmwaterboiler gebruikt, vul dan de Boiler Temperatuurinstelling in met de geschikte waarde. Voor elektrische verwarmingssystemen, voer de waarde 100 in.\n\nDeze instellingen zijn essentieel voor precieze temperatuurregeling en zorgen voor optimale prestaties van uw verwarmingssysteem. Het verstrekken van de juiste Temperatuurinstelling zorgt voor nauwkeurige regulatie en helpt een comfortabele en energie-efficiënte omgeving in uw huis te bereiken.",
+ "title": "PID Thermostaat met PWM ( AAN/UIT )"
+ },
+ "user": {
+ "description": "SAT is een slimme thermostaat die zichzelf kan optimaliseren om de temperatuurregeling te verbeteren. Kies de modus die past bij uw verwarmingssysteem.",
"menu_options": {
- "calibrate": "Kalibratie opnieuw proberen",
- "finish": "Doorgaan met huidige kalibratie"
- }
+ "mosquitto": "MQTT Gateway (OpenTherm, EMS-ESP, andere)",
+ "serial": "Seriële Gateway (bijv. OpenTherm)",
+ "esphome": "ESPHome (Systeemservices en sensoren)",
+ "simulator": "Gesimuleerde Gateway (Alleen voor ontwikkelaars)",
+ "switch": "PID-Thermostaat (PWM Aan/Uit-modus)"
+ },
+ "title": "Smart Autotune Thermostat (SAT)"
}
- },
- "error": {
- "connection": "Kan geen verbinding maken met de gateway.",
- "mqtt_component": "Het MQTT-component is niet beschikbaar.",
- "unable_to_calibrate": "Het kalibratieproces is op een probleem gestuit en kon niet succesvol worden voltooid. Zorg ervoor dat uw verwarmingssysteem correct functioneert en dat alle vereiste sensoren zijn aangesloten en correct werken.\n\nAls u problemen blijft ondervinden met de kalibratie, overweeg dan om contact met ons op te nemen voor verdere hulp. Onze excuses voor het ongemak."
- },
- "abort": {
- "already_configured": "Gateway is al geconfigureerd."
- },
- "progress": {
- "calibration": "Kalibreren en de waarde van de overshootbeveiliging vinden...\n\nEven geduld a.u.b. terwijl wij uw verwarmingssysteem optimaliseren. Dit proces kan ongeveer 20 minuten duren."
}
},
"options": {
"step": {
- "init": {
- "menu_options": {
- "general": "Algemeen",
- "presets": "Voorinstellingen",
- "advanced": "Geavanceerde Opties",
- "system_configuration": "Systeemconfiguratie"
- }
+ "advanced": {
+ "data": {
+ "error_monitoring": "Foutbewaking inschakelen",
+ "climate_valve_offset": "Offset van Klimaatklep",
+ "dynamic_minimum_setpoint": "Dynamisch Minimaal Setpoint (Experimenteel)",
+ "force_pulse_width_modulation": "Dwing Pulsbreedtemodulatie af",
+ "maximum_consumption": "Maximaal Verbruik",
+ "maximum_relative_modulation": "Maximale Relatieve Modulatie",
+ "minimum_consumption": "Minimaal Verbruik",
+ "sample_time": "Sampletijd",
+ "simulation": "Simulatie",
+ "target_temperature_step": "Stap van Doeltemperatuur",
+ "thermal_comfort": "Thermisch Comfort"
+ },
+ "data_description": {
+ "climate_valve_offset": "Offset om de openingsgraad van de klimaatklep aan te passen.",
+ "dynamic_minimum_setpoint": "Activeert de dynamische aanpassing van de minimale instelwaarde op basis van de retourtemperatuur van de ketel, wat ook helpt te identificeren of er kleppen gesloten zijn.",
+ "maximum_consumption": "Het maximale gasverbruik wanneer de ketel actief is.",
+ "maximum_relative_modulation": "Vertegenwoordigt het hoogste modulatieniveau voor een efficiënt verwarmingssysteem.",
+ "minimum_consumption": "Het minimale gasverbruik wanneer de ketel actief is.",
+ "sample_time": "Het minimale tijdsinterval tussen updates aan de PID-regelaar.",
+ "target_temperature_step": "De stap van de doeltemperatuur aanpassen voor fijnafstemming van comfortniveaus.",
+ "thermal_comfort": "Gebruik van de Simmer Index voor aanpassing van thermisch comfort inschakelen."
+ },
+ "title": "Geavanceerd"
},
"general": {
- "title": "Algemeen",
- "description": "Algemene instellingen en configuraties.",
"data": {
- "integral": "Integraal (kI)",
- "derivative": "Afgeleide (kD)",
- "proportional": "Proportioneel (kP)",
- "maximum_setpoint": "Maximaal Setpoint",
- "window_sensors": "Contact Sensoren",
"automatic_gains_value": "Automatische Versterkingswaarde",
+ "derivative": "Afgeleide (kD)",
"derivative_time_weight": "Tijdgewicht van de Afgeleide",
- "heating_curve_version": "Versie van de Verwarmingscurve",
- "heating_curve_coefficient": "Coëfficiënt van de Verwarmingscurve",
"duty_cycle": "Maximale Inschakelduur voor Pulsbreedtemodulatie",
- "sync_with_thermostat": "Synchroniseer setpoint met thermostaat"
+ "heating_curve_coefficient": "Coëfficiënt van de Verwarmingscurve",
+ "heating_curve_version": "Versie van de Verwarmingscurve",
+ "integral": "Integraal (kI)",
+ "maximum_setpoint": "Maximaal Setpoint",
+ "minimum_setpoint_adjustment_factor": "Aanpassingsfactor voor de minimale instelwaarde",
+ "pid_controller_version": "Versie van de PID-regelaar",
+ "proportional": "Proportioneel (kP)",
+ "sync_with_thermostat": "Synchroniseer setpoint met thermostaat",
+ "window_sensors": "Contact Sensoren",
+ "heating_mode": "Verwarmingsmodus"
},
"data_description": {
- "integral": "De integraalterm (kI) in de PID-regelaar, verantwoordelijk voor het verminderen van de blijvende fout.",
- "derivative": "De afgeleideterm (kD) in de PID-regelaar, verantwoordelijk voor het verminderen van overshoot.",
- "proportional": "De proportionele term (kP) in de PID-regelaar, verantwoordelijk voor de directe reactie op fouten.",
- "maximum_setpoint": "De optimale temperatuur voor efficiënte werking van de ketel.",
- "window_sensors": "Contact Sensoren die het systeem activeren wanneer een raam of deur voor een periode geopend is.",
"automatic_gains_value": "De waarde die wordt gebruikt voor automatische versterkingen in de PID-regelaar.",
- "heating_curve_coefficient": "De coëfficiënt die wordt gebruikt om de verwarmingscurve aan te passen.",
+ "derivative": "De afgeleideterm (kD) in de PID-regelaar, verantwoordelijk voor het verminderen van overshoot.",
+ "derivative_time_weight": "Een parameter om de invloed van de afgeleideterm over tijd aan te passen, vooral nuttig om undershoot te verminderen tijdens de opwarmfase wanneer de coëfficiënt van de verwarmingscurve correct is ingesteld.",
"duty_cycle": "De maximale inschakelduur voor Pulsbreedtemodulatie (PWM), die de aan/uit-cycli van de ketel regelt.",
+ "heating_curve_coefficient": "De coëfficiënt die wordt gebruikt om de verwarmingscurve aan te passen.",
+ "integral": "De integraalterm (kI) in de PID-regelaar, verantwoordelijk voor het verminderen van de blijvende fout.",
+ "maximum_setpoint": "De optimale temperatuur voor efficiënte werking van de ketel.",
+ "minimum_setpoint_adjustment_factor": "Deze factor past de instelwaarde voor verwarming aan op basis van de retourtemperatuur van de ketel, wat de responsiviteit en efficiëntie van de verwarming beïnvloedt. Een hogere waarde verhoogt de gevoeligheid voor temperatuurveranderingen, waardoor de controle over comfort en energieverbruik wordt verbeterd. Het aanbevolen startbereik is 0,1 tot 0,5. Aanpassen om aan uw systeem en comfortvoorkeuren te voldoen.",
+ "proportional": "De proportionele term (kP) in de PID-regelaar, verantwoordelijk voor de directe reactie op fouten.",
"sync_with_thermostat": "Synchroniseer het setpoint met de thermostaat om een gecoördineerde temperatuurregeling te waarborgen.",
- "derivative_time_weight": "Een parameter om de invloed van de afgeleideterm over tijd aan te passen, vooral nuttig om undershoot te verminderen tijdens de opwarmfase wanneer de coëfficiënt van de verwarmingscurve correct is ingesteld."
+ "window_sensors": "Contact Sensoren die het systeem activeren wanneer een raam of deur voor een periode geopend is."
+ },
+ "description": "Algemene instellingen en configuraties.",
+ "title": "Algemeen"
+ },
+ "init": {
+ "menu_options": {
+ "advanced": "Geavanceerde Opties",
+ "general": "Algemeen",
+ "presets": "Voorinstellingen",
+ "system_configuration": "Systeemconfiguratie"
}
},
"presets": {
- "title": "Voorinstellingen",
- "description": "Vooraf gedefinieerde temperatuurinstellingen voor verschillende scenario's of activiteiten.",
"data": {
+ "activity_temperature": "Activiteit Temperatuur",
"away_temperature": "Temperatuur bij Afwezigheid",
+ "comfort_temperature": "Comfort Temperatuur",
"home_temperature": "Thuis Temperatuur",
"sleep_temperature": "Slaap Temperatuur",
- "comfort_temperature": "Comfort Temperatuur",
- "activity_temperature": "Activiteit Temperatuur",
"sync_climates_with_preset": "Synchroniseer klimaten met voorinstelling (slaap / afwezig / activiteit)"
- }
+ },
+ "description": "Vooraf gedefinieerde temperatuurinstellingen voor verschillende scenario's of activiteiten.",
+ "title": "Voorinstellingen"
},
"system_configuration": {
- "title": "Systeemconfiguratie",
- "description": "Voor fijnafstelling en aanpassing.",
"data": {
"automatic_duty_cycle": "Automatische inschakelduur",
"overshoot_protection": "Overshoot Bescherming (met PWM)",
- "window_minimum_open_time": "Minimale open tijd van het raam",
- "sensor_max_value_age": "Maximale leeftijd van de waarden van de temperatuursensor"
+ "sync_climates_with_mode": "Synchroniseer klimaten met modus",
+ "sensor_max_value_age": "Maximale leeftijd van de waarden van de temperatuursensor",
+ "window_minimum_open_time": "Minimale open tijd van het raam"
},
"data_description": {
"automatic_duty_cycle": "Automatische inschakelduur voor Pulsbreedtemodulatie (PWM) in- of uitschakelen.",
"overshoot_protection": "Overshoot Bescherming inschakelen met Pulsbreedtemodulatie (PWM) om temperatuur-overshoot van de ketel te voorkomen.",
- "window_minimum_open_time": "De minimale tijd dat een raam open moet zijn voordat het systeem reageert.",
- "sensor_max_value_age": "De maximale leeftijd van de waarden van de temperatuursensor voordat het als verouderd wordt beschouwd."
- }
- },
- "advanced": {
- "title": "Geavanceerd",
- "data": {
- "simulation": "Simulatie",
- "sample_time": "Sampletijd",
- "thermal_comfort": "Thermisch Comfort",
- "minimum_consumption": "Minimaal Verbruik",
- "maximum_consumption": "Maximaal Verbruik",
- "climate_valve_offset": "Offset van Klimaatklep",
- "target_temperature_step": "Stap van Doeltemperatuur",
- "maximum_relative_modulation": "Maximale Relatieve Modulatie",
- "force_pulse_width_modulation": "Dwing Pulsbreedtemodulatie af",
- "dynamic_minimum_setpoint": "Dynamisch Minimaal Setpoint (Experimenteel)"
+ "sensor_max_value_age": "De maximale leeftijd van de waarden van de temperatuursensor voordat het als verouderd wordt beschouwd.",
+ "window_minimum_open_time": "De minimale tijd dat een raam open moet zijn voordat het systeem reageert."
},
- "data_description": {
- "thermal_comfort": "Gebruik van de Simmer Index voor aanpassing van thermisch comfort inschakelen.",
- "minimum_consumption": "Het minimale gasverbruik wanneer de ketel actief is.",
- "maximum_consumption": "Het maximale gasverbruik wanneer de ketel actief is.",
- "climate_valve_offset": "Offset om de openingsgraad van de klimaatklep aan te passen.",
- "target_temperature_step": "De stap van de doeltemperatuur aanpassen voor fijnafstemming van comfortniveaus.",
- "sample_time": "Het minimale tijdsinterval tussen updates aan de PID-regelaar.",
- "maximum_relative_modulation": "Vertegenwoordigt het hoogste modulatieniveau voor een efficiënt verwarmingssysteem."
- }
+ "description": "Voor fijnafstelling en aanpassing.",
+ "title": "Systeemconfiguratie"
}
}
}
diff --git a/custom_components/sat/translations/pt.json b/custom_components/sat/translations/pt.json
new file mode 100644
index 00000000..8d969913
--- /dev/null
+++ b/custom_components/sat/translations/pt.json
@@ -0,0 +1,246 @@
+{
+ "config": {
+ "abort": {
+ "already_configured": "O Gateway já está configurado.",
+ "reconfigure_successful": "O Gateway foi reconfigurado com sucesso."
+ },
+ "error": {
+ "connection": "Não foi possível ligar ao gateway.",
+ "mqtt_component": "O componente MQTT não está disponível.",
+ "unable_to_calibrate": "O processo de calibração encontrou um problema e não pôde ser concluído com sucesso. Por favor, assegure-se de que o seu sistema de aquecimento está a funcionar corretamente e que todos os sensores necessários estão ligados e a funcionar corretamente.\n\nSe continuar a ter problemas com a calibração, considere contactar-nos para obter mais assistência. Pedimos desculpa por qualquer inconveniente causado."
+ },
+ "progress": {
+ "calibration": "A calibrar e a encontrar o valor de proteção contra overshoot...\n\nPor favor, aguarde enquanto otimizamos o seu sistema de aquecimento. Este processo pode demorar aproximadamente 20 minutos."
+ },
+ "step": {
+ "areas": {
+ "data": {
+ "main_climates": "Principal",
+ "secondary_climates": "Quartos"
+ },
+ "description": "Definições relacionadas com climas, multi-sala e controlo de temperatura. Climas principais estão na mesma sala que o sensor interior e os quartos têm as suas próprias temperaturas alvo separadas do sistema.",
+ "title": "Áreas"
+ },
+ "automatic_gains": {
+ "data": {
+ "automatic_gains": "Ganhos Automáticos (recomendado)"
+ },
+ "description": "Esta funcionalidade ajusta os parâmetros de controlo do seu sistema de aquecimento dinamicamente, otimizando o controlo de temperatura para melhor conforto e eficiência energética. Ao ativar esta opção, o SAT adapta-se continuamente e afina as definições de aquecimento com base nas condições ambientais. Isto ajuda a manter um ambiente estável e confortável sem intervenção manual.\n\nNota: Se optar por não ativar os ganhos automáticos, terá de introduzir manualmente os valores PID para controlo preciso da temperatura. Por favor, certifique-se de que tem valores PID precisos para o seu sistema de aquecimento específico para obter desempenho ideal.",
+ "title": "Ganhos Automáticos"
+ },
+ "calibrate_system": {
+ "description": "Otimize o seu sistema de aquecimento determinando automaticamente os valores PID ótimos para a sua configuração. Ao selecionar Ganhos Automáticos, por favor note que o sistema passará por um processo de calibração que pode demorar aproximadamente 20 minutos a completar.\n\nGanhos Automáticos é recomendado para a maioria dos utilizadores, pois simplifica o processo de configuração e assegura desempenho ideal. No entanto, se estiver familiarizado com controlo PID e preferir definir os valores manualmente, pode optar por ignorar os Ganhos Automáticos.\n\nPor favor note que escolher ignorar os Ganhos Automáticos requer um bom entendimento de controlo PID e pode requerer ajustes manuais adicionais para alcançar desempenho ótimo.",
+ "menu_options": {
+ "calibrate": "Calibrar e determinar o valor de proteção contra overshoot (aprox. 20 min).",
+ "overshoot_protection": "Introduzir manualmente o valor de proteção contra overshoot.",
+ "pid_controller": "Introduzir manualmente os valores PID (não recomendado)."
+ },
+ "title": "Calibrar Sistema"
+ },
+ "calibrated": {
+ "description": "O processo de calibração foi concluído com sucesso.\n\nParabéns! O seu Termóstato Smart Autotune (SAT) foi calibrado para otimizar o desempenho de aquecimento do seu sistema. Durante o processo de calibração, o SAT analisou cuidadosamente as características de aquecimento e determinou o valor apropriado de proteção contra overshoot para assegurar controlo de temperatura preciso.\n\nValor de Proteção contra Overshoot: {minimum_setpoint} °C\n\nEste valor representa a quantidade máxima de overshoot permitida durante o processo de aquecimento. O SAT irá monitorizar ativamente e ajustar o aquecimento para prevenir overshoot excessivo, mantendo uma experiência de aquecimento confortável e eficiente na sua casa.\n\nPor favor note que o valor de proteção contra overshoot pode variar dependendo das características específicas do seu sistema de aquecimento e fatores ambientais. Foi afinado para fornecer desempenho ótimo com base nos resultados da calibração.",
+ "menu_options": {
+ "calibrate": "Repetir calibração",
+ "finish": "Continuar com a calibração atual"
+ },
+ "title": "Calibração Concluída"
+ },
+ "heating_system": {
+ "data": {
+ "heating_system": "Sistema"
+ },
+ "description": "Selecionar o tipo correto de sistema de aquecimento é importante para que o SAT controle com precisão a temperatura e otimize o desempenho. Escolha a opção que corresponde à sua configuração para assegurar regulação adequada da temperatura em toda a sua casa.",
+ "title": "Sistema de Aquecimento"
+ },
+ "mosquitto": {
+ "description": "Configure o modo MQTT para o seu sistema de aquecimento. Escolha o modo de gateway e forneça seu nome.",
+ "data": {
+ "mode": "Modo"
+ },
+ "title": "Configuração MQTT"
+ },
+ "mosquitto_opentherm": {
+ "description": "Configure o Gateway OpenTherm. Forneça o tópico MQTT principal e o identificador do dispositivo.",
+ "data": {
+ "name": "Nome",
+ "mqtt_topic": "Tópico MQTT",
+ "device": "ID do dispositivo"
+ },
+ "title": "Configuração do Gateway OpenTherm"
+ },
+ "mosquitto_ems": {
+ "description": "Configure o Gateway EMS-ESP. Forneça o tópico MQTT.",
+ "data": {
+ "name": "Nome",
+ "mqtt_topic": "Tópico MQTT"
+ },
+ "title": "Configuração do Gateway EMS-ESP"
+ },
+ "overshoot_protection": {
+ "data": {
+ "minimum_setpoint": "Valor"
+ },
+ "description": "Ao fornecer o valor de proteção contra overshoot, o SAT ajustará os parâmetros de controlo em conformidade para manter um ambiente de aquecimento confortável e estável. Esta configuração manual permite-lhe afinar o sistema com base na sua configuração específica.\n\nNota: Se não tiver certeza sobre o valor de proteção contra overshoot ou não tiver realizado o processo de calibração, é recomendado cancelar a configuração e passar pelo processo de calibração para permitir que o SAT determine automaticamente o valor para desempenho ótimo.",
+ "title": "Proteção contra Overshoot"
+ },
+ "pid_controller": {
+ "data": {
+ "derivative": "Derivativo (kD)",
+ "integral": "Integral (kI)",
+ "proportional": "Proporcional (kP)"
+ },
+ "description": "Configure os ganhos proporcional, integral e derivativo manualmente para afinar o seu sistema de aquecimento. Use esta opção se preferir ter controlo total sobre os parâmetros do controlador PID. Ajuste os ganhos com base nas características e preferências específicas do seu sistema de aquecimento.",
+ "title": "Configurar o controlador PID manualmente."
+ },
+ "sensors": {
+ "data": {
+ "humidity_sensor_entity_id": "Entidade do Sensor de Humidade",
+ "inside_sensor_entity_id": "Entidade do Sensor Interior",
+ "outside_sensor_entity_id": "Entidade do Sensor Exterior"
+ },
+ "description": "Por favor selecione os sensores que serão usados para monitorizar a temperatura.",
+ "title": "Configurar sensores"
+ },
+ "serial": {
+ "data": {
+ "device": "URL",
+ "name": "Nome"
+ },
+ "description": "Para estabelecer uma ligação com o OpenTherm Gateway usando uma ligação socket, por favor forneça os seguintes detalhes. No campo Nome, insira um nome para o gateway que o ajudará a identificá-lo dentro do seu sistema.\n\nEspecifique o endereço de rede do OpenTherm Gateway no campo Dispositivo. Isto pode ser no formato \"socket://otgw.local:25238\", onde \"otgw.local\" é o nome de host ou endereço IP do gateway e \"25238\" é o número da porta.\n\nEstas definições são essenciais para estabelecer comunicação e integração com o seu OpenTherm Gateway através da ligação socket. Assegure-se de que os detalhes fornecidos são precisos para garantir funcionalidade adequada.",
+ "title": "OpenTherm Gateway ( SERIAL )"
+ },
+ "simulator": {
+ "data": {
+ "maximum_setpoint": "Setpoint Máximo",
+ "minimum_setpoint": "Setpoint Mínimo",
+ "name": "Nome",
+ "simulated_cooling": "Arrefecimento Simulado",
+ "simulated_heating": "Aquecimento Simulado",
+ "simulated_warming_up": "Aquecimento Inicial Simulado"
+ },
+ "description": "Este gateway permite-lhe simular uma caldeira para fins de teste e demonstração. Por favor forneça as seguintes informações para configurar o simulador.\n\nNota: O Simulador Gateway destina-se apenas a fins de teste e demonstração e não deve ser usado em ambientes de produção.",
+ "title": "Gateway Simulado ( AVANÇADO )"
+ },
+ "switch": {
+ "data": {
+ "device": "Entidade",
+ "minimum_setpoint": "Definição de Temperatura",
+ "name": "Nome"
+ },
+ "description": "Por favor preencha os seguintes detalhes para configurar o interruptor. Insira um nome para o interruptor no campo Nome, que o ajudará a identificá-lo dentro do seu sistema. Escolha a entidade apropriada a utilizar para o seu interruptor das opções fornecidas.\n\nNo campo Definição de Temperatura, especifique a temperatura alvo desejada para o seu sistema de aquecimento. Se estiver a usar uma caldeira de água quente, preencha a Definição de Temperatura da Caldeira com o valor apropriado. Para sistemas de aquecimento elétrico, insira o valor 100.\n\nEstas definições são essenciais para controlo preciso da temperatura e assegurar desempenho ideal do seu sistema de aquecimento. Fornecer a Definição de Temperatura correta permite regulação precisa e ajuda a alcançar um ambiente confortável e energeticamente eficiente na sua casa.",
+ "title": "Termóstato PID com PWM ( LIGA/DESLIGA )"
+ },
+ "user": {
+ "description": "SAT é um termostato inteligente capaz de autoajustar-se para otimizar o controle de temperatura. Escolha o modo que corresponde ao seu sistema de aquecimento.",
+ "menu_options": {
+ "mosquitto": "Gateway MQTT (OpenTherm, EMS-ESP, outros)",
+ "serial": "Gateway Serial (ex.: OpenTherm)",
+ "esphome": "ESPHome (Serviços e sensores do sistema)",
+ "simulator": "Gateway Simulado (Apenas para desenvolvedores)",
+ "switch": "Termostato PID (Modo PWM Ligado/Desligado)"
+ },
+ "title": "Smart Autotune Thermostat (SAT)"
+ }
+ }
+ },
+ "options": {
+ "step": {
+ "advanced": {
+ "data": {
+ "error_monitoring": "Ativar monitoramento de erros",
+ "climate_valve_offset": "Offset da válvula do clima",
+ "dynamic_minimum_setpoint": "Setpoint Mínimo Dinâmico (Experimental)",
+ "force_pulse_width_modulation": "Forçar Modulação por Largura de Pulso",
+ "maximum_consumption": "Consumo Máximo",
+ "maximum_relative_modulation": "Modulação Relativa Máxima",
+ "minimum_consumption": "Consumo Mínimo",
+ "sample_time": "Tempo de Amostragem",
+ "simulation": "Simulação",
+ "target_temperature_step": "Passo da Temperatura Alvo",
+ "thermal_comfort": "Conforto Térmico"
+ },
+ "data_description": {
+ "climate_valve_offset": "Offset para ajustar o grau de abertura da válvula do clima.",
+ "dynamic_minimum_setpoint": "Ativa o ajuste dinâmico do setpoint com base na temperatura de retorno da caldeira, o que também ajuda a identificar se alguma válvula está fechada.",
+ "maximum_consumption": "O consumo máximo de gás quando a caldeira está ativa.",
+ "maximum_relative_modulation": "Representa o nível máximo de modulação para um sistema de aquecimento eficiente.",
+ "minimum_consumption": "O consumo mínimo de gás quando a caldeira está ativa.",
+ "sample_time": "O intervalo de tempo mínimo entre atualizações do controlador PID.",
+ "target_temperature_step": "Ajuste o passo da temperatura alvo para afinação do nível de conforto.",
+ "thermal_comfort": "Ativar o uso do Índice de Simmer para ajuste do conforto térmico."
+ },
+ "title": "Avançado"
+ },
+ "general": {
+ "data": {
+ "automatic_gains_value": "Valor de Ganhos Automáticos",
+ "derivative": "Derivativo (kD)",
+ "derivative_time_weight": "Peso Temporal do Derivativo",
+ "duty_cycle": "Ciclo de Trabalho Máximo para Modulação por Largura de Pulso",
+ "heating_curve_coefficient": "Coeficiente da Curva de Aquecimento",
+ "heating_curve_version": "Versão da Curva de Aquecimento",
+ "integral": "Integral (kI)",
+ "maximum_setpoint": "Setpoint Máximo",
+ "minimum_setpoint_adjustment_factor": "Fator de Ajuste para Temperatura de Retorno",
+ "pid_controller_version": "Versão do Controlador PID",
+ "proportional": "Proporcional (kP)",
+ "sync_with_thermostat": "Sincronizar setpoint com termóstato",
+ "window_sensors": "Sensores de Contato",
+ "heating_mode": "Modo de Aquecimento"
+ },
+ "data_description": {
+ "automatic_gains_value": "O valor usado para ganhos automáticos no controlador PID.",
+ "derivative": "O termo derivativo (kD) no controlador PID, responsável por mitigar overshooting.",
+ "derivative_time_weight": "Um parâmetro para ajustar a influência do termo derivativo ao longo do tempo, particularmente útil para reduzir undershoot durante a fase de aquecimento quando o coeficiente da curva de aquecimento está corretamente definido.",
+ "duty_cycle": "O ciclo de trabalho máximo para Modulação por Largura de Pulso (PWM), controlando os ciclos de ligar/desligar da caldeira.",
+ "heating_curve_coefficient": "O coeficiente usado para ajustar a curva de aquecimento.",
+ "integral": "O termo integral (kI) no controlador PID, responsável por reduzir o erro em regime permanente.",
+ "maximum_setpoint": "A temperatura ótima para operação eficiente da caldeira.",
+ "minimum_setpoint_adjustment_factor": "Este fator ajusta o setpoint de aquecimento com base na temperatura de retorno da caldeira, afetando a resposta e eficiência do aquecimento. Um valor mais alto aumenta a sensibilidade às mudanças de temperatura, melhorando o controlo sobre o conforto e uso de energia. Faixa inicial recomendada é de 0.1 a 0.5. Ajuste para adequar ao seu sistema e preferências de conforto.",
+ "proportional": "O termo proporcional (kP) no controlador PID, responsável pela resposta imediata a erros.",
+ "sync_with_thermostat": "Sincronizar setpoint com termóstato para assegurar controlo de temperatura coordenado.",
+ "window_sensors": "Sensores de Contato que ativam o sistema para reagir quando uma janela ou porta está aberta por um período de tempo."
+ },
+ "description": "Definições e configurações gerais.",
+ "title": "Geral"
+ },
+ "init": {
+ "menu_options": {
+ "advanced": "Opções Avançadas",
+ "general": "Geral",
+ "presets": "Predefinições",
+ "system_configuration": "Configuração do Sistema"
+ }
+ },
+ "presets": {
+ "data": {
+ "activity_temperature": "Temperatura de Atividade",
+ "away_temperature": "Temperatura Ausente",
+ "comfort_temperature": "Temperatura de Conforto",
+ "home_temperature": "Temperatura em Casa",
+ "sleep_temperature": "Temperatura de Sono",
+ "sync_climates_with_preset": "Sincronizar climas com predefinição (sono / ausente / atividade)"
+ },
+ "description": "Definições de temperatura predefinidas para diferentes cenários ou atividades.",
+ "title": "Predefinições"
+ },
+ "system_configuration": {
+ "data": {
+ "automatic_duty_cycle": "Ciclo de trabalho automático",
+ "sync_climates_with_mode": "Sincronizar climas com o modo",
+ "overshoot_protection": "Proteção contra Overshoot (com PWM)",
+ "sensor_max_value_age": "Idade máxima do valor do sensor de temperatura",
+ "window_minimum_open_time": "Tempo mínimo para a janela estar aberta"
+ },
+ "data_description": {
+ "automatic_duty_cycle": "Ativar ou desativar ciclo de trabalho automático para Modulação por Largura de Pulso (PWM).",
+ "overshoot_protection": "Ativar proteção contra overshoot com Modulação por Largura de Pulso (PWM) para evitar overshooting da temperatura da caldeira.",
+ "sensor_max_value_age": "A idade máxima do valor do sensor de temperatura antes de ser considerado como estagnado.",
+ "window_minimum_open_time": "O tempo mínimo que uma janela deve estar aberta antes de o sistema reagir."
+ },
+ "description": "Para afinação e personalização.",
+ "title": "Configuração do Sistema"
+ }
+ }
+ }
+}
diff --git a/custom_components/sat/translations/sk.json b/custom_components/sat/translations/sk.json
new file mode 100644
index 00000000..4ede0c46
--- /dev/null
+++ b/custom_components/sat/translations/sk.json
@@ -0,0 +1,249 @@
+{
+ "config": {
+ "abort": {
+ "already_configured": "Gateway je už nakonfigurovaný.",
+ "reconfigure_successful": "Gateway bol prekonfigurovaný."
+ },
+ "error": {
+ "connection": "Nedá sa pripojiť k gateway.",
+ "mqtt_component": "MQTT komponent je nedostupný.",
+ "unable_to_calibrate": "V procese kalibrácie sa vyskytol problém a nebolo možné ho úspešne dokončiť. Uistite sa, že váš vykurovací systém funguje správne a že všetky požadované senzory sú pripojené a fungujú správne.\n\nAk budete mať aj naďalej problémy s kalibráciou, zvážte kontaktovanie nás so žiadosťou o ďalšiu pomoc. Ospravedlňujeme sa za spôsobené nepríjemnosti."
+ },
+ "progress": {
+ "calibration": "Kalibrácia a nájdenie hodnoty ochrany proti prekročeniu...\n\nPočkajte, prosím, kým optimalizujeme váš vykurovací systém. Tento proces môže trvať približne 20 minút."
+ },
+ "step": {
+ "areas": {
+ "data": {
+ "main_climates": "Primárne",
+ "secondary_climates": "Izby"
+ },
+ "description": "Nastavenia súvisiace s klímou, viacerými miestnosťami a reguláciou teploty. Primárna klíma je v rovnakej miestnosti ako vnútorný snímač a miestnosti majú svoje vlastné cieľové teploty oddelené od systému.",
+ "title": "Oblasti"
+ },
+ "automatic_gains": {
+ "data": {
+ "automatic_gains": "Automatické zisky (odporúčané)"
+ },
+ "description": "Táto funkcia dynamicky upravuje parametre regulácie vášho vykurovacieho systému, čím optimalizuje reguláciu teploty pre lepší komfort a energetickú účinnosť. Povolenie tejto možnosti umožňuje SAT nepretržite prispôsobovať a dolaďovať nastavenia vykurovania na základe podmienok prostredia. Pomáha to udržiavať stabilné a pohodlné prostredie bez manuálneho zásahu.\n\nPoznámka: Ak sa rozhodnete nepovoliť automatické zosilnenie, budete musieť manuálne zadať hodnoty PID pre presnú reguláciu teploty. Uistite sa, že máte presné hodnoty PID pre váš konkrétny vykurovací systém, aby ste dosiahli optimálny výkon.",
+ "title": "Automatické zisky"
+ },
+ "calibrate_system": {
+ "description": "Optimalizujte svoj vykurovací systém automatickým určením optimálnych hodnôt PID pre vaše nastavenie. Pri výbere automatického zisku majte na pamäti, že systém prejde procesom kalibrácie, ktorý môže trvať približne 20 minút.\n\nAutomatické zisky sa odporúčajú pre väčšinu používateľov, pretože zjednodušujú proces nastavenia a zaisťujú optimálny výkon. Ak však poznáte reguláciu PID a dávate prednosť manuálnemu nastaveniu hodnôt, môžete automatické zosilnenie preskočiť.\n\nUpozorňujeme, že ak sa rozhodnete preskočiť automatické zosilnenie, musíte dobre porozumieť regulácii PID a môže vyžadovať ďalšie manuálne úpravy na dosiahnutie optimálneho výkonu.",
+ "menu_options": {
+ "calibrate": "Kalibrujte a určte hodnotu ochrany proti prekročeniu (približne 20 min).",
+ "overshoot_protection": "Manuálne zadajte hodnotu ochrany proti prekročeniu.",
+ "pid_controller": "Ručne zadajte hodnoty PID (neodporúča sa)."
+ },
+ "title": "Kalibrovať systém"
+ },
+ "calibrated": {
+ "description": "Proces kalibrácie bol úspešne dokončený.\n\nBlahoželáme! Váš Smart Autotune Thermostat (SAT) bol kalibrovaný tak, aby optimalizoval vykurovací výkon vášho systému. Počas procesu kalibrácie SAT starostlivo analyzoval vykurovacie charakteristiky a určil vhodnú hodnotu ochrany proti prekročeniu, aby sa zabezpečila presná kontrola teploty.\n\nHodnota ochrany proti prekročeniu: {minimum_setpoint} °C\n\nTáto hodnota predstavuje maximálnu hodnotu prekročenia povolenú počas proces zahrievania. SAT bude aktívne monitorovať a upravovať vykurovanie, aby sa zabránilo nadmernému pretáčaniu, a udrží tak komfortné a efektívne vykurovanie vo vašej domácnosti.\n\nUpozorňujeme, že hodnota ochrany proti prekročeniu sa môže líšiť v závislosti od konkrétnych charakteristík vášho vykurovacieho systému a faktorov prostredia. Bol jemne vyladený tak, aby poskytoval optimálny výkon na základe výsledkov kalibrácie.",
+ "menu_options": {
+ "calibrate": "Zopakujte kalibráciu",
+ "finish": "Pokračujte v aktuálnej kalibrácii"
+ },
+ "title": "Kalibrácia dokončená"
+ },
+ "heating_system": {
+ "data": {
+ "heating_system": "Systém"
+ },
+ "description": "Výber správneho typu vykurovacieho systému je pre SAT dôležitý na presné riadenie teploty a optimalizáciu výkonu. Vyberte si možnosť, ktorá zodpovedá vášmu nastaveniu, aby ste zabezpečili správnu reguláciu teploty v celom dome.",
+ "title": "Vykurovací systém"
+ },
+ "mosquitto": {
+ "description": "Nakonfigurujte režim MQTT pre váš vykurovací systém. Vyberte režim brány a zadajte jej názov.",
+ "data": {
+ "mode": "Režim"
+ },
+ "title": "Konfigurácia MQTT"
+ },
+ "mosquitto_opentherm": {
+ "description": "Nakonfigurujte OpenTherm Gateway. Uveďte hlavný predmet MQTT a identifikátor zariadenia.",
+ "data": {
+ "name": "Názov",
+ "mqtt_topic": "Predmet MQTT",
+ "device": "ID zariadenia"
+ },
+ "title": "Konfigurácia OpenTherm Gateway"
+ },
+ "mosquitto_ems": {
+ "description": "Nakonfigurujte EMS-ESP Gateway. Uveďte predmet MQTT.",
+ "data": {
+ "name": "Názov",
+ "mqtt_topic": "Predmet MQTT"
+ },
+ "title": "Konfigurácia EMS-ESP Gateway"
+ },
+ "overshoot_protection": {
+ "data": {
+ "minimum_setpoint": "Hodnota"
+ },
+ "description": "Poskytnutím hodnoty ochrany proti prekročeniu SAT prispôsobí parametre ovládania tak, aby sa udržalo stabilné a pohodlné vykurovacie prostredie. Táto manuálna konfigurácia vám umožňuje doladiť systém na základe vášho špecifického nastavenia.\n\nPoznámka: Ak si nie ste istí hodnotou ochrany proti prekročeniu alebo ste nevykonali proces kalibrácie, odporúča sa zrušiť konfiguráciu a prejsť proces kalibrácie, aby SAT automaticky určil hodnotu pre optimálny výkon.",
+ "title": "Ochrana proti prestreleniu"
+ },
+ "pid_controller": {
+ "data": {
+ "derivative": "Derivačná (kD)",
+ "integral": "Integračná (kI)",
+ "proportional": "Proporcionálna (kP)"
+ },
+ "description": "Manuálne nakonfigurujte proporcionálne, integrálne a derivačné zisky, aby ste doladili svoj vykurovací systém. Túto možnosť použite, ak chcete mať plnú kontrolu nad parametrami PID regulátora. Upravte zisky na základe špecifických charakteristík a preferencií vykurovacieho systému.",
+ "title": "Nakonfigurujte PID regulátor manuálne."
+ },
+ "sensors": {
+ "data": {
+ "humidity_sensor_entity_id": "Entita snímača vlhkosti",
+ "inside_sensor_entity_id": "Vnútorná entita senzora",
+ "outside_sensor_entity_id": "Entita vonkajšieho senzora"
+ },
+ "description": "Vyberte senzory, ktoré sa budú používať na sledovanie teploty.",
+ "title": "Nakonfigurujte senzory"
+ },
+ "serial": {
+ "data": {
+ "device": "URL",
+ "name": "Názov"
+ },
+ "description": "Ak chcete vytvoriť spojenie s bránou OpenTherm pomocou zásuvky, uveďte nasledujúce podrobnosti. Do poľa Názov zadajte názov brány, ktorý vám pomôže identifikovať ju vo vašom systéme.\n\nV poli Zariadenie zadajte sieťovú adresu brány OpenTherm. Môže to byť vo formáte \"socket://otgw.local:25238\", kde \"otgw.local\" je názov hostiteľa alebo IP adresa brány a \"25238\" je číslo portu.\n\nTieto nastavenia sú nevyhnutné pre nadviazanie komunikácie a integráciu s vašou bránou OpenTherm cez pripojenie soketu. Uistite sa, že poskytnuté podrobnosti sú presné, aby sa zabezpečila správna funkčnosť.",
+ "title": "OpenTherm Gateway ( SERIAL )"
+ },
+ "simulator": {
+ "data": {
+ "maximum_setpoint": "Maximálna požadovaná hodnota",
+ "minimum_setpoint": "Minimálna požadovaná hodnota",
+ "name": "Názov",
+ "simulated_cooling": "Simulované chladenie",
+ "simulated_heating": "Simulovaný ohrev",
+ "simulated_warming_up": "Simulované zahrievanie"
+ },
+ "description": "Táto brána vám umožňuje simulovať kotol na testovacie a demonštračné účely. Na konfiguráciu simulátora uveďte nasledujúce informácie.\n\nPoznámka: Brána simulátora je určená len na testovacie a demonštračné účely a nemala by sa používať v produkčnom prostredí.",
+ "title": "Simulovaný gateway (POKROČILÉ)"
+ },
+ "switch": {
+ "data": {
+ "device": "Entita",
+ "minimum_setpoint": "Nastavenie teploty",
+ "name": "Názov"
+ },
+ "description": "Ak chcete nastaviť prepínač, vyplňte nasledujúce údaje. Do poľa Názov zadajte názov prepínača, ktorý vám pomôže identifikovať ho vo vašom systéme. Z poskytnutých možností vyberte vhodnú entitu pre váš prepínač.\n\nV poli Nastavenie teploty zadajte požadovanú cieľovú teplotu pre váš vykurovací systém. Ak používate teplovodný bojler, vyplňte nastavenie teploty kotla príslušnou hodnotou. Pre elektrické vykurovacie systémy zadajte hodnotu 100.\n\nTieto nastavenia sú nevyhnutné pre presnú reguláciu teploty a zabezpečenie optimálneho výkonu vášho vykurovacieho systému. Poskytnutie správneho nastavenia teploty umožňuje presnú reguláciu a pomáha dosiahnuť pohodlné a energeticky efektívne prostredie vo vašej domácnosti.",
+ "title": "PID termostat s PWM (ON/OFF)"
+ },
+ "user": {
+ "description": "SAT je inteligentný termostat schopný samonastavenia na optimalizáciu regulácie teploty. Vyberte režim, ktorý zodpovedá vášmu vykurovaciemu systému.",
+ "menu_options": {
+ "mosquitto": "MQTT Gateway (OpenTherm, EMS-ESP, iné)",
+ "serial": "Sériová brána (napr. OpenTherm)",
+ "esphome": "ESPHome (Systémové služby a senzory)",
+ "simulator": "Simulovaná brána (Len pre vývojárov)",
+ "switch": "PID Termostat (PWM režim Zap/Vyp)"
+ },
+ "title": "Smart Autotune Thermostat (SAT)"
+ }
+ }
+ },
+ "options": {
+ "step": {
+ "advanced": {
+ "data": {
+ "error_monitoring": "Povoliť monitorovanie chýb",
+ "climate_valve_offset": "Offset klimatizačného ventilu",
+ "dynamic_minimum_setpoint": "Dynamická minimálna nastavená hodnota (experimentálna)",
+ "force_pulse_width_modulation": "Vynútená modulácia šírky impulzu",
+ "maximum_consumption": "Maximálna spotreba",
+ "maximum_relative_modulation": "Maximálna relatívna modulácia",
+ "minimum_consumption": "Minimálna spotreba",
+ "sample_time": "Čas vzorky",
+ "simulation": "Simulácia",
+ "target_temperature_step": "Krok cieľovej teploty",
+ "thermal_comfort": "Tepelný komfort"
+ },
+ "data_description": {
+ "climate_valve_offset": "Offset na nastavenie stupňa otvorenia klimatizačného ventilu.",
+ "dynamic_minimum_setpoint": "Aktivuje dynamické nastavenie požadovanej hodnoty na základe teploty spiatočky kotla, čo tiež pomáha identifikovať, či sú nejaké ventily zatvorené.",
+ "maximum_consumption": "Maximálna spotreba plynu, keď je kotol aktívny.",
+ "maximum_relative_modulation": "Predstavuje najvyššiu úroveň modulácie pre efektívny vykurovací systém.",
+ "minimum_consumption": "Minimálna spotreba plynu, keď je kotol aktívny.",
+ "sample_time": "Minimálny časový interval medzi aktualizáciami PID regulátora.",
+ "target_temperature_step": "Upravte krok cieľovej teploty pre jemné doladenie úrovní komfortu.",
+ "thermal_comfort": "Povoľte používanie indexu Simmer na nastavenie tepelného komfortu."
+ },
+ "title": "Rozšírené"
+ },
+ "general": {
+ "data": {
+ "automatic_gains_value": "Hodnota automatického zosilnenia",
+ "derivative": "Derivačná (kD)",
+ "derivative_time_weight": "Časová váha derivácie",
+ "duty_cycle": "Maximálny pracovný cyklus pre moduláciu šírky impulzu",
+ "heating_curve_coefficient": "Koeficient vykurovacej krivky",
+ "heating_curve_version": "Verzia vykurovacej krivky",
+ "integral": "Integračná (kI)",
+ "maximum_setpoint": "Maximálna požadovaná hodnota",
+ "minimum_setpoint_adjustment_factor": "Faktor nastavenia pre teplotu spiatočky",
+ "pid_controller_version": "Verzia PID regulátora",
+ "proportional": "Proporcionálna (kP)",
+ "sync_with_thermostat": "Synchronizujte požadovanú hodnotu s termostatom",
+ "window_sensors": "Kontaktné senzory",
+ "heating_mode": "Režim vykurovania"
+ },
+ "data_description": {
+ "automatic_gains_value": "Hodnota používaná pre automatické zosilnenie v PID regulátore.",
+ "derivative": "Odvodený člen (kD) v PID regulátore, zodpovedný za zmiernenie prekročenia.",
+ "derivative_time_weight": "Parameter na úpravu vplyvu derivačného členu v priebehu času, obzvlášť užitočný na zníženie podkmitu počas zahrievacej fázy, keď je koeficient vykurovacej krivky správne nastavený.",
+ "duty_cycle": "Maximálny pracovný cyklus pre moduláciu šírky impulzu (PWM), ktorá riadi cykly zapnutia a vypnutia kotla.",
+ "heating_curve_coefficient": "Koeficient použitý na úpravu vykurovacej krivky.",
+ "integral": "Integrálny člen (kI) v regulátore PID, zodpovedný za zníženie chyby v ustálenom stave.",
+ "maximum_setpoint": "Optimálna teplota pre efektívnu prevádzku kotla.",
+ "minimum_setpoint_adjustment_factor": "Tento faktor upravuje žiadanú hodnotu vykurovania na základe teploty spiatočky kotla, čím ovplyvňuje odozvu a účinnosť vykurovania. Vyššia hodnota zvyšuje citlivosť na zmeny teploty, čím sa zlepšuje kontrola nad komfortom a spotrebou energie. Odporúčaný počiatočný rozsah je 0,1 až 0,5. Upravte ho tak, aby vyhovoval vášmu systému a preferenciám pohodlia.",
+ "proportional": "Proporcionálny člen (kP) v PID regulátore, zodpovedný za okamžitú reakciu na chyby.",
+ "sync_with_thermostat": "Synchronizujte požadovanú hodnotu s termostatom, aby ste zabezpečili koordinovanú reguláciu teploty.",
+ "window_sensors": "Kontaktné senzory, ktoré spúšťajú reakciu systému, keď je okno alebo dvere otvorené po určitú dobu."
+ },
+ "description": "Všeobecné nastavenia a konfigurácie.",
+ "title": "Všeobecné"
+ },
+ "init": {
+ "menu_options": {
+ "advanced": "Rozšírené možnosti",
+ "general": "Všeobecné",
+ "presets": "Predvoľby",
+ "system_configuration": "Konfigurácia systému"
+ }
+ },
+ "presets": {
+ "data": {
+ "activity_temperature": "Teplota aktivity",
+ "away_temperature": "Teplota pri neprítomnosti",
+ "comfort_temperature": "Komfortná teplota",
+ "home_temperature": "Domáca teplota",
+ "sleep_temperature": "Teplota spánku",
+ "sync_with_thermostat": "Synchronizujte s termostatom pripojeným ku kotlu",
+ "sync_climates_with_preset": "Synchronizujte klímu s predvoľbou (spánok / preč / aktivita)"
+ },
+ "description": "Preddefinované nastavenia teploty pre rôzne scenáre alebo činnosti.",
+ "title": "Predvoľby"
+ },
+ "system_configuration": {
+ "data": {
+ "cycles_per_hour": "Pracovné cykly za hodinu",
+ "automatic_duty_cycle": "Automatický pracovný cyklus",
+ "sync_climates_with_mode": "Synchronizujte klímu s režimom",
+ "overshoot_protection": "Ochrana proti prekročeniu (s PWM)",
+ "sensor_max_value_age": "Vek maximálnej hodnoty snímača teploty",
+ "window_minimum_open_time": "Minimálny čas na otvorenie okna"
+ },
+ "data_description": {
+ "cycles_per_hour": "Maximálny počet pracovných cyklov za hodinu.",
+ "automatic_duty_cycle": "Povoliť alebo zakázať automatický pracovný cyklus pre moduláciu šírky impulzu (PWM).",
+ "overshoot_protection": "Aktivujte ochranu proti prekročeniu pomocou modulácie šírky impulzov (PWM), aby ste zabránili prekročeniu teploty kotla.",
+ "sensor_max_value_age": "Maximálny vek hodnoty teplotného snímača pred tým, než sa považuje za prerušenie.",
+ "window_minimum_open_time": "Minimálny čas, počas ktorého musí byť okno otvorené, kým systém zareaguje."
+ },
+ "description": "Na jemné doladenie a prispôsobenie.",
+ "title": "Konfigurácia systému"
+ }
+ }
+ }
+}
diff --git a/custom_components/sat/util.py b/custom_components/sat/util.py
index 3a9b6de3..d45e3e6e 100644
--- a/custom_components/sat/util.py
+++ b/custom_components/sat/util.py
@@ -1,9 +1,12 @@
from re import sub
+from types import MappingProxyType
+from typing import Any
from homeassistant.util import dt
from .const import *
from .heating_curve import HeatingCurve
+from .minimum_setpoint import MinimumSetpoint
from .pid import PID
from .pwm import PWM
@@ -46,16 +49,20 @@ def create_pid_controller(config_options) -> PID:
kd = float(config_options.get(CONF_DERIVATIVE))
heating_system = config_options.get(CONF_HEATING_SYSTEM)
+ version = int(config_options.get(CONF_PID_CONTROLLER_VERSION))
automatic_gains = bool(config_options.get(CONF_AUTOMATIC_GAINS))
automatic_gains_value = float(config_options.get(CONF_AUTOMATIC_GAINS_VALUE))
derivative_time_weight = float(config_options.get(CONF_DERIVATIVE_TIME_WEIGHT))
+ heating_curve_coefficient = float(config_options.get(CONF_HEATING_CURVE_COEFFICIENT))
sample_time_limit = convert_time_str_to_seconds(config_options.get(CONF_SAMPLE_TIME))
# Return a new PID controller instance with the given configuration options
return PID(
+ version=version,
heating_system=heating_system,
automatic_gain_value=automatic_gains_value,
derivative_time_weight=derivative_time_weight,
+ heating_curve_coefficient=heating_curve_coefficient,
kp=kp, ki=ki, kd=kd,
automatic_gains=automatic_gains,
@@ -74,19 +81,35 @@ def create_heating_curve_controller(config_data, config_options) -> HeatingCurve
return HeatingCurve(heating_system=heating_system, coefficient=coefficient, version=version)
-def create_pwm_controller(heating_curve: HeatingCurve, config_data, config_options) -> PWM | None:
+def create_pwm_controller(heating_curve: HeatingCurve, config_data: MappingProxyType[str, Any], config_options: MappingProxyType[str, Any]) -> PWM | None:
"""Create and return a PWM controller instance with the given configuration options."""
# Extract the configuration options
+ max_duty_cycles = int(config_options.get(CONF_CYCLES_PER_HOUR))
automatic_duty_cycle = bool(config_options.get(CONF_AUTOMATIC_DUTY_CYCLE))
max_cycle_time = int(convert_time_str_to_seconds(config_options.get(CONF_DUTY_CYCLE)))
force = bool(config_data.get(CONF_MODE) == MODE_SWITCH) or bool(config_options.get(CONF_FORCE_PULSE_WIDTH_MODULATION))
# Return a new PWM controller instance with the given configuration options
- return PWM(heating_curve=heating_curve, max_cycle_time=max_cycle_time, automatic_duty_cycle=automatic_duty_cycle, force=force)
+ return PWM(heating_curve=heating_curve, max_cycle_time=max_cycle_time, automatic_duty_cycle=automatic_duty_cycle, max_cycles=max_duty_cycles, force=force)
-def snake_case(s):
+def create_minimum_setpoint_controller(config_data, config_options) -> MinimumSetpoint:
+ minimum_setpoint = config_data.get(CONF_MINIMUM_SETPOINT)
+ adjustment_factor = config_options.get(CONF_MINIMUM_SETPOINT_ADJUSTMENT_FACTOR)
+
+ return MinimumSetpoint(configured_minimum_setpoint=minimum_setpoint, adjustment_factor=adjustment_factor)
+
+
+def snake_case(value: str) -> str:
return '_'.join(
sub('([A-Z][a-z]+)', r' \1',
sub('([A-Z]+)', r' \1',
- s.replace('-', ' '))).split()).lower()
+ value.replace('-', ' '))).split()).lower()
+
+
+def float_value(value) -> float | None:
+ """Helper method to convert a value to float, handling possible errors."""
+ try:
+ return float(value)
+ except (TypeError, ValueError):
+ return None
diff --git a/hacs.json b/hacs.json
index 3d319078..fde4a140 100644
--- a/hacs.json
+++ b/hacs.json
@@ -1,6 +1,6 @@
{
"hacs": "1.6.0",
"render_readme": true,
- "homeassistant": "2023.1.0",
+ "homeassistant": "2024.1.0",
"name": "Smart Autotune Thermostat"
}
diff --git a/requirements_test.txt b/requirements_test.txt
index 6871af61..38342bbe 100644
--- a/requirements_test.txt
+++ b/requirements_test.txt
@@ -5,7 +5,9 @@ pytest-homeassistant-custom-component
homeassistant
aiohttp_cors
aiodiscover
+aiodhcpwatcher
+sentry_sdk
freezegun
pyotgw
scapy
-janus
\ No newline at end of file
+janus
diff --git a/tests/conftest.py b/tests/conftest.py
index 2db936f1..19e31ff4 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -5,7 +5,7 @@
from homeassistant.setup import async_setup_component
from pytest_homeassistant_custom_component.common import assert_setup_component, MockConfigEntry
-from custom_components.sat import DOMAIN, CLIMATE, COORDINATOR
+from custom_components.sat.const import DOMAIN, CLIMATE, COORDINATOR
from custom_components.sat.climate import SatClimate
from custom_components.sat.fake import SatFakeCoordinator
from tests.const import DEFAULT_USER_DATA
diff --git a/tests/const.py b/tests/const.py
index 891884de..932dff53 100644
--- a/tests/const.py
+++ b/tests/const.py
@@ -1,5 +1,14 @@
-from custom_components.sat import CONF_NAME, CONF_DEVICE, CONF_INSIDE_SENSOR_ENTITY_ID, CONF_OUTSIDE_SENSOR_ENTITY_ID, CONF_MODE, MODE_FAKE, CONF_AUTOMATIC_GAINS, \
- CONF_AUTOMATIC_DUTY_CYCLE, CONF_OVERSHOOT_PROTECTION
+from custom_components.sat.const import (
+ MODE_FAKE,
+ CONF_DEVICE,
+ CONF_NAME,
+ CONF_INSIDE_SENSOR_ENTITY_ID,
+ CONF_OUTSIDE_SENSOR_ENTITY_ID,
+ CONF_MODE,
+ CONF_AUTOMATIC_GAINS,
+ CONF_AUTOMATIC_DUTY_CYCLE,
+ CONF_OVERSHOOT_PROTECTION
+)
DEFAULT_USER_DATA = {
CONF_MODE: MODE_FAKE,
diff --git a/tests/test_climate.py b/tests/test_climate.py
index 6efc2080..c95bd225 100644
--- a/tests/test_climate.py
+++ b/tests/test_climate.py
@@ -17,6 +17,7 @@
[(
[(TEMPLATE_DOMAIN, 1)],
{
+ CONF_MODE: MODE_FAKE,
CONF_HEATING_SYSTEM: HEATING_SYSTEM_RADIATORS,
CONF_MINIMUM_SETPOINT: 57,
CONF_MAXIMUM_SETPOINT: 75,
@@ -48,11 +49,11 @@ async def test_scenario_1(hass: HomeAssistant, entry: MockConfigEntry, climate:
await climate.async_set_hvac_mode(HVACMode.HEAT)
assert climate.setpoint == 57
- assert climate.heating_curve.value == 32.6
+ assert climate.heating_curve.value == 32.2
assert climate.pulse_width_modulation_enabled
- assert climate.pwm.last_duty_cycle_percentage == 36.24
- assert climate.pwm.duty_cycle == (326, 573)
+ assert climate.pwm.last_duty_cycle_percentage == 23.83
+ assert climate.pwm.duty_cycle == (285, 914)
@pytest.mark.parametrize(*[
@@ -60,6 +61,7 @@ async def test_scenario_1(hass: HomeAssistant, entry: MockConfigEntry, climate:
[(
[(TEMPLATE_DOMAIN, 1)],
{
+ CONF_MODE: MODE_FAKE,
CONF_HEATING_SYSTEM: HEATING_SYSTEM_RADIATORS,
CONF_MINIMUM_SETPOINT: 58,
CONF_MAXIMUM_SETPOINT: 75
@@ -90,13 +92,13 @@ async def test_scenario_2(hass: HomeAssistant, entry: MockConfigEntry, climate:
await climate.async_set_target_temperature(19.0)
await climate.async_set_hvac_mode(HVACMode.HEAT)
- assert climate.setpoint == 58
- assert climate.heating_curve.value == 30.1
- assert climate.requested_setpoint == 30.6
+ assert climate.setpoint == 10
+ assert climate.heating_curve.value == 27.8
+ assert climate.requested_setpoint == 28.0
assert climate.pulse_width_modulation_enabled
- assert climate.pwm.last_duty_cycle_percentage == 11.04
- assert climate.pwm.duty_cycle == (180, 1450)
+ assert climate.pwm.last_duty_cycle_percentage == 2.6
+ assert climate.pwm.duty_cycle == (0, 2400)
@pytest.mark.parametrize(*[
@@ -104,6 +106,7 @@ async def test_scenario_2(hass: HomeAssistant, entry: MockConfigEntry, climate:
[(
[(TEMPLATE_DOMAIN, 1)],
{
+ CONF_MODE: MODE_FAKE,
CONF_HEATING_SYSTEM: HEATING_SYSTEM_RADIATORS,
CONF_MINIMUM_SETPOINT: 41,
CONF_MAXIMUM_SETPOINT: 75,
@@ -135,9 +138,9 @@ async def test_scenario_3(hass: HomeAssistant, entry: MockConfigEntry, climate:
await climate.async_set_hvac_mode(HVACMode.HEAT)
assert climate.setpoint == 41
- assert climate.heating_curve.value == 32.1
- assert climate.requested_setpoint == 37.4
+ assert climate.heating_curve.value == 32.5
+ assert climate.requested_setpoint == 34.6
assert climate.pulse_width_modulation_enabled
- assert climate.pwm.last_duty_cycle_percentage == 73.91
- assert climate.pwm.duty_cycle == (665, 234)
+ assert climate.pwm.last_duty_cycle_percentage == 53.62
+ assert climate.pwm.duty_cycle == (643, 556)
diff --git a/tests/test_config_flow.py b/tests/test_config_flow.py
index a785ab07..57f2ca17 100644
--- a/tests/test_config_flow.py
+++ b/tests/test_config_flow.py
@@ -1,4 +1,4 @@
-from custom_components.sat import MODE_FAKE
+from custom_components.sat.const import MODE_FAKE
from custom_components.sat.config_flow import SatFlowHandler