add coordinator class
This commit is contained in:
@@ -9,13 +9,16 @@ from .koolnova.device import Koolnova
|
|||||||
|
|
||||||
from .const import DOMAIN, PLATFORMS
|
from .const import DOMAIN, PLATFORMS
|
||||||
|
|
||||||
|
from .coordinator import KoolnovaCoordinator
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant,
|
async def async_setup_entry(hass: HomeAssistant,
|
||||||
entry: ConfigEntry) -> bool: # pylint: disable=unused-argument
|
entry: ConfigEntry) -> bool: # pylint: disable=unused-argument
|
||||||
""" Creation des entités à partir d'une configEntry """
|
""" Creation des entités à partir d'une configEntry """
|
||||||
|
|
||||||
hass.data.setdefault(DOMAIN, [])
|
#hass.data.setdefault(DOMAIN, [])
|
||||||
|
hass.data.setdefault(DOMAIN, {})
|
||||||
|
|
||||||
name: str = entry.data['Name']
|
name: str = entry.data['Name']
|
||||||
port: str = entry.data['Device']
|
port: str = entry.data['Device']
|
||||||
@@ -26,7 +29,7 @@ async def async_setup_entry(hass: HomeAssistant,
|
|||||||
stopbits: int = entry.data['Stopbits']
|
stopbits: int = entry.data['Stopbits']
|
||||||
timeout: int = entry.data['Timeout']
|
timeout: int = entry.data['Timeout']
|
||||||
|
|
||||||
_LOGGER.debug("Appel de async_setup_entry - entry: entry_id={}, data={}".format(entry.entry_id, entry.data))
|
#_LOGGER.debug("Appel de async_setup_entry - entry: entry_id={}, data={}".format(entry.entry_id, entry.data))
|
||||||
try:
|
try:
|
||||||
device = Koolnova(name, port, addr, baudrate, parity, bytesize, stopbits, timeout)
|
device = Koolnova(name, port, addr, baudrate, parity, bytesize, stopbits, timeout)
|
||||||
# connect to modbus client
|
# connect to modbus client
|
||||||
@@ -34,12 +37,13 @@ async def async_setup_entry(hass: HomeAssistant,
|
|||||||
# update attributes
|
# update attributes
|
||||||
await device.update()
|
await device.update()
|
||||||
# record each area in device
|
# 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']:
|
for area in entry.data['areas']:
|
||||||
await device.add_manual_registered_zone(name=area['Name'],
|
await device.add_manual_registered_zone(name=area['Name'],
|
||||||
id_zone=area['Zone_id'])
|
id_zone=area['Area_id'])
|
||||||
_LOGGER.debug("Koolnova device: {}".format(device))
|
hass.data[DOMAIN]['device'] = device
|
||||||
hass.data[DOMAIN].append(device)
|
coordinator = KoolnovaCoordinator(hass, device)
|
||||||
|
hass.data[DOMAIN]['coordinator'] = coordinator
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
_LOGGER.exception("Something went wrong ... {}".format(e))
|
_LOGGER.exception("Something went wrong ... {}".format(e))
|
||||||
|
|
||||||
|
|||||||
@@ -32,9 +32,11 @@ from .const import (
|
|||||||
HVAC_TRANSLATION,
|
HVAC_TRANSLATION,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from .coordinator import KoolnovaCoordinator
|
||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
TEMP_CELSIUS,
|
|
||||||
ATTR_TEMPERATURE,
|
ATTR_TEMPERATURE,
|
||||||
|
UnitOfTemperature,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .koolnova.device import Koolnova, Area
|
from .koolnova.device import Koolnova, Area
|
||||||
@@ -52,7 +54,6 @@ from .koolnova.const import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30)
|
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant,
|
async def async_setup_entry(hass: HomeAssistant,
|
||||||
entry: ConfigEntry,
|
entry: ConfigEntry,
|
||||||
@@ -61,38 +62,19 @@ async def async_setup_entry(hass: HomeAssistant,
|
|||||||
"""Setup switch entries"""
|
"""Setup switch entries"""
|
||||||
|
|
||||||
entities = []
|
entities = []
|
||||||
|
coordinator = hass.data[DOMAIN]["coordinator"]
|
||||||
for device in hass.data[DOMAIN]:
|
device = hass.data[DOMAIN]["device"]
|
||||||
coordinator = ClimateCoordinator(hass, device)
|
|
||||||
for area in device.areas:
|
|
||||||
_LOGGER.debug("Device: {} - Area: {}".format(device, area))
|
|
||||||
entities.append(AreaClimateEntity(coordinator, device, area))
|
|
||||||
async_add_entities(entities)
|
|
||||||
|
|
||||||
class ClimateCoordinator(DataUpdateCoordinator):
|
for area in device.areas:
|
||||||
""" Climate coordinator """
|
entities.append(AreaClimateEntity(coordinator, device, area))
|
||||||
|
async_add_entities(entities)
|
||||||
def __init__(self,
|
|
||||||
hass: HomeAssistant,
|
|
||||||
device: Koolnova,
|
|
||||||
) -> None:
|
|
||||||
""" Class constructor """
|
|
||||||
super().__init__(
|
|
||||||
hass,
|
|
||||||
_LOGGER,
|
|
||||||
# Name of the data. For logging purposes.
|
|
||||||
name=DOMAIN,
|
|
||||||
update_method=device.update_all_areas,
|
|
||||||
# Polling interval. Will only be polled if there are subscribers.
|
|
||||||
update_interval=timedelta(seconds=30),
|
|
||||||
)
|
|
||||||
|
|
||||||
class AreaClimateEntity(CoordinatorEntity, ClimateEntity):
|
class AreaClimateEntity(CoordinatorEntity, ClimateEntity):
|
||||||
""" Reperesentation of a climate entity """
|
""" Reperesentation of a climate entity """
|
||||||
# pylint: disable = too-many-instance-attributes
|
# pylint: disable = too-many-instance-attributes
|
||||||
|
|
||||||
_attr_supported_features: int = SUPPORT_FLAGS
|
_attr_supported_features: int = SUPPORT_FLAGS
|
||||||
_attr_temperature_unit: str = TEMP_CELSIUS
|
_attr_temperature_unit: str = UnitOfTemperature.CELSIUS
|
||||||
_attr_hvac_modes: list[HVACMode] = SUPPORTED_HVAC_MODES
|
_attr_hvac_modes: list[HVACMode] = SUPPORTED_HVAC_MODES
|
||||||
_attr_fan_modes: list[str] = SUPPORTED_FAN_MODES
|
_attr_fan_modes: list[str] = SUPPORTED_FAN_MODES
|
||||||
_attr_hvac_mode: HVACMode = HVACMode.OFF
|
_attr_hvac_mode: HVACMode = HVACMode.OFF
|
||||||
@@ -103,9 +85,10 @@ class AreaClimateEntity(CoordinatorEntity, ClimateEntity):
|
|||||||
_attr_target_temperature_high: float = MAX_TEMP_ORDER
|
_attr_target_temperature_high: float = MAX_TEMP_ORDER
|
||||||
_attr_target_temperature_low: float = MIN_TEMP_ORDER
|
_attr_target_temperature_low: float = MIN_TEMP_ORDER
|
||||||
_attr_target_temperature_step: float = STEP_TEMP_ORDER
|
_attr_target_temperature_step: float = STEP_TEMP_ORDER
|
||||||
|
_enable_turn_on_off_backwards_compatibility: bool = False
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
coordinator: ClimateCoordinator, # pylint: disable=unused-argument
|
coordinator: KoolnovaCoordinator, # pylint: disable=unused-argument
|
||||||
device: Koolnova, # pylint: disable=unused-argument
|
device: Koolnova, # pylint: disable=unused-argument
|
||||||
area: Area, # pylint: disable=unused-argument
|
area: Area, # pylint: disable=unused-argument
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -118,9 +101,7 @@ class AreaClimateEntity(CoordinatorEntity, ClimateEntity):
|
|||||||
self._attr_unique_id = f"{DOMAIN}-{area.name}-area-climate"
|
self._attr_unique_id = f"{DOMAIN}-{area.name}-area-climate"
|
||||||
self._attr_current_temperature = area.real_temp
|
self._attr_current_temperature = area.real_temp
|
||||||
self._attr_target_temperature = area.order_temp
|
self._attr_target_temperature = area.order_temp
|
||||||
_LOGGER.debug("[Climate {}] {} - {}".format(self._area.id_zone, self._area.fan_mode, FAN_TRANSLATION[int(self._area.fan_mode)]))
|
|
||||||
self._attr_fan_mode = FAN_TRANSLATION[int(self._area.fan_mode)]
|
self._attr_fan_mode = FAN_TRANSLATION[int(self._area.fan_mode)]
|
||||||
_LOGGER.debug("[Climate {}] {} - {}".format(self._area.id_zone, self._area.clim_mode, self._translate_to_hvac_mode()))
|
|
||||||
self._attr_hvac_mode = self._translate_to_hvac_mode()
|
self._attr_hvac_mode = self._translate_to_hvac_mode()
|
||||||
|
|
||||||
def _translate_to_hvac_mode(self) -> int:
|
def _translate_to_hvac_mode(self) -> int:
|
||||||
@@ -181,7 +162,7 @@ class AreaClimateEntity(CoordinatorEntity, ClimateEntity):
|
|||||||
@callback
|
@callback
|
||||||
def _handle_coordinator_update(self) -> None:
|
def _handle_coordinator_update(self) -> None:
|
||||||
""" Handle updated data from the coordinator """
|
""" Handle updated data from the coordinator """
|
||||||
for _cur_area in self.coordinator.data:
|
for _cur_area in self.coordinator.data['areas']:
|
||||||
if _cur_area.id_zone == self._area.id_zone:
|
if _cur_area.id_zone == self._area.id_zone:
|
||||||
_LOGGER.debug("[Climate {}] temp:{} - target:{} - state: {} - hvac:{} - fan:{}".format(_cur_area.id_zone,
|
_LOGGER.debug("[Climate {}] temp:{} - target:{} - state: {} - hvac:{} - fan:{}".format(_cur_area.id_zone,
|
||||||
_cur_area.real_temp,
|
_cur_area.real_temp,
|
||||||
|
|||||||
@@ -52,14 +52,13 @@ class TestVBE4ConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
vol.Required("Parity", default="EVEN"): vol.In(["EVEN", "NONE"]),
|
vol.Required("Parity", default="EVEN"): vol.In(["EVEN", "NONE"]),
|
||||||
vol.Required("Stopbits", default=DEFAULT_STOPBITS): vol.Coerce(int),
|
vol.Required("Stopbits", default=DEFAULT_STOPBITS): vol.Coerce(int),
|
||||||
vol.Required("Timeout", default=1): vol.Coerce(int),
|
vol.Required("Timeout", default=1): vol.Coerce(int),
|
||||||
vol.Optional("Discover", default="MANUAL"): vol.In(["MANUAL", "AUTOMATIC"])
|
vol.Optional("Debug", default=False): cv.boolean
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
if user_input:
|
if user_input:
|
||||||
# second call
|
|
||||||
_LOGGER.debug("config_flow [user] - Step 1b -> On a reçu les valeurs: {}".format(user_input))
|
_LOGGER.debug("config_flow [user] - Step 1b -> On a reçu les valeurs: {}".format(user_input))
|
||||||
# On memorise les données dans le dictionnaire
|
# Second call; On memorise les données dans le dictionnaire
|
||||||
self._user_inputs.update(user_input)
|
self._user_inputs.update(user_input)
|
||||||
|
|
||||||
self._conn = Operations(port=self._user_inputs["Device"],
|
self._conn = Operations(port=self._user_inputs["Device"],
|
||||||
@@ -68,12 +67,13 @@ class TestVBE4ConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
parity=self._user_inputs["Parity"][0],
|
parity=self._user_inputs["Parity"][0],
|
||||||
bytesize=self._user_inputs["Sizebyte"],
|
bytesize=self._user_inputs["Sizebyte"],
|
||||||
stopbits=self._user_inputs["Stopbits"],
|
stopbits=self._user_inputs["Stopbits"],
|
||||||
timeout=self._user_inputs["Timeout"])
|
timeout=self._user_inputs["Timeout"],
|
||||||
|
debug=self._user_inputs["Debug"])
|
||||||
try:
|
try:
|
||||||
await self._conn.connect()
|
await self._conn.connect()
|
||||||
if not self._conn.connected():
|
if not self._conn.connected():
|
||||||
raise CannotConnectError(reason="Client Modbus not connected")
|
raise CannotConnectError(reason="Client Modbus not connected")
|
||||||
_LOGGER.debug("test communication with koolnova system")
|
#_LOGGER.debug("test communication with koolnova system")
|
||||||
ret, _ = await self._conn.system_status()
|
ret, _ = await self._conn.system_status()
|
||||||
if not ret:
|
if not ret:
|
||||||
self._conn.disconnect()
|
self._conn.disconnect()
|
||||||
@@ -94,14 +94,21 @@ class TestVBE4ConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
data_schema=user_form,
|
data_schema=user_form,
|
||||||
errors=errors)
|
errors=errors)
|
||||||
|
|
||||||
async def async_step_areas(self,
|
async def async_step_areas(self,
|
||||||
user_input: dict | None = None) -> FlowResult:
|
user_input: dict | None = None) -> FlowResult:
|
||||||
""" Gestion de l'étape de découverte manuelle des zones """
|
""" Gestion de l'étape de découverte manuelle des zones """
|
||||||
errors = {}
|
errors = {}
|
||||||
|
default_id = 1
|
||||||
|
default_area_name = "Area 1"
|
||||||
|
# set default_id to the last id configured
|
||||||
|
# set default_area_name with the last id configured
|
||||||
|
for area in self._user_inputs["areas"]:
|
||||||
|
default_id = area['Area_id'] + 1
|
||||||
|
default_area_name = "Area " + str(default_id)
|
||||||
zone_form = vol.Schema(
|
zone_form = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Required("Name", default="zone"): vol.Coerce(str),
|
vol.Required("Name", default=default_area_name): vol.Coerce(str),
|
||||||
vol.Required("Zone_id", default=1): vol.Coerce(int),
|
vol.Required("Area_id", default=default_id): vol.Coerce(int),
|
||||||
vol.Optional("Other_area", default=False): cv.boolean
|
vol.Optional("Other_area", default=False): cv.boolean
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -109,20 +116,20 @@ class TestVBE4ConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
if user_input:
|
if user_input:
|
||||||
# second call
|
# second call
|
||||||
try:
|
try:
|
||||||
# test if zone_id is already configured
|
# test if area_id is already configured
|
||||||
for area in self._user_inputs["areas"]:
|
for area in self._user_inputs["areas"]:
|
||||||
if user_input['Zone_id'] == area['Zone_id']:
|
if user_input['Area_id'] == area['Area_id']:
|
||||||
raise AreaAlreadySetError(reason="Area is already configured")
|
raise AreaAlreadySetError(reason="Area is already configured")
|
||||||
# Last area to configure or not ?
|
# Last area to configure or not ?
|
||||||
if not user_input['Other_area']:
|
if not user_input['Other_area']:
|
||||||
try:
|
try:
|
||||||
if not self._conn.connected():
|
if not self._conn.connected():
|
||||||
await self._conn.connect()
|
await self._conn.connect()
|
||||||
if user_input['Zone_id'] > NB_ZONE_MAX:
|
if user_input['Area_id'] > NB_ZONE_MAX:
|
||||||
raise ZoneIdError(reason="Zone_Id must be between 1 and 16")
|
raise ZoneIdError(reason="Area_id must be between 1 and 16")
|
||||||
_LOGGER.debug("test area registered with id: {}".format(user_input['Zone_id']))
|
#_LOGGER.debug("test area registered with id: {}".format(user_input['Area_id']))
|
||||||
# test if area is configured into koolnova system
|
# test if area is configured into koolnova system
|
||||||
ret, _ = await self._conn.zone_registered(user_input["Zone_id"])
|
ret, _ = await self._conn.zone_registered(user_input["Area_id"])
|
||||||
if not ret:
|
if not ret:
|
||||||
self._conn.disconnect()
|
self._conn.disconnect()
|
||||||
raise AreaNotRegistredError(reason="Area Id is not registred")
|
raise AreaNotRegistredError(reason="Area Id is not registred")
|
||||||
@@ -137,7 +144,7 @@ class TestVBE4ConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
_LOGGER.exception("Cannot connect to koolnova system")
|
_LOGGER.exception("Cannot connect to koolnova system")
|
||||||
errors[CONF_BASE] = "cannot_connect"
|
errors[CONF_BASE] = "cannot_connect"
|
||||||
except AreaNotRegistredError:
|
except AreaNotRegistredError:
|
||||||
_LOGGER.exception("Area (id:{}) is not registered to the koolnova system".format(user_input['Zone_id']))
|
_LOGGER.exception("Area (id:{}) is not registered to the koolnova system".format(user_input['Area_id']))
|
||||||
errors[CONF_BASE] = "area_not_registered"
|
errors[CONF_BASE] = "area_not_registered"
|
||||||
except ZoneIdError:
|
except ZoneIdError:
|
||||||
_LOGGER.exception("Area Id must be between 1 and 16")
|
_LOGGER.exception("Area Id must be between 1 and 16")
|
||||||
@@ -145,14 +152,14 @@ class TestVBE4ConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
_LOGGER.exception("Config Flow generic error")
|
_LOGGER.exception("Config Flow generic error")
|
||||||
else:
|
else:
|
||||||
_LOGGER.debug("Config_flow [zone] - Une autre zone à configurer")
|
#_LOGGER.debug("Config_flow [zone] - Une autre zone à configurer")
|
||||||
# Update dict
|
# Update dict
|
||||||
self._user_inputs["areas"].append(user_input)
|
self._user_inputs["areas"].append(user_input)
|
||||||
# New area to configure
|
# New area to configure
|
||||||
return await self.async_step_areas()
|
return await self.async_step_areas()
|
||||||
|
|
||||||
except AreaAlreadySetError:
|
except AreaAlreadySetError:
|
||||||
_LOGGER.exception("Area (id:{}) is already configured".format(user_input['Zone_id']))
|
_LOGGER.exception("Area (id:{}) is already configured".format(user_input['Area_id']))
|
||||||
errors[CONF_BASE] = "area_already_configured"
|
errors[CONF_BASE] = "area_already_configured"
|
||||||
|
|
||||||
# first call or error
|
# first call or error
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ from homeassistant.components.climate.const import (
|
|||||||
FAN_LOW,
|
FAN_LOW,
|
||||||
FAN_MEDIUM,
|
FAN_MEDIUM,
|
||||||
FAN_HIGH,
|
FAN_HIGH,
|
||||||
HVAC_MODE_COOL,
|
|
||||||
HVAC_MODE_HEAT,
|
|
||||||
)
|
)
|
||||||
from .koolnova.const import (
|
from .koolnova.const import (
|
||||||
GlobalMode,
|
GlobalMode,
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
""" for Coordinator integration. """
|
||||||
|
from __future__ import annotations
|
||||||
|
from datetime import timedelta
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from homeassistant.core import HomeAssistant, callback
|
||||||
|
from homeassistant.util import Throttle
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
|
from homeassistant.helpers.update_coordinator import (
|
||||||
|
CoordinatorEntity,
|
||||||
|
DataUpdateCoordinator,
|
||||||
|
UpdateFailed,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .const import (
|
||||||
|
DOMAIN,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .koolnova.device import Koolnova
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class KoolnovaCoordinator(DataUpdateCoordinator):
|
||||||
|
""" koolnova coordinator """
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
device: Koolnova,
|
||||||
|
) -> None:
|
||||||
|
""" Class constructor """
|
||||||
|
super().__init__(
|
||||||
|
hass,
|
||||||
|
_LOGGER,
|
||||||
|
# Name of the data. For logging purposes.
|
||||||
|
name=DOMAIN,
|
||||||
|
update_method=device.update_all_areas,
|
||||||
|
# Polling interval. Will only be polled if there are subscribers.
|
||||||
|
update_interval=timedelta(seconds=30),
|
||||||
|
)
|
||||||
@@ -49,7 +49,7 @@ class Area:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def id_zone(self) -> int:
|
def id_zone(self) -> int:
|
||||||
''' Get Zone Id '''
|
''' Get area id '''
|
||||||
return self._id
|
return self._id
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -284,7 +284,6 @@ class Koolnova:
|
|||||||
|
|
||||||
_LOGGER.debug("Retreive engines ...")
|
_LOGGER.debug("Retreive engines ...")
|
||||||
for idx in range(1, const.NUM_OF_ENGINES + 1):
|
for idx in range(1, const.NUM_OF_ENGINES + 1):
|
||||||
_LOGGER.debug("Engine id: {}".format(idx))
|
|
||||||
engine = Engine(engine_id = idx)
|
engine = Engine(engine_id = idx)
|
||||||
ret, engine.throughput = await self._client.engine_throughput(engine_id = idx)
|
ret, engine.throughput = await self._client.engine_throughput(engine_id = idx)
|
||||||
ret, engine.state = await self._client.engine_state(engine_id = idx)
|
ret, engine.state = await self._client.engine_state(engine_id = idx)
|
||||||
@@ -356,7 +355,7 @@ class Koolnova:
|
|||||||
real_temp = zone_dict['real_temp'],
|
real_temp = zone_dict['real_temp'],
|
||||||
order_temp = zone_dict['order_temp']
|
order_temp = zone_dict['order_temp']
|
||||||
))
|
))
|
||||||
_LOGGER.debug("Zones registered: {}".format(self._areas))
|
_LOGGER.debug("Areas registered: {}".format(self._areas))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -376,7 +375,7 @@ class Koolnova:
|
|||||||
return ret, None
|
return ret, None
|
||||||
for idx, area in enumerate(self._areas):
|
for idx, area in enumerate(self._areas):
|
||||||
if area.id_zone == zone_id:
|
if area.id_zone == zone_id:
|
||||||
# update areas list value from modbus response
|
# update areas list values from modbus response
|
||||||
self._areas[idx].state = infos['state']
|
self._areas[idx].state = infos['state']
|
||||||
self._areas[idx].register = infos['register']
|
self._areas[idx].register = infos['register']
|
||||||
self._areas[idx].fan_mode = infos['fan']
|
self._areas[idx].fan_mode = infos['fan']
|
||||||
@@ -387,16 +386,17 @@ class Koolnova:
|
|||||||
return ret, self._areas[zone_id - 1]
|
return ret, self._areas[zone_id - 1]
|
||||||
|
|
||||||
async def update_all_areas(self) -> list:
|
async def update_all_areas(self) -> list:
|
||||||
""" update all areas registered """
|
""" update all areas registered and all engines values """
|
||||||
|
##### Areas
|
||||||
_ret, _vals = await self._client.areas_registered()
|
_ret, _vals = await self._client.areas_registered()
|
||||||
if not _ret:
|
if not _ret:
|
||||||
_LOGGER.error("Error retreiving areas values")
|
_LOGGER.error("Error retreiving areas values")
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
_LOGGER.debug("areas: {}".format(_vals))
|
|
||||||
for k,v in _vals.items():
|
for k,v in _vals.items():
|
||||||
for _idx, _area in enumerate(self._areas):
|
for _idx, _area in enumerate(self._areas):
|
||||||
if k == _area.id_zone:
|
if k == _area.id_zone:
|
||||||
|
# update areas list values from modbus response
|
||||||
self._areas[_idx].state = v['state']
|
self._areas[_idx].state = v['state']
|
||||||
self._areas[_idx].register = v['register']
|
self._areas[_idx].register = v['register']
|
||||||
self._areas[_idx].fan_mode = v['fan']
|
self._areas[_idx].fan_mode = v['fan']
|
||||||
@@ -404,7 +404,35 @@ class Koolnova:
|
|||||||
self._areas[_idx].real_temp = v['real_temp']
|
self._areas[_idx].real_temp = v['real_temp']
|
||||||
self._areas[_idx].order_temp = v['order_temp']
|
self._areas[_idx].order_temp = v['order_temp']
|
||||||
|
|
||||||
return self._areas
|
##### Engines
|
||||||
|
for _idx in range(1, const.NUM_OF_ENGINES + 1):
|
||||||
|
ret, self._engines[_idx - 1].throughput = await self._client.engine_throughput(engine_id = _idx)
|
||||||
|
ret, self._engines[_idx - 1].state = await self._client.engine_state(engine_id = _idx)
|
||||||
|
ret, self._engines[_idx - 1].order_temp = await self._client.engine_order_temp(engine_id = _idx)
|
||||||
|
|
||||||
|
##### Global mode
|
||||||
|
ret, self._global_mode = await self._client.global_mode()
|
||||||
|
if not ret:
|
||||||
|
_LOGGER.error("Error retreiving global mode")
|
||||||
|
self._global_mode = const.GlobalMode.COLD
|
||||||
|
|
||||||
|
##### Efficiency
|
||||||
|
ret, self._efficiency = await self._client.efficiency()
|
||||||
|
if not ret:
|
||||||
|
_LOGGER.error("Error retreiving efficiency")
|
||||||
|
self._efficiency = const.Efficiency.LOWER_EFF
|
||||||
|
|
||||||
|
##### Sys state
|
||||||
|
ret, self._sys_state = await self._client.system_status()
|
||||||
|
if not ret:
|
||||||
|
_LOGGER.error("Error retreiving system status")
|
||||||
|
self._sys_state = const.SysState.SYS_STATE_OFF
|
||||||
|
|
||||||
|
return {"areas": self._areas,
|
||||||
|
"engines": self._engines,
|
||||||
|
"glob": self._global_mode,
|
||||||
|
"eff": self._efficiency,
|
||||||
|
"sys": self._sys_state}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def engines(self) -> list:
|
def engines(self) -> list:
|
||||||
@@ -439,6 +467,7 @@ class Koolnova:
|
|||||||
raise AssertionError('Input variable must be Enum GlobalMode')
|
raise AssertionError('Input variable must be Enum GlobalMode')
|
||||||
ret = await self._client.set_global_mode(val)
|
ret = await self._client.set_global_mode(val)
|
||||||
if not ret:
|
if not ret:
|
||||||
|
_LOGGER.error("[GLOBAL] Error writing {} to modbus".format(val))
|
||||||
raise UpdateValueError('Error writing to modbus updated value')
|
raise UpdateValueError('Error writing to modbus updated value')
|
||||||
self._global_mode = val
|
self._global_mode = val
|
||||||
|
|
||||||
@@ -456,6 +485,7 @@ class Koolnova:
|
|||||||
raise AssertionError('Input variable must be Enum Efficiency')
|
raise AssertionError('Input variable must be Enum Efficiency')
|
||||||
ret = await self._client.set_efficiency(val)
|
ret = await self._client.set_efficiency(val)
|
||||||
if not ret:
|
if not ret:
|
||||||
|
_LOGGER.error("[EFF] Error writing {} to modbus".format(val))
|
||||||
raise UpdateValueError('Error writing to modbus updated value')
|
raise UpdateValueError('Error writing to modbus updated value')
|
||||||
self._efficiency = val
|
self._efficiency = val
|
||||||
|
|
||||||
@@ -472,6 +502,7 @@ class Koolnova:
|
|||||||
raise AssertionError('Input variable must be Enum SysState')
|
raise AssertionError('Input variable must be Enum SysState')
|
||||||
ret = await self._client.set_system_status(val)
|
ret = await self._client.set_system_status(val)
|
||||||
if not ret:
|
if not ret:
|
||||||
|
_LOGGER.error("[SYS_STATE] Error writing {} to modbus".format(val))
|
||||||
raise UpdateValueError('Error writing to modbus updated value')
|
raise UpdateValueError('Error writing to modbus updated value')
|
||||||
self._sys_state = val
|
self._sys_state = val
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ _LOGGER = log.getLogger(__name__)
|
|||||||
class Operations:
|
class Operations:
|
||||||
''' koolnova BMS Modbus operations class '''
|
''' koolnova BMS Modbus operations class '''
|
||||||
|
|
||||||
def __init__(self, port:str, timeout:int) -> None:
|
def __init__(self, port:str, timeout:int, debug:bool=False) -> None:
|
||||||
''' Class constructor '''
|
''' Class constructor '''
|
||||||
self._port = port
|
self._port = port
|
||||||
self._timeout = timeout
|
self._timeout = timeout
|
||||||
@@ -33,8 +33,8 @@ class Operations:
|
|||||||
stopbits=self._stopbits,
|
stopbits=self._stopbits,
|
||||||
bytesize=self._bytesize,
|
bytesize=self._bytesize,
|
||||||
timeout=self._timeout)
|
timeout=self._timeout)
|
||||||
|
if debug:
|
||||||
pymodbus_apply_logging_config("DEBUG")
|
pymodbus_apply_logging_config("DEBUG")
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
port:str="",
|
port:str="",
|
||||||
@@ -43,7 +43,8 @@ class Operations:
|
|||||||
parity:str=const.DEFAULT_PARITY,
|
parity:str=const.DEFAULT_PARITY,
|
||||||
stopbits:int=const.DEFAULT_STOPBITS,
|
stopbits:int=const.DEFAULT_STOPBITS,
|
||||||
bytesize:int=const.DEFAULT_BYTESIZE,
|
bytesize:int=const.DEFAULT_BYTESIZE,
|
||||||
timeout:int=1) -> None:
|
timeout:int=1,
|
||||||
|
debug:bool=False) -> None:
|
||||||
''' Class constructor '''
|
''' Class constructor '''
|
||||||
self._port = port
|
self._port = port
|
||||||
self._addr = addr
|
self._addr = addr
|
||||||
@@ -58,8 +59,8 @@ class Operations:
|
|||||||
stopbits=self._stopbits,
|
stopbits=self._stopbits,
|
||||||
bytesize=self._bytesize,
|
bytesize=self._bytesize,
|
||||||
timeout=self._timeout)
|
timeout=self._timeout)
|
||||||
|
if debug:
|
||||||
pymodbus_apply_logging_config("DEBUG")
|
pymodbus_apply_logging_config("DEBUG")
|
||||||
|
|
||||||
async def __read_register(self, reg:int) -> (int, bool):
|
async def __read_register(self, reg:int) -> (int, bool):
|
||||||
''' Read one holding register (code 0x03) '''
|
''' Read one holding register (code 0x03) '''
|
||||||
@@ -67,7 +68,7 @@ class Operations:
|
|||||||
if not self._client.connected:
|
if not self._client.connected:
|
||||||
raise ModbusConnexionError('Client Modbus not connected')
|
raise ModbusConnexionError('Client Modbus not connected')
|
||||||
try:
|
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)
|
rr = await self._client.read_holding_registers(address=reg, count=1, slave=self._addr)
|
||||||
if rr.isError():
|
if rr.isError():
|
||||||
_LOGGER.error("reading holding register error")
|
_LOGGER.error("reading holding register error")
|
||||||
@@ -113,7 +114,7 @@ class Operations:
|
|||||||
if not self._client.connected:
|
if not self._client.connected:
|
||||||
raise ModbusConnexionError('Client Modbus not connected')
|
raise ModbusConnexionError('Client Modbus not connected')
|
||||||
try:
|
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)
|
rq = await self._client.write_register(address=reg, value=val, slave=self._addr)
|
||||||
if rq.isError():
|
if rq.isError():
|
||||||
_LOGGER.error("writing register error")
|
_LOGGER.error("writing register error")
|
||||||
@@ -174,7 +175,7 @@ class Operations:
|
|||||||
zone_id:int = 0,
|
zone_id:int = 0,
|
||||||
) -> (bool, dict):
|
) -> (bool, dict):
|
||||||
''' Get Zone Status from Id '''
|
''' Get Zone Status from Id '''
|
||||||
_LOGGER.debug("Area : {}".format(zone_id))
|
#_LOGGER.debug("Area : {}".format(zone_id))
|
||||||
if zone_id > const.NB_ZONE_MAX or zone_id == 0:
|
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))
|
raise ZoneIdError('Zone Id must be between 1 to {}'.format(const.NB_ZONE_MAX))
|
||||||
zone_dict = {}
|
zone_dict = {}
|
||||||
@@ -338,7 +339,7 @@ class Operations:
|
|||||||
return False
|
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.__write_register(reg = const.REG_START_ZONE + (4 * (zone_id - 1)) + const.REG_TEMP_ORDER, val = int(val * 2))
|
||||||
if not ret:
|
if not ret:
|
||||||
_LOGGER.error('Error writing zone order temperature')
|
_LOGGER.error('Error writing area order temperature')
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
@@ -389,13 +390,13 @@ class Operations:
|
|||||||
""" set area state """
|
""" set area state """
|
||||||
register:const.ZoneRegister = const.ZoneRegister.REGISTER_OFF
|
register:const.ZoneRegister = const.ZoneRegister.REGISTER_OFF
|
||||||
if id_zone > const.NB_ZONE_MAX or id_zone == 0:
|
if id_zone > const.NB_ZONE_MAX or id_zone == 0:
|
||||||
raise ZoneIdError('Zone Id must be between 1 to 16')
|
raise ZoneIdError('Area Id must be between 1 to 16')
|
||||||
# retreive values to combine the new state with register read
|
# retreive values to combine the new state with register read
|
||||||
ret, register, _ = await self.area_state_and_register(id_zone = id_zone)
|
ret, register, _ = await self.area_state_and_register(id_zone = id_zone)
|
||||||
if not ret:
|
if not ret:
|
||||||
_LOGGER.error("Error reading state and register mode")
|
_LOGGER.error("Error reading state and register mode")
|
||||||
return ret
|
return ret
|
||||||
_LOGGER.debug("register & state: {}".format(hex((int(register) << 1) | (int(val) & 0b01))))
|
#_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.__write_register(reg = const.REG_START_ZONE + (4 * (id_zone - 1)) + const.REG_LOCK_ZONE,
|
||||||
val = int(int(register) << 1) | (int(val) & 0b01))
|
val = int(int(register) << 1) | (int(val) & 0b01))
|
||||||
if not ret:
|
if not ret:
|
||||||
@@ -416,7 +417,7 @@ class Operations:
|
|||||||
if not ret:
|
if not ret:
|
||||||
_LOGGER.error("Error reading fan and clim mode")
|
_LOGGER.error("Error reading fan and clim mode")
|
||||||
return ret
|
return ret
|
||||||
_LOGGER.debug("Fan & Clim: {}".format(hex((int(fan) << 4) | (int(val) & 0x0F))))
|
#_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.__write_register(reg = const.REG_START_ZONE + (4 * (id_zone - 1)) + const.REG_STATE_AND_FLOW,
|
||||||
val = int(int(fan) << 4) | (int(val) & 0x0F))
|
val = int(int(fan) << 4) | (int(val) & 0x0F))
|
||||||
if not ret:
|
if not ret:
|
||||||
@@ -437,7 +438,7 @@ class Operations:
|
|||||||
if not ret:
|
if not ret:
|
||||||
_LOGGER.error("Error reading fan and clim mode")
|
_LOGGER.error("Error reading fan and clim mode")
|
||||||
return ret
|
return ret
|
||||||
_LOGGER.debug("Fan & Clim: {}".format(hex((int(val) << 4) | (int(clim) & 0x0F))))
|
#_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.__write_register(reg = const.REG_START_ZONE + (4 * (id_zone - 1)) + const.REG_STATE_AND_FLOW,
|
||||||
val = int(int(val) << 4) | (int(clim) & 0x0F))
|
val = int(int(val) << 4) | (int(clim) & 0x0F))
|
||||||
if not ret:
|
if not ret:
|
||||||
|
|||||||
+34
-23
@@ -3,11 +3,15 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant, callback, Event, State
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.components.select import SelectEntity
|
from homeassistant.components.select import SelectEntity
|
||||||
from homeassistant.util import Throttle
|
from homeassistant.util import Throttle
|
||||||
|
from homeassistant.const import UnitOfTime
|
||||||
|
from homeassistant.helpers.update_coordinator import (
|
||||||
|
CoordinatorEntity,
|
||||||
|
)
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
@@ -17,7 +21,9 @@ from .const import (
|
|||||||
EFF_MODES,
|
EFF_MODES,
|
||||||
EFF_TRANSLATION,
|
EFF_TRANSLATION,
|
||||||
)
|
)
|
||||||
from homeassistant.const import UnitOfTime
|
|
||||||
|
from .coordinator import KoolnovaCoordinator
|
||||||
|
|
||||||
from .koolnova.device import Koolnova
|
from .koolnova.device import Koolnova
|
||||||
from .koolnova.const import (
|
from .koolnova.const import (
|
||||||
GlobalMode,
|
GlobalMode,
|
||||||
@@ -32,21 +38,23 @@ async def async_setup_entry(hass: HomeAssistant,
|
|||||||
):
|
):
|
||||||
""" Setup select entries """
|
""" Setup select entries """
|
||||||
|
|
||||||
for device in hass.data[DOMAIN]:
|
device = hass.data[DOMAIN]["device"]
|
||||||
_LOGGER.debug("Device: {}".format(device))
|
coordinator = hass.data[DOMAIN]["coordinator"]
|
||||||
entities = [
|
|
||||||
GlobalModeSelect(device),
|
|
||||||
EfficiencySelect(device),
|
|
||||||
]
|
|
||||||
async_add_entities(entities)
|
|
||||||
|
|
||||||
class GlobalModeSelect(SelectEntity):
|
entities = [
|
||||||
|
GlobalModeSelect(coordinator, device),
|
||||||
|
EfficiencySelect(coordinator, device),
|
||||||
|
]
|
||||||
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
class GlobalModeSelect(CoordinatorEntity, SelectEntity):
|
||||||
""" Select component to set global HVAC mode """
|
""" Select component to set global HVAC mode """
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
|
coordinator: KoolnovaCoordinator, # pylint: disable=unused-argument
|
||||||
device: Koolnova, # pylint: disable=unused-argument,
|
device: Koolnova, # pylint: disable=unused-argument,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__()
|
super().__init__(coordinator)
|
||||||
self._attr_options = GLOBAL_MODES
|
self._attr_options = GLOBAL_MODES
|
||||||
self._device = device
|
self._device = device
|
||||||
self._attr_name = f"{device.name} Global HVAC Mode"
|
self._attr_name = f"{device.name} Global HVAC Mode"
|
||||||
@@ -71,20 +79,22 @@ class GlobalModeSelect(SelectEntity):
|
|||||||
await self._device.set_global_mode(GlobalMode(opt))
|
await self._device.set_global_mode(GlobalMode(opt))
|
||||||
self.select_option(option)
|
self.select_option(option)
|
||||||
|
|
||||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
@callback
|
||||||
async def async_update(self):
|
def _handle_coordinator_update(self) -> None:
|
||||||
""" Retrieve latest state of global mode """
|
""" Handle updated data from the coordinator
|
||||||
|
Retrieve latest state of global mode """
|
||||||
self.select_option(
|
self.select_option(
|
||||||
GLOBAL_MODE_TRANSLATION[int(self._device.global_mode)]
|
GLOBAL_MODE_TRANSLATION[int(self.coordinator.data['glob'])]
|
||||||
)
|
)
|
||||||
|
|
||||||
class EfficiencySelect(SelectEntity):
|
class EfficiencySelect(CoordinatorEntity, SelectEntity):
|
||||||
"""Select component to set global efficiency """
|
"""Select component to set global efficiency """
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
|
coordinator: KoolnovaCoordinator, # pylint: disable=unused-argument
|
||||||
device: Koolnova, # pylint: disable=unused-argument,
|
device: Koolnova, # pylint: disable=unused-argument,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__()
|
super().__init__(coordinator)
|
||||||
self._attr_options = EFF_MODES
|
self._attr_options = EFF_MODES
|
||||||
self._device = device
|
self._device = device
|
||||||
self._attr_name = f"{device.name} Global HVAC Efficiency"
|
self._attr_name = f"{device.name} Global HVAC Efficiency"
|
||||||
@@ -114,9 +124,10 @@ class EfficiencySelect(SelectEntity):
|
|||||||
await self._device.set_efficiency(Efficiency(opt))
|
await self._device.set_efficiency(Efficiency(opt))
|
||||||
self.select_option(option)
|
self.select_option(option)
|
||||||
|
|
||||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
@callback
|
||||||
async def async_update(self):
|
def _handle_coordinator_update(self) -> None:
|
||||||
""" Retrieve latest state of global efficiency """
|
""" Handle updated data from the coordinator
|
||||||
|
Retrieve latest state of global efficiency """
|
||||||
self.select_option(
|
self.select_option(
|
||||||
EFF_TRANSLATION[int(self._device.efficiency)]
|
EFF_TRANSLATION[int(self.coordinator.data['eff'])]
|
||||||
)
|
)
|
||||||
+74
-21
@@ -1,4 +1,4 @@
|
|||||||
""" Implementation du composant sensors """
|
""" for sensors components """
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
@@ -11,20 +11,35 @@ from homeassistant.components.sensor import (
|
|||||||
SensorDeviceClass,
|
SensorDeviceClass,
|
||||||
SensorStateClass,
|
SensorStateClass,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from homeassistant.helpers.update_coordinator import (
|
||||||
|
CoordinatorEntity,
|
||||||
|
)
|
||||||
|
|
||||||
from homeassistant.helpers.event import (
|
from homeassistant.helpers.event import (
|
||||||
async_track_time_interval,
|
async_track_time_interval,
|
||||||
async_track_state_change_event,
|
async_track_state_change_event,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from homeassistant.const import (
|
||||||
|
ATTR_TEMPERATURE,
|
||||||
|
UnitOfTime,
|
||||||
|
UnitOfTemperature
|
||||||
|
)
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
DOMAIN
|
DOMAIN
|
||||||
)
|
)
|
||||||
from homeassistant.const import UnitOfTime
|
|
||||||
|
from .coordinator import KoolnovaCoordinator
|
||||||
|
|
||||||
from .koolnova.device import (
|
from .koolnova.device import (
|
||||||
Koolnova,
|
Koolnova,
|
||||||
Engine,
|
Engine,
|
||||||
)
|
)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30)
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant,
|
async def async_setup_entry(hass: HomeAssistant,
|
||||||
entry: ConfigEntry,
|
entry: ConfigEntry,
|
||||||
@@ -32,19 +47,18 @@ async def async_setup_entry(hass: HomeAssistant,
|
|||||||
""" Configuration des entités sensor à partir de la configuration
|
""" Configuration des entités sensor à partir de la configuration
|
||||||
ConfigEntry passée en argument
|
ConfigEntry passée en argument
|
||||||
"""
|
"""
|
||||||
_LOGGER.debug("Calling async_setup_entry - datas: {}".format(entry.data))
|
|
||||||
_LOGGER.debug("HASS data: {}".format(hass.data[DOMAIN]))
|
device = hass.data[DOMAIN]["device"]
|
||||||
for device in hass.data[DOMAIN]:
|
coordinator = hass.data[DOMAIN]["coordinator"]
|
||||||
_LOGGER.debug("Device: {}".format(device))
|
entities = [
|
||||||
entities = [
|
DiagnosticsSensor(device, "Device", entry.data),
|
||||||
DiagnosticsSensor(device, "Device", entry.data),
|
DiagnosticsSensor(device, "Address", entry.data),
|
||||||
DiagnosticsSensor(device, "Address", entry.data),
|
DiagModbusSensor(device, entry.data),
|
||||||
DiagModbusSensor(device, entry.data),
|
]
|
||||||
]
|
for engine in device.engines:
|
||||||
for engine in device.engines:
|
entities.append(DiagEngineThroughputSensor(coordinator, device, engine))
|
||||||
_LOGGER.debug("Engine: {}".format(engine))
|
entities.append(DiagEngineTempOrderSensor(coordinator, device, engine))
|
||||||
entities.append(DiagEngineSensor(device, engine))
|
async_add_entities(entities)
|
||||||
async_add_entities(entities)
|
|
||||||
|
|
||||||
class DiagnosticsSensor(SensorEntity):
|
class DiagnosticsSensor(SensorEntity):
|
||||||
# pylint: disable = too-many-instance-attributes
|
# pylint: disable = too-many-instance-attributes
|
||||||
@@ -104,30 +118,69 @@ class DiagModbusSensor(SensorEntity):
|
|||||||
""" Do not poll for those entities """
|
""" Do not poll for those entities """
|
||||||
return False
|
return False
|
||||||
|
|
||||||
class DiagEngineSensor(SensorEntity):
|
class DiagEngineThroughputSensor(CoordinatorEntity, SensorEntity):
|
||||||
# pylint: disable = too-many-instance-attributes
|
# pylint: disable = too-many-instance-attributes
|
||||||
""" Representation of a Sensor """
|
""" Representation of a Sensor """
|
||||||
|
|
||||||
_attr_entity_category: EntityCategory | None = EntityCategory.DIAGNOSTIC
|
_attr_entity_category: EntityCategory | None = EntityCategory.DIAGNOSTIC
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
|
coordinator: KoolnovaCoordinator, # pylint: disable=unused-argument
|
||||||
device: Koolnova, # pylint: disable=unused-argument
|
device: Koolnova, # pylint: disable=unused-argument
|
||||||
engine: Engine, # pylint: disable=unused-argument
|
engine: Engine, # pylint: disable=unused-argument
|
||||||
) -> None:
|
) -> None:
|
||||||
""" Class constructor """
|
""" Class constructor """
|
||||||
|
super().__init__(coordinator)
|
||||||
self._device = device
|
self._device = device
|
||||||
self._engine = engine
|
self._engine = engine
|
||||||
self._attr_name = f"{device.name} Engine AC{engine.engine_id} Throughput"
|
self._attr_name = f"{device.name} Engine AC{engine.engine_id} Throughput"
|
||||||
self._attr_entity_registry_enabled_default = True
|
self._attr_entity_registry_enabled_default = True
|
||||||
self._attr_device_info = self._device.device_info
|
self._attr_device_info = self._device.device_info
|
||||||
self._attr_unique_id = f"{DOMAIN}-Engine-AC{engine.engine_id}-throughput-sensor"
|
self._attr_unique_id = f"{DOMAIN}-Engine-AC{engine.engine_id}-throughput-sensor"
|
||||||
self._attr_native_value = f"TEST"
|
self._attr_native_value = "{}".format(engine.throughput)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def icon(self) -> str | None:
|
def icon(self) -> str | None:
|
||||||
return "mdi:monitor"
|
return "mdi:thermostat-cog"
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _handle_coordinator_update(self) -> None:
|
||||||
|
""" Handle updated data from the coordinator """
|
||||||
|
for _cur_engine in self.coordinator.data['engines']:
|
||||||
|
if self._engine.engine_id == _cur_engine.engine_id:
|
||||||
|
self._attr_native_value = "{}".format(_cur_engine.throughput)
|
||||||
|
self.async_write_ha_state()
|
||||||
|
|
||||||
|
class DiagEngineTempOrderSensor(CoordinatorEntity, SensorEntity):
|
||||||
|
# pylint: disable = too-many-instance-attributes
|
||||||
|
""" Representation of a Sensor """
|
||||||
|
|
||||||
|
_attr_entity_category: EntityCategory | None = EntityCategory.DIAGNOSTIC
|
||||||
|
_attr_native_unit_of_measurement: str = UnitOfTemperature.CELSIUS
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
coordinator: KoolnovaCoordinator, # pylint: disable=unused-argument
|
||||||
|
device: Koolnova, # pylint: disable=unused-argument
|
||||||
|
engine: Engine, # pylint: disable=unused-argument
|
||||||
|
) -> None:
|
||||||
|
""" Class constructor """
|
||||||
|
super().__init__(coordinator)
|
||||||
|
self._device = device
|
||||||
|
self._engine = engine
|
||||||
|
self._attr_name = f"{device.name} Engine AC{engine.engine_id} Temperature Order"
|
||||||
|
self._attr_entity_registry_enabled_default = True
|
||||||
|
self._attr_device_info = self._device.device_info
|
||||||
|
self._attr_unique_id = f"{DOMAIN}-Engine-AC{engine.engine_id}-temp-order-sensor"
|
||||||
|
self._attr_native_value = "{}".format(engine.order_temp)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def should_poll(self) -> bool:
|
def icon(self) -> str | None:
|
||||||
""" Do not poll for those entities """
|
return "mdi:thermometer-lines"
|
||||||
return False
|
|
||||||
|
@callback
|
||||||
|
def _handle_coordinator_update(self) -> None:
|
||||||
|
""" Handle updated data from the coordinator """
|
||||||
|
for _cur_engine in self.coordinator.data['engines']:
|
||||||
|
if self._engine.engine_id == _cur_engine.engine_id:
|
||||||
|
self._attr_native_value = "{}".format(_cur_engine.order_temp)
|
||||||
|
self.async_write_ha_state()
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
"Parity": "Parity",
|
"Parity": "Parity",
|
||||||
"Stopbits": "Stopbits",
|
"Stopbits": "Stopbits",
|
||||||
"Timeout": "Timeout",
|
"Timeout": "Timeout",
|
||||||
"DiscoverArea": "Area discovery"
|
"Debug": "Debug"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"areas": {
|
"areas": {
|
||||||
@@ -23,16 +23,16 @@
|
|||||||
"description": "Information sur la zone à configurer",
|
"description": "Information sur la zone à configurer",
|
||||||
"data": {
|
"data": {
|
||||||
"Name": "Name",
|
"Name": "Name",
|
||||||
"Zone_id": "Zone_id",
|
"Area_id": "Identifiant de la zone",
|
||||||
"Other_area": "Ajouter une nouvelle zone"
|
"Other_area": "Ajouter une nouvelle zone"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"cannot_connect": "Cannot connected to Koolnova system",
|
"cannot_connect": "Cannot connected to Koolnova system",
|
||||||
"area_not_registered": "Area is not registered to the Koolnova system",
|
"area_not_registered": "This Area is not registered to the Koolnova system",
|
||||||
"area_already_configured": "Area is already configured",
|
"area_already_configured": "This Area is already configured",
|
||||||
"zone_id_error": "Zone Id must an integer between 1 and 16"
|
"zone_id_error": "Area Id must an integer between 1 and 16"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+26
-21
@@ -3,16 +3,22 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant, callback, Event, State
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.components.switch import SwitchEntity
|
from homeassistant.components.switch import SwitchEntity
|
||||||
from homeassistant.util import Throttle
|
from homeassistant.util import Throttle
|
||||||
|
from homeassistant.helpers.update_coordinator import (
|
||||||
|
CoordinatorEntity,
|
||||||
|
)
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
MIN_TIME_BETWEEN_UPDATES,
|
MIN_TIME_BETWEEN_UPDATES,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from .coordinator import KoolnovaCoordinator
|
||||||
|
|
||||||
from homeassistant.const import UnitOfTime
|
from homeassistant.const import UnitOfTime
|
||||||
from .koolnova.device import Koolnova
|
from .koolnova.device import Koolnova
|
||||||
from .koolnova.const import (
|
from .koolnova.const import (
|
||||||
@@ -27,21 +33,23 @@ async def async_setup_entry(hass: HomeAssistant,
|
|||||||
):
|
):
|
||||||
""" Setup switch entries """
|
""" Setup switch entries """
|
||||||
|
|
||||||
for device in hass.data[DOMAIN]:
|
device = hass.data[DOMAIN]["device"]
|
||||||
_LOGGER.debug("Device: {}".format(device))
|
coordinator = hass.data[DOMAIN]["coordinator"]
|
||||||
entities = [
|
|
||||||
SystemStateSwitch(device),
|
|
||||||
]
|
|
||||||
async_add_entities(entities)
|
|
||||||
|
|
||||||
class SystemStateSwitch(SwitchEntity):
|
entities = [
|
||||||
|
SystemStateSwitch(coordinator, device),
|
||||||
|
]
|
||||||
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
class SystemStateSwitch(CoordinatorEntity, SwitchEntity):
|
||||||
"""Select component to set system state """
|
"""Select component to set system state """
|
||||||
_attr_has_entity_name = True
|
_attr_has_entity_name = True
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
|
coordinator: KoolnovaCoordinator, # pylint: disable=unused-argument
|
||||||
device: Koolnova, # pylint: disable=unused-argument,
|
device: Koolnova, # pylint: disable=unused-argument,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__()
|
super().__init__(coordinator)
|
||||||
self._device = device
|
self._device = device
|
||||||
self._attr_name = f"{device.name} Global HVAC State"
|
self._attr_name = f"{device.name} Global HVAC State"
|
||||||
self._attr_device_info = device.device_info
|
self._attr_device_info = device.device_info
|
||||||
@@ -50,18 +58,20 @@ class SystemStateSwitch(SwitchEntity):
|
|||||||
|
|
||||||
async def async_turn_on(self, **kwargs):
|
async def async_turn_on(self, **kwargs):
|
||||||
""" Turn the entity on. """
|
""" Turn the entity on. """
|
||||||
self._is_on = True
|
_LOGGER.debug("Turn on system")
|
||||||
await self._device.set_sys_state(SysState.SYS_STATE_ON)
|
await self._device.set_sys_state(SysState.SYS_STATE_ON)
|
||||||
|
self._is_on = True
|
||||||
|
|
||||||
async def async_turn_off(self, **kwargs):
|
async def async_turn_off(self, **kwargs):
|
||||||
""" Turn the entity off. """
|
""" Turn the entity off. """
|
||||||
self._is_on = False
|
_LOGGER.debug("Turn off system")
|
||||||
await self._device.set_sys_state(SysState.SYS_STATE_OFF)
|
await self._device.set_sys_state(SysState.SYS_STATE_OFF)
|
||||||
|
self._is_on = False
|
||||||
|
|
||||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
@callback
|
||||||
async def async_update(self):
|
def _handle_coordinator_update(self) -> None:
|
||||||
""" Retrieve latest state. """
|
""" Handle updated data from the coordinator """
|
||||||
self._is_on = bool(int(self._device.sys_state))
|
self._is_on = bool(int(self.coordinator.data['sys']))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_on(self):
|
||||||
@@ -71,9 +81,4 @@ class SystemStateSwitch(SwitchEntity):
|
|||||||
@property
|
@property
|
||||||
def icon(self) -> str | None:
|
def icon(self) -> str | None:
|
||||||
"""Icon of the entity."""
|
"""Icon of the entity."""
|
||||||
return "mdi:power"
|
return "mdi:power"
|
||||||
|
|
||||||
@property
|
|
||||||
def should_poll(self) -> bool:
|
|
||||||
""" Do not poll for this entity """
|
|
||||||
return False
|
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
"Parity": "Parité",
|
"Parity": "Parité",
|
||||||
"Stopbits": "Nombre de bits de stop",
|
"Stopbits": "Nombre de bits de stop",
|
||||||
"Timeout": "Timeout",
|
"Timeout": "Timeout",
|
||||||
"DiscoverArea": "Type de découverte de zones"
|
"Debug": "Debug"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"areas": {
|
"areas": {
|
||||||
@@ -23,16 +23,16 @@
|
|||||||
"description": "Information sur la zone à configurer",
|
"description": "Information sur la zone à configurer",
|
||||||
"data": {
|
"data": {
|
||||||
"Name": "Nom de la zone",
|
"Name": "Nom de la zone",
|
||||||
"Zone_id": "Zone_id",
|
"Area_id": "Identifiant de la zone",
|
||||||
"Other_area": "Ajouter une nouvelle zone"
|
"Other_area": "Ajouter une nouvelle zone"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"cannot_connect": "Cannot connected to Koolnova system",
|
"cannot_connect": "Cannot connected to Koolnova system",
|
||||||
"area_not_registered": "Area is not registered to the Koolnova system",
|
"area_not_registered": "This Area is not registered to the Koolnova system",
|
||||||
"area_already_configured": "Area is already configured",
|
"area_already_configured": "This Area is already configured",
|
||||||
"zone_id_error": "Zone Id must an integer between 1 and 16"
|
"zone_id_error": "Area Id must an integer between 1 and 16"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user