update HA interface source files
This commit is contained in:
@@ -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()
|
||||||
|
|
||||||
|
|||||||
@@ -1,55 +1,85 @@
|
|||||||
"""Constants used by the koolnova-bms component."""
|
"""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"
|
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
|
ATTR_INSIDE_TEMPERATURE = "inside_temperature"
|
||||||
REG_ENABLED = 1
|
|
||||||
REG_MODE = 2
|
|
||||||
REG_TARGET_TEMP = 3
|
|
||||||
REG_CURRENT_TEMP = 4
|
|
||||||
|
|
||||||
REG_AIRFLOW = 65
|
SENSOR_TYPE_TEMPERATURE = "temperature"
|
||||||
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
|
|
||||||
|
|
||||||
FIRST_ZONE_REGISTER = REG_ENABLED
|
SENSOR_TYPES = {
|
||||||
TOTAL_ZONE_REGISTERS = NUM_ZONES * REG_PER_ZONE
|
ATTR_INSIDE_TEMPERATURE: {
|
||||||
FIRST_SYS_REGISTER = REG_AIRFLOW
|
CONF_NAME: "Inside Temperature",
|
||||||
TOTAL_SYS_REGISTERS = 18
|
CONF_ICON: "mdi:thermometer",
|
||||||
|
CONF_TYPE: SENSOR_TYPE_TEMPERATURE,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
FAN_OFF = 0
|
SUPPORT_FLAGS = (
|
||||||
FAN_LOW = 1
|
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||||
FAN_MED = 2
|
| ClimateEntityFeature.FAN_MODE
|
||||||
FAN_HIGH = 3
|
)
|
||||||
FAN_AUTO = 4
|
|
||||||
|
|
||||||
MODE_AIR_COOLING = 0x01
|
SUPPORTED_HVAC_MODES = [
|
||||||
MODE_AIR_HEATING = 0x02
|
HVACMode.OFF,
|
||||||
MODE_UNDERFLOOR_HEATING = 0x04
|
HVACMode.AUTO,
|
||||||
MODE_UNDERFLOOR_AIR_COOLING = 0x05
|
HVACMode.COOL,
|
||||||
MODE_UNDERFLOOR_AIR_HEATING = 0x06
|
HVACMode.DRY,
|
||||||
|
HVACMode.HEAT,
|
||||||
|
HVACMode.FAN_ONLY,
|
||||||
|
]
|
||||||
|
|
||||||
HOLD_MODE_UNDERFLOOR_ONLY = "underfloor"
|
HVAC_TRANSLATION = {
|
||||||
HOLD_MODE_FAN_ONLY = "fan"
|
HVAC_MODE_AUTO: 0,
|
||||||
HOLD_MODE_UNDERFLOOR_AND_FAN = "underfloor and fan"
|
HVAC_MODE_COOL: 1,
|
||||||
|
HVAC_MODE_HEAT: 2,
|
||||||
|
HVAC_MODE_FAN_ONLY: 3,
|
||||||
|
HVAC_MODE_DRY: 4,
|
||||||
|
}
|
||||||
|
|
||||||
HVAC_MODE_OFF = "off"
|
FAN_MODE_1 = "1 Lowest"
|
||||||
HVAC_MODE_COOL = "cool"
|
FAN_MODE_2 = "2 Low"
|
||||||
HVAC_MODE_HEAT = "heat"
|
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
|
SUPPORTED_FAN_MODES = [
|
||||||
AC2 = 2
|
FAN_AUTO,
|
||||||
AC3 = 3
|
FAN_MODE_1,
|
||||||
AC4 = 4
|
FAN_MODE_2,
|
||||||
|
FAN_MODE_3,
|
||||||
|
FAN_MODE_4,
|
||||||
|
]
|
||||||
|
|
||||||
HA_COMPONENT_SENSOR = "sensor"
|
OPERATION_LIST = {
|
||||||
HA_COMPONENT_CLIMATE = "climate"
|
# 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",
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
{
|
{
|
||||||
"domain": "koolnova_bms",
|
"domain": "koolnova_bms",
|
||||||
"name": "koolnova BMS Modbus",
|
"name": "koolnova BMS Modbus RTU",
|
||||||
"codeowners": ["@sinseman44"],
|
"codeowners": ["@vbenoit"],
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://git.nas.benserv.fr/vincent/koolnova-BMS-Integration/src/branch/main/README.md",
|
"documentation": "https://git.nas.benserv.fr/vincent/koolnova-BMS-Integration/src/branch/main/README.md",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"issue_tracker": "https://git.nas.benserv.fr/vincent/koolnova-BMS-Integration/issues",
|
"issue_tracker": "https://git.nas.benserv.fr/vincent/koolnova-BMS-Integration/issues",
|
||||||
|
"integration_type": "hub",
|
||||||
"requirements": [
|
"requirements": [
|
||||||
"pymodbus>=3.5.0"
|
"pymodbus>=3.5.4",
|
||||||
|
"pyserial>=3.5"
|
||||||
],
|
],
|
||||||
"version": "0.1.0"
|
"version": "0.1.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user