From 8eae65de57049b8a4bfa00e346482880f7ba0a00 Mon Sep 17 00:00:00 2001 From: sinseman44 Date: Tue, 27 Feb 2024 14:52:29 +0100 Subject: [PATCH] correct switch bug and rename async functions --- custom_components/__init__.py | 3 +- custom_components/climate.py | 2 +- custom_components/const.py | 6 +-- custom_components/koolnova/device.py | 1 + custom_components/koolnova/operations.py | 56 ++++++++++++------------ custom_components/select.py | 12 ++++- custom_components/sensor.py | 2 + custom_components/switch.py | 54 +++++++++++++++++------ 8 files changed, 86 insertions(+), 50 deletions(-) diff --git a/custom_components/__init__.py b/custom_components/__init__.py index 6c5d078..7ebe833 100644 --- a/custom_components/__init__.py +++ b/custom_components/__init__.py @@ -29,7 +29,6 @@ async def async_setup_entry(hass: HomeAssistant, stopbits: int = entry.data['Stopbits'] timeout: int = entry.data['Timeout'] - #_LOGGER.debug("Appel de async_setup_entry - entry: entry_id={}, data={}".format(entry.entry_id, entry.data)) try: device = Koolnova(name, port, addr, baudrate, parity, bytesize, stopbits, timeout) # connect to modbus client @@ -37,7 +36,7 @@ async def async_setup_entry(hass: HomeAssistant, # update attributes await device.update() # record each area in device - #_LOGGER.debug("Koolnova areas: {}".format(entry.data['areas'])) + _LOGGER.debug("Koolnova areas: {}".format(entry.data['areas'])) for area in entry.data['areas']: await device.add_manual_registered_zone(name=area['Name'], id_zone=area['Area_id']) diff --git a/custom_components/climate.py b/custom_components/climate.py index 706d78b..2d09a41 100644 --- a/custom_components/climate.py +++ b/custom_components/climate.py @@ -164,7 +164,7 @@ class AreaClimateEntity(CoordinatorEntity, ClimateEntity): """ Handle updated data from the coordinator """ for _cur_area in self.coordinator.data['areas']: if _cur_area.id_zone == self._area.id_zone: - _LOGGER.debug("[Climate {}] temp:{} - target:{} - state: {} - hvac:{} - fan:{}".format(_cur_area.id_zone, + _LOGGER.debug("[UPDATE] [Climate {}] temp:{} - target:{} - state: {} - hvac:{} - fan:{}".format(_cur_area.id_zone, _cur_area.real_temp, _cur_area.order_temp, _cur_area.state, diff --git a/custom_components/const.py b/custom_components/const.py index 64c973a..b7fd889 100644 --- a/custom_components/const.py +++ b/custom_components/const.py @@ -22,8 +22,8 @@ from .koolnova.const import ( DOMAIN = "testVBE_4" PLATFORMS: list[Platform] = [Platform.SENSOR, - Platform.SELECT, - Platform.SWITCH, + Platform.SELECT, + Platform.SWITCH, Platform.CLIMATE] CONF_NAME = "koolnova_test_HA" @@ -31,7 +31,7 @@ CONF_DEVICE_ID = "device_id" DEVICE_MANUFACTURER = "koolnova" -MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60) +#MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60) GLOBAL_MODE_POS_1 = "cold" GLOBAL_MODE_POS_2 = "heat" diff --git a/custom_components/koolnova/device.py b/custom_components/koolnova/device.py index 8bcf4a4..3e36a0d 100644 --- a/custom_components/koolnova/device.py +++ b/custom_components/koolnova/device.py @@ -500,6 +500,7 @@ class Koolnova: ''' Set System State ''' if not isinstance(val, const.SysState): raise AssertionError('Input variable must be Enum SysState') + _LOGGER.debug("set system state : {}".format(val)) ret = await self._client.set_system_status(val) if not ret: _LOGGER.error("[SYS_STATE] Error writing {} to modbus".format(val)) diff --git a/custom_components/koolnova/operations.py b/custom_components/koolnova/operations.py index f7943b8..b8d798c 100644 --- a/custom_components/koolnova/operations.py +++ b/custom_components/koolnova/operations.py @@ -62,13 +62,13 @@ class Operations: if debug: pymodbus_apply_logging_config("DEBUG") - async def __read_register(self, reg:int) -> (int, bool): + async def __async_read_register(self, reg:int) -> (int, bool): ''' Read one holding register (code 0x03) ''' rr = None if not self._client.connected: raise ModbusConnexionError('Client Modbus not connected') try: - #_LOGGER.debug("reading holding register: {} - Addr: {}".format(hex(reg), self._addr)) + _LOGGER.debug("reading holding register: {} - Addr: {}".format(hex(reg), self._addr)) rr = await self._client.read_holding_registers(address=reg, count=1, slave=self._addr) if rr.isError(): _LOGGER.error("reading holding register error") @@ -85,7 +85,7 @@ class Operations: return None, False return rr.registers[0], True - async def __read_registers(self, start_reg:int, count:int) -> (int, bool): + async def __async_read_registers(self, start_reg:int, count:int) -> (int, bool): ''' Read holding registers (code 0x03) ''' rr = None if not self._client.connected: @@ -107,14 +107,14 @@ class Operations: return None, False return rr.registers, True - async def __write_register(self, reg:int, val:int) -> bool: + async def __async_write_register(self, reg:int, val:int) -> bool: ''' Write one register (code 0x06) ''' rq = None ret = True if not self._client.connected: raise ModbusConnexionError('Client Modbus not connected') try: - #_LOGGER.debug("writing single register: {} - Addr: {} - Val: {}".format(hex(reg), self._addr, hex(val))) + _LOGGER.debug("writing single register: {} - Addr: {} - Val: {}".format(hex(reg), self._addr, hex(val))) rq = await self._client.write_register(address=reg, value=val, slave=self._addr) if rq.isError(): _LOGGER.error("writing register error") @@ -142,8 +142,8 @@ class Operations: self._client.close() async def discover_registered_zones(self) -> list: - ''' Discover all zones registered to the system ''' - regs, ret = await self.__read_registers(start_reg=const.REG_START_ZONE, + ''' Discover all areas registered to the system ''' + regs, ret = await self.__async_read_registers(start_reg=const.REG_START_ZONE, count=const.NB_ZONE_MAX * const.NUM_REG_PER_ZONE) if not ret: raise ReadRegistersError("Read holding regsiter error") @@ -179,7 +179,7 @@ class Operations: if zone_id > const.NB_ZONE_MAX or zone_id == 0: raise ZoneIdError('Zone Id must be between 1 to {}'.format(const.NB_ZONE_MAX)) zone_dict = {} - regs, ret = await self.__read_registers(start_reg = const.REG_START_ZONE + (4 * (zone_id - 1)), + regs, ret = await self.__async_read_registers(start_reg = const.REG_START_ZONE + (4 * (zone_id - 1)), count = const.NUM_REG_PER_ZONE) if not ret: raise ReadRegistersError("Error reading holding register") @@ -199,7 +199,7 @@ class Operations: """ Get all areas values """ _areas_dict:dict = {} # retreive all areas (registered and unregistered) - regs, ret = await self.__read_registers(start_reg = const.REG_START_ZONE, + regs, ret = await self.__async_read_registers(start_reg = const.REG_START_ZONE, count = const.NUM_REG_PER_ZONE * const.NB_ZONE_MAX) if not ret: raise ReadRegistersError("Error reading holding register") @@ -221,7 +221,7 @@ class Operations: async def system_status(self) -> (bool, const.SysState): ''' Read system status register ''' - reg, ret = await self.__read_register(const.REG_SYS_STATE) + reg, ret = await self.__async_read_register(const.REG_SYS_STATE) if not ret: _LOGGER.error('Error retreive system status') reg = 0 @@ -231,14 +231,14 @@ class Operations: opt:const.SysState, ) -> bool: ''' Write system status ''' - ret = await self.__write_register(reg = const.REG_SYS_STATE, val = int(opt)) + ret = await self.__async_write_register(reg = const.REG_SYS_STATE, val = int(opt)) if not ret: _LOGGER.error('Error writing system status') return ret async def global_mode(self) -> (bool, const.GlobalMode): ''' Read global mode ''' - reg, ret = await self.__read_register(const.REG_GLOBAL_MODE) + reg, ret = await self.__async_read_register(const.REG_GLOBAL_MODE) if not ret: _LOGGER.error('Error retreive global mode') reg = 0 @@ -248,14 +248,14 @@ class Operations: opt:const.GlobalMode, ) -> bool: ''' Write global mode ''' - ret = await self.__write_register(reg = const.REG_GLOBAL_MODE, val = int(opt)) + ret = await self.__async_write_register(reg = const.REG_GLOBAL_MODE, val = int(opt)) if not ret: _LOGGER.error('Error writing global mode') return ret async def efficiency(self) -> (bool, const.Efficiency): ''' read efficiency/speed ''' - reg, ret = await self.__read_register(const.REG_EFFICIENCY) + reg, ret = await self.__async_read_register(const.REG_EFFICIENCY) if not ret: _LOGGER.error('Error retreive efficiency') reg = 0 @@ -265,7 +265,7 @@ class Operations: opt:const.GlobalMode, ) -> bool: ''' Write efficiency ''' - ret = await self.__write_register(reg = const.REG_EFFICIENCY, val = int(opt)) + ret = await self.__async_write_register(reg = const.REG_EFFICIENCY, val = int(opt)) if not ret: _LOGGER.error('Error writing efficiency') return ret @@ -273,7 +273,7 @@ class Operations: async def engines_throughput(self) -> (bool, list): ''' read engines throughput AC1, AC2, AC3, AC4 ''' engines_lst = [] - regs, ret = await self.__read_registers(const.REG_START_FLOW_ENGINE, + regs, ret = await self.__async_read_registers(const.REG_START_FLOW_ENGINE, const.NUM_OF_ENGINES) if ret: for idx, reg in enumerate(regs): @@ -288,7 +288,7 @@ class Operations: ''' read engine throughput specified by id ''' if engine_id < 1 or engine_id > 4: raise UnitIdError("engine Id must be between 1 and 4") - reg, ret = await self.__read_register(const.REG_START_FLOW_ENGINE + (engine_id - 1)) + reg, ret = await self.__async_read_register(const.REG_START_FLOW_ENGINE + (engine_id - 1)) if not ret: _LOGGER.error('Error retreive engine throughput for id:{}'.format(engine_id)) reg = 0 @@ -299,7 +299,7 @@ class Operations: ) -> (bool, const.FlowEngine): if engine_id < 1 or engine_id > 4: raise UnitIdError("Engine id must be between 1 and 4") - reg, ret = await self.__read_register(const.REG_START_FLOW_STATE_ENGINE + (engine_id - 1)) + reg, ret = await self.__async_read_register(const.REG_START_FLOW_STATE_ENGINE + (engine_id - 1)) if not ret: _LOGGER.error('Error retreive engine state for id:{}'.format(engine_id)) reg = 0 @@ -310,7 +310,7 @@ class Operations: ) -> (bool, float): if engine_id < 1 or engine_id > 4: raise UnitIdError("Engine id must be between 1 and 4") - reg, ret = await self.__read_register(const.REG_START_ORDER_TEMP + (engine_id - 1)) + reg, ret = await self.__async_read_register(const.REG_START_ORDER_TEMP + (engine_id - 1)) if not ret: _LOGGER.error('Error retreive engine order temp for id:{}'.format(engine_id)) reg = 0 @@ -319,7 +319,7 @@ class Operations: async def engine_orders_temp(self) -> (bool, list): ''' read orders temperature for engines : AC1, AC2, AC3, AC4 ''' engines_lst = [] - regs, ret = await self.__read_registers(const.REG_START_ORDER_TEMP, const.NUM_OF_ENGINES) + regs, ret = await self.__async_read_registers(const.REG_START_ORDER_TEMP, const.NUM_OF_ENGINES) if ret: for idx, reg in enumerate(regs): engines_lst.append(reg/2) @@ -337,7 +337,7 @@ class Operations: if val > const.MAX_TEMP_ORDER or val < const.MIN_TEMP_ORDER: _LOGGER.error('Order Temperature must be between {} and {}'.format(const.MIN_TEMP_ORDER, const.MAX_TEMP_ORDER)) return False - ret = await self.__write_register(reg = const.REG_START_ZONE + (4 * (zone_id - 1)) + const.REG_TEMP_ORDER, val = int(val * 2)) + ret = await self.__async_write_register(reg = const.REG_START_ZONE + (4 * (zone_id - 1)) + const.REG_TEMP_ORDER, val = int(val * 2)) if not ret: _LOGGER.error('Error writing area order temperature') @@ -347,7 +347,7 @@ class Operations: id_zone:int = 0, ) -> (bool, float): """ get temperature of specific area id """ - reg, ret = await self.__read_register(reg = const.REG_START_ZONE + (4 * (id_zone - 1)) + const.REG_TEMP_REAL) + reg, ret = await self.__async_read_register(reg = const.REG_START_ZONE + (4 * (id_zone - 1)) + const.REG_TEMP_REAL) if not ret: _LOGGER.error('Error retreive area real temp') reg = 0 @@ -357,7 +357,7 @@ class Operations: id_zone:int = 0, ) -> (bool, float): """ get target temperature of specific area id """ - reg, ret = await self.__read_register(reg = const.REG_START_ZONE + (4 * (id_zone - 1)) + const.REG_TEMP_ORDER) + reg, ret = await self.__async_read_register(reg = const.REG_START_ZONE + (4 * (id_zone - 1)) + const.REG_TEMP_ORDER) if not ret: _LOGGER.error('Error retreive area target temp') reg = 0 @@ -367,7 +367,7 @@ class Operations: id_zone:int = 0, ) -> (bool, const.ZoneFanMode, const.ZoneClimMode): """ get climate and fan mode of specific area id """ - reg, ret = await self.__read_register(reg = const.REG_START_ZONE + (4 * (id_zone - 1)) + const.REG_STATE_AND_FLOW) + reg, ret = await self.__async_read_register(reg = const.REG_START_ZONE + (4 * (id_zone - 1)) + const.REG_STATE_AND_FLOW) if not ret: _LOGGER.error('Error retreive area fan and climate values') reg = 0 @@ -377,7 +377,7 @@ class Operations: id_zone:int = 0, ) -> (bool, const.ZoneRegister, const.ZoneState): """ get area state and register """ - reg, ret = await self.__read_register(reg = const.REG_START_ZONE + (4 * (id_zone - 1)) + const.REG_LOCK_ZONE) + reg, ret = await self.__async_read_register(reg = const.REG_START_ZONE + (4 * (id_zone - 1)) + const.REG_LOCK_ZONE) if not ret: _LOGGER.error('Error retreive area register value') reg = 0 @@ -397,7 +397,7 @@ class Operations: _LOGGER.error("Error reading state and register mode") return ret #_LOGGER.debug("register & state: {}".format(hex((int(register) << 1) | (int(val) & 0b01)))) - ret = await self.__write_register(reg = const.REG_START_ZONE + (4 * (id_zone - 1)) + const.REG_LOCK_ZONE, + ret = await self.__async_write_register(reg = const.REG_START_ZONE + (4 * (id_zone - 1)) + const.REG_LOCK_ZONE, val = int(int(register) << 1) | (int(val) & 0b01)) if not ret: _LOGGER.error('Error writing area state value') @@ -418,7 +418,7 @@ class Operations: _LOGGER.error("Error reading fan and clim mode") return ret #_LOGGER.debug("Fan & Clim: {}".format(hex((int(fan) << 4) | (int(val) & 0x0F)))) - ret = await self.__write_register(reg = const.REG_START_ZONE + (4 * (id_zone - 1)) + const.REG_STATE_AND_FLOW, + ret = await self.__async_write_register(reg = const.REG_START_ZONE + (4 * (id_zone - 1)) + const.REG_STATE_AND_FLOW, val = int(int(fan) << 4) | (int(val) & 0x0F)) if not ret: _LOGGER.error('Error writing area climate mode') @@ -439,7 +439,7 @@ class Operations: _LOGGER.error("Error reading fan and clim mode") return ret #_LOGGER.debug("Fan & Clim: {}".format(hex((int(val) << 4) | (int(clim) & 0x0F)))) - ret = await self.__write_register(reg = const.REG_START_ZONE + (4 * (id_zone - 1)) + const.REG_STATE_AND_FLOW, + ret = await self.__async_write_register(reg = const.REG_START_ZONE + (4 * (id_zone - 1)) + const.REG_STATE_AND_FLOW, val = int(int(val) << 4) | (int(clim) & 0x0F)) if not ret: _LOGGER.error('Error writing area fan mode') diff --git a/custom_components/select.py b/custom_components/select.py index ed0fd99..e99c1e1 100644 --- a/custom_components/select.py +++ b/custom_components/select.py @@ -6,6 +6,7 @@ import logging from homeassistant.core import HomeAssistant, callback, Event, State from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.entity import EntityCategory from homeassistant.components.select import SelectEntity from homeassistant.util import Throttle from homeassistant.const import UnitOfTime @@ -15,7 +16,6 @@ from homeassistant.helpers.update_coordinator import ( from .const import ( DOMAIN, - MIN_TIME_BETWEEN_UPDATES, GLOBAL_MODES, GLOBAL_MODE_TRANSLATION, EFF_MODES, @@ -50,6 +50,8 @@ async def async_setup_entry(hass: HomeAssistant, class GlobalModeSelect(CoordinatorEntity, SelectEntity): """ Select component to set global HVAC mode """ + _attr_entity_category: EntityCategory = EntityCategory.CONFIG + def __init__(self, coordinator: KoolnovaCoordinator, # pylint: disable=unused-argument device: Koolnova, # pylint: disable=unused-argument, @@ -83,13 +85,17 @@ class GlobalModeSelect(CoordinatorEntity, SelectEntity): def _handle_coordinator_update(self) -> None: """ Handle updated data from the coordinator Retrieve latest state of global mode """ + _LOGGER.debug("[UPDATE] Global Mode: {}".format(self.coordinator.data['glob'])) self.select_option( GLOBAL_MODE_TRANSLATION[int(self.coordinator.data['glob'])] ) + self.async_write_ha_state() class EfficiencySelect(CoordinatorEntity, SelectEntity): """Select component to set global efficiency """ + _attr_entity_category: EntityCategory = EntityCategory.CONFIG + def __init__(self, coordinator: KoolnovaCoordinator, # pylint: disable=unused-argument device: Koolnova, # pylint: disable=unused-argument, @@ -128,6 +134,8 @@ class EfficiencySelect(CoordinatorEntity, SelectEntity): def _handle_coordinator_update(self) -> None: """ Handle updated data from the coordinator Retrieve latest state of global efficiency """ + _LOGGER.debug("[UPDATE] Efficiency: {}".format(self.coordinator.data['eff'])) self.select_option( EFF_TRANSLATION[int(self.coordinator.data['eff'])] - ) \ No newline at end of file + ) + self.async_write_ha_state() \ No newline at end of file diff --git a/custom_components/sensor.py b/custom_components/sensor.py index fe09d75..38767f3 100644 --- a/custom_components/sensor.py +++ b/custom_components/sensor.py @@ -148,6 +148,7 @@ class DiagEngineThroughputSensor(CoordinatorEntity, SensorEntity): """ Handle updated data from the coordinator """ for _cur_engine in self.coordinator.data['engines']: if self._engine.engine_id == _cur_engine.engine_id: + _LOGGER.debug("[UPDATE] [ENGINE AC{}] Troughput: {}".format(_cur_engine.engine_id, _cur_engine.throughput)) self._attr_native_value = "{}".format(_cur_engine.throughput) self.async_write_ha_state() @@ -182,5 +183,6 @@ class DiagEngineTempOrderSensor(CoordinatorEntity, SensorEntity): """ Handle updated data from the coordinator """ for _cur_engine in self.coordinator.data['engines']: if self._engine.engine_id == _cur_engine.engine_id: + _LOGGER.debug("[UPDATE] [ENGINE AC{}] Order temp: {}".format(_cur_engine.engine_id, _cur_engine.order_temp)) self._attr_native_value = "{}".format(_cur_engine.order_temp) self.async_write_ha_state() \ No newline at end of file diff --git a/custom_components/switch.py b/custom_components/switch.py index 58fe1a9..e5afa24 100644 --- a/custom_components/switch.py +++ b/custom_components/switch.py @@ -5,21 +5,29 @@ import logging from homeassistant.core import HomeAssistant, callback, Event, State from homeassistant.config_entries import ConfigEntry +from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.components.switch import SwitchEntity +from homeassistant.components.switch import ( + SwitchEntity, + SwitchDeviceClass +) + from homeassistant.util import Throttle from homeassistant.helpers.update_coordinator import ( CoordinatorEntity, ) from .const import ( - DOMAIN, - MIN_TIME_BETWEEN_UPDATES, + DOMAIN ) from .coordinator import KoolnovaCoordinator -from homeassistant.const import UnitOfTime +from homeassistant.const import ( + STATE_OFF, + STATE_ON +) + from .koolnova.device import Koolnova from .koolnova.const import ( SysState, @@ -43,40 +51,58 @@ async def async_setup_entry(hass: HomeAssistant, class SystemStateSwitch(CoordinatorEntity, SwitchEntity): """Select component to set system state """ - _attr_has_entity_name = True + _attr_has_entity_name: bool = True + _attr_device_class: SwitchDeviceClass = SwitchDeviceClass.SWITCH + _attr_entity_category: EntityCategory = EntityCategory.CONFIG def __init__(self, coordinator: KoolnovaCoordinator, # pylint: disable=unused-argument - device: Koolnova, # pylint: disable=unused-argument, + device: Koolnova, # pylint: disable=unused-argument ) -> None: super().__init__(coordinator) self._device = device self._attr_name = f"{device.name} Global HVAC State" self._attr_device_info = device.device_info - self._attr_unique_id = f"{DOMAIN}-Global-HVACState-switch" - self._is_on = bool(int(self._device.sys_state)) + self._attr_unique_id = f"{DOMAIN}-Global-HVAC-State-switch" + self._attr_is_on = bool(int(self._device.sys_state)) + if bool(int(self._device.sys_state)): + self._attr_state = STATE_ON + else: + self._attr_state = STATE_OFF async def async_turn_on(self, **kwargs): """ Turn the entity on. """ _LOGGER.debug("Turn on system") await self._device.set_sys_state(SysState.SYS_STATE_ON) - self._is_on = True + self._attr_is_on = True + self._attr_state = STATE_ON + self.async_write_ha_state() async def async_turn_off(self, **kwargs): """ Turn the entity off. """ _LOGGER.debug("Turn off system") await self._device.set_sys_state(SysState.SYS_STATE_OFF) - self._is_on = False + self._attr_is_on = False + self._attr_state = STATE_OFF + self.async_write_ha_state() @callback def _handle_coordinator_update(self) -> None: """ Handle updated data from the coordinator """ - self._is_on = bool(int(self.coordinator.data['sys'])) + self._attr_is_on = bool(int(self.coordinator.data['sys'])) + _LOGGER.debug("[UPDATE] Switch State: {} - is_on ? {} - state ? {}".format(bool(int(self.coordinator.data['sys'])), + self._attr_is_on, + self._attr_state)) + if bool(int(self.coordinator.data['sys'])): + self._attr_state = STATE_ON + else: + self._attr_state = STATE_OFF + self.async_write_ha_state() @property - def is_on(self): - """ If the switch is currently on or off. """ - return self._is_on + def is_on(self) -> bool | None: + """Return True if entity is on.""" + return bool(int(self._device.sys_state)) @property def icon(self) -> str | None: