Skip to content

Commit 7b0e1f7

Browse files
committed
Changes from HA
* Climate, button, sensor and number integration from HA * Migrations for number unique ids * set_datetime service replaced with button
1 parent 0bc6d00 commit 7b0e1f7

14 files changed

Lines changed: 175 additions & 196 deletions

File tree

custom_components/eurotronic_cometblue/__init__.py

Lines changed: 24 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,11 @@
3030
from .const import CONF_ALL_DAYS, DOMAIN
3131
from .coordinator import CometBlueConfigEntry, CometBlueDataUpdateCoordinator
3232
from .entity import CometBlueBluetoothEntity
33-
from .utils import (
34-
SERVICE_DATETIME_SCHEMA,
35-
SERVICE_HOLIDAY_SCHEMA,
36-
SERVICE_SCHEDULE_SCHEMA,
37-
)
33+
from .utils import SERVICE_HOLIDAY_SCHEMA, SERVICE_SCHEDULE_SCHEMA
3834

3935
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
4036
PLATFORMS: list[Platform] = [
37+
Platform.BUTTON,
4138
Platform.CLIMATE,
4239
Platform.NUMBER,
4340
Platform.SENSOR,
@@ -71,27 +68,33 @@ async def _async_migrate_entries(
7168

7269
@callback
7370
def update_unique_id(entry: er.RegistryEntry) -> dict[str, str] | None:
71+
new_unique_id = None
7472
if entry.domain == "climate" and entry.unique_id.endswith("-climate"):
7573
new_unique_id = entry.unique_id.replace("-climate", "")
74+
elif entry.domain == "number" and entry.unique_id.endswith("-target_temp_low"):
75+
new_unique_id = entry.unique_id.replace("-target_temp_low", "-eco_setpoint")
76+
elif entry.domain == "number" and entry.unique_id.endswith("-target_temp_high"):
77+
new_unique_id = entry.unique_id.replace("-target_temp_high", "-comfort_setpoint")
78+
else:
79+
return None
80+
LOGGER.debug(
81+
"Migrating entity '%s' unique_id from '%s' to '%s'",
82+
entry.entity_id,
83+
entry.unique_id,
84+
new_unique_id,
85+
)
86+
if existing_entity_id := entity_registry.async_get_entity_id(
87+
entry.domain, entry.platform, new_unique_id
88+
):
7689
LOGGER.debug(
77-
"Migrating entity '%s' unique_id from '%s' to '%s'",
78-
entry.entity_id,
79-
entry.unique_id,
90+
"Cannot migrate to unique_id '%s', already exists for '%s'",
8091
new_unique_id,
92+
existing_entity_id,
8193
)
82-
if existing_entity_id := entity_registry.async_get_entity_id(
83-
entry.domain, entry.platform, new_unique_id
84-
):
85-
LOGGER.debug(
86-
"Cannot migrate to unique_id '%s', already exists for '%s'",
87-
new_unique_id,
88-
existing_entity_id,
89-
)
90-
return None
91-
return {
92-
"new_unique_id": new_unique_id,
93-
}
94-
return None
94+
return None
95+
return {
96+
"new_unique_id": new_unique_id,
97+
}
9598

9699
await er.async_migrate_entries(hass, config_entry.entry_id, update_unique_id)
97100

@@ -160,16 +163,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: CometBlueConfigEntry) ->
160163
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
161164
"""Set up Eurotronic Comet Blue entity services."""
162165

163-
async def set_datetime(
164-
entity: CometBlueBluetoothEntity, service_call: ServiceCall
165-
) -> None:
166-
"""Service call to update the datetime on the device."""
167-
target_datetime = service_call.data.get("datetime") or datetime.now()
168-
await entity.coordinator.send_command(
169-
entity.coordinator.device.set_datetime_async,
170-
{"date": target_datetime},
171-
)
172-
173166
async def get_schedule(
174167
entity: CometBlueBluetoothEntity, service_call: ServiceCall
175168
) -> ServiceResponse:
@@ -236,15 +229,6 @@ async def set_holiday(
236229
},
237230
)
238231

239-
service.async_register_platform_entity_service(
240-
hass,
241-
DOMAIN,
242-
"set_datetime",
243-
entity_domain="climate",
244-
schema=cv.make_entity_service_schema(SERVICE_DATETIME_SCHEMA),
245-
supports_response=SupportsResponse.NONE,
246-
func=set_datetime,
247-
)
248232
service.async_register_platform_entity_service(
249233
hass,
250234
DOMAIN,
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
"""Comet Blue button platform."""
2+
3+
from homeassistant.components.button import ButtonEntity, ButtonEntityDescription
4+
from homeassistant.const import EntityCategory
5+
from homeassistant.core import HomeAssistant
6+
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
7+
from homeassistant.util import dt as dt_util
8+
9+
from .coordinator import CometBlueConfigEntry, CometBlueDataUpdateCoordinator
10+
from .entity import CometBlueBluetoothEntity
11+
12+
PARALLEL_UPDATES = 1
13+
14+
DESCRIPTIONS = [
15+
ButtonEntityDescription(
16+
key="sync_time",
17+
translation_key="sync_time",
18+
entity_category=EntityCategory.CONFIG,
19+
),
20+
]
21+
22+
23+
async def async_setup_entry(
24+
hass: HomeAssistant,
25+
entry: CometBlueConfigEntry,
26+
async_add_entities: AddConfigEntryEntitiesCallback,
27+
) -> None:
28+
"""Set up the client entities."""
29+
30+
coordinator = entry.runtime_data
31+
32+
async_add_entities(
33+
[
34+
CometBlueButtonEntity(coordinator, description)
35+
for description in DESCRIPTIONS
36+
]
37+
)
38+
39+
40+
class CometBlueButtonEntity(CometBlueBluetoothEntity, ButtonEntity):
41+
"""Representation of a button."""
42+
43+
def __init__(
44+
self,
45+
coordinator: CometBlueDataUpdateCoordinator,
46+
description: ButtonEntityDescription,
47+
) -> None:
48+
"""Initialize CometBlueButtonEntity."""
49+
50+
super().__init__(coordinator)
51+
self.entity_description = description
52+
self._attr_unique_id = f"{coordinator.address}-{description.key}"
53+
54+
async def async_press(self) -> None:
55+
"""Handle the button press."""
56+
if self.entity_description.key == "sync_time":
57+
await self.coordinator.send_command(
58+
self.coordinator.device.set_datetime_async, {"date": dt_util.now()}
59+
)

custom_components/eurotronic_cometblue/climate.py

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
"""Comet Blue climate integration."""
22

3-
from __future__ import annotations
4-
5-
import logging
63
from typing import Any
74

85
from homeassistant.components.climate import (
@@ -25,8 +22,6 @@
2522
from .coordinator import CometBlueConfigEntry, CometBlueDataUpdateCoordinator
2623
from .entity import CometBlueBluetoothEntity
2724

28-
LOGGER = logging.getLogger(__name__)
29-
3025
PARALLEL_UPDATES = 1
3126
MIN_TEMP = 7.5
3227
MAX_TEMP = 28.5
@@ -59,7 +54,6 @@ class CometBlueClimateEntity(CometBlueBluetoothEntity, ClimateEntity):
5954
]
6055
_attr_supported_features: ClimateEntityFeature = (
6156
ClimateEntityFeature.TARGET_TEMPERATURE
62-
| ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
6357
| ClimateEntityFeature.PRESET_MODE
6458
| ClimateEntityFeature.TURN_ON
6559
| ClimateEntityFeature.TURN_OFF
@@ -84,13 +78,19 @@ def target_temperature(self) -> float | None:
8478
return self.coordinator.data.temperatures["manualTemp"]
8579

8680
@property
87-
def target_temperature_high(self) -> float | None:
88-
"""Return the upper bound target temperature."""
81+
def _device_comfort_setpoint(self) -> float | None:
82+
"""Return the comfort setpoint temperature.
83+
84+
Internally used for preset selection.
85+
"""
8986
return self.coordinator.data.temperatures["targetTempHigh"]
9087

9188
@property
92-
def target_temperature_low(self) -> float | None:
93-
"""Return the lower bound target temperature."""
89+
def _device_eco_setpoint(self) -> float | None:
90+
"""Return the eco setpoint temperature.
91+
92+
Internally used for preset selection.
93+
"""
9494
return self.coordinator.data.temperatures["targetTempLow"]
9595

9696
@property
@@ -116,9 +116,9 @@ def preset_mode(self) -> str | None:
116116
return PRESET_AWAY
117117
if self.target_temperature == MAX_TEMP:
118118
return PRESET_BOOST
119-
if self.target_temperature == self.target_temperature_high:
119+
if self.target_temperature == self._device_comfort_setpoint:
120120
return PRESET_COMFORT
121-
if self.target_temperature == self.target_temperature_low:
121+
if self.target_temperature == self._device_eco_setpoint:
122122
return PRESET_ECO
123123
return PRESET_NONE
124124

@@ -156,11 +156,11 @@ async def async_set_preset_mode(self, preset_mode: str) -> None:
156156
)
157157
if preset_mode == PRESET_ECO:
158158
return await self.async_set_temperature(
159-
temperature=self.target_temperature_low
159+
temperature=self._device_eco_setpoint
160160
)
161161
if preset_mode == PRESET_COMFORT:
162162
return await self.async_set_temperature(
163-
temperature=self.target_temperature_high
163+
temperature=self._device_comfort_setpoint
164164
)
165165
if preset_mode == PRESET_BOOST:
166166
return await self.async_set_temperature(temperature=MAX_TEMP)
@@ -175,7 +175,7 @@ async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
175175
return await self.async_set_temperature(temperature=MAX_TEMP)
176176
if hvac_mode == HVACMode.AUTO:
177177
return await self.async_set_temperature(
178-
temperature=self.target_temperature_low
178+
temperature=self._device_eco_setpoint
179179
)
180180
raise ServiceValidationError(f"Unknown HVAC mode '{hvac_mode}'")
181181

custom_components/eurotronic_cometblue/config_flow.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
"""Config flow for CometBlue."""
22

3-
from __future__ import annotations
4-
53
import logging
64
from typing import Any
75

6+
from bleak.exc import BleakError
87
from eurotronic_cometblue_ha import AsyncCometBlue
98
from eurotronic_cometblue_ha.const import SERVICE
109
from habluetooth import BluetoothServiceInfoBleak
@@ -87,9 +86,12 @@ async def _try_connect(self, user_input: dict[str, Any]) -> dict[str, str]:
8786
except TimeoutError:
8887
LOGGER.debug("Connection to device timed out", exc_info=True)
8988
return {"base": "timeout_connect"}
90-
except Exception: # noqa: BLE001
89+
except BleakError:
9190
LOGGER.debug("Failed to connect to device", exc_info=True)
9291
return {"base": "cannot_connect"}
92+
except Exception: # noqa: BLE001
93+
LOGGER.debug("Unknown error", exc_info=True)
94+
return {"base": "unknown"}
9395
return {}
9496

9597
def _create_entry(

custom_components/eurotronic_cometblue/const.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66

77
CONF_DATETIME: Final = "datetime"
88
CONF_SCHEDULE: Final = "schedule"
9-
CONF_RETRY_COUNT: Final = "retry_count"
10-
119

1210
CONF_MONDAY: Final = "monday"
1311
CONF_TUESDAY: Final = "tuesday"

custom_components/eurotronic_cometblue/coordinator.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
"""Provides the DataUpdateCoordinator."""
2-
3-
from __future__ import annotations
1+
"""Provides the DataUpdateCoordinator for Comet Blue."""
42

53
import asyncio
64
from collections.abc import Awaitable, Callable
@@ -54,6 +52,7 @@ def __init__(
5452
)
5553
self.device = cometblue
5654
self.address = cometblue.client.address
55+
self.data = CometBlueCoordinatorData()
5756

5857
async def send_command(
5958
self,
@@ -65,11 +64,11 @@ async def send_command(
6564
LOGGER.debug("Updating device %s with '%s'", self.name, payload)
6665
retry_count = 0
6766
while retry_count < MAX_RETRIES:
67+
retry_count += 1
6868
try:
6969
async with self.device:
7070
return await function(**payload)
7171
except (InvalidByteValueError, TimeoutError, BleakError) as ex:
72-
retry_count += 1
7372
if retry_count >= MAX_RETRIES:
7473
raise HomeAssistantError(
7574
f"Error sending command to '{self.name}': {ex}"
@@ -89,17 +88,18 @@ async def send_command(
8988

9089
async def _async_update_data(self) -> CometBlueCoordinatorData:
9190
"""Poll the device."""
92-
data: CometBlueCoordinatorData = CometBlueCoordinatorData()
91+
data = CometBlueCoordinatorData()
9392

9493
retry_count = 0
9594

9695
while retry_count < MAX_RETRIES and not data.temperatures:
9796
try:
97+
retry_count += 1
9898
async with self.device:
9999
# temperatures are required and must trigger a retry if not available
100100
if not data.temperatures:
101101
data.temperatures = await self.device.get_temperature_async()
102-
# holiday is optional and should not trigger a retry
102+
# holiday and battery are optional and should not trigger a retry
103103
try:
104104
if not data.holiday:
105105
data.holiday = await self.device.get_holiday_async(1) or {}
@@ -113,7 +113,6 @@ async def _async_update_data(self) -> CometBlueCoordinatorData:
113113
ex,
114114
)
115115
except (InvalidByteValueError, TimeoutError, BleakError) as ex:
116-
retry_count += 1
117116
if retry_count >= MAX_RETRIES:
118117
raise UpdateFailed(
119118
f"Error retrieving data: {ex}", retry_after=30
@@ -132,8 +131,8 @@ async def _async_update_data(self) -> CometBlueCoordinatorData:
132131

133132
# If one value was not retrieved correctly, keep the old value
134133
if not data.holiday:
135-
data.holiday = self.data.holiday if self.data else {}
134+
data.holiday = self.data.holiday
136135
if not data.battery:
137-
data.battery = self.data.battery if self.data else None
136+
data.battery = self.data.battery
138137
LOGGER.debug("Received data for %s: %s", self.name, data)
139138
return data

custom_components/eurotronic_cometblue/entity.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
11
"""Coordinator entity base class for CometBlue."""
22

3-
import logging
4-
53
from homeassistant.components import bluetooth
64
from homeassistant.helpers.device_registry import DeviceInfo
75
from homeassistant.helpers.update_coordinator import CoordinatorEntity
86

97
from . import DOMAIN
108
from .coordinator import CometBlueDataUpdateCoordinator
119

12-
LOGGER = logging.getLogger(__name__)
13-
1410

1511
class CometBlueBluetoothEntity(CoordinatorEntity[CometBlueDataUpdateCoordinator]):
1612
"""Coordinator entity for CometBlue."""

0 commit comments

Comments
 (0)