Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions homeassistant/components/eq3btsmart/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

from eq3btsmart import Thermostat
from eq3btsmart.exceptions import Eq3Exception
from eq3btsmart.thermostat_config import ThermostatConfig

from homeassistant.components import bluetooth
from homeassistant.config_entries import ConfigEntry
Expand Down Expand Up @@ -53,12 +52,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: Eq3ConfigEntry) -> bool:
f"[{eq3_config.mac_address}] Device could not be found"
)

thermostat = Thermostat(
thermostat_config=ThermostatConfig(
mac_address=mac_address,
),
ble_device=device,
)
thermostat = Thermostat(mac_address=device) # type: ignore[arg-type]

entry.runtime_data = Eq3ConfigEntryData(
eq3_config=eq3_config, thermostat=thermostat
Expand Down
4 changes: 0 additions & 4 deletions homeassistant/components/eq3btsmart/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

from collections.abc import Callable
from dataclasses import dataclass
from typing import TYPE_CHECKING

from eq3btsmart.models import Status

Expand Down Expand Up @@ -80,7 +79,4 @@ def __init__(
def is_on(self) -> bool:
"""Return the state of the binary sensor."""

if TYPE_CHECKING:
assert self._thermostat.status is not None

return self.entity_description.value_func(self._thermostat.status)
73 changes: 27 additions & 46 deletions homeassistant/components/eq3btsmart/climate.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
"""Platform for eQ-3 climate entities."""

from datetime import timedelta
import logging
from typing import Any

from eq3btsmart.const import EQ3BT_MAX_TEMP, EQ3BT_OFF_TEMP, Eq3Preset, OperationMode
from eq3btsmart.const import (
EQ3_DEFAULT_AWAY_TEMP,
EQ3_MAX_TEMP,
EQ3_OFF_TEMP,
Eq3OperationMode,
Eq3Preset,
)
from eq3btsmart.exceptions import Eq3Exception

from homeassistant.components.climate import (
Expand All @@ -20,9 +27,11 @@
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.device_registry import CONNECTION_BLUETOOTH
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
import homeassistant.util.dt as dt_util

from . import Eq3ConfigEntry
from .const import (
DEFAULT_AWAY_HOURS,
EQ_TO_HA_HVAC,
HA_TO_EQ_HVAC,
CurrentTemperatureSelector,
Expand Down Expand Up @@ -57,8 +66,8 @@ class Eq3Climate(Eq3Entity, ClimateEntity):
| ClimateEntityFeature.TURN_ON
)
_attr_temperature_unit = UnitOfTemperature.CELSIUS
_attr_min_temp = EQ3BT_OFF_TEMP
_attr_max_temp = EQ3BT_MAX_TEMP
_attr_min_temp = EQ3_OFF_TEMP
_attr_max_temp = EQ3_MAX_TEMP
_attr_precision = PRECISION_HALVES
_attr_hvac_modes = list(HA_TO_EQ_HVAC.keys())
_attr_preset_modes = list(Preset)
Expand All @@ -70,47 +79,31 @@ class Eq3Climate(Eq3Entity, ClimateEntity):
_target_temperature: float | None = None

@callback
def _async_on_updated(self) -> None:
"""Handle updated data from the thermostat."""

if self._thermostat.status is not None:
self._async_on_status_updated()

if self._thermostat.device_data is not None:
self._async_on_device_updated()

super()._async_on_updated()

@callback
def _async_on_status_updated(self) -> None:
def _async_on_status_updated(self, data: Any) -> None:
"""Handle updated status from the thermostat."""

if self._thermostat.status is None:
return

self._target_temperature = self._thermostat.status.target_temperature.value
self._target_temperature = self._thermostat.status.target_temperature
self._attr_hvac_mode = EQ_TO_HA_HVAC[self._thermostat.status.operation_mode]
self._attr_current_temperature = self._get_current_temperature()
self._attr_target_temperature = self._get_target_temperature()
self._attr_preset_mode = self._get_current_preset_mode()
self._attr_hvac_action = self._get_current_hvac_action()
super()._async_on_status_updated(data)

@callback
def _async_on_device_updated(self) -> None:
def _async_on_device_updated(self, data: Any) -> None:
"""Handle updated device data from the thermostat."""

if self._thermostat.device_data is None:
return

device_registry = dr.async_get(self.hass)
if device := device_registry.async_get_device(
connections={(CONNECTION_BLUETOOTH, self._eq3_config.mac_address)},
):
device_registry.async_update_device(
device.id,
sw_version=str(self._thermostat.device_data.firmware_version),
serial_number=self._thermostat.device_data.device_serial.value,
serial_number=self._thermostat.device_data.device_serial,
)
super()._async_on_device_updated(data)

def _get_current_temperature(self) -> float | None:
"""Return the current temperature."""
Expand All @@ -119,17 +112,11 @@ def _get_current_temperature(self) -> float | None:
case CurrentTemperatureSelector.NOTHING:
return None
case CurrentTemperatureSelector.VALVE:
if self._thermostat.status is None:
return None

return float(self._thermostat.status.valve_temperature)
case CurrentTemperatureSelector.UI:
return self._target_temperature
case CurrentTemperatureSelector.DEVICE:
if self._thermostat.status is None:
return None

return float(self._thermostat.status.target_temperature.value)
return float(self._thermostat.status.target_temperature)
case CurrentTemperatureSelector.ENTITY:
state = self.hass.states.get(self._eq3_config.external_temp_sensor)
if state is not None:
Expand All @@ -147,16 +134,12 @@ def _get_target_temperature(self) -> float | None:
case TargetTemperatureSelector.TARGET:
return self._target_temperature
case TargetTemperatureSelector.LAST_REPORTED:
if self._thermostat.status is None:
return None

return float(self._thermostat.status.target_temperature.value)
return float(self._thermostat.status.target_temperature)

def _get_current_preset_mode(self) -> str:
"""Return the current preset mode."""

if (status := self._thermostat.status) is None:
return PRESET_NONE
status = self._thermostat.status
if status.is_window_open:
return Preset.WINDOW_OPEN
if status.is_boost:
Expand All @@ -165,7 +148,7 @@ def _get_current_preset_mode(self) -> str:
return Preset.LOW_BATTERY
if status.is_away:
return Preset.AWAY
if status.operation_mode is OperationMode.ON:
if status.operation_mode is Eq3OperationMode.ON:
return Preset.OPEN
if status.presets is None:
return PRESET_NONE
Expand All @@ -179,10 +162,7 @@ def _get_current_preset_mode(self) -> str:
def _get_current_hvac_action(self) -> HVACAction:
"""Return the current hvac action."""

if (
self._thermostat.status is None
or self._thermostat.status.operation_mode is OperationMode.OFF
):
if self._thermostat.status.operation_mode is Eq3OperationMode.OFF:
return HVACAction.OFF
if self._thermostat.status.valve == 0:
return HVACAction.IDLE
Expand Down Expand Up @@ -227,7 +207,7 @@ async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
"""Set new target hvac mode."""

if hvac_mode is HVACMode.OFF:
await self.async_set_temperature(temperature=EQ3BT_OFF_TEMP)
await self.async_set_temperature(temperature=EQ3_OFF_TEMP)

try:
await self._thermostat.async_set_mode(HA_TO_EQ_HVAC[hvac_mode])
Expand All @@ -241,10 +221,11 @@ async def async_set_preset_mode(self, preset_mode: str) -> None:
case Preset.BOOST:
await self._thermostat.async_set_boost(True)
case Preset.AWAY:
await self._thermostat.async_set_away(True)
away_until = dt_util.now() + timedelta(hours=DEFAULT_AWAY_HOURS)
await self._thermostat.async_set_away(away_until, EQ3_DEFAULT_AWAY_TEMP)
case Preset.ECO:
await self._thermostat.async_set_preset(Eq3Preset.ECO)
case Preset.COMFORT:
await self._thermostat.async_set_preset(Eq3Preset.COMFORT)
case Preset.OPEN:
await self._thermostat.async_set_mode(OperationMode.ON)
await self._thermostat.async_set_mode(Eq3OperationMode.ON)
19 changes: 10 additions & 9 deletions homeassistant/components/eq3btsmart/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from enum import Enum

from eq3btsmart.const import OperationMode
from eq3btsmart.const import Eq3OperationMode

from homeassistant.components.climate import (
PRESET_AWAY,
Expand Down Expand Up @@ -34,17 +34,17 @@

GET_DEVICE_TIMEOUT = 5 # seconds

EQ_TO_HA_HVAC: dict[OperationMode, HVACMode] = {
OperationMode.OFF: HVACMode.OFF,
OperationMode.ON: HVACMode.HEAT,
OperationMode.AUTO: HVACMode.AUTO,
OperationMode.MANUAL: HVACMode.HEAT,
EQ_TO_HA_HVAC: dict[Eq3OperationMode, HVACMode] = {
Eq3OperationMode.OFF: HVACMode.OFF,
Eq3OperationMode.ON: HVACMode.HEAT,
Eq3OperationMode.AUTO: HVACMode.AUTO,
Eq3OperationMode.MANUAL: HVACMode.HEAT,
}

HA_TO_EQ_HVAC = {
HVACMode.OFF: OperationMode.OFF,
HVACMode.AUTO: OperationMode.AUTO,
HVACMode.HEAT: OperationMode.MANUAL,
HVACMode.OFF: Eq3OperationMode.OFF,
HVACMode.AUTO: Eq3OperationMode.AUTO,
HVACMode.HEAT: Eq3OperationMode.MANUAL,
}


Expand Down Expand Up @@ -81,6 +81,7 @@ class TargetTemperatureSelector(str, Enum):
DEFAULT_CURRENT_TEMP_SELECTOR = CurrentTemperatureSelector.DEVICE
DEFAULT_TARGET_TEMP_SELECTOR = TargetTemperatureSelector.TARGET
DEFAULT_SCAN_INTERVAL = 10 # seconds
DEFAULT_AWAY_HOURS = 30 * 24

SIGNAL_THERMOSTAT_DISCONNECTED = f"{DOMAIN}.thermostat_disconnected"
SIGNAL_THERMOSTAT_CONNECTED = f"{DOMAIN}.thermostat_connected"
Expand Down
43 changes: 38 additions & 5 deletions homeassistant/components/eq3btsmart/entity.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
"""Base class for all eQ-3 entities."""

from typing import Any

from eq3btsmart import Eq3Exception
from eq3btsmart.const import Eq3Event

from homeassistant.core import callback
from homeassistant.helpers.device_registry import (
CONNECTION_BLUETOOTH,
Expand Down Expand Up @@ -45,7 +50,15 @@ def __init__(
async def async_added_to_hass(self) -> None:
"""Run when entity about to be added to hass."""

self._thermostat.register_update_callback(self._async_on_updated)
self._thermostat.register_callback(
Eq3Event.DEVICE_DATA_RECEIVED, self._async_on_device_updated
)
self._thermostat.register_callback(
Eq3Event.STATUS_RECEIVED, self._async_on_status_updated
)
self._thermostat.register_callback(
Eq3Event.SCHEDULE_RECEIVED, self._async_on_status_updated
)

self.async_on_remove(
async_dispatcher_connect(
Expand All @@ -65,10 +78,25 @@ async def async_added_to_hass(self) -> None:
async def async_will_remove_from_hass(self) -> None:
"""Run when entity will be removed from hass."""

self._thermostat.unregister_update_callback(self._async_on_updated)
self._thermostat.unregister_callback(
Eq3Event.DEVICE_DATA_RECEIVED, self._async_on_device_updated
)
self._thermostat.unregister_callback(
Eq3Event.STATUS_RECEIVED, self._async_on_status_updated
)
self._thermostat.unregister_callback(
Eq3Event.SCHEDULE_RECEIVED, self._async_on_status_updated
)

@callback
def _async_on_status_updated(self, data: Any) -> None:
"""Handle updated status from the thermostat."""

def _async_on_updated(self) -> None:
"""Handle updated data from the thermostat."""
self.async_write_ha_state()

@callback
def _async_on_device_updated(self, data: Any) -> None:
"""Handle updated device data from the thermostat."""

self.async_write_ha_state()

Expand All @@ -90,4 +118,9 @@ def _async_on_connected(self) -> None:
def available(self) -> bool:
"""Whether the entity is available."""

return self._thermostat.status is not None and self._attr_available
try:
_ = self._thermostat.status
except Eq3Exception:
return False

return self._attr_available
2 changes: 1 addition & 1 deletion homeassistant/components/eq3btsmart/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,5 @@
"integration_type": "device",
"iot_class": "local_polling",
"loggers": ["eq3btsmart"],
"requirements": ["eq3btsmart==1.4.1", "bleak-esphome==2.16.0"]
"requirements": ["eq3btsmart==2.1.0", "bleak-esphome==2.16.0"]
}
Loading
Loading