Skip to content

Commit ca2a6a8

Browse files
Merge pull request #28 from jakubczaplicki/fix/202510_fix
fix: fixes HA >2025.1.0 incompatibility issue
2 parents 1b94119 + ad4be12 commit ca2a6a8

File tree

3 files changed

+151
-164
lines changed

3 files changed

+151
-164
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
__pycache__/
22
.vscode
3+
.idea

custom_components/tech/__init__.py

+32-24
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,62 @@
11
"""The Tech Controllers integration."""
2+
from __future__ import annotations
3+
24
import logging
3-
import asyncio
45
import voluptuous as vol
6+
57
from homeassistant.config_entries import ConfigEntry
8+
from homeassistant.const import Platform
69
from homeassistant.core import HomeAssistant
710
from homeassistant.helpers import aiohttp_client
8-
from .tech import Tech
11+
from homeassistant.helpers.typing import ConfigType
12+
913
from .const import DOMAIN
14+
from .tech import Tech
1015

1116
_LOGGER = logging.getLogger(__name__)
1217
CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({})}, extra=vol.ALLOW_EXTRA)
1318

1419
# List the platforms that you want to support.
15-
# For your initial PR, limit it to 1 platform.
16-
PLATFORMS = ["climate"]
20+
PLATFORMS = [Platform.CLIMATE]
1721

1822

19-
async def async_setup(hass: HomeAssistant, config: dict):
23+
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
2024
"""Set up the Tech Controllers component."""
25+
hass.data.setdefault(DOMAIN, {})
2126
return True
2227

2328

24-
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
29+
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
2530
"""Set up Tech Controllers from a config entry."""
2631
_LOGGER.debug("Setting up component's entry.")
27-
_LOGGER.debug("Entry id: " + str(entry.entry_id))
28-
_LOGGER.debug("Entry -> title: " + entry.title + ", data: " + str(entry.data) + ", id: " + entry.entry_id + ", domain: " + entry.domain)
32+
_LOGGER.debug("Entry id: %s", entry.entry_id)
33+
_LOGGER.debug(
34+
"Entry -> title: %s, data: %s, id: %s, domain: %s",
35+
entry.title,
36+
entry.data,
37+
entry.entry_id,
38+
entry.domain
39+
)
40+
2941
# Store an API object for your platforms to access
3042
hass.data.setdefault(DOMAIN, {})
3143
http_session = aiohttp_client.async_get_clientsession(hass)
32-
hass.data[DOMAIN][entry.entry_id] = Tech(http_session, entry.data["user_id"], entry.data["token"])
33-
34-
for component in PLATFORMS:
35-
hass.async_create_task(
36-
hass.config_entries.async_forward_entry_setup(entry, component)
37-
)
38-
44+
hass.data[DOMAIN][entry.entry_id] = Tech(
45+
http_session,
46+
entry.data["user_id"],
47+
entry.data["token"]
48+
)
49+
50+
# Use async_forward_entry_setups instead of async_forward_entry_setup
51+
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
3952
return True
4053

4154

42-
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
55+
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
4356
"""Unload a config entry."""
44-
unload_ok = all(
45-
await asyncio.gather(
46-
*[
47-
hass.config_entries.async_forward_entry_unload(entry, component)
48-
for component in PLATFORMS
49-
]
50-
)
51-
)
57+
# Use async_unload_platforms instead of async_forward_entry_unload
58+
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
59+
5260
if unload_ok:
5361
hass.data[DOMAIN].pop(entry.entry_id)
5462

custom_components/tech/climate.py

+118-140
Original file line numberDiff line numberDiff line change
@@ -1,163 +1,141 @@
11
"""Support for Tech HVAC system."""
2+
from __future__ import annotations
3+
24
import logging
3-
import json
4-
from typing import List, Optional
5-
from homeassistant.components.climate import ClimateEntity
6-
from homeassistant.components.climate.const import (
7-
HVAC_MODE_HEAT,
8-
HVAC_MODE_COOL,
9-
HVAC_MODE_HEAT_COOL,
10-
HVAC_MODE_OFF,
11-
CURRENT_HVAC_HEAT,
12-
CURRENT_HVAC_COOL,
13-
CURRENT_HVAC_IDLE,
14-
CURRENT_HVAC_OFF,
15-
SUPPORT_PRESET_MODE,
16-
SUPPORT_TARGET_TEMPERATURE,
17-
ATTR_CURRENT_HUMIDITY
5+
from typing import Any, Final
6+
7+
from homeassistant.components.climate import (
8+
ClimateEntity,
9+
ClimateEntityFeature,
10+
HVACMode,
11+
HVACAction,
1812
)
19-
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
13+
from homeassistant.const import (
14+
ATTR_TEMPERATURE,
15+
UnitOfTemperature,
16+
)
17+
from homeassistant.config_entries import ConfigEntry
18+
from homeassistant.core import HomeAssistant
19+
from homeassistant.helpers.entity_platform import AddEntitiesCallback
20+
2021
from .const import DOMAIN
22+
from .tech import Tech
2123

2224
_LOGGER = logging.getLogger(__name__)
2325

24-
SUPPORT_HVAC = [HVAC_MODE_HEAT, HVAC_MODE_OFF]
26+
SUPPORT_HVAC: Final = [HVACMode.HEAT, HVACMode.OFF]
2527

26-
async def async_setup_entry(hass, config_entry, async_add_entities):
27-
"""Set up entry."""
28-
udid = config_entry.data["module"]["udid"]
29-
_LOGGER.debug("Setting up entry, module udid: " + udid)
30-
api = hass.data[DOMAIN][config_entry.entry_id]
31-
zones = await api.get_module_zones(udid)
32-
thermostats = [
33-
TechThermostat(
34-
zones[zone],
35-
api,
36-
udid
37-
)
38-
for zone in zones
39-
]
28+
async def async_setup_entry(
29+
hass: HomeAssistant,
30+
entry: ConfigEntry,
31+
async_add_entities: AddEntitiesCallback,
32+
) -> bool:
33+
"""Set up Tech climate based on config_entry."""
34+
api: Tech = hass.data[DOMAIN][entry.entry_id]
35+
udid: str = entry.data["module"]["udid"]
4036

41-
async_add_entities(thermostats, True)
37+
try:
38+
zones = await api.get_module_zones(udid)
39+
async_add_entities(
40+
TechThermostat(zones[zone], api, udid)
41+
for zone in zones
42+
)
43+
return True
44+
except Exception as ex:
45+
_LOGGER.error("Failed to set up Tech climate: %s", ex)
46+
return False
4247

4348

4449
class TechThermostat(ClimateEntity):
4550
"""Representation of a Tech climate."""
4651

47-
def __init__(self, device, api, udid):
52+
_attr_temperature_unit = UnitOfTemperature.CELSIUS
53+
_attr_hvac_modes = SUPPORT_HVAC
54+
_attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
55+
56+
def __init__(self, device: dict[str, Any], api: Tech, udid: str) -> None:
4857
"""Initialize the Tech device."""
49-
_LOGGER.debug("Init TechThermostat...")
50-
self._udid = udid
5158
self._api = api
52-
self._id = device["zone"]["id"]
53-
self._unique_id = udid + "_" + str(device["zone"]["id"])
59+
self._id: int = device["zone"]["id"]
60+
self._udid = udid
61+
62+
# Set unique_id first as it's required for entity registry
63+
self._attr_unique_id = f"{udid}_{device['zone']['id']}"
64+
self._attr_device_info = {
65+
"identifiers": {(DOMAIN, self._attr_unique_id)},
66+
"name": device["description"]["name"],
67+
"manufacturer": "Tech",
68+
}
69+
70+
# Initialize attributes that will be updated
71+
self._attr_name: str | None = None
72+
self._attr_target_temperature: float | None = None
73+
self._attr_current_temperature: float | None = None
74+
self._attr_current_humidity: int | None = None
75+
self._attr_hvac_action: str | None = None
76+
self._attr_hvac_mode: str = HVACMode.OFF
77+
5478
self.update_properties(device)
5579

56-
def update_properties(self, device):
57-
self._name = device["description"]["name"]
58-
if device["zone"]["setTemperature"] is not None:
59-
self._target_temperature = device["zone"]["setTemperature"] / 10
60-
else:
61-
self._target_temperature = None
62-
if device["zone"]["currentTemperature"] is not None:
63-
self._temperature = device["zone"]["currentTemperature"] / 10
64-
else:
65-
self._temperature = None
66-
if device["zone"]["humidity"] is not None:
67-
self._humidity = device["zone"]["humidity"]
68-
else:
69-
self._humidity = None
70-
state = device["zone"]["flags"]["relayState"]
80+
def update_properties(self, device: dict[str, Any]) -> None:
81+
"""Update the properties from device data."""
82+
self._attr_name = device["description"]["name"]
83+
84+
zone = device["zone"]
85+
if zone["setTemperature"] is not None:
86+
self._attr_target_temperature = zone["setTemperature"] / 10
87+
88+
if zone["currentTemperature"] is not None:
89+
self._attr_current_temperature = zone["currentTemperature"] / 10
90+
91+
if zone["humidity"] is not None:
92+
self._attr_current_humidity = zone["humidity"]
93+
94+
state = zone["flags"]["relayState"]
7195
if state == "on":
72-
self._state = CURRENT_HVAC_HEAT
96+
self._attr_hvac_action = HVACAction.HEATING
7397
elif state == "off":
74-
self._state = CURRENT_HVAC_IDLE
98+
self._attr_hvac_action = HVACAction.IDLE
7599
else:
76-
self._state = CURRENT_HVAC_OFF
77-
mode = device["zone"]["zoneState"]
78-
if mode == "zoneOn" or mode == "noAlarm":
79-
self._mode = HVAC_MODE_HEAT
80-
else:
81-
self._mode = HVAC_MODE_OFF
82-
83-
@property
84-
def unique_id(self) -> str:
85-
"""Return a unique ID."""
86-
return self._unique_id
87-
88-
@property
89-
def name(self):
90-
"""Return the name of the device."""
91-
return self._name
92-
93-
@property
94-
def supported_features(self):
95-
"""Return the list of supported features."""
96-
return SUPPORT_TARGET_TEMPERATURE #| SUPPORT_PRESET_MODE
97-
98-
@property
99-
def hvac_mode(self):
100-
"""Return hvac operation ie. heat, cool mode.
101-
102-
Need to be one of HVAC_MODE_*.
103-
"""
104-
return self._mode
105-
106-
@property
107-
def hvac_modes(self):
108-
"""Return the list of available hvac operation modes.
109-
110-
Need to be a subset of HVAC_MODES.
111-
"""
112-
return SUPPORT_HVAC
113-
114-
@property
115-
def hvac_action(self) -> Optional[str]:
116-
"""Return the current running hvac operation if supported.
117-
118-
Need to be one of CURRENT_HVAC_*.
119-
"""
120-
return self._state
121-
122-
async def async_update(self):
123-
"""Call by the Tech device callback to update state."""
124-
_LOGGER.debug("Updating Tech zone: %s, udid: %s, id: %s", self._name, self._udid, self._id)
125-
device = await self._api.get_zone(self._udid, self._id)
126-
self.update_properties(device)
127-
128-
@property
129-
def temperature_unit(self):
130-
"""Return the unit of measurement."""
131-
return TEMP_CELSIUS
132-
133-
@property
134-
def current_temperature(self):
135-
"""Return the current temperature."""
136-
return self._temperature
137-
138-
@property
139-
def current_humidity(self):
140-
"""Return current humidity."""
141-
return self._humidity
142-
143-
@property
144-
def target_temperature(self):
145-
"""Return the temperature we try to reach."""
146-
return self._target_temperature
147-
148-
async def async_set_temperature(self, **kwargs):
149-
"""Set new target temperatures."""
100+
self._attr_hvac_action = HVACAction.OFF
101+
102+
mode = zone["zoneState"]
103+
self._attr_hvac_mode = HVACMode.HEAT if mode in ["zoneOn", "noAlarm"] else HVACMode.OFF
104+
105+
async def async_update(self) -> None:
106+
"""Update the entity."""
107+
try:
108+
device = await self._api.get_zone(self._udid, self._id)
109+
self.update_properties(device)
110+
except Exception as ex:
111+
_LOGGER.error("Failed to update Tech zone %s: %s", self._attr_name, ex)
112+
113+
async def async_set_temperature(self, **kwargs: Any) -> None:
114+
"""Set new target temperature."""
150115
temperature = kwargs.get(ATTR_TEMPERATURE)
151-
if temperature:
152-
_LOGGER.debug("%s: Setting temperature to %s", self._name, temperature)
153-
self._temperature = temperature
154-
await self._api.set_const_temp(self._udid, self._id, temperature)
155-
156-
async def async_set_hvac_mode(self, hvac_mode):
116+
if temperature is not None:
117+
try:
118+
await self._api.set_const_temp(self._udid, self._id, temperature)
119+
except Exception as ex:
120+
_LOGGER.error(
121+
"Failed to set temperature for %s to %s: %s",
122+
self._attr_name,
123+
temperature,
124+
ex
125+
)
126+
127+
async def async_set_hvac_mode(self, hvac_mode: str) -> None:
157128
"""Set new target hvac mode."""
158-
_LOGGER.debug("%s: Setting hvac mode to %s", self._name, hvac_mode)
159-
if hvac_mode == HVAC_MODE_OFF:
160-
await self._api.set_zone(self._udid, self._id, False)
161-
elif hvac_mode == HVAC_MODE_HEAT:
162-
await self._api.set_zone(self._udid, self._id, True)
163-
129+
try:
130+
await self._api.set_zone(
131+
self._udid,
132+
self._id,
133+
hvac_mode == HVACMode.HEAT
134+
)
135+
except Exception as ex:
136+
_LOGGER.error(
137+
"Failed to set hvac mode for %s to %s: %s",
138+
self._attr_name,
139+
hvac_mode,
140+
ex
141+
)

0 commit comments

Comments
 (0)