from homeassistant.components.number import NumberEntity from pymodbus.client.sync import ModbusTcpClient import logging from datetime import timedelta from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.translation import async_get_translations from .const import DOMAIN _LOGGER = logging.getLogger(__name__) async def async_setup_entry(hass, config_entry, async_add_entities): data = config_entry.data translations = await async_get_translations(hass, hass.config.language, "entity") def create_numbers(): numbers = [] if data.get('kessel', False): numbers.extend([ FroelingNumber(hass, translations, data, "Kessel_Solltemperatur", 40001, "°C", 2, 0, 70, 90), FroelingNumber(hass, translations, data, "Bei_welcher_RL_Temperatur_an_der_Zirkulationsleitung_soll_die_Pumpe_ausschalten", 40601, "°C", 2, 0, 20, 120) ]) if data.get('hk01', False): numbers.extend([ FroelingNumber(hass, translations, data, "HK1_Vorlauf_Temperatur_10C_Aussentemperatur", 41032, "°C", 2, 0, 10, 110), FroelingNumber(hass, translations, data, "HK1_Vorlauf_Temperatur_minus_10C_Aussentemperatur", 41033, "°C", 2, 0, 10, 110), FroelingNumber(hass, translations, data, "HK1_Heizkreispumpe_ausschalten_wenn_Vorlauf_Soll_kleiner_ist_als", 41040, "°C", 2, 0, 10, 30), FroelingNumber(hass, translations, data, "HK1_Absenkung_der_Vorlauftemperatur_im_Absenkbetrieb", 41034, "°C", 2, 0, 0, 70), FroelingNumber(hass, translations, data, "HK1_Aussentemperatur_unter_der_die_Heizkreispumpe_im_Heizbetrieb_einschaltet", 41037, "°C", 2, 0, -20, 50), FroelingNumber(hass, translations, data, "HK1_Aussentemperatur_unter_der_die_Heizkreispumpe_im_Absenkbetrieb_einschaltet", 41038, "°C", 2, 0, -20, 50), FroelingNumber(hass, translations, data, "HK1_Frostschutztemperatur", 41039, "°C", 2, 0, 10, 20), FroelingNumber(hass, translations, data, "HK1_Temp_am_Puffer_oben_ab_der_der_Ueberhitzungsschutz_aktiv_wird", 41048, "°C", 1, 0, 60, 120) ]) if data.get('hk02', False): numbers.extend([ FroelingNumber(hass, translations, data, "HK2_Vorlauf_Temperatur_10C_Aussentemperatur", 41062, "°C", 2, 0, 10, 110), FroelingNumber(hass, translations, data, "HK2_Vorlauf_Temperatur_minus_10C_Aussentemperatur", 41063, "°C", 2, 0, 10, 110), FroelingNumber(hass, translations, data, "HK2_Heizkreispumpe_ausschalten_wenn_Vorlauf_Soll_kleiner_ist_als", 41070, "°C", 2, 0, 10, 30), FroelingNumber(hass, translations, data, "HK2_Absenkung_der_Vorlauftemperatur_im_Absenkbetrieb", 41064, "°C", 2, 0, 0, 70), FroelingNumber(hass, translations, data, "HK2_Aussentemperatur_unter_der_die_Heizkreispumpe_im_Heizbetrieb_einschaltet", 41067, "°C", 2, 0, -20, 50), FroelingNumber(hass, translations, data, "HK2_Aussentemperatur_unter_der_die_Heizkreispumpe_im_Absenkbetrieb_einschaltet", 41068, "°C", 2, 0, -20, 50), FroelingNumber(hass, translations, data, "HK2_Frostschutztemperatur", 41069, "°C", 2, 0, -10, 20), FroelingNumber(hass, translations, data, "HK2_Temp_am_Puffer_oben_ab_der_der_Ueberhitzungsschutz_aktiv_wird", 41079, "°C", 1, 0, 60, 120) ]) if data.get('boiler01', False): numbers.extend([ FroelingNumber(hass, translations, data, "Boiler_1_Gewuenschte_Boilertemperatur", 41632, "°C", 2, 0, 10, 100), FroelingNumber(hass, translations, data, "Boiler_1_Nachladen_wenn_Boilertemperatur_unter", 41633, "°C", 2, 0, 1, 90) ]) if data.get('austragung', False): numbers.extend([ FroelingNumber(hass, translations, data, "Pelletlager_Restbestand", 40320, "t", 10, 1, 0, 100) ]) return numbers numbers = create_numbers() async_add_entities(numbers) update_interval = timedelta(seconds=data.get('update_interval', 60)) for number in numbers: async_track_time_interval(hass, number.async_update, update_interval) class FroelingNumber(NumberEntity): def __init__(self, hass, translations, data, entity_id, register, unit, scaling_factor, decimal_places=0, min_value=0, max_value=0): self._hass = hass self._translations = translations self._host = data['host'] self._port = data['port'] self._device_name = data['name'] self._entity_id = entity_id self._register = register self._unit = unit self._scaling_factor = scaling_factor self._decimal_places = decimal_places self._min_value = min_value self._max_value = max_value self._value = None @property def unique_id(self): return f"{self._device_name}_{self._entity_id}" @property def name(self): translated_name = self._translations.get(f"component.froeling_lambdatronic_modbus.entity.number.{self._entity_id}.name", self._entity_id) return f"{self._device_name} {translated_name}" @property def native_value(self): return self._value @property def native_unit_of_measurement(self): return self._unit @property def native_min_value(self): return self._min_value @property def native_max_value(self): return self._max_value @property def device_info(self): return { "identifiers": {(DOMAIN, self._device_name)}, "name": self._device_name, "manufacturer": "Froeling", "model": "Lambdatronic Modbus", "sw_version": "1.0", } async def async_set_native_value(self, value): client = ModbusTcpClient(self._host, port=self._port) if client.connect(): try: scaled_value = int(value * self._scaling_factor) client.write_register(self._register - 40001, scaled_value, unit=2) self._value = value except Exception as e: _LOGGER.error("Exception during Modbus communication: %s", e) finally: client.close() async def async_update(self, _=None): client = ModbusTcpClient(self._host, port=self._port) if client.connect(): try: result = client.read_holding_registers(self._register - 40001, 1, unit=2) if result.isError(): _LOGGER.error("Error reading Modbus holding register %s", self._register - 40001) self._value = None else: raw_value = result.registers[0] _LOGGER.debug("Error reading Modbus holding register %s", self._register - 40001) self._value = round(raw_value / self._scaling_factor, self._decimal_places) _LOGGER.debug("processed Modbus holding register %s: raw_value=%s, _value=%s", self._register - 40001, raw_value, self._value) except Exception as e: _LOGGER.error("Exception during Modbus communication: %s", e) finally: client.close()