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."""
|
||||
|
||||
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",
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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