Skip to content

Commit

Permalink
Many improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
BenPru committed Oct 26, 2021
1 parent 2c87327 commit e01b307
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 61 deletions.
52 changes: 49 additions & 3 deletions custom_components/luxtronik/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Support for Luxtronik heatpump controllers."""
# region Imports
import threading
import time
from datetime import timedelta
from typing import Optional

Expand All @@ -9,6 +10,7 @@
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP
from homeassistant.core import Event, HomeAssistant
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.update_coordinator import (CoordinatorEntity,
DataUpdateCoordinator)
from homeassistant.util import Throttle
Expand All @@ -19,7 +21,9 @@
from .const import (ATTR_PARAMETER, ATTR_VALUE, CONF_CALCULATIONS,
CONF_COORDINATOR, CONF_LOCK_TIMEOUT, CONF_PARAMETERS,
CONF_SAFE, CONF_UPDATE_IMMEDIATELY_AFTER_WRITE,
CONF_VISIBILITIES, DEFAULT_PORT, DOMAIN, LOGGER, PLATFORMS)
CONF_VISIBILITIES, DEFAULT_PORT, DOMAIN, LOGGER,
LUX_SENSOR_DETECT_COOLING, PLATFORMS)

# endregion Imports

# region Constants
Expand Down Expand Up @@ -120,8 +124,22 @@ def setup_internal(hass, conf):
update_immediately_after_write = conf[CONF_UPDATE_IMMEDIATELY_AFTER_WRITE]

luxtronik = LuxtronikDevice(host, port, safe, lock_timeout)
luxtronik.read()

hass.data[DOMAIN] = luxtronik
# Create DeviceInfos:
sn = luxtronik.get_value('parameters.ID_WP_SerienNummer_DATUM')
hass.data[f"{DOMAIN}_DeviceInfo"] = build_device_info(luxtronik, sn)
hass.data[f"{DOMAIN}_DeviceInfo_Domestic_Water"] = DeviceInfo(
identifiers={(DOMAIN, 'Domestic_Water', sn)},
default_name='Domestic Water')
hass.data[f"{DOMAIN}_DeviceInfo_Heating"] = DeviceInfo(
identifiers={(DOMAIN, 'Heating', sn)},
default_name='Heating')
if luxtronik.get_value(LUX_SENSOR_DETECT_COOLING):
hass.data[f"{DOMAIN}_DeviceInfo_Cooling"] = DeviceInfo(
identifiers={(DOMAIN, 'Cooling', sn)},
default_name='Cooling')

def write_parameter(service):
"""Write a parameter to the Luxtronik heatpump."""
Expand Down Expand Up @@ -183,8 +201,6 @@ def write(self, parameter, value, update_immediately_after_write):
if self.lock.acquire(blocking=True, timeout=self._lock_timeout_sec):
self._luxtronik.parameters.set(parameter, value)
self._luxtronik.write()
if update_immediately_after_write:
self._luxtronik.read()
else:
LOGGER.warning(
"Couldn't write luxtronik parameter %s with value %s because of lock timeout %s",
Expand All @@ -194,9 +210,15 @@ def write(self, parameter, value, update_immediately_after_write):
)
finally:
self.lock.release()
if update_immediately_after_write:
time.sleep(3)
self._luxtronik.read()

@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
self.read()

def read(self):
"""Get the data from Luxtronik."""
try:
if self.lock.acquire(blocking=True, timeout=self._lock_timeout_sec):
Expand All @@ -210,6 +232,30 @@ def update(self):
self.lock.release()


def build_device_info(luxtronik: LuxtronikDevice, sn: str) -> DeviceInfo:
model = luxtronik.get_value('calculations.ID_WEB_Code_WP_akt')
deviceInfo = DeviceInfo(
identifiers={(DOMAIN, 'Heatpump', sn)},
name=f"Heatpump S/N {sn}",
default_name='Heatpump',
default_manufacturer='Alpha Innotec',
manufacturer=get_manufacturer_by_model(model),
default_model='',
model=model,
sw_version=luxtronik.get_value('calculations.ID_WEB_SoftStand')
)
LOGGER.info("build_device_info '%s'", deviceInfo)
return deviceInfo


def get_manufacturer_by_model(model: str) -> str:
if model is None:
return None
if model.startswith('LD'):
return 'Novelan'
return None


async def async_unload_entry(hass, config_entry):
"""Unloading the luxtronik platforms."""

Expand Down
46 changes: 28 additions & 18 deletions custom_components/luxtronik/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
PRESET_BOOST, PRESET_NONE,
SUPPORT_PRESET_MODE,
SUPPORT_TARGET_TEMPERATURE)
from homeassistant.components.sensor import ENTITY_ID_FORMAT
from homeassistant.components.water_heater import ATTR_TEMPERATURE
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (CONF_SENSORS, PRECISION_HALVES,
Expand All @@ -39,32 +40,39 @@
# endregion Constants


async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
async def async_setup_platform(
hass: HomeAssistant, config: ConfigType, async_add_entities: AddEntitiesCallback, discovery_info: dict[str, Any] = None,
) -> None:
"""Set up a Luxtronik thermostat from ConfigEntry."""
LOGGER.info("climate.async_setup_entry 1")
# coordinator = hass.data[LUXTRONIK_DOMAIN][entry.entry_id][CONF_COORDINATOR]

luxtronik: LuxtronikDevice = hass.data.get(LUXTRONIK_DOMAIN)
LOGGER.info("climate.async_setup_entry 2")
if not luxtronik:
LOGGER.warning("climate.async_setup_entry no luxtronik!")
LOGGER.warning("climate.async_setup_platform no luxtronik!")
return False

deviceInfo = DeviceInfo(
identifiers={(LUXTRONIK_DOMAIN)},
name='Luxtronik',
)
# build_device_info(luxtronik)
deviceInfo = hass.data[f"{DOMAIN}_DeviceInfo"]
deviceInfoDomesticWater = hass.data[f"{DOMAIN}_DeviceInfo_Domestic_Water"]
deviceInfoHeating = hass.data[f"{DOMAIN}_DeviceInfo_Heating"]
entities = [
LuxtronikDomesticWaterThermostat(hass, luxtronik, deviceInfo)
# , LuxtronikHeatingThermostat(hass, luxtronik)
LuxtronikDomesticWaterThermostat(
hass, luxtronik, deviceInfoDomesticWater)
# , LuxtronikHeatingThermostat(hass, luxtronik, deviceInfoHeating)
]

if luxtronik.get_value(LUX_SENSOR_DETECT_COOLING):
entities.append(LuxtronikCoolingThermostat(hass, luxtronik))
deviceInfoCooling = hass.data[f"{DOMAIN}_DeviceInfo_Cooling"]
entities.append(LuxtronikCoolingThermostat(
hass, luxtronik, deviceInfoCooling))
async_add_entities(entities)


async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up a Luxtronik thermostat from ConfigEntry."""
LOGGER.info("climate.async_setup_entry entry: '%s'", entry)
await async_setup_platform(hass, {}, async_add_entities)


# class LuxtronikThermostat(CoordinatorEntity, ClimateEntity):
class LuxtronikThermostat(ClimateEntity):
"""Representation of a Luxtronik Thermostat device."""
Expand All @@ -88,6 +96,8 @@ def __init__(self, hass: HomeAssistant, luxtronik: LuxtronikDevice, deviceInfo:
self._hass = hass
self._luxtronik = luxtronik
self._attr_device_info = deviceInfo
self.entity_id = ENTITY_ID_FORMAT.format(
f"{LUXTRONIK_DOMAIN}_{self._attr_unique_id}")

@property
def hvac_action(self):
Expand Down Expand Up @@ -158,7 +168,7 @@ def __get_luxmode(self, hvac_mode: str, preset_mode: str) -> str:
async def async_set_hvac_mode(self, hvac_mode: str) -> None:
"""Set new operation mode."""
self._luxtronik.write(self._heater_sensor.split('.')[1],
self.__get_luxmode(hvac_mode, self.preset_mode), False)
self.__get_luxmode(hvac_mode, self.preset_mode), True)

@property
def preset_mode(self): # -> str | None:
Expand All @@ -177,7 +187,7 @@ def preset_mode(self): # -> str | None:
async def async_set_preset_mode(self, preset_mode: str) -> None:
"""Set preset mode."""
self._luxtronik.write(self._heater_sensor.split('.')[1],
self.__get_luxmode(self.hvac_mode, preset_mode), False)
self.__get_luxmode(self.hvac_mode, preset_mode), True)

# @property
# def extra_state_attributes(self) -> ClimateExtraAttributes:
Expand Down Expand Up @@ -216,7 +226,7 @@ async def async_set_preset_mode(self, preset_mode: str) -> None:

class LuxtronikDomesticWaterThermostat(LuxtronikThermostat):
_attr_unique_id: Final = 'domestic_water'
_name = "Domestic Water"
_attr_name = "Domestic Water"
_attr_icon = 'mdi:water-boiler'
_attr_device_class: Final = f"{LUXTRONIK_DOMAIN}__{_attr_unique_id}"

Expand Down
8 changes: 4 additions & 4 deletions custom_components/luxtronik/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,12 @@ def discover(self):
# if the response starts with the magic nonsense
if res.startswith("2500;111;"):
res = res.split(";")
port = None
LOGGER.debug(f"Received answer from {ip} \"{res}\"")
try:
port = int(res[2])
LOGGER.debug(f"Received answer from {ip}:{port} \"{res}\"")
except Exception as e:
LOGGER.debug(f"Received answer from {ip}:invalid_port \"{res}\"")
except ValueError:
LOGGER.debug("Response did not contain a valid port number, an old Luxtronic software version might be the reason.")
port = None
return (ip, port)
# if not, continue
else:
Expand Down
26 changes: 20 additions & 6 deletions custom_components/luxtronik/const.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Constants for the Paul Novus 300 Bus integration."""
import logging
from typing import Final
from typing import Dict, Final

DOMAIN: Final = "luxtronik2"

Expand Down Expand Up @@ -32,11 +32,25 @@
LUX_MODE_PARTY: Final = 'Party'
LUX_MODE_HOLIDAYS: Final = 'Holidays'

LUX_STATUS_NO_REQUEST: Final = 'no request'
LUX_STATUS_HEATING: Final = 'heating'
LUX_STATUS_DOMESTIC_WATER: Final = 'hot water'
LUX_STATUS_DEFROST: Final = 'defrost'
LUX_STATUS_EVU: Final = 'evu'
LUX_STATUS_HEATING: Final = 'heating' # 0
LUX_STATUS_DOMESTIC_WATER: Final = 'hot water' # 1
LUX_STATUS_SWIMMING_POOL_SOLAR: Final = 'swimming pool/solar' # 2
LUX_STATUS_EVU: Final = 'evu' # 3
LUX_STATUS_DEFROST: Final = 'defrost' # 4
LUX_STATUS_NO_REQUEST: Final = 'no request' # 5
LUX_STATUS_HEATING_EXTERNAL_SOURCE: Final = 'heating external source' # 6
LUX_STATUS_COOLING: Final = 'cooling' # 7

LUX_STATE_ICON_MAP: Dict[str, str] = {
LUX_STATUS_HEATING: 'mdi:radiator',
LUX_STATUS_DOMESTIC_WATER: 'mdi:waves',
LUX_STATUS_SWIMMING_POOL_SOLAR: None,
LUX_STATUS_EVU: 'mdi:power-plug-off',
LUX_STATUS_DEFROST: 'mdi:car-defrost-rear',
LUX_STATUS_NO_REQUEST: 'mdi:radiator-disabled',
LUX_STATUS_HEATING_EXTERNAL_SOURCE: None,
LUX_STATUS_COOLING: 'mdi:air-conditioner'
}

# region Luxtronik Sensor ids
LUX_SENSOR_DETECT_COOLING: Final = 'calculations.ID_WEB_FreigabKuehl'
Expand Down
Loading

0 comments on commit e01b307

Please sign in to comment.