Skip to content

Commit

Permalink
use ACTIVE_PROFILE in climate presets (#90)
Browse files Browse the repository at this point in the history
* Use datetime for last_updated (time_initialized)

* Fix example

* Sort Fields in EntityDefinition

* use ACTIVE_PROFILE in climate presets

* Add TEMPERATURE as separate entities for Bidcos thermostats
  • Loading branch information
SukramJ authored Dec 24, 2021
1 parent 6cebedc commit d6b35b9
Show file tree
Hide file tree
Showing 11 changed files with 132 additions and 61 deletions.
8 changes: 8 additions & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
Version 0.4.0 (2021-12-24)
- Use datetime for last_updated (time_initialized)
- Fix example
- Add ACTUAL_TEMPERATURE as separate entity by @towo
- Add HEATING_COOLING to IPThermostat and Group
- Add (*)HUMIDITY and (*)TEMPERATURE as separate entities for Bidcos thermostats
- use ACTIVE_PROFILE in climate presets

Version 0.3.1 (2021-12-23)
- Make HmIP-BSM a switch (only dimable devices should be lights)

Expand Down
3 changes: 2 additions & 1 deletion example.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ def __init__(self):
def systemcallback(self, src, *args):
self.got_devices = True
print("systemcallback: %s" % src)
if src == const.HH_EVENT_NEW_DEVICES:
if src == const.HH_EVENT_NEW_DEVICES and args and args[0] and len(args[0]) > 0:
self.got_devices = True
print("Number of new device descriptions: %i" % len(args[0]))
return
elif src == const.HH_EVENT_DEVICES_CREATED:
Expand Down
3 changes: 2 additions & 1 deletion example_multi.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ def __init__(self):
def systemcallback(self, src, *args):
self.got_devices = True
print("systemcallback: %s" % src)
if src == const.HH_EVENT_NEW_DEVICES:
if src == const.HH_EVENT_NEW_DEVICES and args and args[0] and len(args[0]) > 0:
self.got_devices = True
print("Number of new device descriptions: %i" % len(args[0]))
return
elif src == const.HH_EVENT_DEVICES_CREATED:
Expand Down
25 changes: 13 additions & 12 deletions hahomematic/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
from __future__ import annotations

from abc import ABC, abstractmethod
from datetime import datetime, timedelta
import logging
import time
from typing import Any

from hahomematic import config
Expand All @@ -22,6 +22,7 @@
BACKEND_HOMEGEAR,
BACKEND_PYDEVCCU,
HM_VIRTUAL_REMOTES,
INIT_DATETIME,
PROXY_DE_INIT_FAILED,
PROXY_DE_INIT_SKIPPED,
PROXY_DE_INIT_SUCCESS,
Expand Down Expand Up @@ -58,7 +59,7 @@ def __init__(self, client_config: ClientConfig):
self._init_url: str = self._client_config.init_url
# for all device related interaction
self.proxy: XmlRpcProxy = self._client_config.xml_rpc_proxy
self.time_initialized: int = 0
self.last_updated: datetime = INIT_DATETIME
self._json_rpc_session: JsonRpcAioHttpClient = self._central.json_rpc_session

self._central.clients[self.interface_id] = self
Expand Down Expand Up @@ -96,9 +97,9 @@ async def proxy_init(self) -> int:
_LOGGER.exception(
"proxy_init: Failed to initialize proxy for %s", self.name
)
self.time_initialized = 0
self.last_updated = INIT_DATETIME
return PROXY_INIT_FAILED
self.time_initialized = int(time.time())
self.last_updated = datetime.now()
return PROXY_INIT_SUCCESS

async def proxy_de_init(self) -> int:
Expand All @@ -107,7 +108,7 @@ async def proxy_de_init(self) -> int:
"""
if self._json_rpc_session.is_activated:
await self._json_rpc_session.logout()
if not self.time_initialized:
if self.last_updated == INIT_DATETIME:
_LOGGER.debug(
"proxy_de_init: Skipping de-init for %s (not initialized)", self.name
)
Expand All @@ -121,7 +122,7 @@ async def proxy_de_init(self) -> int:
)
return PROXY_DE_INIT_FAILED

self.time_initialized = 0
self.last_updated = INIT_DATETIME
return PROXY_DE_INIT_SUCCESS

async def proxy_re_init(self) -> int:
Expand Down Expand Up @@ -149,8 +150,8 @@ async def is_connected(self) -> bool:
if not is_connected:
return False

diff = int(time.time()) - self.time_initialized
if diff < config.INIT_TIMEOUT:
diff: timedelta = datetime.now() - self.last_updated
if diff.total_seconds() < config.INIT_TIMEOUT:
return True
return False

Expand Down Expand Up @@ -403,13 +404,13 @@ async def _check_connection(self) -> bool:
try:
success = await self.proxy.ping(self.interface_id)
if success:
self.time_initialized = int(time.time())
self.last_updated = datetime.now()
return True
except NoConnection:
_LOGGER.exception("ping: NoConnection")
except ProxyException:
_LOGGER.exception("ping: ProxyException")
self.time_initialized = 0
self.last_updated = INIT_DATETIME
return False

async def set_system_variable(self, name: str, value: Any) -> None:
Expand Down Expand Up @@ -561,7 +562,7 @@ async def _check_connection(self) -> bool:
"""Check if proxy is still initialized."""
try:
if await self.proxy.clientServerInitialized(self.interface_id):
self.time_initialized = int(time.time())
self.last_updated = datetime.now()
return True
except NoConnection:
_LOGGER.exception("ping: NoConnection")
Expand All @@ -570,7 +571,7 @@ async def _check_connection(self) -> bool:
_LOGGER.warning(
"homegear_check_init: Setting initialized to 0 for %s", self.interface_id
)
self.time_initialized = 0
self.last_updated = INIT_DATETIME
return False

async def set_system_variable(self, name: str, value: Any) -> None:
Expand Down
1 change: 0 additions & 1 deletion hahomematic/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,6 @@
"EMERGENCY_OPERATION",
"EXTERNAL_CLOCK",
"FROST_PROTECTION",
"HEATING_COOLING",
"HUMIDITY_LIMITER",
"INCLUSION_UNSUPPORTED_DEVICE",
"INHIBIT",
Expand Down
6 changes: 3 additions & 3 deletions hahomematic/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
from __future__ import annotations

from collections.abc import Callable
from datetime import datetime
import functools
import logging
import time
from typing import Any

from hahomematic.data import get_client_by_interface_id
Expand Down Expand Up @@ -35,7 +35,7 @@ def wrapper_callback_system_event(*args: Any) -> Any:
_LOGGER.warning("Failed to reduce args for callback_system_event.")
raise Exception("args-exception callback_system_event") from err
if client:
client.time_initialized = int(time.time())
client.last_updated = datetime.now()
if client.central.callback_system_event is not None:
client.central.callback_system_event(name, *args)
return return_value
Expand Down Expand Up @@ -63,7 +63,7 @@ def wrapper_callback_event(*args: Any) -> Any:
_LOGGER.warning("Failed to reduce args for callback_event.")
raise Exception("args-exception callback_event") from err
if client:
client.time_initialized = int(time.time())
client.last_updated = datetime.now()
if client.central.callback_entity_event is not None:
client.central.callback_entity_event(*args)
return return_value
Expand Down
69 changes: 58 additions & 11 deletions hahomematic/devices/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
from hahomematic.const import ATTR_HM_MAX, ATTR_HM_MIN, HmPlatform
import hahomematic.device as hm_device
from hahomematic.devices.entity_definition import (
FIELD_ACTIVE_PROFILE,
FIELD_AUTO_MODE,
FIELD_BOOST_MODE,
FIELD_COMFORT_MODE,
FIELD_CONTROL_MODE,
FIELD_HEATING_COOLING,
FIELD_HUMIDITY,
FIELD_LOWERING_MODE,
FIELD_MANU_MODE,
Expand Down Expand Up @@ -47,6 +49,9 @@
SUPPORT_TARGET_TEMPERATURE = 1
SUPPORT_PRESET_MODE = 16

HEATING_PROFILES = {"Profile 1": 1, "Profile 2": 2, "Profile 3": 3}
COOLING_PROFILES = {"Profile 4": 4, "Profile 5": 5, "Profile 6": 6}


class BaseClimateEntity(CustomEntity):
"""Base HomeMatic climate entity."""
Expand Down Expand Up @@ -83,16 +88,16 @@ def _humidity(self) -> int | None:
"""Return the humidity of the device."""
return self._get_entity_state(FIELD_HUMIDITY)

@property
def _temperature(self) -> float | None:
"""Return the temperature of the device."""
return self._get_entity_state(FIELD_TEMPERATURE)

@property
def _setpoint(self) -> float | None:
"""Return the setpoint of the device."""
return self._get_entity_state(FIELD_SETPOINT)

@property
def _temperature(self) -> float | None:
"""Return the temperature of the device."""
return self._get_entity_state(FIELD_TEMPERATURE)

@property
def temperature_unit(self) -> str:
"""Return temperature unit."""
Expand Down Expand Up @@ -267,21 +272,31 @@ class IPThermostat(BaseClimateEntity):
"""homematic IP thermostat like HmIP-eTRV-B."""

@property
def _set_point_mode(self) -> int | None:
return self._get_entity_state(FIELD_SET_POINT_MODE)
def _active_profile(self) -> int | None:
return self._get_entity_state(FIELD_ACTIVE_PROFILE)

@property
def _boost_mode(self) -> bool | None:
return self._get_entity_state(FIELD_BOOST_MODE)

@property
def _control_mode(self) -> int | None:
return self._get_entity_state(FIELD_CONTROL_MODE)

@property
def _boost_mode(self) -> bool | None:
return self._get_entity_state(FIELD_BOOST_MODE)
def _is_heating(self) -> bool | None:
if heating_cooling := self._get_entity_state(FIELD_HEATING_COOLING):
return str(heating_cooling) == "HEATING"
return True

@property
def _party_mode(self) -> bool | None:
return self._get_entity_state(FIELD_PARTY_MODE)

@property
def _set_point_mode(self) -> int | None:
return self._get_entity_state(FIELD_SET_POINT_MODE)

@property
def supported_features(self) -> int:
"""Return the list of supported features."""
Expand Down Expand Up @@ -312,12 +327,15 @@ def preset_mode(self) -> str:
# we can't set it from the Home Assistant UI natively.
# if self.set_point_mode == HMIP_SET_POINT_MODE_AWAY:
# return PRESET_AWAY
return PRESET_NONE
return self._current_profile_name if self._current_profile_name else PRESET_NONE

@property
def preset_modes(self) -> list[str]:
"""Return available preset modes."""
return [PRESET_BOOST, PRESET_NONE]
presets = [PRESET_BOOST, PRESET_NONE]
presets.extend(self._profile_names)

return presets

async def set_hvac_mode(self, hvac_mode: str) -> None:
"""Set new target hvac mode."""
Expand All @@ -339,6 +357,35 @@ async def set_preset_mode(self, preset_mode: str) -> None:
if preset_mode == PRESET_NONE:
await self._send_value(FIELD_BOOST_MODE, False)

if preset_mode in self._profile_names:
if self.hvac_mode != HVAC_MODE_AUTO:
await self.set_hvac_mode(HVAC_MODE_AUTO)
profile_idx = self._get_profile_idx_by_name(preset_mode)
await self._send_value(FIELD_BOOST_MODE, False)
await self._send_value(FIELD_ACTIVE_PROFILE, profile_idx)

@property
def _profile_names(self) -> list[str]:
"""Return a collection of profile names."""
return list(self._relevant_profiles.keys())

@property
def _current_profile_name(self) -> str | None:
"""Return a profile index by name."""
inv_profiles: dict[int, str] = {
v: k for k, v in self._relevant_profiles.items()
}
return inv_profiles[self._active_profile] if self._active_profile else None

def _get_profile_idx_by_name(self, profile_name: str) -> int:
"""Return a profile index by name."""
return self._relevant_profiles[profile_name]

@property
def _relevant_profiles(self) -> dict[str, int]:
"""Return the relevant profile groups."""
return HEATING_PROFILES if self._is_heating else COOLING_PROFILES


def make_simple_thermostat(
device: hm_device.HmDevice, address: str, group_base_channels: list[int]
Expand Down
Loading

0 comments on commit d6b35b9

Please sign in to comment.