diff --git a/custom_components/solvis_control/__init__.py b/custom_components/solvis_control/__init__.py index 3b929a4..41ac740 100644 --- a/custom_components/solvis_control/__init__.py +++ b/custom_components/solvis_control/__init__.py @@ -23,6 +23,8 @@ CONF_OPTION_2, CONF_OPTION_3, CONF_OPTION_4, + POLL_RATE_SLOW, + POLL_RATE_DEFAULT, ) from .coordinator import SolvisModbusCoordinator @@ -69,6 +71,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: entry.data.get(CONF_OPTION_2), entry.data.get(CONF_OPTION_3), entry.data.get(CONF_OPTION_4), + entry.data.get(POLL_RATE_DEFAULT), + entry.data.get(POLL_RATE_SLOW), ) await coordinator.async_config_entry_first_refresh() hass.data[DOMAIN][entry.entry_id].setdefault(DATA_COORDINATOR, coordinator) @@ -127,6 +131,13 @@ async def async_migrate_entry(hass, config_entry: ConfigEntry): if DEVICE_VERSION not in new_data: new_data[DEVICE_VERSION] = "SC3" config_entry.minor_version = 3 + if config_entry.minor_version < 4: + _LOGGER.info(f"Migrating from version {config_entry.version}") + if POLL_RATE_DEFAULT not in new_data: + new_data[POLL_RATE_DEFAULT] = 30 + if POLL_RATE_SLOW not in new_data: + new_data[POLL_RATE_SLOW] = 300 + config_entry.minor_version = 4 hass.config_entries.async_update_entry(config_entry, data=new_data) _LOGGER.info(f"Migration to version {config_entry.version} successful") return True diff --git a/custom_components/solvis_control/config_flow.py b/custom_components/solvis_control/config_flow.py index d2dc8b9..1920a68 100644 --- a/custom_components/solvis_control/config_flow.py +++ b/custom_components/solvis_control/config_flow.py @@ -10,6 +10,7 @@ from homeassistant.core import callback from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.typing import ConfigType +from homeassistant.helpers import config_validation as cv from .const import ( CONF_HOST, @@ -21,11 +22,19 @@ CONF_OPTION_3, CONF_OPTION_4, DEVICE_VERSION, + POLL_RATE_DEFAULT, + POLL_RATE_SLOW, ) _LOGGER = logging.getLogger(__name__) +def validate_poll_rates(data): + if data[POLL_RATE_SLOW] % data[POLL_RATE_DEFAULT] != 0: + raise vol.Invalid("POLL_RATE_SLOW must be a multiple of POLL_RATE_DEFAULT") + return data + + def get_host_schema_config(data: ConfigType) -> Schema: return vol.Schema( { @@ -51,6 +60,12 @@ def get_solvis_devices(data: ConfigType) -> Schema: return vol.Schema( { vol.Required(DEVICE_VERSION, default="SC3"): vol.In({"SC2": 2, "SC3": 1}), + vol.Required(POLL_RATE_DEFAULT, default=30): vol.All( + vol.Coerce(int), vol.Range(min=30) + ), + vol.Required(POLL_RATE_SLOW, default=300): vol.All( + vol.Coerce(int), vol.Range(min=60) + ), } ) @@ -80,7 +95,15 @@ def get_solvis_devices_options(data: ConfigType) -> Schema: vol.Required( DEVICE_VERSION, default=data.get(DEVICE_VERSION, "SC3") ): vol.In({"SC2": 2, "SC3": 1}), - } + vol.Required(POLL_RATE_DEFAULT, default=30): vol.All( + vol.Coerce(int), vol.Range(min=30) + ), + vol.Required(POLL_RATE_SLOW, default=300): vol.All( + vol.Coerce(int), vol.Range(min=60) + ), + }, + extra=vol.ALLOW_EXTRA, + validator=validate_poll_rates, ) @@ -95,7 +118,7 @@ def get_host_schema_options(data: ConfigType) -> Schema: class SolvisConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 - MINOR_VERSION = 3 + MINOR_VERSION = 4 def __init__(self) -> None: """Init the ConfigFlow.""" @@ -108,21 +131,6 @@ async def async_step_user(self, user_input: ConfigType | None = None) -> FlowRes errors = {} if user_input is not None: self.data = user_input - # try: - # self.client = ModbusClient.AsyncModbusTcpClient( - # user_input[CONF_HOST], user_input[CONF_PORT] - # ) - # await self.client.connect() - # # Perform a simple read to check the connection - # await self.client.read_holding_registers(2818, 1, 1) - # except (ConnectionException, ModbusException) as exc: - # _LOGGER.error(f"Modbus connection failed: {exc}") - # errors["base"] = "cannot_connect" - # else: - # await self.client.close() - # await self.async_set_unique_id( - # self.data[CONF_HOST], raise_on_progress=False - # ) self._abort_if_unique_id_configured() return await self.async_step_device() @@ -136,9 +144,12 @@ async def async_step_device( """Handle the device step.""" errors = {} if user_input is not None: - self.data.update(user_input) - return await self.async_step_features() - + try: + self.data.update(user_input) + return await self.async_step_features() + except vol.Invalid as exc: + errors["base"] = str(exc) + errors["device"] = exc.error_message return self.async_show_form( step_id="device", data_schema=get_solvis_devices(self.data), @@ -167,7 +178,7 @@ def async_get_options_flow( class SolvisOptionsFlow(config_entries.OptionsFlow): VERSION = 1 - MINOR_VERSION = 3 + MINOR_VERSION = 4 def __init__(self, config) -> None: """Init the ConfigFlow.""" @@ -181,18 +192,6 @@ async def async_step_init(self, user_input: ConfigType | None = None) -> FlowRes _LOGGER.debug(f"Options flow values_1: {str(self.data)}", DOMAIN) if user_input is not None: self.data.update(user_input) - # try: - # self.client = ModbusClient.AsyncModbusTcpClient( - # user_input[CONF_HOST], user_input[CONF_PORT] - # ) - # await self.client.connect() - # # Perform a simple read to check the connection - # await self.client.read_holding_registers(2818, 1, 1) - # except (ConnectionException, ModbusException) as exc: - # _LOGGER.error(f"Modbus connection failed: {exc}") - # errors["base"] = "cannot_connect" - # else: - # await self.client.close() return await self.async_step_device() return self.async_show_form( @@ -208,9 +207,12 @@ async def async_step_device( errors = {} _LOGGER.debug(f"Options flow values_1: {str(self.data)}", DOMAIN) if user_input is not None: - self.data.update(user_input) - return await self.async_step_features() - + try: + self.data.update(user_input) + return await self.async_step_features() + except vol.Invalid as exc: + errors["base"] = str(exc) + errors["device"] = exc return self.async_show_form( step_id="device", data_schema=get_solvis_devices(self.data), diff --git a/custom_components/solvis_control/const.py b/custom_components/solvis_control/const.py index 1b240c8..1f6095d 100644 --- a/custom_components/solvis_control/const.py +++ b/custom_components/solvis_control/const.py @@ -7,6 +7,8 @@ CONF_PORT = "port" DEVICE_VERSION = "device_version" +POLL_RATE_DEFAULT = "poll_rate_default" +POLL_RATE_SLOW = "poll_rate_slow" # Option attributes to make certain values configurable CONF_OPTION_1 = "HKR2" # HKR 2 @@ -53,6 +55,12 @@ class ModbusFieldConfig: # Supported Version # 0: SC2 & SC3, 1: SC3, 2: SC2 supported_version: int = 0 + # Poll rate + # False: default, True: slow + poll_rate: bool = False + # Internal variable to store the value of the last poll + # Don't change + poll_time: int = 0 PORT = 502 @@ -268,6 +276,7 @@ class ModbusFieldConfig: multiplier=1, entity_category="diagnostic", absolute_value=True, + poll_rate=True, ), ModbusFieldConfig( # Ionisationsstrom name="ionisation_voltage", @@ -372,6 +381,7 @@ class ModbusFieldConfig: multiplier=1, options=("2", "3", "4", "5", "6", "7"), input_type=1, + poll_rate=True, ), ModbusFieldConfig( # HKR1 Warmwasser Vorrang name="hkr1_warmwasser_vorrang", @@ -382,6 +392,7 @@ class ModbusFieldConfig: register=2, multiplier=1, input_type=3, + poll_rate=True, ), ModbusFieldConfig( # HKR1 Vorlaufart name="hkr1_vorlaufart", @@ -392,6 +403,7 @@ class ModbusFieldConfig: register=2, multiplier=1, enabled_by_default=False, + poll_rate=True, ), ModbusFieldConfig( # HKR1 Fix Vorlauf Tag name="hkr1_fix_vorlauf_tag", @@ -403,6 +415,7 @@ class ModbusFieldConfig: multiplier=1, input_type=2, range_data=(5, 75), + poll_rate=True, ), ModbusFieldConfig( # HKR1 Fix Vorlauf Nacht name="hkr1_fix_vorlauf_nacht", @@ -415,6 +428,7 @@ class ModbusFieldConfig: edit=True, input_type=2, range_data=(5, 75), + poll_rate=True, ), ModbusFieldConfig( # HKR1 Heizkurve Tag Temp. 1 name="hkr1_heizkurve_temp_tag_1", @@ -427,6 +441,7 @@ class ModbusFieldConfig: edit=True, input_type=2, range_data=(5, 50), + poll_rate=True, ), ModbusFieldConfig( # HKR1 Heizkurve Tag Temp. 2 name="hkr1_heizkurve_temp_tag_2", @@ -439,6 +454,7 @@ class ModbusFieldConfig: edit=True, input_type=2, range_data=(5, 30), + poll_rate=True, ), ModbusFieldConfig( # HKR1 Heizkurve Tag Temp. 3 name="hkr1_heizkurve_temp_tag_3", @@ -451,6 +467,7 @@ class ModbusFieldConfig: edit=True, input_type=2, range_data=(5, 30), + poll_rate=True, ), ModbusFieldConfig( # HKR1 Heizkurve Absenkung name="hkr1_heizkurve_temp_absenkung", @@ -463,6 +480,7 @@ class ModbusFieldConfig: edit=True, input_type=2, range_data=(5, 30), + poll_rate=True, ), ModbusFieldConfig( # HKR1 Heizkurve Steilheit name="hkr1_heizkurve_steilheit", @@ -476,6 +494,7 @@ class ModbusFieldConfig: input_type=2, range_data=(0.2, 2.5), step_size=0.05, + poll_rate=True, ), ModbusFieldConfig( # Raumtemperatur_HKR1 name="raumtemperatur_hkr1", @@ -497,6 +516,7 @@ class ModbusFieldConfig: options=("2", "3", "4", "5", "6", "7"), conf_option=1, input_type=1, + poll_rate=True, ), ModbusFieldConfig( # HKR2 Vorlaufart name="hkr2_vorlaufart", @@ -508,6 +528,7 @@ class ModbusFieldConfig: multiplier=1, conf_option=1, enabled_by_default=False, + poll_rate=True, ), ModbusFieldConfig( # HKR2 Warmwasser Vorrang name="hkr2_warmwasser_vorrang", @@ -532,6 +553,7 @@ class ModbusFieldConfig: input_type=2, range_data=(5, 75), conf_option=1, + poll_rate=True, ), ModbusFieldConfig( # HKR2 Fix Vorlauf Nacht name="hkr2_fix_vorlauf_nacht", @@ -545,6 +567,7 @@ class ModbusFieldConfig: input_type=2, range_data=(5, 75), conf_option=1, + poll_rate=True, ), ModbusFieldConfig( # HKR2 Heizkurve Tag Temp. 1 name="hkr2_heizkurve_temp_tag_1", @@ -558,6 +581,7 @@ class ModbusFieldConfig: input_type=2, range_data=(5, 50), conf_option=1, + poll_rate=True, ), ModbusFieldConfig( # HKR2 Heizkurve Tag Temp. 2 name="hkr2_heizkurve_temp_tag_2", @@ -571,6 +595,7 @@ class ModbusFieldConfig: input_type=2, range_data=(5, 30), conf_option=1, + poll_rate=True, ), ModbusFieldConfig( # HKR2 Heizkurve Tag Temp. 3 name="hkr2_heizkurve_temp_tag_3", @@ -584,6 +609,7 @@ class ModbusFieldConfig: input_type=2, range_data=(5, 30), conf_option=1, + poll_rate=True, ), ModbusFieldConfig( # HKR2 Heizkurve Absenkung name="hkr2_heizkurve_temp_absenkung", @@ -597,6 +623,7 @@ class ModbusFieldConfig: input_type=2, range_data=(5, 30), conf_option=1, + poll_rate=True, ), ModbusFieldConfig( # HKR2 Heizkurve Steilheit name="hkr2_heizkurve_steilheit", @@ -611,6 +638,7 @@ class ModbusFieldConfig: range_data=(0.2, 2.5), conf_option=1, step_size=0.05, + poll_rate=True, ), ModbusFieldConfig( # Raumtemperatur_HKR2 name="raumtemperatur_hkr2", @@ -633,6 +661,7 @@ class ModbusFieldConfig: options=("2", "3", "4", "5", "6", "7"), conf_option=2, input_type=1, + poll_rate=True, ), ModbusFieldConfig( # HKR3 Vorlaufart name="hkr3_vorlaufart", @@ -644,6 +673,7 @@ class ModbusFieldConfig: multiplier=1, conf_option=2, enabled_by_default=False, + poll_rate=True, ), ModbusFieldConfig( # HKR3 Warmwasser Vorrang name="hkr3_warmwasser_vorrang", @@ -655,6 +685,7 @@ class ModbusFieldConfig: multiplier=1, input_type=3, conf_option=2, + poll_rate=True, ), ModbusFieldConfig( # HKR3 Fix Vorlauf Tag name="hkr3_fix_vorlauf_tag", @@ -668,6 +699,7 @@ class ModbusFieldConfig: range_data=(5, 75), conf_option=2, input_type=2, + poll_rate=True, ), ModbusFieldConfig( # HKR3 Fix Vorlauf Nacht name="hkr3_fix_vorlauf_nacht", @@ -681,6 +713,7 @@ class ModbusFieldConfig: input_type=2, range_data=(5, 75), conf_option=2, + poll_rate=True, ), ModbusFieldConfig( # HKR3 Heizkurve Tag Temp. 1 name="hkr3_heizkurve_temp_tag_1", @@ -694,6 +727,7 @@ class ModbusFieldConfig: input_type=2, range_data=(5, 50), conf_option=2, + poll_rate=True, ), ModbusFieldConfig( # HKR3 Heizkurve Tag Temp. 2 name="hkr3_heizkurve_temp_tag_2", @@ -707,6 +741,7 @@ class ModbusFieldConfig: input_type=2, range_data=(5, 30), conf_option=2, + poll_rate=True, ), ModbusFieldConfig( # HKR3 Heizkurve Tag Temp. 3 name="hkr3_heizkurve_temp_tag_3", @@ -720,6 +755,7 @@ class ModbusFieldConfig: input_type=2, range_data=(5, 30), conf_option=2, + poll_rate=True, ), ModbusFieldConfig( # HKR3 Heizkurve Absenkung name="hkr3_heizkurve_temp_absenkung", @@ -747,6 +783,7 @@ class ModbusFieldConfig: range_data=(0.2, 2.5), conf_option=2, step_size=0.05, + poll_rate=True, ), ModbusFieldConfig( # Raumtemperatur_HKR3 name="raumtemperatur_hkr3", @@ -779,6 +816,7 @@ class ModbusFieldConfig: multiplier=1, entity_category="diagnostic", data_processing=1, + poll_rate=True, ), ModbusFieldConfig( # VersionNBG name="version_nbg", @@ -789,6 +827,7 @@ class ModbusFieldConfig: multiplier=1, entity_category="diagnostic", data_processing=1, + poll_rate=True, ), ModbusFieldConfig( name="digin_error", diff --git a/custom_components/solvis_control/coordinator.py b/custom_components/solvis_control/coordinator.py index 70a0367..d4d136a 100644 --- a/custom_components/solvis_control/coordinator.py +++ b/custom_components/solvis_control/coordinator.py @@ -28,13 +28,15 @@ def __init__( option_hkr3: bool, option_solar: bool, option_heatpump: bool, + poll_rate_default: int, + poll_rate_slow: int, ): """Initializes the Solvis Modbus data coordinator.""" super().__init__( hass, _LOGGER, name=DOMAIN, - update_interval=timedelta(seconds=30), + update_interval=timedelta(seconds=poll_rate_default), ) self.host = host self.port = port @@ -43,6 +45,8 @@ def __init__( self.option_solar = option_solar self.option_heatpump = option_heatpump self.supported_version = supported_version + self.poll_rate_default = poll_rate_default + self.poll_rate_slow = poll_rate_slow _LOGGER.debug("Creating Modbus client") self.modbus = ModbusClient.AsyncModbusTcpClient(host=host, port=port) @@ -73,6 +77,17 @@ async def _async_update_data(self): elif self.supported_version == 2 and register.supported_version == 1: continue + # Calculation for passing entites, which are in SLOW_POLL_GOUP + if register.poll_rate: + if register.poll_time > 0: + register.poll_time -= self.poll_rate_default + _LOGGER.debug( + f"Skipping entity {register.name}/{register.address} due to slow poll rate" + ) + continue + if register.poll_time <= 0: + register.poll_time = register.poll_rate_slow + entity_id = f"{DOMAIN}.{register.name}" entity_entry = self.hass.data["entity_registry"].async_get(entity_id) if entity_entry and entity_entry.disabled: diff --git a/custom_components/solvis_control/number.py b/custom_components/solvis_control/number.py index 96d7fe3..0138ea2 100644 --- a/custom_components/solvis_control/number.py +++ b/custom_components/solvis_control/number.py @@ -107,6 +107,7 @@ async def async_setup_entry( register.address, register.multiplier, register.data_processing, + register.poll_rate, ) ) @@ -131,6 +132,7 @@ def __init__( modbus_address: int = None, multiplier: float = 1, data_processing: int = 0, + poll_rate: bool = False, ): """Initialize the Solvis number entity.""" super().__init__(coordinator) @@ -158,6 +160,7 @@ def __init__( self.native_min_value = range_data[0] self.native_max_value = range_data[1] self.data_processing = data_processing + self.poll_rate = poll_rate @callback def _handle_coordinator_update(self) -> None: diff --git a/custom_components/solvis_control/select.py b/custom_components/solvis_control/select.py index 7a259ad..ed9cf4d 100644 --- a/custom_components/solvis_control/select.py +++ b/custom_components/solvis_control/select.py @@ -101,6 +101,7 @@ async def async_setup_entry( register.options, # These are the options for the select entity register.address, register.data_processing, + register.poll_rate, ) ) @@ -120,6 +121,7 @@ def __init__( options: tuple = None, # Renamed for clarity modbus_address: int = None, data_processing: int = None, + poll_rate: bool = False, ): """Initialize the Solvis select entity.""" super().__init__(coordinator) @@ -136,6 +138,7 @@ def __init__( self._attr_current_option = None self._attr_options = options # Set the options for the select entity self.data_processing = data_processing + self.poll_rate = poll_rate @callback def _handle_coordinator_update(self) -> None: diff --git a/custom_components/solvis_control/sensor.py b/custom_components/solvis_control/sensor.py index 478af91..3a98fd8 100644 --- a/custom_components/solvis_control/sensor.py +++ b/custom_components/solvis_control/sensor.py @@ -103,6 +103,7 @@ async def async_setup_entry( register.entity_category, register.enabled_by_default, register.data_processing, + register.poll_rate, ) ) @@ -124,6 +125,7 @@ def __init__( entity_category: str | None = None, enabled_by_default: bool = True, data_processing: int = 0, + poll_rate: bool = False, ): """Initialize the Solvis sensor.""" super().__init__(coordinator) @@ -142,6 +144,7 @@ def __init__( self.unique_id = f"{re.sub('^[A-Za-z0-9_-]*$', '', name)}_{name}" self.translation_key = name self.data_processing = data_processing + self.poll_rate = poll_rate @callback def _handle_coordinator_update(self) -> None: diff --git a/custom_components/solvis_control/switch.py b/custom_components/solvis_control/switch.py index 379e7a5..05bc0ee 100644 --- a/custom_components/solvis_control/switch.py +++ b/custom_components/solvis_control/switch.py @@ -100,6 +100,7 @@ async def async_setup_entry( register.enabled_by_default, register.address, register.data_processing, + register.poll_rate, ) ) @@ -118,6 +119,7 @@ def __init__( enabled_by_default: bool = True, modbus_address: int = None, data_processing: int = 0, + poll_rate: bool = False, ): """Initialize the Solvis switch.""" super().__init__(coordinator) @@ -133,6 +135,7 @@ def __init__( self.translation_key = name self._attr_current_option = None self.data_processing = data_processing + self.poll_rate = poll_rate @callback def _handle_coordinator_update(self) -> None: