Skip to content

Commit 993d561

Browse files
committed
Updates
1 parent ab60bd0 commit 993d561

File tree

6 files changed

+116
-43
lines changed

6 files changed

+116
-43
lines changed

custom_components/cometblue/__init__.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,17 @@ async def set_holiday(service_call: ServiceCall) -> None:
151151

152152
data = service_call.data.copy()
153153

154+
if (
155+
datetime(
156+
data["start"].year,
157+
data["start"].month,
158+
data["start"].day,
159+
data["start"].hour,
160+
)
161+
< datetime.now()
162+
):
163+
raise ValueError("Start date (truncated to hour) must be in the future")
164+
154165
for entity_id in data.pop("entity_id", []):
155166
entity_coordinator = await get_coordinator_for_service(hass, entity_id)
156167
LOGGER.info(
@@ -166,7 +177,7 @@ async def set_holiday(service_call: ServiceCall) -> None:
166177
"start": data["start"],
167178
"end": data["end"],
168179
"temperature": data["temperature"],
169-
}
180+
},
170181
},
171182
service_call.service,
172183
)

custom_components/cometblue/climate.py

Lines changed: 80 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
PRESET_NONE,
1414
ClimateEntity,
1515
ClimateEntityFeature,
16+
HVACAction,
1617
HVACMode,
1718
)
1819
from homeassistant.config_entries import ConfigEntry
@@ -25,8 +26,8 @@
2526

2627
LOGGER = logging.getLogger(__name__)
2728

28-
MIN_TEMP = 8
29-
MAX_TEMP = 28
29+
MIN_TEMP = 7.5
30+
MAX_TEMP = 28.5
3031

3132

3233
async def async_setup_entry(
@@ -43,28 +44,27 @@ class CometBlueClimateEntity(CometBlueBluetoothEntity, ClimateEntity):
4344

4445
_attr_min_temp = MIN_TEMP
4546
_attr_max_temp = MAX_TEMP
46-
_attr_target_temperature_step = PRECISION_HALVES
4747
_attr_name = None
48+
_attr_hvac_modes = [HVACMode.AUTO, HVACMode.HEAT, HVACMode.OFF]
49+
_attr_preset_modes = [
50+
PRESET_NONE,
51+
PRESET_ECO,
52+
PRESET_AWAY,
53+
PRESET_COMFORT,
54+
]
55+
_attr_supported_features: ClimateEntityFeature = (
56+
ClimateEntityFeature.TARGET_TEMPERATURE
57+
| ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
58+
| ClimateEntityFeature.PRESET_MODE
59+
)
60+
_attr_target_temperature_step = PRECISION_HALVES
61+
_attr_temperature_unit = UnitOfTemperature.CELSIUS
4862

4963
def __init__(self, coordinator: CometBlueDataUpdateCoordinator) -> None:
5064
"""Initialize CometBlueClimateEntity."""
5165

5266
super().__init__(coordinator)
5367
self._attr_unique_id = f"{coordinator.address}-climate"
54-
self._attr_temperature_unit = UnitOfTemperature.CELSIUS
55-
self._attr_hvac_modes = [HVACMode.AUTO]
56-
self._attr_hvac_mode = HVACMode.AUTO
57-
self._attr_supported_features: ClimateEntityFeature = (
58-
ClimateEntityFeature.TARGET_TEMPERATURE
59-
| ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
60-
| ClimateEntityFeature.PRESET_MODE
61-
)
62-
self._attr_preset_modes = [
63-
PRESET_NONE,
64-
PRESET_ECO,
65-
PRESET_AWAY,
66-
PRESET_COMFORT,
67-
]
6868

6969
@property
7070
def current_temperature(self) -> float | None:
@@ -74,7 +74,11 @@ def current_temperature(self) -> float | None:
7474
@property
7575
def target_temperature(self) -> float | None:
7676
"""Return the temperature currently set to be reached."""
77-
return self.coordinator.data["manualTemp"]
77+
# if holiday mode is active (i.e. temperature is available), return holiday temperature
78+
return (
79+
self.coordinator.data["holiday"].get("temperature")
80+
or self.coordinator.data["manualTemp"]
81+
)
7882

7983
@property
8084
def target_temperature_high(self) -> float | None:
@@ -87,21 +91,53 @@ def target_temperature_low(self) -> float | None:
8791
return self.coordinator.data["targetTempLow"]
8892

8993
@property
90-
def preset_mode(self) -> str | None:
91-
"""Return the current preset mode, e.g., home, away, temp.
94+
def hvac_mode(self) -> HVACMode | None:
95+
"""Return hvac operation mode."""
96+
if self.coordinator.data["manualTemp"] == 7.5:
97+
return HVACMode.OFF
98+
if self.coordinator.data["manualTemp"] == 28.5:
99+
return HVACMode.HEAT
100+
return HVACMode.AUTO
92101

93-
Requires ClimateEntityFeature.PRESET_MODE.
94-
"""
95-
if self.target_temperature == self.target_temperature_low:
96-
return PRESET_ECO
102+
@property
103+
def hvac_action(self) -> HVACAction | None:
104+
"""Return the current running hvac action if supported."""
105+
106+
if self.coordinator.data["manualTemp"] == 7.5:
107+
return HVACAction.OFF
108+
if (
109+
self.coordinator.data["currentTemp"] + 0.5
110+
< self.coordinator.data["manualTemp"]
111+
):
112+
return HVACAction.HEATING
113+
return HVACAction.IDLE
114+
115+
@property
116+
def preset_mode(self) -> str | None:
117+
"""Return the current preset mode, e.g., home, away, temp."""
118+
# presets have an order in which they are displayed on TRV:
119+
# away, comfort, eco, none (or manual)
120+
if (
121+
self.coordinator.data["holiday"].get("start") is None
122+
and self.coordinator.data["holiday"].get("end") is not None
123+
):
124+
return PRESET_AWAY
97125
if self.target_temperature == self.target_temperature_high:
98126
return PRESET_COMFORT
99-
# AWAY MODE NOT SUPPORTED YET
127+
if self.target_temperature == self.target_temperature_low:
128+
return PRESET_ECO
100129
return PRESET_NONE
101130

102131
async def async_set_temperature(self, **kwargs: Any) -> None:
103132
"""Set new target temperatures."""
104133

134+
# Not sure if actually the case. Maybe set vacation mode to really hot and check
135+
# what happens when changing it manually
136+
if self.preset_mode == PRESET_AWAY:
137+
raise ValueError(
138+
"Cannot adjust TRV remotely, manually disable 'away' mode on TRV first"
139+
)
140+
105141
await self.coordinator.send_command(
106142
"set_temperature_async",
107143
{
@@ -125,6 +161,22 @@ async def async_set_preset_mode(self, preset_mode: str) -> None:
125161
if preset_mode in [PRESET_NONE, PRESET_AWAY]:
126162
raise ValueError(f"Setting preset '{preset_mode}' is not supported.")
127163
if preset_mode == PRESET_ECO:
128-
await self.async_set_temperature(temperature=self.target_temperature_low)
129-
elif preset_mode == PRESET_COMFORT:
130-
await self.async_set_temperature(temperature=self.target_temperature_high)
164+
return await self.async_set_temperature(
165+
temperature=self.target_temperature_low
166+
)
167+
if preset_mode == PRESET_COMFORT:
168+
return await self.async_set_temperature(
169+
temperature=self.target_temperature_high
170+
)
171+
172+
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
173+
"""Set new target hvac mode."""
174+
175+
if hvac_mode == HVACMode.OFF:
176+
return await self.async_set_temperature(temperature=7.5)
177+
if hvac_mode == HVACMode.HEAT:
178+
return await self.async_set_temperature(temperature=28.5)
179+
if hvac_mode == HVACMode.AUTO:
180+
return await self.async_set_temperature(
181+
temperature=self.target_temperature_low
182+
)

custom_components/cometblue/config_flow.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010
from homeassistant import config_entries
1111
from homeassistant.components.bluetooth import async_discovered_service_info
1212
from homeassistant.components.bluetooth.models import BluetoothServiceInfoBleak
13-
from homeassistant.const import CONF_ADDRESS, CONF_PIN
13+
from homeassistant.const import CONF_ADDRESS, CONF_NAME, CONF_PIN
1414
from homeassistant.data_entry_flow import FlowResult
15+
from homeassistant.helpers.device_registry import format_mac
1516

1617
from .const import CONF_DEVICE_NAME, DOMAIN
1718

@@ -20,6 +21,8 @@ def name_from_discovery(discovery: BluetoothServiceInfoBleak | None) -> str:
2021
"""Get the name from a discovery."""
2122
if discovery is None:
2223
raise ValueError("Discovery info not set")
24+
if discovery.name == str(discovery.address):
25+
return discovery.address
2326
return f"{discovery.name} {discovery.address}"
2427

2528

@@ -62,15 +65,19 @@ async def async_step_bluetooth_confirm(
6265
return self.async_show_form(
6366
step_id="bluetooth_confirm",
6467
description_placeholders={
65-
"name": name_from_discovery(self._discovery_info),
66-
"pin": "0",
68+
CONF_NAME: name_from_discovery(self._discovery_info),
6769
},
6870
data_schema=vol.Schema(
6971
{
70-
vol.Required(CONF_PIN): vol.All(
71-
vol.Coerce(int), vol.Range(min=0, max=99999999)
72-
),
73-
vol.Optional(CONF_DEVICE_NAME): str,
72+
vol.Required(
73+
CONF_PIN, description={"suggested_value": "0"}
74+
): vol.All(vol.Coerce(int), vol.Range(min=0, max=99999999)),
75+
vol.Optional(
76+
CONF_DEVICE_NAME,
77+
description={
78+
"suggested_value": name_from_discovery(self._discovery_info)
79+
},
80+
): str,
7481
},
7582
),
7683
)
@@ -82,7 +89,7 @@ async def async_step_bluetooth(
8289
self._discovery_info = discovery_info
8390
self._discovery_info.address = discovery_info.address
8491

85-
await self.async_set_unique_id(discovery_info.address)
92+
await self.async_set_unique_id(format_mac(discovery_info.address))
8693
self._abort_if_unique_id_configured(
8794
updates={CONF_ADDRESS: discovery_info.address}
8895
)

custom_components/cometblue/coordinator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ async def _async_update_data(self) -> dict[str, bytes]:
9797

9898

9999
class CometBlueBluetoothEntity(CoordinatorEntity[CometBlueDataUpdateCoordinator]):
100-
"""Coordinator entity for Gardena Bluetooth."""
100+
"""Coordinator entity for CometBlue."""
101101

102102
coordinator: CometBlueDataUpdateCoordinator
103103
_attr_has_entity_name = True

custom_components/cometblue/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,5 @@
1717
"requirements": [
1818
"git+https://github.com/rikroe/eurotronic-cometblue.git@ha-component#eurotronic-cometblue==1.0"
1919
],
20-
"version": "20231025.1"
20+
"version": "20231028.1"
2121
}

custom_components/cometblue/utils.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,8 @@ def validate_half_precision(value: float) -> float:
3434
)
3535
return value
3636
except TypeError as err:
37-
raise vol.Invalid(
38-
f"value {value} is not a float"
39-
) from err
37+
raise vol.Invalid(f"value {value} is not a float") from err
38+
4039

4140
def validate_cometblue_schedule(schedule: dict[str, time]) -> dict[str, time] | None:
4241
"""Validate the schedule of time ranges.
@@ -111,7 +110,11 @@ def valid_cometblue_schedule_keys() -> list[str]:
111110
SERVICE_HOLIDAY_SCHEMA = {
112111
vol.Required(CONF_START): cv.datetime,
113112
vol.Required(CONF_END): cv.datetime,
114-
vol.Required(CONF_TEMPERATURE): vol.All(vol.Coerce(float), vol.Range(min=MIN_TEMP, max=MAX_TEMP), validate_half_precision)
113+
vol.Required(CONF_TEMPERATURE): vol.All(
114+
vol.Coerce(float),
115+
vol.Range(min=MIN_TEMP, max=MAX_TEMP),
116+
validate_half_precision,
117+
),
115118
}
116119

117120

0 commit comments

Comments
 (0)