Flexit component fix for updated modbus ()

* pyflexit first argument should be a ModbusSerialClient
This component broke with 2021.6
I have tested this patch on my setup and it restores functionality

* Implemented async reading of modbus values
Stopped using pyflexit as this is outdated and not needed
Instead using async_pymodbus_call from ModbusHub class

* Bugfix: Reading fan mode from wrong register

* Implemented async writing
Set target temperature and fan mode using modbus call
Added some error handling

* No longer require pyflexit

* Review comments.

Co-authored-by: jan Iversen <jancasacondor@gmail.com>
This commit is contained in:
Jørgen Rørvik 2021-08-07 09:45:53 +02:00 committed by GitHub
parent 0b52e13eb8
commit 6830eec549
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 119 additions and 34 deletions
homeassistant/components/flexit
requirements_all.txt

View file

@ -3,7 +3,6 @@ from __future__ import annotations
import logging import logging
from pyflexit.pyflexit import pyflexit
import voluptuous as vol import voluptuous as vol
from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateEntity from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateEntity
@ -12,7 +11,15 @@ from homeassistant.components.climate.const import (
SUPPORT_FAN_MODE, SUPPORT_FAN_MODE,
SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE,
) )
from homeassistant.components.modbus.const import CONF_HUB, DEFAULT_HUB, MODBUS_DOMAIN from homeassistant.components.modbus.const import (
CALL_TYPE_REGISTER_HOLDING,
CALL_TYPE_REGISTER_INPUT,
CALL_TYPE_WRITE_REGISTER,
CONF_HUB,
DEFAULT_HUB,
MODBUS_DOMAIN,
)
from homeassistant.components.modbus.modbus import ModbusHub
from homeassistant.const import ( from homeassistant.const import (
ATTR_TEMPERATURE, ATTR_TEMPERATURE,
CONF_NAME, CONF_NAME,
@ -20,7 +27,9 @@ from homeassistant.const import (
DEVICE_DEFAULT_NAME, DEVICE_DEFAULT_NAME,
TEMP_CELSIUS, TEMP_CELSIUS,
) )
from homeassistant.core import HomeAssistant
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{ {
@ -35,18 +44,25 @@ _LOGGER = logging.getLogger(__name__)
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE
def setup_platform(hass, config, add_entities, discovery_info=None): async def async_setup_platform(
hass: HomeAssistant,
config: ConfigType,
async_add_entities,
discovery_info: DiscoveryInfoType = None,
):
"""Set up the Flexit Platform.""" """Set up the Flexit Platform."""
modbus_slave = config.get(CONF_SLAVE) modbus_slave = config.get(CONF_SLAVE)
name = config.get(CONF_NAME) name = config.get(CONF_NAME)
hub = hass.data[MODBUS_DOMAIN][config.get(CONF_HUB)] hub = hass.data[MODBUS_DOMAIN][config.get(CONF_HUB)]
add_entities([Flexit(hub, modbus_slave, name)], True) async_add_entities([Flexit(hub, modbus_slave, name)], True)
class Flexit(ClimateEntity): class Flexit(ClimateEntity):
"""Representation of a Flexit AC unit.""" """Representation of a Flexit AC unit."""
def __init__(self, hub, modbus_slave, name): def __init__(
self, hub: ModbusHub, modbus_slave: int | None, name: str | None
) -> None:
"""Initialize the unit.""" """Initialize the unit."""
self._hub = hub self._hub = hub
self._name = name self._name = name
@ -64,34 +80,65 @@ class Flexit(ClimateEntity):
self._heating = None self._heating = None
self._cooling = None self._cooling = None
self._alarm = False self._alarm = False
self.unit = pyflexit(hub, modbus_slave) self._outdoor_air_temp = None
@property @property
def supported_features(self): def supported_features(self):
"""Return the list of supported features.""" """Return the list of supported features."""
return SUPPORT_FLAGS return SUPPORT_FLAGS
def update(self): async def async_update(self):
"""Update unit attributes.""" """Update unit attributes."""
if not self.unit.update(): self._target_temperature = await self._async_read_temp_from_register(
_LOGGER.warning("Modbus read failed") CALL_TYPE_REGISTER_HOLDING, 8
)
self._current_temperature = await self._async_read_temp_from_register(
CALL_TYPE_REGISTER_INPUT, 9
)
res = await self._async_read_int16_from_register(CALL_TYPE_REGISTER_HOLDING, 17)
if res < len(self._fan_modes):
self._current_fan_mode = res
self._filter_hours = await self._async_read_int16_from_register(
CALL_TYPE_REGISTER_INPUT, 8
)
# # Mechanical heat recovery, 0-100%
self._heat_recovery = await self._async_read_int16_from_register(
CALL_TYPE_REGISTER_INPUT, 14
)
# # Heater active 0-100%
self._heating = await self._async_read_int16_from_register(
CALL_TYPE_REGISTER_INPUT, 15
)
# # Cooling active 0-100%
self._cooling = await self._async_read_int16_from_register(
CALL_TYPE_REGISTER_INPUT, 13
)
# # Filter alarm 0/1
self._filter_alarm = await self._async_read_int16_from_register(
CALL_TYPE_REGISTER_INPUT, 27
)
# # Heater enabled or not. Does not mean it's necessarily heating
self._heater_enabled = await self._async_read_int16_from_register(
CALL_TYPE_REGISTER_INPUT, 28
)
self._outdoor_air_temp = await self._async_read_temp_from_register(
CALL_TYPE_REGISTER_INPUT, 11
)
self._target_temperature = self.unit.get_target_temp actual_air_speed = await self._async_read_int16_from_register(
self._current_temperature = self.unit.get_temp CALL_TYPE_REGISTER_INPUT, 48
self._current_fan_mode = self._fan_modes[self.unit.get_fan_speed] )
self._filter_hours = self.unit.get_filter_hours
# Mechanical heat recovery, 0-100% if self._heating:
self._heat_recovery = self.unit.get_heat_recovery self._current_operation = "Heating"
# Heater active 0-100% elif self._cooling:
self._heating = self.unit.get_heating self._current_operation = "Cooling"
# Cooling active 0-100% elif self._heat_recovery:
self._cooling = self.unit.get_cooling self._current_operation = "Recovering"
# Filter alarm 0/1 elif actual_air_speed:
self._filter_alarm = self.unit.get_filter_alarm self._current_operation = "Fan Only"
# Heater enabled or not. Does not mean it's necessarily heating else:
self._heater_enabled = self.unit.get_heater_enabled self._current_operation = "Off"
# Current operation mode
self._current_operation = self.unit.get_operation
@property @property
def extra_state_attributes(self): def extra_state_attributes(self):
@ -103,6 +150,7 @@ class Flexit(ClimateEntity):
"heating": self._heating, "heating": self._heating,
"heater_enabled": self._heater_enabled, "heater_enabled": self._heater_enabled,
"cooling": self._cooling, "cooling": self._cooling,
"outdoor_air_temp": self._outdoor_air_temp,
} }
@property @property
@ -153,12 +201,53 @@ class Flexit(ClimateEntity):
"""Return the list of available fan modes.""" """Return the list of available fan modes."""
return self._fan_modes return self._fan_modes
def set_temperature(self, **kwargs): async def async_set_temperature(self, **kwargs):
"""Set new target temperature.""" """Set new target temperature."""
if kwargs.get(ATTR_TEMPERATURE) is not None: if kwargs.get(ATTR_TEMPERATURE) is not None:
self._target_temperature = kwargs.get(ATTR_TEMPERATURE) target_temperature = kwargs.get(ATTR_TEMPERATURE)
self.unit.set_temp(self._target_temperature) else:
_LOGGER.error("Received invalid temperature")
return
def set_fan_mode(self, fan_mode): if await self._async_write_int16_to_register(8, target_temperature * 10):
self._target_temperature = target_temperature
else:
_LOGGER.error("Modbus error setting target temperature to Flexit")
async def async_set_fan_mode(self, fan_mode):
"""Set new fan mode.""" """Set new fan mode."""
self.unit.set_fan_speed(self._fan_modes.index(fan_mode)) if await self._async_write_int16_to_register(
17, self.fan_modes.index(fan_mode)
):
self._current_fan_mode = self.fan_modes.index(fan_mode)
else:
_LOGGER.error("Modbus error setting fan mode to Flexit")
# Based on _async_read_register in ModbusThermostat class
async def _async_read_int16_from_register(self, register_type, register) -> int:
"""Read register using the Modbus hub slave."""
result = await self._hub.async_pymodbus_call(
self._slave, register, 1, register_type
)
if result is None:
_LOGGER.error("Error reading value from Flexit modbus adapter")
return -1
return int(result.registers[0])
async def _async_read_temp_from_register(self, register_type, register) -> float:
result = float(
await self._async_read_int16_from_register(register_type, register)
)
if result == -1:
return -1
return result / 10.0
async def _async_write_int16_to_register(self, register, value) -> bool:
value = int(value)
result = await self._hub.async_pymodbus_call(
self._slave, register, value, CALL_TYPE_WRITE_REGISTER
)
if result == -1:
return False
return True

View file

@ -2,7 +2,6 @@
"domain": "flexit", "domain": "flexit",
"name": "Flexit", "name": "Flexit",
"documentation": "https://www.home-assistant.io/integrations/flexit", "documentation": "https://www.home-assistant.io/integrations/flexit",
"requirements": ["pyflexit==0.3"],
"dependencies": ["modbus"], "dependencies": ["modbus"],
"codeowners": [], "codeowners": [],
"iot_class": "local_polling" "iot_class": "local_polling"

View file

@ -1437,9 +1437,6 @@ pyfido==2.1.1
# homeassistant.components.fireservicerota # homeassistant.components.fireservicerota
pyfireservicerota==0.0.43 pyfireservicerota==0.0.43
# homeassistant.components.flexit
pyflexit==0.3
# homeassistant.components.flic # homeassistant.components.flic
pyflic==2.0.3 pyflic==2.0.3