Skip to content

Commit 1259b40

Browse files
committed
Chanes from first HA Core PR
1 parent 4b1df60 commit 1259b40

File tree

10 files changed

+183
-257
lines changed

10 files changed

+183
-257
lines changed

custom_components/eurotronic_cometblue/__init__.py

Lines changed: 33 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from bleak.exc import BleakError
99
from eurotronic_cometblue_ha import AsyncCometBlue
1010

11-
from homeassistant.components import bluetooth
11+
from homeassistant.components.bluetooth import async_ble_device_from_address
1212
from homeassistant.config_entries import ConfigEntry
1313
from homeassistant.const import CONF_ADDRESS, CONF_PIN, Platform
1414
from homeassistant.core import (
@@ -18,13 +18,17 @@
1818
SupportsResponse,
1919
callback,
2020
)
21-
from homeassistant.exceptions import ConfigEntryNotReady
22-
from homeassistant.helpers import config_validation as cv, service
23-
from homeassistant.helpers.device_registry import DeviceInfo
21+
from homeassistant.exceptions import ConfigEntryError, ConfigEntryNotReady
22+
from homeassistant.helpers import (
23+
config_validation as cv,
24+
device_registry as dr,
25+
entity_registry as er,
26+
service,
27+
)
2428
from homeassistant.helpers.typing import ConfigType
2529

2630
from .const import CONF_ALL_DAYS, DOMAIN
27-
from .coordinator import CometBlueDataUpdateCoordinator
31+
from .coordinator import CometBlueConfigEntry, CometBlueDataUpdateCoordinator
2832
from .entity import CometBlueBluetoothEntity
2933
from .utils import (
3034
SERVICE_DATETIME_SCHEMA,
@@ -40,8 +44,6 @@
4044
]
4145
LOGGER = logging.getLogger(__name__)
4246

43-
type CometBlueConfigEntry = ConfigEntry[CometBlueDataUpdateCoordinator]
44-
4547

4648
@callback
4749
def _async_migrate_options_if_missing(hass: HomeAssistant, entry: ConfigEntry) -> None:
@@ -103,10 +105,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: CometBlueConfigEntry) ->
103105

104106
await _async_migrate_entries(hass, entry)
105107

106-
107108
address = entry.data[CONF_ADDRESS]
108109

109-
ble_device = bluetooth.async_ble_device_from_address(hass, entry.data[CONF_ADDRESS])
110+
ble_device = async_ble_device_from_address(hass, entry.data[CONF_ADDRESS])
110111

111112
if not ble_device:
112113
raise ConfigEntryNotReady(
@@ -120,23 +121,33 @@ async def async_setup_entry(hass: HomeAssistant, entry: CometBlueConfigEntry) ->
120121
try:
121122
async with cometblue_device:
122123
ble_device_info = await cometblue_device.get_device_info_async()
123-
device_info = DeviceInfo(
124-
identifiers={(DOMAIN, address)},
125-
name=f"{ble_device_info['model']} {cometblue_device.device.address}",
126-
sw_version=ble_device_info["version"],
127-
manufacturer=ble_device_info["manufacturer"],
128-
model=ble_device_info["model"],
129-
)
124+
try:
125+
# Device only returns battery level if PIN is correct
126+
await cometblue_device.get_battery_async()
127+
except TimeoutError as ex:
128+
# This likely means PIN was incorrect on Linux and ESPHome backends
129+
raise ConfigEntryError(
130+
"Failed to read battery level, likely due to incorrect PIN"
131+
) from ex
130132
except BleakError as ex:
131133
raise ConfigEntryNotReady(
132134
f"Failed to get device info from '{cometblue_device.device.address}'"
133135
) from ex
134136

137+
device_registry = dr.async_get(hass)
138+
device_registry.async_get_or_create(
139+
config_entry_id=entry.entry_id,
140+
identifiers={(DOMAIN, address)},
141+
name=f"{ble_device_info['model']} {cometblue_device.device.address}",
142+
manufacturer=ble_device_info["manufacturer"],
143+
model=ble_device_info["model"],
144+
sw_version=ble_device_info["version"],
145+
)
146+
135147
coordinator = CometBlueDataUpdateCoordinator(
136148
hass,
137149
entry,
138150
cometblue_device,
139-
device_info,
140151
)
141152
await coordinator.async_config_entry_first_refresh()
142153
entry.runtime_data = coordinator
@@ -155,19 +166,17 @@ async def set_datetime(
155166
"""Service call to update the datetime on the device."""
156167
target_datetime = service_call.data.get("datetime") or datetime.now()
157168
await entity.coordinator.send_command(
158-
"set_datetime_async",
169+
entity.coordinator.device.set_datetime_async,
159170
{"date": target_datetime},
160-
service_call.service,
161171
)
162172

163173
async def get_schedule(
164174
entity: CometBlueBluetoothEntity, service_call: ServiceCall
165175
) -> ServiceResponse:
166176
"""Service call to retrieve the schedule from the device."""
167177
return await entity.coordinator.send_command(
168-
"get_multiple_async",
178+
entity.coordinator.device.get_multiple_async,
169179
{"values": ["weekdays"]},
170-
service_call.service,
171180
)
172181

173182
async def set_schedule(
@@ -191,9 +200,8 @@ async def set_schedule(
191200
if sched is not None and day in CONF_ALL_DAYS
192201
}
193202
await entity.coordinator.send_command(
194-
"set_weekdays_async",
203+
entity.coordinator.device.set_weekdays_async,
195204
{"values": values},
196-
service_call.service,
197205
)
198206

199207
async def set_holiday(
@@ -217,7 +225,7 @@ async def set_holiday(
217225
entity.coordinator.device.device.address,
218226
)
219227
await entity.coordinator.send_command(
220-
"set_holiday_async",
228+
entity.coordinator.device.set_holiday_async,
221229
{
222230
"number": 1,
223231
"values": {
@@ -226,7 +234,6 @@ async def set_holiday(
226234
"temperature": service_call.data["temperature"],
227235
},
228236
},
229-
service_call.service,
230237
)
231238

232239
service.async_register_platform_entity_service(
@@ -271,8 +278,4 @@ async def set_holiday(
271278

272279
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
273280
"""Unload a config entry."""
274-
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
275-
coordinator: CometBlueDataUpdateCoordinator = entry.runtime_data
276-
await coordinator.async_shutdown()
277-
278-
return unload_ok
281+
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

custom_components/eurotronic_cometblue/climate.py

Lines changed: 29 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,20 @@
99
ATTR_TARGET_TEMP_HIGH,
1010
ATTR_TARGET_TEMP_LOW,
1111
PRESET_AWAY,
12+
PRESET_BOOST,
1213
PRESET_COMFORT,
1314
PRESET_ECO,
1415
PRESET_NONE,
1516
ClimateEntity,
1617
ClimateEntityFeature,
17-
HVACAction,
1818
HVACMode,
1919
)
20-
from homeassistant.config_entries import ConfigEntry
2120
from homeassistant.const import ATTR_TEMPERATURE, PRECISION_HALVES, UnitOfTemperature
2221
from homeassistant.core import HomeAssistant
22+
from homeassistant.exceptions import ServiceValidationError
2323
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
2424

25-
from .coordinator import CometBlueDataUpdateCoordinator
25+
from .coordinator import CometBlueConfigEntry, CometBlueDataUpdateCoordinator
2626
from .entity import CometBlueBluetoothEntity
2727

2828
LOGGER = logging.getLogger(__name__)
@@ -31,17 +31,15 @@
3131
MIN_TEMP = 7.5
3232
MAX_TEMP = 28.5
3333

34-
DEFAULT_PRESETS = {PRESET_COMFORT, PRESET_ECO}
35-
3634

3735
async def async_setup_entry(
3836
hass: HomeAssistant,
39-
entry: ConfigEntry,
37+
entry: CometBlueConfigEntry,
4038
async_add_entities: AddConfigEntryEntitiesCallback,
4139
) -> None:
4240
"""Set up the client entities."""
4341

44-
coordinator: CometBlueDataUpdateCoordinator = entry.runtime_data
42+
coordinator = entry.runtime_data
4543
async_add_entities([CometBlueClimateEntity(coordinator)])
4644

4745

@@ -53,10 +51,11 @@ class CometBlueClimateEntity(CometBlueBluetoothEntity, ClimateEntity):
5351
_attr_name = None
5452
_attr_hvac_modes = [HVACMode.AUTO, HVACMode.HEAT, HVACMode.OFF]
5553
_attr_preset_modes = [
56-
PRESET_NONE,
54+
PRESET_COMFORT,
5755
PRESET_ECO,
56+
PRESET_BOOST,
5857
PRESET_AWAY,
59-
PRESET_COMFORT,
58+
PRESET_NONE,
6059
]
6160
_attr_supported_features: ClimateEntityFeature = (
6261
ClimateEntityFeature.TARGET_TEMPERATURE
@@ -72,27 +71,27 @@ def __init__(self, coordinator: CometBlueDataUpdateCoordinator) -> None:
7271
"""Initialize CometBlueClimateEntity."""
7372

7473
super().__init__(coordinator)
75-
self._attr_unique_id = f"{coordinator.address}-climate"
74+
self._attr_unique_id = coordinator.address
7675

7776
@property
7877
def current_temperature(self) -> float | None:
7978
"""Return the current temperature."""
80-
return self.coordinator.data["currentTemp"]
79+
return self.coordinator.data.temperatures["currentTemp"]
8180

8281
@property
8382
def target_temperature(self) -> float | None:
8483
"""Return the temperature currently set to be reached."""
85-
return self.coordinator.data["manualTemp"]
84+
return self.coordinator.data.temperatures["manualTemp"]
8685

8786
@property
8887
def target_temperature_high(self) -> float | None:
8988
"""Return the upper bound target temperature."""
90-
return self.coordinator.data["targetTempHigh"]
89+
return self.coordinator.data.temperatures["targetTempHigh"]
9190

9291
@property
9392
def target_temperature_low(self) -> float | None:
9493
"""Return the lower bound target temperature."""
95-
return self.coordinator.data["targetTempLow"]
94+
return self.coordinator.data.temperatures["targetTempLow"]
9695

9796
@property
9897
def hvac_mode(self) -> HVACMode | None:
@@ -103,76 +102,58 @@ def hvac_mode(self) -> HVACMode | None:
103102
return HVACMode.HEAT
104103
return HVACMode.AUTO
105104

106-
@property
107-
def hvac_action(self) -> HVACAction | None:
108-
"""Return the current running hvac action if supported."""
109-
110-
if self.target_temperature == MIN_TEMP:
111-
return HVACAction.OFF
112-
if (self.target_temperature or 0.0) > (
113-
self.target_temperature_low or 0.0
114-
) or self.target_temperature == MAX_TEMP:
115-
return HVACAction.HEATING
116-
return HVACAction.IDLE
117-
118105
@property
119106
def preset_mode(self) -> str | None:
120107
"""Return the current preset mode, e.g., home, away, temp."""
121108
# presets have an order in which they are displayed on TRV:
122-
# away, comfort, eco, none (manual)
109+
# away, boost, comfort, eco, none (manual)
123110
if (
124-
self.coordinator.data["holiday"].get("start") is None
125-
and self.coordinator.data["holiday"].get("end") is not None
111+
self.coordinator.data.holiday.get("start") is None
112+
and self.coordinator.data.holiday.get("end") is not None
126113
and self.target_temperature
127-
== self.coordinator.data["holiday"].get("temperature")
114+
== self.coordinator.data.holiday.get("temperature")
128115
):
129116
return PRESET_AWAY
117+
if self.target_temperature == MAX_TEMP:
118+
return PRESET_BOOST
130119
if self.target_temperature == self.target_temperature_high:
131120
return PRESET_COMFORT
132121
if self.target_temperature == self.target_temperature_low:
133122
return PRESET_ECO
134123
return PRESET_NONE
135124

136-
@property
137-
def preset_modes(self) -> list[str] | None:
138-
"""Return a list of available preset modes.
139-
140-
Will only show presets that are supported by the device.
141-
"""
142-
if self.preset_mode:
143-
return list(DEFAULT_PRESETS | {self.preset_mode})
144-
return list(DEFAULT_PRESETS)
145-
146125
async def async_set_temperature(self, **kwargs: Any) -> None:
147126
"""Set new target temperatures."""
148127

149128
if self.preset_mode == PRESET_AWAY:
150-
raise ValueError(
129+
raise ServiceValidationError(
151130
"Cannot adjust TRV remotely, manually disable 'holiday' mode on TRV first"
152131
)
153132

154133
await self.coordinator.send_command(
155-
"set_temperature_async",
134+
self.coordinator.device.set_temperature_async,
156135
{
157136
"values": {
158137
# manual temperature always needs to be set, otherwise TRV will turn OFF
159138
"manualTemp": kwargs.get(ATTR_TEMPERATURE)
160139
or self.target_temperature,
140+
# other temperatures can be left unchanged by setting them to None
161141
"targetTempLow": kwargs.get(ATTR_TARGET_TEMP_LOW),
162142
"targetTempHigh": kwargs.get(ATTR_TARGET_TEMP_HIGH),
163143
}
164144
},
165-
self.entity_id,
166145
)
167146
await self.coordinator.async_request_refresh()
168147

169148
async def async_set_preset_mode(self, preset_mode: str) -> None:
170149
"""Set new target preset mode."""
171150

172151
if self.preset_modes and preset_mode not in self.preset_modes:
173-
raise ValueError(f"Unsupported preset_mode '{preset_mode}'")
152+
raise ServiceValidationError(f"Unsupported preset_mode '{preset_mode}'")
174153
if preset_mode in [PRESET_NONE, PRESET_AWAY]:
175-
raise ValueError(f"Unable to set preset '{preset_mode}', display only.")
154+
raise ServiceValidationError(
155+
f"Unable to set preset '{preset_mode}', display only."
156+
)
176157
if preset_mode == PRESET_ECO:
177158
return await self.async_set_temperature(
178159
temperature=self.target_temperature_low
@@ -181,6 +162,8 @@ async def async_set_preset_mode(self, preset_mode: str) -> None:
181162
return await self.async_set_temperature(
182163
temperature=self.target_temperature_high
183164
)
165+
if preset_mode == PRESET_BOOST:
166+
return await self.async_set_temperature(temperature=MAX_TEMP)
184167
return None
185168

186169
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
@@ -194,7 +177,7 @@ async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
194177
return await self.async_set_temperature(
195178
temperature=self.target_temperature_low
196179
)
197-
raise ValueError(f"Unknown HVAC mode '{hvac_mode}'")
180+
raise ServiceValidationError(f"Unknown HVAC mode '{hvac_mode}'")
198181

199182
async def async_turn_on(self) -> None:
200183
"""Turn the entity on."""

0 commit comments

Comments
 (0)