Skip to content

Commit

Permalink
New option: display target temperature immediately (#78)
Browse files Browse the repository at this point in the history
* fix: display target temperature immediately

* fix: handle exceptions when setting temperature

* refactor: adjust log message format

* feat: add config option for target temperature display mode

* fix: add headline for target temperature selector config option

* fix: use last known target temperature for valve calculation and preset mode
  • Loading branch information
EuleMitKeule authored Jan 13, 2024
1 parent 3e67ef5 commit 4cb8300
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 39 deletions.
97 changes: 58 additions & 39 deletions custom_components/dbuezas_eq3btsmart/climate.py
Original file line number Diff line number Diff line change
@@ -1,52 +1,54 @@
"""Support for dbuezas_eQ-3 Bluetooth Smart thermostats."""

from __future__ import annotations
from datetime import timedelta
import logging

import asyncio
import logging
from datetime import timedelta

from .const import (
CONF_CURRENT_TEMP_SELECTOR,
CONF_EXTERNAL_TEMP_SENSOR,
DEFAULT_CURRENT_TEMP_SELECTOR,
DEFAULT_SCAN_INTERVAL,
EQ_TO_HA_HVAC,
HA_TO_EQ_HVAC,
Preset,
DOMAIN,
CurrentTemperatureSelector,
import voluptuous as vol
from homeassistant.components.climate import ClimateEntity, HVACMode
from homeassistant.components.climate.const import (
ATTR_HVAC_MODE,
PRESET_NONE,
ClimateEntityFeature,
HVACAction,
)
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.device_registry import format_mac, CONNECTION_BLUETOOTH
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.entity import DeviceInfo, EntityPlatformState
from homeassistant.helpers.event import async_call_later
from homeassistant.core import HomeAssistant, callback
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
ATTR_TEMPERATURE,
CONF_MAC,
CONF_SCAN_INTERVAL,
PRECISION_TENTHS,
UnitOfTemperature,
)
from homeassistant.components.climate.const import (
ATTR_HVAC_MODE,
ClimateEntityFeature,
HVACAction,
PRESET_NONE,
)
from homeassistant.components.climate import HVACMode

from homeassistant.components.climate import ClimateEntity
import voluptuous as vol
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.device_registry import CONNECTION_BLUETOOTH, format_mac
from homeassistant.helpers.entity import DeviceInfo, EntityPlatformState
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.event import async_call_later

from .const import (
CONF_CURRENT_TEMP_SELECTOR,
CONF_EXTERNAL_TEMP_SENSOR,
CONF_TARGET_TEMP_SELECTOR,
DEFAULT_CURRENT_TEMP_SELECTOR,
DEFAULT_SCAN_INTERVAL,
DEFAULT_TARGET_TEMP_SELECTOR,
DOMAIN,
EQ_TO_HA_HVAC,
HA_TO_EQ_HVAC,
CurrentTemperatureSelector,
Preset,
TargetTemperatureSelector,
)
from .python_eq3bt.eq3bt.eq3btsmart import (
EQ3BT_MAX_TEMP,
EQ3BT_OFF_TEMP,
Mode,
Thermostat,
)
from homeassistant.config_entries import ConfigEntry

_LOGGER = logging.getLogger(__name__)
DEVICE_SCHEMA = vol.Schema({vol.Required(CONF_MAC): cv.string})
Expand All @@ -69,6 +71,9 @@ async def async_setup_entry(
conf_current_temp_selector=config_entry.options.get(
CONF_CURRENT_TEMP_SELECTOR, DEFAULT_CURRENT_TEMP_SELECTOR
),
conf_target_temp_selector=config_entry.options.get(
CONF_TARGET_TEMP_SELECTOR, DEFAULT_TARGET_TEMP_SELECTOR
),
conf_external_temp_sensor=config_entry.options.get(
CONF_EXTERNAL_TEMP_SENSOR, ""
),
Expand All @@ -90,13 +95,15 @@ def __init__(
thermostat: Thermostat,
scan_interval: float,
conf_current_temp_selector: CurrentTemperatureSelector,
conf_target_temp_selector: TargetTemperatureSelector,
conf_external_temp_sensor: str,
):
"""Initialize the thermostat."""
self._thermostat = thermostat
self._thermostat.register_update_callback(self._on_updated)
self._scan_interval = scan_interval
self._conf_current_temp_selector = conf_current_temp_selector
self._conf_target_temp_selector = conf_target_temp_selector
self._conf_external_temp_sensor = conf_external_temp_sensor
self._target_temperature_to_set = None
self._is_setting_temperature = False
Expand Down Expand Up @@ -141,11 +148,11 @@ async def _async_scan_loop(self, now=None):
@callback
def _on_updated(self):
self._is_available = True
if self._target_temperature_to_set == self.target_temperature:
if self._target_temperature_to_set == self._thermostat.target_temperature:
self._is_setting_temperature = False
if not self._is_setting_temperature:
# temperature may have been updated from the thermostat
self._target_temperature_to_set = self.target_temperature
self._target_temperature_to_set = self._thermostat.target_temperature
if self.entity_id is None:
_LOGGER.warn(
"[%s] Updated but the entity is not loaded", self._thermostat.name
Expand Down Expand Up @@ -176,11 +183,11 @@ def current_temperature(self):
if self._thermostat.valve_state is None:
return None
valve: int = self._thermostat.valve_state
return (1 - valve / 100) * 2 + self.target_temperature - 2
return (1 - valve / 100) * 2 + self._thermostat.target_temperature - 2
if self._conf_current_temp_selector == CurrentTemperatureSelector.UI:
return self._target_temperature_to_set
if self._conf_current_temp_selector == CurrentTemperatureSelector.DEVICE:
return self.target_temperature
return self._thermostat.target_temperature
if self._conf_current_temp_selector == CurrentTemperatureSelector.ENTITY:
state = self.hass.states.get(self._conf_external_temp_sensor)
if state is not None:
Expand All @@ -192,7 +199,11 @@ def current_temperature(self):
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return self._thermostat.target_temperature
match self._conf_target_temp_selector:
case TargetTemperatureSelector.TARGET:
return self._target_temperature_to_set
case TargetTemperatureSelector.LAST_REPORTED:
return self._thermostat.target_temperature

async def async_set_temperature(self, **kwargs):
"""Set new target temperature."""
Expand All @@ -217,11 +228,19 @@ async def async_set_temperature(self, **kwargs):
temperature = round(temperature * 2) / 2 # increments of 0.5
temperature = min(temperature, self.max_temp)
temperature = max(temperature, self.min_temp)

previous_temperature = self._target_temperature_to_set
self._is_setting_temperature = True
self._target_temperature_to_set = temperature
# show current temp now
self.async_schedule_update_ha_state()
await self.async_set_temperature_now()

try:
await self.async_set_temperature_now()
except Exception as ex:
_LOGGER.error(f"[{self._thermostat.name}] Failed setting temperature: {ex}")
self._target_temperature_to_set = previous_temperature
self.async_schedule_update_ha_state()

async def async_set_temperature_now(self):
await self._thermostat.async_set_target_temperature(
Expand All @@ -243,7 +262,7 @@ async def async_set_hvac_mode(self, hvac_mode):
self._target_temperature_to_set = EQ3BT_OFF_TEMP
self._is_setting_temperature = True
else: # auto or manual/heat
self._target_temperature_to_set = self.target_temperature
self._target_temperature_to_set = self._thermostat.target_temperature
self._is_setting_temperature = False
self.async_schedule_update_ha_state()

Expand All @@ -262,9 +281,9 @@ def preset_mode(self):
return "Low Battery"
if self._thermostat.away:
return Preset.AWAY
if self.target_temperature == self._thermostat.eco_temperature:
if self._thermostat.target_temperature == self._thermostat.eco_temperature:
return Preset.ECO
if self.target_temperature == self._thermostat.comfort_temperature:
if self._thermostat.target_temperature == self._thermostat.comfort_temperature:
return Preset.COMFORT
if self._thermostat.mode == Mode.On:
return Preset.OPEN
Expand Down Expand Up @@ -300,7 +319,7 @@ async def async_set_preset_mode(self, preset_mode):
await self._thermostat.async_set_mode(Mode.On)

# by now, the target temperature should have been (maybe set) and fetched
self._target_temperature_to_set = self.target_temperature
self._target_temperature_to_set = self._thermostat.target_temperature
self._is_setting_temperature = False

@property
Expand Down
28 changes: 28 additions & 0 deletions custom_components/dbuezas_eq3btsmart/config_flow.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import datetime
from typing import Any
from setuptools.config.setupcfg import Target
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.const import CONF_MAC, CONF_NAME, CONF_SCAN_INTERVAL
Expand All @@ -17,13 +18,16 @@
CONF_EXTERNAL_TEMP_SENSOR,
CONF_STAY_CONNECTED,
CONF_DEBUG_MODE,
CONF_TARGET_TEMP_SELECTOR,
DEFAULT_TARGET_TEMP_SELECTOR,
Adapter,
CurrentTemperatureSelector,
DEFAULT_ADAPTER,
DEFAULT_CURRENT_TEMP_SELECTOR,
DEFAULT_SCAN_INTERVAL,
DEFAULT_STAY_CONNECTED,
DOMAIN,
TargetTemperatureSelector,
)
import logging

Expand Down Expand Up @@ -177,6 +181,30 @@ async def async_step_init(
}
}
),
vol.Required(
CONF_TARGET_TEMP_SELECTOR,
description={
"suggested_value": self.config_entry.options.get(
CONF_TARGET_TEMP_SELECTOR,
DEFAULT_TARGET_TEMP_SELECTOR,
)
},
): selector(
{
"select": {
"options": [
{
"label": "target temperature to be set (fast)",
"value": TargetTemperatureSelector.TARGET,
},
{
"label": "target temperature in device",
"value": TargetTemperatureSelector.LAST_REPORTED,
},
],
}
}
),
vol.Optional(
CONF_EXTERNAL_TEMP_SENSOR,
description={
Expand Down
6 changes: 6 additions & 0 deletions custom_components/dbuezas_eq3btsmart/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class Preset(str, Enum):

CONF_ADAPTER = "conf_adapter"
CONF_CURRENT_TEMP_SELECTOR = "conf_current_temp_selector"
CONF_TARGET_TEMP_SELECTOR = "conf_target_temp_selector"
CONF_EXTERNAL_TEMP_SENSOR = "conf_external_temp_sensor"
CONF_STAY_CONNECTED = "conf_stay_connected"
CONF_DEBUG_MODE = "conf_debug_mode"
Expand All @@ -55,7 +56,12 @@ class CurrentTemperatureSelector(str, Enum):
VALVE = "VALVE"
ENTITY = "ENTITY"

class TargetTemperatureSelector(str, Enum):
TARGET = "TARGET"
LAST_REPORTED = "LAST_REPORTED"


DEFAULT_ADAPTER = Adapter.AUTO
DEFAULT_CURRENT_TEMP_SELECTOR = CurrentTemperatureSelector.UI
DEFAULT_TARGET_TEMP_SELECTOR = TargetTemperatureSelector.TARGET
DEFAULT_STAY_CONNECTED = True
1 change: 1 addition & 0 deletions custom_components/dbuezas_eq3btsmart/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"data": {
"scan_interval": "Scan interval in minutes",
"conf_current_temp_selector": "What to show as current temperature",
"conf_target_temp_selector": "What to show as target temperature",
"conf_external_temp_sensor": "External temperature sensor",
"conf_adapter": "Bluetooth adapter",
"conf_stay_connected": "Keep bluetooth connection open",
Expand Down

0 comments on commit 4cb8300

Please sign in to comment.