update HA interface source files

This commit is contained in:
2023-11-01 11:02:24 +01:00
parent 4661609990
commit bd924ca909
4 changed files with 351 additions and 44 deletions

View File

@@ -0,0 +1,153 @@
""" for Climate integration."""
from __future__ import annotations
from datetime import timedelta
import logging
import voluptuous as vol
from homeassistant.components.climate import (
ClimateEntity,
ConfigEntry,
)
from homeassistant.components.climate.const import HVACMode, FAN_AUTO
from homeassistant.const import TEMP_CELSIUS, ATTR_TEMPERATURE
from homeassistant.util import Throttle
from homeassistant.const import CONF_HOST
from homeassistant.helpers import config_validation as cv, entity_platform
from .koolnova.device import MIN_TIME_BETWEEN_UPDATES, Koolnova
from .const import (
DOMAIN,
FAN_MODE_TRANSLATION,
HVAC_TRANSLATION,
SERVICE_SET_HORIZONTAL_SWING_MODE,
SERVICE_SET_VERTICAL_SWING_MODE,
SUPPORT_FLAGS,
SWING_HORIZONTAL_AUTO,
SWING_VERTICAL_AUTO,
SUPPORT_SWING_MODES,
SUPPORTED_FAN_MODES,
SUPPORTED_HVAC_MODES,
SWING_3D_AUTO,
SWING_MODE_TRANSLATION,
HORIZONTAL_SWING_MODE_TRANSLATION,
)
_LOGGER = logging.getLogger(__name__)
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=10)
async def async_setup_entry(hass, entry: ConfigEntry, async_add_entities):
"""Setup climate entities"""
for device in hass.data[DOMAIN]:
if device.host == entry.data[CONF_HOST]:
_LOGGER.info("Setup climate for: %s, %s", device.name, device.airco_id)
async_add_entities([KoolnovaZoneClimate(device)])
platform = entity_platform.async_get_current_platform()
class KoolnovaZoneClimate(ClimateEntity):
"""Representation of a climate entity"""
_attr_supported_features: int = SUPPORT_FLAGS
_attr_temperature_unit: str = TEMP_CELSIUS
_attr_hvac_modes: list[HVACMode] = SUPPORTED_HVAC_MODES
_attr_fan_modes: list[str] = SUPPORTED_FAN_MODES
_attr_hvac_mode: HVACMode = HVACMode.OFF
_attr_fan_mode: str = FAN_AUTO
_attr_min_temp: float = 16
_attr_max_temp: float = 30
def __init__(self, device: Device) -> None:
self._device = device
self._attr_name = device.name
self._attr_device_info = device.device_info
self._attr_unique_id = f"{DOMAIN}-{self._device.airco_id}-climate"
self._update_state()
async def async_set_temperature(self, **kwargs) -> None:
"""Set new target temperature."""
opts = {AirconCommands.PresetTemp: kwargs.get(ATTR_TEMPERATURE)}
if "hvac_mode" in kwargs:
hvac_mode = kwargs.get("hvac_mode")
opts.update(
{
AirconCommands.OperationMode: self._device.airco.OperationMode
if hvac_mode == HVACMode.OFF
else HVAC_TRANSLATION[hvac_mode],
AirconCommands.Operation: hvac_mode != HVACMode.OFF,
}
)
await self._device.set_airco(opts)
self._update_state()
async def async_set_fan_mode(self, fan_mode: str) -> None:
"""Set new target fan mode."""
await self._device.set_airco(
{AirconCommands.AirFlow: FAN_MODE_TRANSLATION[fan_mode]}
)
self._update_state()
async def async_turn_on(self) -> None:
"""Turn the entity on."""
await self._device.set_airco({AirconCommands.Operation: True})
self._update_state()
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
"""Set new target hvac mode."""
await self._device.set_airco(
{
AirconCommands.OperationMode: self._device.airco.OperationMode
if hvac_mode == HVACMode.OFF
else HVAC_TRANSLATION[hvac_mode],
AirconCommands.Operation: hvac_mode != HVACMode.OFF,
}
)
self._update_state()
async def async_turn_off(self) -> None:
"""Turn the entity off."""
await self._device.set_airco({AirconCommands.Operation: False})
self._update_state()
def _update_state(self) -> None:
"""Private update attributes"""
airco = self._device.airco
self._attr_target_temperature = airco.PresetTemp
self._attr_current_temperature = airco.IndoorTemp
self._attr_fan_mode = list(FAN_MODE_TRANSLATION.keys())[airco.AirFlow]
self._attr_swing_mode = (
SWING_3D_AUTO
if airco.Entrust
else list(SWING_MODE_TRANSLATION.keys())[airco.WindDirectionUD]
)
# self._attr_horizontal_swing_mode = list(
# HORIZONTAL_SWING_MODE_TRANSLATION.keys()
# )[airco.WindDirectionLR]
self._attr_hvac_mode = list(HVAC_TRANSLATION.keys())[airco.OperationMode]
if airco.Operation is False:
self._attr_hvac_mode = HVACMode.OFF
else:
_new_mode: HVACMode = None
_mode = airco.OperationMode
if _mode == 0:
_new_mode = HVACMode.AUTO
elif _mode == 1:
_new_mode = HVACMode.COOL
elif _mode == 2:
_new_mode = HVACMode.HEAT
elif _mode == 3:
_new_mode = HVACMode.FAN_ONLY
elif _mode == 4:
_new_mode = HVACMode.DRY
self._attr_hvac_mode = _new_mode
@Throttle(MIN_TIME_BETWEEN_UPDATES)
async def async_update(self):
"""Retrieve latest state."""
await self._device.update()
self._update_state()

View File

@@ -1,55 +1,85 @@
"""Constants used by the koolnova-bms component."""
from homeassistant.const import CONF_ICON, CONF_NAME, CONF_TYPE
from homeassistant.components.climate.const import (
HVAC_MODE_COOL,
HVAC_MODE_HEAT,
HVAC_MODE_DRY,
HVAC_MODE_FAN_ONLY,
HVAC_MODE_AUTO,
ClimateEntityFeature,
HVACMode,
FAN_AUTO,
)
DOMAIN = "koolnova_bms"
DEVICES = "wf-rac-devices"
NUM_ZONES = 16
CONF_OPERATOR_ID = "operator_id"
CONF_AIRCO_ID = "airco_id"
ATTR_DEVICE_ID = "device_id"
ATTR_CONNECTED_ACCOUNTS = "connected_accounts"
REG_PER_ZONE = 4
REG_ENABLED = 1
REG_MODE = 2
REG_TARGET_TEMP = 3
REG_CURRENT_TEMP = 4
ATTR_INSIDE_TEMPERATURE = "inside_temperature"
REG_AIRFLOW = 65
REG_AC_TARGET_TEMP = 69
REG_AC_TARGET_FAN_MODE = 73
REG_SERIAL_CONFIG = 77
REG_SLAVE_ID = 78
REG_EFFICIENCY = 79
REG_SYSTEM_ENABLED = 81
REG_SYS_KN_MODE = 82
SENSOR_TYPE_TEMPERATURE = "temperature"
FIRST_ZONE_REGISTER = REG_ENABLED
TOTAL_ZONE_REGISTERS = NUM_ZONES * REG_PER_ZONE
FIRST_SYS_REGISTER = REG_AIRFLOW
TOTAL_SYS_REGISTERS = 18
SENSOR_TYPES = {
ATTR_INSIDE_TEMPERATURE: {
CONF_NAME: "Inside Temperature",
CONF_ICON: "mdi:thermometer",
CONF_TYPE: SENSOR_TYPE_TEMPERATURE,
},
}
FAN_OFF = 0
FAN_LOW = 1
FAN_MED = 2
FAN_HIGH = 3
FAN_AUTO = 4
SUPPORT_FLAGS = (
ClimateEntityFeature.TARGET_TEMPERATURE
| ClimateEntityFeature.FAN_MODE
)
MODE_AIR_COOLING = 0x01
MODE_AIR_HEATING = 0x02
MODE_UNDERFLOOR_HEATING = 0x04
MODE_UNDERFLOOR_AIR_COOLING = 0x05
MODE_UNDERFLOOR_AIR_HEATING = 0x06
SUPPORTED_HVAC_MODES = [
HVACMode.OFF,
HVACMode.AUTO,
HVACMode.COOL,
HVACMode.DRY,
HVACMode.HEAT,
HVACMode.FAN_ONLY,
]
HOLD_MODE_UNDERFLOOR_ONLY = "underfloor"
HOLD_MODE_FAN_ONLY = "fan"
HOLD_MODE_UNDERFLOOR_AND_FAN = "underfloor and fan"
HVAC_TRANSLATION = {
HVAC_MODE_AUTO: 0,
HVAC_MODE_COOL: 1,
HVAC_MODE_HEAT: 2,
HVAC_MODE_FAN_ONLY: 3,
HVAC_MODE_DRY: 4,
}
HVAC_MODE_OFF = "off"
HVAC_MODE_COOL = "cool"
HVAC_MODE_HEAT = "heat"
FAN_MODE_1 = "1 Lowest"
FAN_MODE_2 = "2 Low"
FAN_MODE_3 = "3 High"
FAN_MODE_4 = "4 Highest"
ACMACHINES = 4
FAN_MODE_TRANSLATION = {
FAN_AUTO: 0,
FAN_MODE_1: 1,
FAN_MODE_2: 2,
FAN_MODE_3: 3,
FAN_MODE_4: 4,
}
AC1 = 1
AC2 = 2
AC3 = 3
AC4 = 4
SUPPORTED_FAN_MODES = [
FAN_AUTO,
FAN_MODE_1,
FAN_MODE_2,
FAN_MODE_3,
FAN_MODE_4,
]
HA_COMPONENT_SENSOR = "sensor"
HA_COMPONENT_CLIMATE = "climate"
OPERATION_LIST = {
# HVAC_MODE_OFF: "Off",
HVAC_MODE_HEAT: "Heat",
HVAC_MODE_COOL: "Cool",
HVAC_MODE_AUTO: "Auto",
HVAC_MODE_DRY: "Dry",
HVAC_MODE_FAN_ONLY: "Fan",
}

View File

@@ -1,13 +1,15 @@
{
"domain": "koolnova_bms",
"name": "koolnova BMS Modbus",
"codeowners": ["@sinseman44"],
"name": "koolnova BMS Modbus RTU",
"codeowners": ["@vbenoit"],
"config_flow": true,
"documentation": "https://git.nas.benserv.fr/vincent/koolnova-BMS-Integration/src/branch/main/README.md",
"iot_class": "local_polling",
"issue_tracker": "https://git.nas.benserv.fr/vincent/koolnova-BMS-Integration/issues",
"integration_type": "hub",
"requirements": [
"pymodbus>=3.5.0"
"pymodbus>=3.5.4",
"pyserial>=3.5"
],
"version": "0.1.0"
}

View File

@@ -0,0 +1,122 @@
""" for sensor integration. """
# pylint: disable = too-few-public-methods
from __future__ import annotations
from datetime import timedelta
import logging
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
SensorStateClass,
)
from homeassistant.const import (
TEMP_CELSIUS,
CONF_HOST,
CONF_ERROR,
)
from homeassistant.util import Throttle
from homeassistant.helpers.entity import EntityCategory
from .koolnova.device import Koolnova
from .const import (
DOMAIN,
ATTR_INSIDE_TEMPERATURE,
CONF_OPERATOR_ID,
CONF_AIRCO_ID,
ATTR_DEVICE_ID,
ATTR_CONNECTED_ACCOUNTS,
)
_LOGGER = logging.getLogger(__name__)
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30)
async def async_setup_entry(hass, entry, async_add_entities):
"""Setup sensor entries"""
for device in hass.data[DOMAIN]:
if device.host == entry.data[CONF_HOST]:
_LOGGER.info("Setup: %s, %s", device.name, device.airco_id)
entities = [
TemperatureSensor(device, "Indoor", ATTR_INSIDE_TEMPERATURE),
DiagnosticsSensor(device, "Airco ID", CONF_AIRCO_ID),
DiagnosticsSensor(device, "Operator ID", CONF_OPERATOR_ID, True),
DiagnosticsSensor(device, "Device ID", ATTR_DEVICE_ID, True),
DiagnosticsSensor(device, "IP", CONF_HOST, True),
DiagnosticsSensor(device, "Accounts", ATTR_CONNECTED_ACCOUNTS, True),
DiagnosticsSensor(device, "Error", CONF_ERROR),
]
if device.airco.Electric is not None:
entities.append(EnergySensor(device))
async_add_entities(entities)
class DiagnosticsSensor(SensorEntity):
# pylint: disable = too-many-instance-attributes
"""Representation of a Sensor."""
_attr_entity_category: EntityCategory | None = EntityCategory.DIAGNOSTIC
def __init__(self, device: Device, name: str, custom_type: str, enable=False) -> None:
"""Initialize the sensor."""
self._device = device
self._attr_name = f"{device.name} {name}"
self._attr_entity_registry_enabled_default = enable
self._custom_type = custom_type
self._attr_device_info = device.device_info
self._attr_native_unit_of_measurement = (
"Accounts" if custom_type == ATTR_CONNECTED_ACCOUNTS else None
)
self._attr_icon = (
"mdi:account-group" if custom_type == ATTR_CONNECTED_ACCOUNTS else None
)
self._attr_unique_id = (
f"{DOMAIN}-{self._device.airco_id}-{self._custom_type}-sensor"
)
self._update_state()
def _update_state(self) -> None:
if self._custom_type == CONF_OPERATOR_ID:
self._attr_native_value = self._device.operator_id
elif self._custom_type == CONF_AIRCO_ID:
self._attr_native_value = self._device.airco_id
elif self._custom_type == CONF_HOST:
self._attr_native_value = self._device.host
elif self._custom_type == ATTR_DEVICE_ID:
self._attr_native_value = self._device.device_id
elif self._custom_type == ATTR_CONNECTED_ACCOUNTS:
self._attr_native_value = self._device.num_accounts
elif self._custom_type == CONF_ERROR:
self._attr_native_value = self._device.airco.ErrorCode
async def async_update(self):
"""Retrieve latest state."""
self._update_state()
class TemperatureSensor(SensorEntity):
"""Representation of a Sensor."""
_attr_native_unit_of_measurement = TEMP_CELSIUS
_attr_device_class = SensorDeviceClass.TEMPERATURE
_attr_state_class = SensorStateClass.MEASUREMENT
def __init__(self, device: Device, name: str, custom_type: str) -> None:
"""Initialize the sensor."""
self._device = device
self._custom_type = custom_type
self._attr_name = f"{device.name} {name}"
self._attr_device_info = device.device_info
self._attr_unique_id = (
f"{DOMAIN}-{self._device.airco_id}-{self._custom_type}-sensor"
)
self._update_state()
def _update_state(self) -> None:
if self._custom_type == ATTR_INSIDE_TEMPERATURE:
self._attr_native_value = self._device.airco.IndoorTemp
@Throttle(MIN_TIME_BETWEEN_UPDATES)
async def async_update(self):
"""Retrieve latest state."""
self._update_state()