forked from home-assistant/core
Compare commits
257 Commits
2024.2.0b0
...
2024.2.0b9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c605c29c3 | ||
|
|
74a75e709f | ||
|
|
2103875ff7 | ||
|
|
5c83b774bb | ||
|
|
2c870f9da9 | ||
|
|
40adb3809f | ||
|
|
8aa1242221 | ||
|
|
8569ddc5f9 | ||
|
|
7032415528 | ||
|
|
d099fb2a26 | ||
|
|
35fad52913 | ||
|
|
c170132827 | ||
|
|
439f82a4ec | ||
|
|
2481d14632 | ||
|
|
3cf826dc93 | ||
|
|
e25ddf9650 | ||
|
|
8d79ac67f5 | ||
|
|
5025c15165 | ||
|
|
ffd5e04a29 | ||
|
|
9fcdfd1b16 | ||
|
|
c1e5b2e6cc | ||
|
|
31c0d21204 | ||
|
|
3ba63fc78f | ||
|
|
0395315267 | ||
|
|
6b354457c2 | ||
|
|
df88335370 | ||
|
|
4c6c5ee63d | ||
|
|
65476914ed | ||
|
|
d30a2e3611 | ||
|
|
eb510e3630 | ||
|
|
532df5b5f1 | ||
|
|
1534f99c80 | ||
|
|
a19aa9595a | ||
|
|
e3191d098f | ||
|
|
cc36071612 | ||
|
|
2d90ee8237 | ||
|
|
16266703df | ||
|
|
dd2cc52119 | ||
|
|
c48c8c25fa | ||
|
|
83a5659d57 | ||
|
|
3183cd346d | ||
|
|
5930c841d7 | ||
|
|
f05ba22b5c | ||
|
|
ecc6cc280a | ||
|
|
f50afd6004 | ||
|
|
44ecaa740b | ||
|
|
91b1a8e962 | ||
|
|
74f1b18b73 | ||
|
|
8ab1c044bd | ||
|
|
66d8856033 | ||
|
|
5c1e4379a9 | ||
|
|
bca9826e18 | ||
|
|
3a067d445d | ||
|
|
4d7c96205d | ||
|
|
3934524d4a | ||
|
|
5747f8ce9d | ||
|
|
02ebf1d7f8 | ||
|
|
d789e83879 | ||
|
|
ce29b4a7e3 | ||
|
|
e2695ba88f | ||
|
|
7ca83a7648 | ||
|
|
c0efec4a84 | ||
|
|
f766fbfb98 | ||
|
|
bd78c44ac5 | ||
|
|
fd2469e2a7 | ||
|
|
a7010e3e80 | ||
|
|
d379a9aaae | ||
|
|
2dc630a4af | ||
|
|
ba0c065750 | ||
|
|
4fca06256b | ||
|
|
0a25788822 | ||
|
|
94e1eaa15d | ||
|
|
dfc26e4509 | ||
|
|
b14add5914 | ||
|
|
7be6aa455e | ||
|
|
40636f2273 | ||
|
|
f91f98e309 | ||
|
|
838b1338b8 | ||
|
|
d3aa7375f0 | ||
|
|
514ce59a8f | ||
|
|
25063821e1 | ||
|
|
b3c257fb79 | ||
|
|
cb02c2e6d0 | ||
|
|
1000fae905 | ||
|
|
30b9a28502 | ||
|
|
42bf086c97 | ||
|
|
064f412da4 | ||
|
|
122652b396 | ||
|
|
32b25c7e53 | ||
|
|
83c487a319 | ||
|
|
75c0c7bda0 | ||
|
|
4d7abbf8c5 | ||
|
|
bf4bc9d935 | ||
|
|
e1699b4d65 | ||
|
|
67362db547 | ||
|
|
dafdcd369c | ||
|
|
384070c158 | ||
|
|
46016004fa | ||
|
|
38fcc88c57 | ||
|
|
9cde864224 | ||
|
|
439f1766a0 | ||
|
|
12e32fb799 | ||
|
|
14ad2e91f3 | ||
|
|
8c0cd6bbab | ||
|
|
c02e96c5c0 | ||
|
|
3a08e3bec6 | ||
|
|
a62c05b983 | ||
|
|
79846d5668 | ||
|
|
482032cb87 | ||
|
|
9ebf985010 | ||
|
|
f16c0bd559 | ||
|
|
1df5ad23ef | ||
|
|
d99ba75ed8 | ||
|
|
ceeef1eacc | ||
|
|
856780ed30 | ||
|
|
500b0a9b52 | ||
|
|
855edba3a2 | ||
|
|
e7203d6015 | ||
|
|
97446a5af3 | ||
|
|
ac2e05b5c0 | ||
|
|
af07ac120e | ||
|
|
f2de666c54 | ||
|
|
38288dd68e | ||
|
|
3e2f97d105 | ||
|
|
5f014f42ac | ||
|
|
4b2adab24d | ||
|
|
ef6fed5067 | ||
|
|
0a627aed6d | ||
|
|
cb03a6e29b | ||
|
|
e630027455 | ||
|
|
280d7ef4ec | ||
|
|
93b7ffa807 | ||
|
|
cc8e9ac141 | ||
|
|
2724b115da | ||
|
|
150fb151fa | ||
|
|
4b8cb35ba0 | ||
|
|
5cce878b85 | ||
|
|
92ebc5b436 | ||
|
|
463320c8ee | ||
|
|
3bf5fa9302 | ||
|
|
650ab70444 | ||
|
|
bb8a74a3f4 | ||
|
|
49445c46a0 | ||
|
|
490101fa92 | ||
|
|
2ac4bb8e9f | ||
|
|
1c0a6970e2 | ||
|
|
f3f69a8107 | ||
|
|
a77bcccbbb | ||
|
|
e98da8596a | ||
|
|
5843c93371 | ||
|
|
5991b06574 | ||
|
|
fecdfbfb9f | ||
|
|
8cfe3821da | ||
|
|
cba67d1525 | ||
|
|
39df394414 | ||
|
|
db91a40b55 | ||
|
|
3181358484 | ||
|
|
776e2da4e6 | ||
|
|
72ffdf4f4b | ||
|
|
36bba95fd0 | ||
|
|
13fc69d8a8 | ||
|
|
94464f220c | ||
|
|
13decb9b10 | ||
|
|
b6226acd2b | ||
|
|
d04282a41c | ||
|
|
a671d0bc6c | ||
|
|
6c84e8dff0 | ||
|
|
57279b1c7b | ||
|
|
a5646e0df2 | ||
|
|
63ad3ebdf4 | ||
|
|
f2a9ef6591 | ||
|
|
2a5bb66c06 | ||
|
|
0c0be6d6a1 | ||
|
|
e99a58ad53 | ||
|
|
6c4b773bc1 | ||
|
|
c571f36c6c | ||
|
|
f1b041afbe | ||
|
|
183af92658 | ||
|
|
d71dd12263 | ||
|
|
18a7aa20b4 | ||
|
|
f8fde71ef3 | ||
|
|
966798b588 | ||
|
|
66b6f81996 | ||
|
|
4a1a5b9e87 | ||
|
|
f01f033b3f | ||
|
|
cd884de79e | ||
|
|
14be7e4a72 | ||
|
|
2be71d53c5 | ||
|
|
1c960d300d | ||
|
|
87a1482e4d | ||
|
|
24b8e60978 | ||
|
|
404e30911b | ||
|
|
9c599f7513 | ||
|
|
f3e8360949 | ||
|
|
43f8731f8a | ||
|
|
4a60d36216 | ||
|
|
07a1ee0baa | ||
|
|
a57c30be88 | ||
|
|
b1bf69689e | ||
|
|
4735aadaa8 | ||
|
|
f18f161efa | ||
|
|
3c90e3d83f | ||
|
|
57a43ef151 | ||
|
|
6afc6ca126 | ||
|
|
92a3edc536 | ||
|
|
e91e67b400 | ||
|
|
7a7bcf1a92 | ||
|
|
1383a0c13a | ||
|
|
12e3077895 | ||
|
|
05c0973937 | ||
|
|
7467d588c8 | ||
|
|
0c82e0a618 | ||
|
|
5a4f88349a | ||
|
|
e720e82fd6 | ||
|
|
8ed0af2fb7 | ||
|
|
550c0bf3c3 | ||
|
|
8bb98b4146 | ||
|
|
bd5bc6b83d | ||
|
|
a2b6b0a0bc | ||
|
|
41ad3d8987 | ||
|
|
3e7dc3588d | ||
|
|
66d802b5e5 | ||
|
|
f77bd13cc0 | ||
|
|
fe4ad30ade | ||
|
|
15a1a4bfdf | ||
|
|
3d80c4f7f6 | ||
|
|
0015af0b3c | ||
|
|
a535bda821 | ||
|
|
ca539630a6 | ||
|
|
faf2a90cd1 | ||
|
|
6aba79d7b9 | ||
|
|
b464e77112 | ||
|
|
a8b39ce332 | ||
|
|
77b25553e3 | ||
|
|
c31dfd6d00 | ||
|
|
647ac10dd9 | ||
|
|
50dfe4dec0 | ||
|
|
52a8216150 | ||
|
|
c98228110a | ||
|
|
403c2d8440 | ||
|
|
e4fc35c563 | ||
|
|
e2bbdda016 | ||
|
|
0070d2171f | ||
|
|
74ce778691 | ||
|
|
fc6cc45ee2 | ||
|
|
1353c1d24c | ||
|
|
0b6df23ee5 | ||
|
|
e34ebcb195 | ||
|
|
29f4c2d513 | ||
|
|
70f0d77ba5 | ||
|
|
133b68a68d | ||
|
|
3f619a8022 | ||
|
|
ada37f558c | ||
|
|
f76689fb75 | ||
|
|
16ad2728a6 | ||
|
|
ddc1c4bb27 | ||
|
|
bbe4483b4a |
@@ -786,8 +786,6 @@ build.json @home-assistant/supervisor
|
||||
/homeassistant/components/media_source/ @hunterjm
|
||||
/tests/components/media_source/ @hunterjm
|
||||
/homeassistant/components/mediaroom/ @dgomes
|
||||
/homeassistant/components/melcloud/ @vilppuvuorinen
|
||||
/tests/components/melcloud/ @vilppuvuorinen
|
||||
/homeassistant/components/melissa/ @kennedyshead
|
||||
/tests/components/melissa/ @kennedyshead
|
||||
/homeassistant/components/melnor/ @vanstinator
|
||||
|
||||
10
build.yaml
10
build.yaml
@@ -1,10 +1,10 @@
|
||||
image: ghcr.io/home-assistant/{arch}-homeassistant
|
||||
build_from:
|
||||
aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2024.01.0
|
||||
armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2024.01.0
|
||||
armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2024.01.0
|
||||
amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2024.01.0
|
||||
i386: ghcr.io/home-assistant/i386-homeassistant-base:2024.01.0
|
||||
aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2024.02.0
|
||||
armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2024.02.0
|
||||
armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2024.02.0
|
||||
amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2024.02.0
|
||||
i386: ghcr.io/home-assistant/i386-homeassistant-base:2024.02.0
|
||||
codenotary:
|
||||
signer: notary@home-assistant.io
|
||||
base_image: notary@home-assistant.io
|
||||
|
||||
@@ -74,6 +74,7 @@ class AdaxDevice(ClimateEntity):
|
||||
)
|
||||
_attr_target_temperature_step = PRECISION_WHOLE
|
||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
def __init__(self, heater_data: dict[str, Any], adax_data_handler: Adax) -> None:
|
||||
"""Initialize the heater."""
|
||||
|
||||
@@ -83,6 +83,7 @@ class AdvantageAirAC(AdvantageAirAcEntity, ClimateEntity):
|
||||
_attr_max_temp = 32
|
||||
_attr_min_temp = 16
|
||||
_attr_name = None
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
def __init__(self, instance: AdvantageAirData, ac_key: str) -> None:
|
||||
"""Initialize an AdvantageAir AC unit."""
|
||||
@@ -202,11 +203,16 @@ class AdvantageAirZone(AdvantageAirZoneEntity, ClimateEntity):
|
||||
"""AdvantageAir MyTemp Zone control."""
|
||||
|
||||
_attr_hvac_modes = [HVACMode.OFF, HVACMode.HEAT_COOL]
|
||||
_attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
_attr_supported_features = (
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
| ClimateEntityFeature.TURN_OFF
|
||||
| ClimateEntityFeature.TURN_ON
|
||||
)
|
||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
_attr_target_temperature_step = PRECISION_WHOLE
|
||||
_attr_max_temp = 32
|
||||
_attr_min_temp = 16
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
def __init__(self, instance: AdvantageAirData, ac_key: str, zone_key: str) -> None:
|
||||
"""Initialize an AdvantageAir Zone control."""
|
||||
|
||||
@@ -24,5 +24,5 @@
|
||||
"dependencies": ["bluetooth_adapters"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/airthings_ble",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["airthings-ble==0.6.0"]
|
||||
"requirements": ["airthings-ble==0.6.1"]
|
||||
}
|
||||
|
||||
@@ -88,9 +88,13 @@ class AirtouchAC(CoordinatorEntity, ClimateEntity):
|
||||
_attr_name = None
|
||||
|
||||
_attr_supported_features = (
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.FAN_MODE
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
| ClimateEntityFeature.FAN_MODE
|
||||
| ClimateEntityFeature.TURN_OFF
|
||||
| ClimateEntityFeature.TURN_ON
|
||||
)
|
||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
def __init__(self, coordinator, ac_number, info):
|
||||
"""Initialize the climate device."""
|
||||
@@ -192,9 +196,14 @@ class AirtouchGroup(CoordinatorEntity, ClimateEntity):
|
||||
|
||||
_attr_has_entity_name = True
|
||||
_attr_name = None
|
||||
_attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
_attr_supported_features = (
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
| ClimateEntityFeature.TURN_OFF
|
||||
| ClimateEntityFeature.TURN_ON
|
||||
)
|
||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
_attr_hvac_modes = AT_GROUP_MODES
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
def __init__(self, coordinator, group_number, info):
|
||||
"""Initialize the climate device."""
|
||||
|
||||
@@ -120,15 +120,12 @@ class Airtouch5ClimateEntity(ClimateEntity, Airtouch5Entity):
|
||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
_attr_target_temperature_step = 1
|
||||
_attr_name = None
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
|
||||
class Airtouch5AC(Airtouch5ClimateEntity):
|
||||
"""Representation of the AC unit. Used to control the overall HVAC Mode."""
|
||||
|
||||
_attr_supported_features = (
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.FAN_MODE
|
||||
)
|
||||
|
||||
def __init__(self, client: Airtouch5SimpleClient, ability: AcAbility) -> None:
|
||||
"""Initialise the Climate Entity."""
|
||||
super().__init__(client)
|
||||
@@ -152,6 +149,14 @@ class Airtouch5AC(Airtouch5ClimateEntity):
|
||||
if ability.supports_mode_heat:
|
||||
self._attr_hvac_modes.append(HVACMode.HEAT)
|
||||
|
||||
self._attr_supported_features = (
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.FAN_MODE
|
||||
)
|
||||
if len(self.hvac_modes) > 1:
|
||||
self._attr_supported_features |= (
|
||||
ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON
|
||||
)
|
||||
|
||||
self._attr_fan_modes = []
|
||||
if ability.supports_fan_speed_quiet:
|
||||
self._attr_fan_modes.append(FAN_DIFFUSE)
|
||||
@@ -262,7 +267,10 @@ class Airtouch5Zone(Airtouch5ClimateEntity):
|
||||
_attr_hvac_modes = [HVACMode.OFF, HVACMode.FAN_ONLY]
|
||||
_attr_preset_modes = [PRESET_NONE, PRESET_BOOST]
|
||||
_attr_supported_features = (
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
| ClimateEntityFeature.PRESET_MODE
|
||||
| ClimateEntityFeature.TURN_OFF
|
||||
| ClimateEntityFeature.TURN_ON
|
||||
)
|
||||
|
||||
def __init__(
|
||||
|
||||
@@ -117,6 +117,7 @@ class AirzoneClimate(AirzoneZoneEntity, ClimateEntity):
|
||||
_attr_name = None
|
||||
_speeds: dict[int, str] = {}
|
||||
_speeds_reverse: dict[str, int] = {}
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -129,7 +130,11 @@ class AirzoneClimate(AirzoneZoneEntity, ClimateEntity):
|
||||
super().__init__(coordinator, entry, system_zone_id, zone_data)
|
||||
|
||||
self._attr_unique_id = f"{self._attr_unique_id}_{system_zone_id}"
|
||||
self._attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
self._attr_supported_features = (
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
| ClimateEntityFeature.TURN_OFF
|
||||
| ClimateEntityFeature.TURN_ON
|
||||
)
|
||||
self._attr_target_temperature_step = API_TEMPERATURE_STEP
|
||||
self._attr_temperature_unit = TEMP_UNIT_LIB_TO_HASS[
|
||||
self.get_airzone_value(AZD_TEMP_UNIT)
|
||||
|
||||
@@ -144,8 +144,8 @@ class AirzoneClimate(AirzoneEntity, ClimateEntity):
|
||||
"""Define an Airzone Cloud climate."""
|
||||
|
||||
_attr_name = None
|
||||
_attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
@callback
|
||||
def _handle_coordinator_update(self) -> None:
|
||||
@@ -175,6 +175,12 @@ class AirzoneClimate(AirzoneEntity, ClimateEntity):
|
||||
class AirzoneDeviceClimate(AirzoneClimate):
|
||||
"""Define an Airzone Cloud Device base class."""
|
||||
|
||||
_attr_supported_features = (
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
| ClimateEntityFeature.TURN_OFF
|
||||
| ClimateEntityFeature.TURN_ON
|
||||
)
|
||||
|
||||
async def async_turn_on(self) -> None:
|
||||
"""Turn the entity on."""
|
||||
params = {
|
||||
@@ -212,6 +218,12 @@ class AirzoneDeviceClimate(AirzoneClimate):
|
||||
class AirzoneDeviceGroupClimate(AirzoneClimate):
|
||||
"""Define an Airzone Cloud DeviceGroup base class."""
|
||||
|
||||
_attr_supported_features = (
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
| ClimateEntityFeature.TURN_OFF
|
||||
| ClimateEntityFeature.TURN_ON
|
||||
)
|
||||
|
||||
async def async_turn_on(self) -> None:
|
||||
"""Turn the entity on."""
|
||||
params = {
|
||||
|
||||
@@ -3,18 +3,46 @@ from __future__ import annotations
|
||||
|
||||
import amberelectric
|
||||
from amberelectric.api import amber_api
|
||||
from amberelectric.model.site import Site
|
||||
from amberelectric.model.site import Site, SiteStatus
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.const import CONF_API_TOKEN
|
||||
from homeassistant.data_entry_flow import FlowResult
|
||||
from homeassistant.helpers.selector import (
|
||||
SelectOptionDict,
|
||||
SelectSelector,
|
||||
SelectSelectorConfig,
|
||||
SelectSelectorMode,
|
||||
)
|
||||
|
||||
from .const import CONF_SITE_ID, CONF_SITE_NAME, CONF_SITE_NMI, DOMAIN
|
||||
from .const import CONF_SITE_ID, CONF_SITE_NAME, DOMAIN
|
||||
|
||||
API_URL = "https://app.amber.com.au/developers"
|
||||
|
||||
|
||||
def generate_site_selector_name(site: Site) -> str:
|
||||
"""Generate the name to show in the site drop down in the configuration flow."""
|
||||
if site.status == SiteStatus.CLOSED:
|
||||
return site.nmi + " (Closed: " + site.closed_on.isoformat() + ")" # type: ignore[no-any-return]
|
||||
if site.status == SiteStatus.PENDING:
|
||||
return site.nmi + " (Pending)" # type: ignore[no-any-return]
|
||||
return site.nmi # type: ignore[no-any-return]
|
||||
|
||||
|
||||
def filter_sites(sites: list[Site]) -> list[Site]:
|
||||
"""Deduplicates the list of sites."""
|
||||
filtered: list[Site] = []
|
||||
filtered_nmi: set[str] = set()
|
||||
|
||||
for site in sorted(sites, key=lambda site: site.status.value):
|
||||
if site.status == SiteStatus.ACTIVE or site.nmi not in filtered_nmi:
|
||||
filtered.append(site)
|
||||
filtered_nmi.add(site.nmi)
|
||||
|
||||
return filtered
|
||||
|
||||
|
||||
class AmberElectricConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow."""
|
||||
|
||||
@@ -31,7 +59,7 @@ class AmberElectricConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
api: amber_api.AmberApi = amber_api.AmberApi.create(configuration)
|
||||
|
||||
try:
|
||||
sites: list[Site] = api.get_sites()
|
||||
sites: list[Site] = filter_sites(api.get_sites())
|
||||
if len(sites) == 0:
|
||||
self._errors[CONF_API_TOKEN] = "no_site"
|
||||
return None
|
||||
@@ -86,38 +114,31 @@ class AmberElectricConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
assert self._sites is not None
|
||||
assert self._api_token is not None
|
||||
|
||||
api_token = self._api_token
|
||||
if user_input is not None:
|
||||
site_nmi = user_input[CONF_SITE_NMI]
|
||||
sites = [site for site in self._sites if site.nmi == site_nmi]
|
||||
site = sites[0]
|
||||
site_id = site.id
|
||||
site_id = user_input[CONF_SITE_ID]
|
||||
name = user_input.get(CONF_SITE_NAME, site_id)
|
||||
return self.async_create_entry(
|
||||
title=name,
|
||||
data={
|
||||
CONF_SITE_ID: site_id,
|
||||
CONF_API_TOKEN: api_token,
|
||||
CONF_SITE_NMI: site.nmi,
|
||||
},
|
||||
data={CONF_SITE_ID: site_id, CONF_API_TOKEN: self._api_token},
|
||||
)
|
||||
|
||||
user_input = {
|
||||
CONF_API_TOKEN: api_token,
|
||||
CONF_SITE_NMI: "",
|
||||
CONF_SITE_NAME: "",
|
||||
}
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="site",
|
||||
data_schema=vol.Schema(
|
||||
{
|
||||
vol.Required(
|
||||
CONF_SITE_NMI, default=user_input[CONF_SITE_NMI]
|
||||
): vol.In([site.nmi for site in self._sites]),
|
||||
vol.Optional(
|
||||
CONF_SITE_NAME, default=user_input[CONF_SITE_NAME]
|
||||
): str,
|
||||
vol.Required(CONF_SITE_ID): SelectSelector(
|
||||
SelectSelectorConfig(
|
||||
options=[
|
||||
SelectOptionDict(
|
||||
value=site.id,
|
||||
label=generate_site_selector_name(site),
|
||||
)
|
||||
for site in self._sites
|
||||
],
|
||||
mode=SelectSelectorMode.DROPDOWN,
|
||||
)
|
||||
),
|
||||
vol.Optional(CONF_SITE_NAME): str,
|
||||
}
|
||||
),
|
||||
errors=self._errors,
|
||||
|
||||
@@ -6,7 +6,6 @@ from homeassistant.const import Platform
|
||||
DOMAIN = "amberelectric"
|
||||
CONF_SITE_NAME = "site_name"
|
||||
CONF_SITE_ID = "site_id"
|
||||
CONF_SITE_NMI = "site_nmi"
|
||||
|
||||
ATTRIBUTION = "Data provided by Amber Electric"
|
||||
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/amberelectric",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["amberelectric"],
|
||||
"requirements": ["amberelectric==1.0.4"]
|
||||
"requirements": ["amberelectric==1.1.0"]
|
||||
}
|
||||
|
||||
@@ -153,10 +153,15 @@ class AmbiclimateEntity(ClimateEntity):
|
||||
|
||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
_attr_target_temperature_step = 1
|
||||
_attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
_attr_supported_features = (
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
| ClimateEntityFeature.TURN_OFF
|
||||
| ClimateEntityFeature.TURN_ON
|
||||
)
|
||||
_attr_hvac_modes = [HVACMode.HEAT, HVACMode.OFF]
|
||||
_attr_has_entity_name = True
|
||||
_attr_name = None
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
def __init__(self, heater: AmbiclimateDevice, store: Store[dict[str, Any]]) -> None:
|
||||
"""Initialize the thermostat."""
|
||||
|
||||
@@ -3,11 +3,15 @@ from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from python_homeassistant_analytics import HomeassistantAnalyticsClient
|
||||
from python_homeassistant_analytics import (
|
||||
HomeassistantAnalyticsClient,
|
||||
HomeassistantAnalyticsConnectionError,
|
||||
)
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .const import CONF_TRACKED_INTEGRATIONS, DOMAIN
|
||||
@@ -28,7 +32,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up Homeassistant Analytics from a config entry."""
|
||||
client = HomeassistantAnalyticsClient(session=async_get_clientsession(hass))
|
||||
|
||||
integrations = await client.get_integrations()
|
||||
try:
|
||||
integrations = await client.get_integrations()
|
||||
except HomeassistantAnalyticsConnectionError as ex:
|
||||
raise ConfigEntryNotReady("Could not fetch integration list") from ex
|
||||
|
||||
names = {}
|
||||
for integration in entry.options[CONF_TRACKED_INTEGRATIONS]:
|
||||
|
||||
@@ -10,6 +10,7 @@ from homeassistant.components.sensor import (
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
@@ -93,6 +94,7 @@ class HomeassistantAnalyticsSensor(
|
||||
"""Home Assistant Analytics Sensor."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
_attr_entity_category = EntityCategory.DIAGNOSTIC
|
||||
|
||||
entity_description: AnalyticsSensorEntityDescription
|
||||
|
||||
|
||||
@@ -3,7 +3,12 @@
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"tracked_integrations": "Integrations"
|
||||
"tracked_integrations": "Integrations",
|
||||
"tracked_custom_integrations": "Custom integrations"
|
||||
},
|
||||
"data_description": {
|
||||
"tracked_integrations": "Select the integrations you want to track",
|
||||
"tracked_custom_integrations": "Select the custom integrations you want to track"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -16,7 +21,12 @@
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"tracked_integrations": "[%key:component::analytics_insights::config::step::user::data::tracked_integrations%]"
|
||||
"tracked_integrations": "[%key:component::analytics_insights::config::step::user::data::tracked_integrations%]",
|
||||
"tracked_custom_integrations": "[%key:component::analytics_insights::config::step::user::data::tracked_custom_integrations%]"
|
||||
},
|
||||
"data_description": {
|
||||
"tracked_integrations": "[%key:component::analytics_insights::config::step::user::data_description::tracked_integrations%]",
|
||||
"tracked_custom_integrations": "[%key:component::analytics_insights::config::step::user::data_description::tracked_custom_integrations%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -155,7 +155,7 @@ class AppleTvMediaPlayer(AppleTVEntity, MediaPlayerEntity):
|
||||
else:
|
||||
self._app_list = {
|
||||
app_name: app.identifier
|
||||
for app in sorted(apps, key=lambda app: app_name.lower())
|
||||
for app in sorted(apps, key=lambda app: (app.name or "").lower())
|
||||
if (app_name := app.name) is not None
|
||||
}
|
||||
self.async_write_ha_state()
|
||||
|
||||
@@ -11,6 +11,7 @@ from typing import Any, TypeVar, cast
|
||||
from aioasuswrt.asuswrt import AsusWrt as AsusWrtLegacy
|
||||
from aiohttp import ClientSession
|
||||
from pyasuswrt import AsusWrtError, AsusWrtHttp
|
||||
from pyasuswrt.exceptions import AsusWrtNotAvailableInfoError
|
||||
|
||||
from homeassistant.const import (
|
||||
CONF_HOST,
|
||||
@@ -354,13 +355,14 @@ class AsusWrtHttpBridge(AsusWrtBridge):
|
||||
async def async_get_available_sensors(self) -> dict[str, dict[str, Any]]:
|
||||
"""Return a dictionary of available sensors for this bridge."""
|
||||
sensors_temperatures = await self._get_available_temperature_sensors()
|
||||
sensors_loadavg = await self._get_loadavg_sensors_availability()
|
||||
sensors_types = {
|
||||
SENSORS_TYPE_BYTES: {
|
||||
KEY_SENSORS: SENSORS_BYTES,
|
||||
KEY_METHOD: self._get_bytes,
|
||||
},
|
||||
SENSORS_TYPE_LOAD_AVG: {
|
||||
KEY_SENSORS: SENSORS_LOAD_AVG,
|
||||
KEY_SENSORS: sensors_loadavg,
|
||||
KEY_METHOD: self._get_load_avg,
|
||||
},
|
||||
SENSORS_TYPE_RATES: {
|
||||
@@ -393,6 +395,16 @@ class AsusWrtHttpBridge(AsusWrtBridge):
|
||||
return []
|
||||
return available_sensors
|
||||
|
||||
async def _get_loadavg_sensors_availability(self) -> list[str]:
|
||||
"""Check if load avg is available on the router."""
|
||||
try:
|
||||
await self._api.async_get_loadavg()
|
||||
except AsusWrtNotAvailableInfoError:
|
||||
return []
|
||||
except AsusWrtError:
|
||||
pass
|
||||
return SENSORS_LOAD_AVG
|
||||
|
||||
@handle_errors_and_zip(AsusWrtError, SENSORS_BYTES)
|
||||
async def _get_bytes(self) -> Any:
|
||||
"""Fetch byte information from the router."""
|
||||
|
||||
@@ -46,6 +46,7 @@ class AtagThermostat(AtagEntity, ClimateEntity):
|
||||
_attr_supported_features = (
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE
|
||||
)
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
def __init__(self, coordinator, atag_id):
|
||||
"""Initialize an Atag climate device."""
|
||||
|
||||
@@ -28,5 +28,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/august",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["pubnub", "yalexs"],
|
||||
"requirements": ["yalexs==1.10.0", "yalexs-ble==2.4.0"]
|
||||
"requirements": ["yalexs==1.10.0", "yalexs-ble==2.4.1"]
|
||||
}
|
||||
|
||||
@@ -33,10 +33,15 @@ async def async_setup_entry(
|
||||
class BAFAutoComfort(BAFEntity, ClimateEntity):
|
||||
"""BAF climate auto comfort."""
|
||||
|
||||
_attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
_attr_supported_features = (
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
| ClimateEntityFeature.TURN_OFF
|
||||
| ClimateEntityFeature.TURN_ON
|
||||
)
|
||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
_attr_hvac_modes = [HVACMode.OFF, HVACMode.FAN_ONLY]
|
||||
_attr_translation_key = "auto_comfort"
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
@callback
|
||||
def _async_update_attrs(self) -> None:
|
||||
|
||||
@@ -63,6 +63,7 @@ class BalboaClimateEntity(BalboaEntity, ClimateEntity):
|
||||
)
|
||||
_attr_translation_key = DOMAIN
|
||||
_attr_name = None
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
def __init__(self, client: SpaClient) -> None:
|
||||
"""Initialize the climate entity."""
|
||||
|
||||
@@ -53,8 +53,13 @@ async def async_setup_entry(
|
||||
class BleBoxClimateEntity(BleBoxEntity[blebox_uniapi.climate.Climate], ClimateEntity):
|
||||
"""Representation of a BleBox climate feature (saunaBox)."""
|
||||
|
||||
_attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
_attr_supported_features = (
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
| ClimateEntityFeature.TURN_OFF
|
||||
| ClimateEntityFeature.TURN_ON
|
||||
)
|
||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
@property
|
||||
def hvac_modes(self):
|
||||
|
||||
@@ -14,6 +14,7 @@ from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryError, ConfigEntryNotReady
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import BringDataUpdateCoordinator
|
||||
@@ -29,14 +30,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
email = entry.data[CONF_EMAIL]
|
||||
password = entry.data[CONF_PASSWORD]
|
||||
|
||||
bring = Bring(email, password)
|
||||
|
||||
def login_and_load_lists() -> None:
|
||||
bring.login()
|
||||
bring.loadLists()
|
||||
session = async_get_clientsession(hass)
|
||||
bring = Bring(email, password, sessionAsync=session)
|
||||
|
||||
try:
|
||||
await hass.async_add_executor_job(login_and_load_lists)
|
||||
await bring.loginAsync()
|
||||
await bring.loadListsAsync()
|
||||
except BringRequestException as e:
|
||||
raise ConfigEntryNotReady(
|
||||
f"Timeout while connecting for email '{email}'"
|
||||
|
||||
@@ -11,6 +11,7 @@ import voluptuous as vol
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD
|
||||
from homeassistant.data_entry_flow import FlowResult
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.selector import (
|
||||
TextSelector,
|
||||
TextSelectorConfig,
|
||||
@@ -48,14 +49,14 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"""Handle the initial step."""
|
||||
errors: dict[str, str] = {}
|
||||
if user_input is not None:
|
||||
bring = Bring(user_input[CONF_EMAIL], user_input[CONF_PASSWORD])
|
||||
|
||||
def login_and_load_lists() -> None:
|
||||
bring.login()
|
||||
bring.loadLists()
|
||||
session = async_get_clientsession(self.hass)
|
||||
bring = Bring(
|
||||
user_input[CONF_EMAIL], user_input[CONF_PASSWORD], sessionAsync=session
|
||||
)
|
||||
|
||||
try:
|
||||
await self.hass.async_add_executor_job(login_and_load_lists)
|
||||
await bring.loginAsync()
|
||||
await bring.loadListsAsync()
|
||||
except BringRequestException:
|
||||
errors["base"] = "cannot_connect"
|
||||
except BringAuthException:
|
||||
|
||||
@@ -40,9 +40,7 @@ class BringDataUpdateCoordinator(DataUpdateCoordinator[dict[str, BringData]]):
|
||||
|
||||
async def _async_update_data(self) -> dict[str, BringData]:
|
||||
try:
|
||||
lists_response = await self.hass.async_add_executor_job(
|
||||
self.bring.loadLists
|
||||
)
|
||||
lists_response = await self.bring.loadListsAsync()
|
||||
except BringRequestException as e:
|
||||
raise UpdateFailed("Unable to connect and retrieve data from bring") from e
|
||||
except BringParseException as e:
|
||||
@@ -51,9 +49,7 @@ class BringDataUpdateCoordinator(DataUpdateCoordinator[dict[str, BringData]]):
|
||||
list_dict = {}
|
||||
for lst in lists_response["lists"]:
|
||||
try:
|
||||
items = await self.hass.async_add_executor_job(
|
||||
self.bring.getItems, lst["listUuid"]
|
||||
)
|
||||
items = await self.bring.getItemsAsync(lst["listUuid"])
|
||||
except BringRequestException as e:
|
||||
raise UpdateFailed(
|
||||
"Unable to connect and retrieve data from bring"
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/bring",
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"requirements": ["python-bring-api==2.0.0"]
|
||||
"requirements": ["python-bring-api==3.0.0"]
|
||||
}
|
||||
|
||||
@@ -91,11 +91,8 @@ class BringTodoListEntity(
|
||||
async def async_create_todo_item(self, item: TodoItem) -> None:
|
||||
"""Add an item to the To-do list."""
|
||||
try:
|
||||
await self.hass.async_add_executor_job(
|
||||
self.coordinator.bring.saveItem,
|
||||
self.bring_list["listUuid"],
|
||||
item.summary,
|
||||
item.description or "",
|
||||
await self.coordinator.bring.saveItemAsync(
|
||||
self.bring_list["listUuid"], item.summary, item.description or ""
|
||||
)
|
||||
except BringRequestException as e:
|
||||
raise HomeAssistantError("Unable to save todo item for bring") from e
|
||||
@@ -126,16 +123,14 @@ class BringTodoListEntity(
|
||||
assert item.uid
|
||||
|
||||
if item.status == TodoItemStatus.COMPLETED:
|
||||
await self.hass.async_add_executor_job(
|
||||
self.coordinator.bring.removeItem,
|
||||
await self.coordinator.bring.removeItemAsync(
|
||||
bring_list["listUuid"],
|
||||
item.uid,
|
||||
)
|
||||
|
||||
elif item.summary == item.uid:
|
||||
try:
|
||||
await self.hass.async_add_executor_job(
|
||||
self.coordinator.bring.updateItem,
|
||||
await self.coordinator.bring.updateItemAsync(
|
||||
bring_list["listUuid"],
|
||||
item.uid,
|
||||
item.description or "",
|
||||
@@ -144,13 +139,11 @@ class BringTodoListEntity(
|
||||
raise HomeAssistantError("Unable to update todo item for bring") from e
|
||||
else:
|
||||
try:
|
||||
await self.hass.async_add_executor_job(
|
||||
self.coordinator.bring.removeItem,
|
||||
await self.coordinator.bring.removeItemAsync(
|
||||
bring_list["listUuid"],
|
||||
item.uid,
|
||||
)
|
||||
await self.hass.async_add_executor_job(
|
||||
self.coordinator.bring.saveItem,
|
||||
await self.coordinator.bring.saveItemAsync(
|
||||
bring_list["listUuid"],
|
||||
item.summary,
|
||||
item.description or "",
|
||||
@@ -164,8 +157,8 @@ class BringTodoListEntity(
|
||||
"""Delete an item from the To-do list."""
|
||||
for uid in uids:
|
||||
try:
|
||||
await self.hass.async_add_executor_job(
|
||||
self.coordinator.bring.removeItem, self.bring_list["listUuid"], uid
|
||||
await self.coordinator.bring.removeItemAsync(
|
||||
self.bring_list["listUuid"], uid
|
||||
)
|
||||
except BringRequestException as e:
|
||||
raise HomeAssistantError("Unable to delete todo item for bring") from e
|
||||
|
||||
@@ -35,9 +35,14 @@ class BroadlinkThermostat(ClimateEntity, BroadlinkEntity):
|
||||
|
||||
_attr_has_entity_name = True
|
||||
_attr_hvac_modes = [HVACMode.HEAT, HVACMode.OFF, HVACMode.AUTO]
|
||||
_attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
_attr_supported_features = (
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
| ClimateEntityFeature.TURN_OFF
|
||||
| ClimateEntityFeature.TURN_ON
|
||||
)
|
||||
_attr_target_temperature_step = PRECISION_HALVES
|
||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
def __init__(self, device: BroadlinkDevice) -> None:
|
||||
"""Initialize the climate entity."""
|
||||
|
||||
@@ -73,12 +73,16 @@ class BSBLANClimate(
|
||||
_attr_name = None
|
||||
# Determine preset modes
|
||||
_attr_supported_features = (
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
| ClimateEntityFeature.PRESET_MODE
|
||||
| ClimateEntityFeature.TURN_OFF
|
||||
| ClimateEntityFeature.TURN_ON
|
||||
)
|
||||
_attr_preset_modes = PRESET_MODES
|
||||
|
||||
# Determine hvac modes
|
||||
_attr_hvac_modes = HVAC_MODES
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
||||
@@ -98,6 +98,6 @@ class TurboJPEGSingleton:
|
||||
TurboJPEGSingleton.__instance = TurboJPEG()
|
||||
except Exception: # pylint: disable=broad-except
|
||||
_LOGGER.exception(
|
||||
"Error loading libturbojpeg; Cameras may impact HomeKit performance"
|
||||
"Error loading libturbojpeg; Camera snapshot performance will be sub-optimal"
|
||||
)
|
||||
TurboJPEGSingleton.__instance = False
|
||||
|
||||
@@ -64,8 +64,11 @@ class CCM15Climate(CoordinatorEntity[CCM15Coordinator], ClimateEntity):
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
| ClimateEntityFeature.FAN_MODE
|
||||
| ClimateEntityFeature.SWING_MODE
|
||||
| ClimateEntityFeature.TURN_OFF
|
||||
| ClimateEntityFeature.TURN_ON
|
||||
)
|
||||
_attr_name = None
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
def __init__(
|
||||
self, ac_host: str, ac_index: int, coordinator: CCM15Coordinator
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"integration_type": "system",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["hass_nabucasa"],
|
||||
"requirements": ["hass-nabucasa==0.75.1"]
|
||||
"requirements": ["hass-nabucasa==0.76.0"]
|
||||
}
|
||||
|
||||
@@ -4,8 +4,11 @@ from __future__ import annotations
|
||||
from collections.abc import Mapping
|
||||
from typing import Any
|
||||
|
||||
from aioelectricitymaps import ElectricityMaps
|
||||
from aioelectricitymaps.exceptions import ElectricityMapsError, InvalidToken
|
||||
from aioelectricitymaps import (
|
||||
ElectricityMaps,
|
||||
ElectricityMapsError,
|
||||
ElectricityMapsInvalidTokenError,
|
||||
)
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
@@ -146,7 +149,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
|
||||
try:
|
||||
await fetch_latest_carbon_intensity(self.hass, em, data)
|
||||
except InvalidToken:
|
||||
except ElectricityMapsInvalidTokenError:
|
||||
errors["base"] = "invalid_auth"
|
||||
except ElectricityMapsError:
|
||||
errors["base"] = "unknown"
|
||||
|
||||
@@ -4,9 +4,12 @@ from __future__ import annotations
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
from aioelectricitymaps import ElectricityMaps
|
||||
from aioelectricitymaps.exceptions import ElectricityMapsError, InvalidToken
|
||||
from aioelectricitymaps.models import CarbonIntensityResponse
|
||||
from aioelectricitymaps import (
|
||||
CarbonIntensityResponse,
|
||||
ElectricityMaps,
|
||||
ElectricityMapsError,
|
||||
ElectricityMapsInvalidTokenError,
|
||||
)
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
@@ -43,7 +46,7 @@ class CO2SignalCoordinator(DataUpdateCoordinator[CarbonIntensityResponse]):
|
||||
return await fetch_latest_carbon_intensity(
|
||||
self.hass, self.client, self.config_entry.data
|
||||
)
|
||||
except InvalidToken as err:
|
||||
except ElectricityMapsInvalidTokenError as err:
|
||||
raise ConfigEntryAuthFailed from err
|
||||
except ElectricityMapsError as err:
|
||||
raise UpdateFailed(str(err)) from err
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["aioelectricitymaps"],
|
||||
"requirements": ["aioelectricitymaps==0.2.0"]
|
||||
"requirements": ["aioelectricitymaps==0.3.1"]
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
"""Support for climates."""
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from enum import StrEnum
|
||||
from typing import Any
|
||||
|
||||
from aiocomelit import ComelitSerialBridgeObject
|
||||
from aiocomelit.const import CLIMATE, SLEEP_BETWEEN_CALLS
|
||||
from aiocomelit.const import CLIMATE
|
||||
|
||||
from homeassistant.components.climate import (
|
||||
ClimateEntity,
|
||||
@@ -91,11 +90,16 @@ class ComelitClimateEntity(CoordinatorEntity[ComelitSerialBridge], ClimateEntity
|
||||
_attr_hvac_modes = [HVACMode.AUTO, HVACMode.COOL, HVACMode.HEAT, HVACMode.OFF]
|
||||
_attr_max_temp = 30
|
||||
_attr_min_temp = 5
|
||||
_attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
_attr_supported_features = (
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
| ClimateEntityFeature.TURN_OFF
|
||||
| ClimateEntityFeature.TURN_ON
|
||||
)
|
||||
_attr_target_temperature_step = PRECISION_TENTHS
|
||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
_attr_has_entity_name = True
|
||||
_attr_name = None
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -186,7 +190,6 @@ class ComelitClimateEntity(CoordinatorEntity[ComelitSerialBridge], ClimateEntity
|
||||
await self.coordinator.api.set_clima_status(
|
||||
self._device.index, ClimaAction.MANUAL
|
||||
)
|
||||
await asyncio.sleep(SLEEP_BETWEEN_CALLS)
|
||||
await self.coordinator.api.set_clima_status(
|
||||
self._device.index, ClimaAction.SET, target_temp
|
||||
)
|
||||
@@ -198,7 +201,6 @@ class ComelitClimateEntity(CoordinatorEntity[ComelitSerialBridge], ClimateEntity
|
||||
await self.coordinator.api.set_clima_status(
|
||||
self._device.index, ClimaAction.ON
|
||||
)
|
||||
await asyncio.sleep(SLEEP_BETWEEN_CALLS)
|
||||
await self.coordinator.api.set_clima_status(
|
||||
self._device.index, MODE_TO_ACTION[hvac_mode]
|
||||
)
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/comelit",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["aiocomelit"],
|
||||
"requirements": ["aiocomelit==0.8.2"]
|
||||
"requirements": ["aiocomelit==0.8.3"]
|
||||
}
|
||||
|
||||
@@ -349,7 +349,7 @@ async def websocket_hass_agent_debug(
|
||||
},
|
||||
# Slot values that would be received by the intent
|
||||
"slots": { # direct access to values
|
||||
entity_key: entity.value
|
||||
entity_key: entity.text or entity.value
|
||||
for entity_key, entity in result.entities.items()
|
||||
},
|
||||
# Extra slot details, such as the originally matched text
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"""Standard conversation implementation for Home Assistant."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
@@ -12,22 +13,15 @@ import re
|
||||
from typing import IO, Any
|
||||
|
||||
from hassil.expression import Expression, ListReference, Sequence
|
||||
from hassil.intents import (
|
||||
Intents,
|
||||
ResponseType,
|
||||
SlotList,
|
||||
TextSlotList,
|
||||
WildcardSlotList,
|
||||
)
|
||||
from hassil.intents import Intents, SlotList, TextSlotList, WildcardSlotList
|
||||
from hassil.recognize import (
|
||||
MISSING_ENTITY,
|
||||
RecognizeResult,
|
||||
UnmatchedEntity,
|
||||
UnmatchedTextEntity,
|
||||
recognize_all,
|
||||
)
|
||||
from hassil.util import merge_dict
|
||||
from home_assistant_intents import get_intents, get_languages
|
||||
from home_assistant_intents import ErrorKey, get_intents, get_languages
|
||||
import yaml
|
||||
|
||||
from homeassistant import core, setup
|
||||
@@ -238,7 +232,10 @@ class DefaultAgent(AbstractConversationAgent):
|
||||
)
|
||||
)
|
||||
|
||||
# Use last non-empty result as response
|
||||
# Use last non-empty result as response.
|
||||
#
|
||||
# There may be multiple copies of a trigger running when editing in
|
||||
# the UI, so it's critical that we filter out empty responses here.
|
||||
response_text: str | None = None
|
||||
for trigger_response in trigger_responses:
|
||||
response_text = response_text or trigger_response
|
||||
@@ -246,7 +243,7 @@ class DefaultAgent(AbstractConversationAgent):
|
||||
# Convert to conversation result
|
||||
response = intent.IntentResponse(language=language)
|
||||
response.response_type = intent.IntentResponseType.ACTION_DONE
|
||||
response.async_set_speech(response_text or "")
|
||||
response.async_set_speech(response_text or "Done")
|
||||
|
||||
return ConversationResult(response=response)
|
||||
|
||||
@@ -259,7 +256,7 @@ class DefaultAgent(AbstractConversationAgent):
|
||||
return _make_error_result(
|
||||
language,
|
||||
intent.IntentResponseErrorCode.NO_INTENT_MATCH,
|
||||
self._get_error_text(ResponseType.NO_INTENT, lang_intents),
|
||||
self._get_error_text(ErrorKey.NO_INTENT, lang_intents),
|
||||
conversation_id,
|
||||
)
|
||||
|
||||
@@ -268,14 +265,14 @@ class DefaultAgent(AbstractConversationAgent):
|
||||
_LOGGER.debug(
|
||||
"Recognized intent '%s' for template '%s' but had unmatched: %s",
|
||||
result.intent.name,
|
||||
result.intent_sentence.text
|
||||
if result.intent_sentence is not None
|
||||
else "",
|
||||
(
|
||||
result.intent_sentence.text
|
||||
if result.intent_sentence is not None
|
||||
else ""
|
||||
),
|
||||
result.unmatched_entities_list,
|
||||
)
|
||||
error_response_type, error_response_args = _get_unmatched_response(
|
||||
result.unmatched_entities
|
||||
)
|
||||
error_response_type, error_response_args = _get_unmatched_response(result)
|
||||
return _make_error_result(
|
||||
language,
|
||||
intent.IntentResponseErrorCode.NO_VALID_TARGETS,
|
||||
@@ -291,7 +288,8 @@ class DefaultAgent(AbstractConversationAgent):
|
||||
|
||||
# Slot values to pass to the intent
|
||||
slots = {
|
||||
entity.name: {"value": entity.value} for entity in result.entities_list
|
||||
entity.name: {"value": entity.value, "text": entity.text or entity.value}
|
||||
for entity in result.entities_list
|
||||
}
|
||||
|
||||
try:
|
||||
@@ -325,7 +323,7 @@ class DefaultAgent(AbstractConversationAgent):
|
||||
return _make_error_result(
|
||||
language,
|
||||
intent.IntentResponseErrorCode.FAILED_TO_HANDLE,
|
||||
self._get_error_text(ResponseType.HANDLE_ERROR, lang_intents),
|
||||
self._get_error_text(ErrorKey.HANDLE_ERROR, lang_intents),
|
||||
conversation_id,
|
||||
)
|
||||
except intent.IntentUnexpectedError:
|
||||
@@ -333,7 +331,7 @@ class DefaultAgent(AbstractConversationAgent):
|
||||
return _make_error_result(
|
||||
language,
|
||||
intent.IntentResponseErrorCode.UNKNOWN,
|
||||
self._get_error_text(ResponseType.HANDLE_ERROR, lang_intents),
|
||||
self._get_error_text(ErrorKey.HANDLE_ERROR, lang_intents),
|
||||
conversation_id,
|
||||
)
|
||||
|
||||
@@ -480,9 +478,11 @@ class DefaultAgent(AbstractConversationAgent):
|
||||
for entity_name, entity_value in recognize_result.entities.items()
|
||||
},
|
||||
# First matched or unmatched state
|
||||
"state": template.TemplateState(self.hass, state1)
|
||||
if state1 is not None
|
||||
else None,
|
||||
"state": (
|
||||
template.TemplateState(self.hass, state1)
|
||||
if state1 is not None
|
||||
else None
|
||||
),
|
||||
"query": {
|
||||
# Entity states that matched the query (e.g, "on")
|
||||
"matched": [
|
||||
@@ -740,7 +740,7 @@ class DefaultAgent(AbstractConversationAgent):
|
||||
|
||||
if not entity:
|
||||
# Default name
|
||||
entity_names.append((state.name, state.name, context))
|
||||
entity_names.append((state.name, state.entity_id, context))
|
||||
continue
|
||||
|
||||
if entity.aliases:
|
||||
@@ -748,10 +748,10 @@ class DefaultAgent(AbstractConversationAgent):
|
||||
if not alias.strip():
|
||||
continue
|
||||
|
||||
entity_names.append((alias, alias, context))
|
||||
entity_names.append((alias, state.entity_id, context))
|
||||
|
||||
# Default name
|
||||
entity_names.append((state.name, state.name, context))
|
||||
entity_names.append((state.name, state.entity_id, context))
|
||||
|
||||
# Expose all areas
|
||||
areas = ar.async_get(self.hass)
|
||||
@@ -791,11 +791,11 @@ class DefaultAgent(AbstractConversationAgent):
|
||||
if device_area is None:
|
||||
return None
|
||||
|
||||
return {"area": device_area.id}
|
||||
return {"area": {"value": device_area.id, "text": device_area.name}}
|
||||
|
||||
def _get_error_text(
|
||||
self,
|
||||
response_type: ResponseType,
|
||||
error_key: ErrorKey,
|
||||
lang_intents: LanguageIntents | None,
|
||||
**response_args,
|
||||
) -> str:
|
||||
@@ -803,7 +803,7 @@ class DefaultAgent(AbstractConversationAgent):
|
||||
if lang_intents is None:
|
||||
return _DEFAULT_ERROR_TEXT
|
||||
|
||||
response_key = response_type.value
|
||||
response_key = error_key.value
|
||||
response_str = (
|
||||
lang_intents.error_responses.get(response_key) or _DEFAULT_ERROR_TEXT
|
||||
)
|
||||
@@ -916,59 +916,72 @@ def _make_error_result(
|
||||
return ConversationResult(response, conversation_id)
|
||||
|
||||
|
||||
def _get_unmatched_response(
|
||||
unmatched_entities: dict[str, UnmatchedEntity],
|
||||
) -> tuple[ResponseType, dict[str, Any]]:
|
||||
error_response_type = ResponseType.NO_INTENT
|
||||
error_response_args: dict[str, Any] = {}
|
||||
def _get_unmatched_response(result: RecognizeResult) -> tuple[ErrorKey, dict[str, Any]]:
|
||||
"""Get key and template arguments for error when there are unmatched intent entities/slots."""
|
||||
|
||||
if unmatched_name := unmatched_entities.get("name"):
|
||||
# Unmatched device or entity
|
||||
assert isinstance(unmatched_name, UnmatchedTextEntity)
|
||||
error_response_type = ResponseType.NO_ENTITY
|
||||
error_response_args["entity"] = unmatched_name.text
|
||||
# Filter out non-text and missing context entities
|
||||
unmatched_text: dict[str, str] = {
|
||||
key: entity.text.strip()
|
||||
for key, entity in result.unmatched_entities.items()
|
||||
if isinstance(entity, UnmatchedTextEntity) and entity.text != MISSING_ENTITY
|
||||
}
|
||||
|
||||
elif unmatched_area := unmatched_entities.get("area"):
|
||||
# Unmatched area
|
||||
assert isinstance(unmatched_area, UnmatchedTextEntity)
|
||||
error_response_type = ResponseType.NO_AREA
|
||||
error_response_args["area"] = unmatched_area.text
|
||||
if unmatched_area := unmatched_text.get("area"):
|
||||
# area only
|
||||
return ErrorKey.NO_AREA, {"area": unmatched_area}
|
||||
|
||||
return error_response_type, error_response_args
|
||||
# Area may still have matched
|
||||
matched_area: str | None = None
|
||||
if matched_area_entity := result.entities.get("area"):
|
||||
matched_area = matched_area_entity.text.strip()
|
||||
|
||||
if unmatched_name := unmatched_text.get("name"):
|
||||
if matched_area:
|
||||
# device in area
|
||||
return ErrorKey.NO_ENTITY_IN_AREA, {
|
||||
"entity": unmatched_name,
|
||||
"area": matched_area,
|
||||
}
|
||||
|
||||
# device only
|
||||
return ErrorKey.NO_ENTITY, {"entity": unmatched_name}
|
||||
|
||||
# Default error
|
||||
return ErrorKey.NO_INTENT, {}
|
||||
|
||||
|
||||
def _get_no_states_matched_response(
|
||||
no_states_error: intent.NoStatesMatchedError,
|
||||
) -> tuple[ResponseType, dict[str, Any]]:
|
||||
"""Return error response type and template arguments for error."""
|
||||
if not (
|
||||
no_states_error.area
|
||||
and (no_states_error.device_classes or no_states_error.domains)
|
||||
):
|
||||
# Device class and domain must be paired with an area for the error
|
||||
# message.
|
||||
return ResponseType.NO_INTENT, {}
|
||||
) -> tuple[ErrorKey, dict[str, Any]]:
|
||||
"""Return key and template arguments for error when intent returns no matching states."""
|
||||
|
||||
error_response_args: dict[str, Any] = {"area": no_states_error.area}
|
||||
|
||||
# Check device classes first, since it's more specific than domain
|
||||
# Device classes should be checked before domains
|
||||
if no_states_error.device_classes:
|
||||
# No exposed entities of a particular class in an area.
|
||||
# Example: "close the bedroom windows"
|
||||
#
|
||||
# Only use the first device class for the error message
|
||||
error_response_args["device_class"] = next(iter(no_states_error.device_classes))
|
||||
device_class = next(iter(no_states_error.device_classes)) # first device class
|
||||
if no_states_error.area:
|
||||
# device_class in area
|
||||
return ErrorKey.NO_DEVICE_CLASS_IN_AREA, {
|
||||
"device_class": device_class,
|
||||
"area": no_states_error.area,
|
||||
}
|
||||
|
||||
return ResponseType.NO_DEVICE_CLASS, error_response_args
|
||||
# device_class only
|
||||
return ErrorKey.NO_DEVICE_CLASS, {"device_class": device_class}
|
||||
|
||||
# No exposed entities of a domain in an area.
|
||||
# Example: "turn on lights in kitchen"
|
||||
assert no_states_error.domains
|
||||
#
|
||||
# Only use the first domain for the error message
|
||||
error_response_args["domain"] = next(iter(no_states_error.domains))
|
||||
if no_states_error.domains:
|
||||
domain = next(iter(no_states_error.domains)) # first domain
|
||||
if no_states_error.area:
|
||||
# domain in area
|
||||
return ErrorKey.NO_DOMAIN_IN_AREA, {
|
||||
"domain": domain,
|
||||
"area": no_states_error.area,
|
||||
}
|
||||
|
||||
return ResponseType.NO_DOMAIN, error_response_args
|
||||
# domain only
|
||||
return ErrorKey.NO_DOMAIN, {"domain": domain}
|
||||
|
||||
# Default error
|
||||
return ErrorKey.NO_INTENT, {}
|
||||
|
||||
|
||||
def _collect_list_references(expression: Expression, list_names: set[str]) -> None:
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"integration_type": "system",
|
||||
"iot_class": "local_push",
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["hassil==1.6.0", "home-assistant-intents==2024.1.29"]
|
||||
"requirements": ["hassil==1.6.1", "home-assistant-intents==2024.2.2"]
|
||||
}
|
||||
|
||||
@@ -98,7 +98,12 @@ async def async_attach_trigger(
|
||||
# mypy does not understand the type narrowing, unclear why
|
||||
return automation_result.conversation_response # type: ignore[return-value]
|
||||
|
||||
return "Done"
|
||||
# It's important to return None here instead of a string.
|
||||
#
|
||||
# When editing in the UI, a copy of this trigger is registered.
|
||||
# If we return a string from here, there is a race condition between the
|
||||
# two trigger copies for who will provide a response.
|
||||
return None
|
||||
|
||||
default_agent = await _get_agent_manager(hass).async_get_agent(HOME_ASSISTANT_AGENT)
|
||||
assert isinstance(default_agent, DefaultAgent)
|
||||
|
||||
@@ -54,6 +54,7 @@ class CoolmasterClimate(CoolmasterEntity, ClimateEntity):
|
||||
"""Representation of a coolmaster climate device."""
|
||||
|
||||
_attr_name = None
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
def __init__(self, coordinator, unit_id, info, supported_modes):
|
||||
"""Initialize the climate device."""
|
||||
|
||||
8
homeassistant/components/counter/icons.json
Normal file
8
homeassistant/components/counter/icons.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"services": {
|
||||
"decrement": "mdi:numeric-negative-1",
|
||||
"increment": "mdi:numeric-positive-1",
|
||||
"reset": "mdi:refresh",
|
||||
"set_value": "mdi:counter"
|
||||
}
|
||||
}
|
||||
@@ -128,6 +128,7 @@ class DaikinClimate(ClimateEntity):
|
||||
_attr_target_temperature_step = 1
|
||||
_attr_fan_modes: list[str]
|
||||
_attr_swing_modes: list[str]
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
def __init__(self, api: DaikinApi) -> None:
|
||||
"""Initialize the climate device."""
|
||||
@@ -142,7 +143,11 @@ class DaikinClimate(ClimateEntity):
|
||||
ATTR_SWING_MODE: self._attr_swing_modes,
|
||||
}
|
||||
|
||||
self._attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
self._attr_supported_features = (
|
||||
ClimateEntityFeature.TURN_ON
|
||||
| ClimateEntityFeature.TURN_OFF
|
||||
| ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
)
|
||||
|
||||
if api.device.support_away_mode or api.device.support_advanced_modes:
|
||||
self._attr_supported_features |= ClimateEntityFeature.PRESET_MODE
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/daikin",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["pydaikin"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["pydaikin==2.11.1"],
|
||||
"zeroconf": ["_dkapi._tcp.local."]
|
||||
}
|
||||
|
||||
@@ -100,6 +100,7 @@ class DeconzThermostat(DeconzDevice[Thermostat], ClimateEntity):
|
||||
TYPE = DOMAIN
|
||||
|
||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
def __init__(self, device: Thermostat, gateway: DeconzGateway) -> None:
|
||||
"""Set up thermostat device."""
|
||||
@@ -119,7 +120,11 @@ class DeconzThermostat(DeconzDevice[Thermostat], ClimateEntity):
|
||||
HVAC_MODE_TO_DECONZ[item]: item for item in self._attr_hvac_modes
|
||||
}
|
||||
|
||||
self._attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
self._attr_supported_features = (
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
| ClimateEntityFeature.TURN_OFF
|
||||
| ClimateEntityFeature.TURN_ON
|
||||
)
|
||||
|
||||
if device.fan_mode:
|
||||
self._attr_supported_features |= ClimateEntityFeature.FAN_MODE
|
||||
|
||||
@@ -97,6 +97,7 @@ class DemoClimate(ClimateEntity):
|
||||
_attr_name = None
|
||||
_attr_should_poll = False
|
||||
_attr_translation_key = "ubercool"
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -137,6 +138,9 @@ class DemoClimate(ClimateEntity):
|
||||
self._attr_supported_features |= (
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
|
||||
)
|
||||
self._attr_supported_features |= (
|
||||
ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON
|
||||
)
|
||||
self._target_temperature = target_temperature
|
||||
self._target_humidity = target_humidity
|
||||
self._unit_of_measurement = unit_of_measurement
|
||||
|
||||
9
homeassistant/components/derivative/icons.json
Normal file
9
homeassistant/components/derivative/icons.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"entity": {
|
||||
"sensor": {
|
||||
"derivative": {
|
||||
"default": "mdi:chart-line"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,8 +64,6 @@ UNIT_TIME = {
|
||||
UnitOfTime.DAYS: 24 * 60 * 60,
|
||||
}
|
||||
|
||||
ICON = "mdi:chart-line"
|
||||
|
||||
DEFAULT_ROUND = 3
|
||||
DEFAULT_TIME_WINDOW = 0
|
||||
|
||||
@@ -157,9 +155,9 @@ async def async_setup_platform(
|
||||
|
||||
|
||||
class DerivativeSensor(RestoreSensor, SensorEntity):
|
||||
"""Representation of an derivative sensor."""
|
||||
"""Representation of a derivative sensor."""
|
||||
|
||||
_attr_icon = ICON
|
||||
_attr_translation_key = "derivative"
|
||||
_attr_should_poll = False
|
||||
|
||||
def __init__(
|
||||
|
||||
@@ -56,6 +56,7 @@ class DevoloClimateDeviceEntity(DevoloMultiLevelSwitchDeviceEntity, ClimateEntit
|
||||
_attr_precision = PRECISION_TENTHS
|
||||
_attr_hvac_mode = HVACMode.HEAT
|
||||
_attr_hvac_modes = [HVACMode.HEAT]
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
def __init__(
|
||||
self, homecontrol: HomeControl, device_instance: Zwave, element_uid: str
|
||||
|
||||
@@ -47,12 +47,16 @@ class DuotecnoClimate(DuotecnoEntity, ClimateEntity):
|
||||
|
||||
_unit: SensUnit
|
||||
_attr_supported_features = (
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
| ClimateEntityFeature.PRESET_MODE
|
||||
| ClimateEntityFeature.TURN_OFF
|
||||
| ClimateEntityFeature.TURN_ON
|
||||
)
|
||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
_attr_hvac_modes = list(HVACMODE_REVERSE)
|
||||
_attr_preset_modes = list(PRESETMODES)
|
||||
_attr_translation_key = "duotecno"
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
@property
|
||||
def current_temperature(self) -> float | None:
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/easyenergy",
|
||||
"iot_class": "cloud_polling",
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["easyenergy==2.1.0"]
|
||||
"requirements": ["easyenergy==2.1.1"]
|
||||
}
|
||||
|
||||
@@ -323,6 +323,7 @@ class Thermostat(ClimateEntity):
|
||||
_attr_fan_modes = [FAN_AUTO, FAN_ON]
|
||||
_attr_name = None
|
||||
_attr_has_entity_name = True
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
def __init__(
|
||||
self, data: EcobeeData, thermostat_index: int, thermostat: dict
|
||||
@@ -375,6 +376,10 @@ class Thermostat(ClimateEntity):
|
||||
supported = supported | ClimateEntityFeature.TARGET_HUMIDITY
|
||||
if self.has_aux_heat:
|
||||
supported = supported | ClimateEntityFeature.AUX_HEAT
|
||||
if len(self.hvac_modes) > 1 and HVACMode.OFF in self.hvac_modes:
|
||||
supported = (
|
||||
supported | ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON
|
||||
)
|
||||
return supported
|
||||
|
||||
@property
|
||||
|
||||
@@ -66,6 +66,7 @@ class EcoNetThermostat(EcoNetEntity, ClimateEntity):
|
||||
|
||||
_attr_should_poll = True
|
||||
_attr_temperature_unit = UnitOfTemperature.FAHRENHEIT
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
def __init__(self, thermostat):
|
||||
"""Initialize."""
|
||||
@@ -79,12 +80,13 @@ class EcoNetThermostat(EcoNetEntity, ClimateEntity):
|
||||
ha_mode = ECONET_STATE_TO_HA[mode]
|
||||
self._attr_hvac_modes.append(ha_mode)
|
||||
|
||||
@property
|
||||
def supported_features(self) -> ClimateEntityFeature:
|
||||
"""Return the list of supported features."""
|
||||
if self._econet.supports_humidifier:
|
||||
return SUPPORT_FLAGS_THERMOSTAT | ClimateEntityFeature.TARGET_HUMIDITY
|
||||
return SUPPORT_FLAGS_THERMOSTAT
|
||||
self._attr_supported_features |= SUPPORT_FLAGS_THERMOSTAT
|
||||
if thermostat.supports_humidifier:
|
||||
self._attr_supported_features |= ClimateEntityFeature.TARGET_HUMIDITY
|
||||
if len(self.hvac_modes) > 1 and HVACMode.OFF in self.hvac_modes:
|
||||
self._attr_supported_features |= (
|
||||
ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON
|
||||
)
|
||||
|
||||
@property
|
||||
def current_temperature(self):
|
||||
|
||||
@@ -75,7 +75,7 @@ async def _validate_input(
|
||||
rest_config = create_rest_config(
|
||||
aiohttp_client.async_get_clientsession(hass),
|
||||
device_id=device_id,
|
||||
country=country,
|
||||
alpha_2_country=country,
|
||||
override_rest_url=rest_url,
|
||||
)
|
||||
|
||||
@@ -266,6 +266,10 @@ class EcovacsConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
# If not we will inform the user about the mismatch.
|
||||
error = None
|
||||
placeholders = None
|
||||
|
||||
# Convert the country to upper case as ISO 3166-1 alpha-2 country codes are upper case
|
||||
user_input[CONF_COUNTRY] = user_input[CONF_COUNTRY].upper()
|
||||
|
||||
if len(user_input[CONF_COUNTRY]) != 2:
|
||||
error = "invalid_country_length"
|
||||
placeholders = {"countries_url": "https://www.iso.org/obp/ui/#search/code/"}
|
||||
|
||||
@@ -49,7 +49,7 @@ class EcovacsController:
|
||||
create_rest_config(
|
||||
aiohttp_client.async_get_clientsession(self._hass),
|
||||
device_id=self._device_id,
|
||||
country=country,
|
||||
alpha_2_country=country,
|
||||
override_rest_url=config.get(CONF_OVERRIDE_REST_URL),
|
||||
),
|
||||
config[CONF_USERNAME],
|
||||
@@ -74,11 +74,16 @@ class EcovacsController:
|
||||
|
||||
async def initialize(self) -> None:
|
||||
"""Init controller."""
|
||||
mqtt_config_verfied = False
|
||||
try:
|
||||
devices = await self._api_client.get_devices()
|
||||
credentials = await self._authenticator.authenticate()
|
||||
for device_config in devices:
|
||||
if isinstance(device_config, DeviceInfo):
|
||||
# MQTT device
|
||||
if not mqtt_config_verfied:
|
||||
await self._mqtt.verify_config()
|
||||
mqtt_config_verfied = True
|
||||
device = Device(device_config, self._authenticator)
|
||||
await device.initialize(self._mqtt)
|
||||
self.devices.append(device)
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/ecovacs",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["sleekxmppfs", "sucks", "deebot_client"],
|
||||
"requirements": ["py-sucks==0.9.8", "deebot-client==5.0.0"]
|
||||
"requirements": ["py-sucks==0.9.8", "deebot-client==5.1.0"]
|
||||
}
|
||||
|
||||
@@ -47,13 +47,13 @@
|
||||
"name": "Relocate"
|
||||
},
|
||||
"reset_lifespan_brush": {
|
||||
"name": "Reset brush lifespan"
|
||||
"name": "Reset main brush lifespan"
|
||||
},
|
||||
"reset_lifespan_filter": {
|
||||
"name": "Reset filter lifespan"
|
||||
},
|
||||
"reset_lifespan_side_brush": {
|
||||
"name": "Reset side brush lifespan"
|
||||
"name": "Reset side brushes lifespan"
|
||||
}
|
||||
},
|
||||
"image": {
|
||||
@@ -79,13 +79,13 @@
|
||||
}
|
||||
},
|
||||
"lifespan_brush": {
|
||||
"name": "Brush lifespan"
|
||||
"name": "Main brush lifespan"
|
||||
},
|
||||
"lifespan_filter": {
|
||||
"name": "Filter lifespan"
|
||||
},
|
||||
"lifespan_side_brush": {
|
||||
"name": "Side brush lifespan"
|
||||
"name": "Side brushes lifespan"
|
||||
},
|
||||
"network_ip": {
|
||||
"name": "IP address"
|
||||
@@ -100,7 +100,7 @@
|
||||
"name": "Area cleaned"
|
||||
},
|
||||
"stats_time": {
|
||||
"name": "Time cleaned"
|
||||
"name": "Cleaning duration"
|
||||
},
|
||||
"total_stats_area": {
|
||||
"name": "Total area cleaned"
|
||||
@@ -109,12 +109,12 @@
|
||||
"name": "Total cleanings"
|
||||
},
|
||||
"total_stats_time": {
|
||||
"name": "Total time cleaned"
|
||||
"name": "Total cleaning duration"
|
||||
}
|
||||
},
|
||||
"select": {
|
||||
"water_amount": {
|
||||
"name": "Water amount",
|
||||
"name": "Water flow level",
|
||||
"state": {
|
||||
"high": "High",
|
||||
"low": "Low",
|
||||
@@ -137,7 +137,7 @@
|
||||
"name": "Advanced mode"
|
||||
},
|
||||
"carpet_auto_fan_boost": {
|
||||
"name": "Carpet auto fan speed boost"
|
||||
"name": "Carpet auto-boost suction"
|
||||
},
|
||||
"clean_preference": {
|
||||
"name": "Clean preference"
|
||||
|
||||
@@ -38,8 +38,8 @@ class EcowittEntity(Entity):
|
||||
"""Update the state on callback."""
|
||||
self.async_write_ha_state()
|
||||
|
||||
self.ecowitt.update_cb.append(_update_state) # type: ignore[arg-type] # upstream bug
|
||||
self.async_on_remove(lambda: self.ecowitt.update_cb.remove(_update_state)) # type: ignore[arg-type] # upstream bug
|
||||
self.ecowitt.update_cb.append(_update_state)
|
||||
self.async_on_remove(lambda: self.ecowitt.update_cb.remove(_update_state))
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"dependencies": ["webhook"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/ecowitt",
|
||||
"iot_class": "local_push",
|
||||
"requirements": ["aioecowitt==2023.5.0"]
|
||||
"requirements": ["aioecowitt==2024.2.0"]
|
||||
}
|
||||
|
||||
@@ -111,6 +111,7 @@ class ElectraClimateEntity(ClimateEntity):
|
||||
_attr_hvac_modes = ELECTRA_MODES
|
||||
_attr_has_entity_name = True
|
||||
_attr_name = None
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
def __init__(self, device: ElectraAirConditioner, api: ElectraAPI) -> None:
|
||||
"""Initialize Electra climate entity."""
|
||||
@@ -121,6 +122,8 @@ class ElectraClimateEntity(ClimateEntity):
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
| ClimateEntityFeature.FAN_MODE
|
||||
| ClimateEntityFeature.PRESET_MODE
|
||||
| ClimateEntityFeature.TURN_OFF
|
||||
| ClimateEntityFeature.TURN_ON
|
||||
)
|
||||
|
||||
swing_modes: list = []
|
||||
|
||||
@@ -7,6 +7,6 @@
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["elgato==5.1.1"],
|
||||
"requirements": ["elgato==5.1.2"],
|
||||
"zeroconf": ["_elg._tcp.local."]
|
||||
}
|
||||
|
||||
@@ -79,6 +79,8 @@ class ElkThermostat(ElkEntity, ClimateEntity):
|
||||
ClimateEntityFeature.FAN_MODE
|
||||
| ClimateEntityFeature.AUX_HEAT
|
||||
| ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
|
||||
| ClimateEntityFeature.TURN_OFF
|
||||
| ClimateEntityFeature.TURN_ON
|
||||
)
|
||||
_attr_min_temp = 1
|
||||
_attr_max_temp = 99
|
||||
@@ -87,6 +89,7 @@ class ElkThermostat(ElkEntity, ClimateEntity):
|
||||
_attr_target_temperature_step = 1
|
||||
_attr_fan_modes = [FAN_AUTO, FAN_ON]
|
||||
_element: Thermostat
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
@property
|
||||
def temperature_unit(self) -> str:
|
||||
|
||||
@@ -35,8 +35,10 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
self._api_token = api_token = user_input[CONF_API_TOKEN]
|
||||
client = Elvia(meter_value_token=api_token).meter_value()
|
||||
try:
|
||||
end_time = dt_util.utcnow()
|
||||
results = await client.get_meter_values(
|
||||
start_time=(dt_util.now() - timedelta(hours=1)).isoformat()
|
||||
start_time=(end_time - timedelta(hours=1)).isoformat(),
|
||||
end_time=end_time.isoformat(),
|
||||
)
|
||||
|
||||
except ElviaError.AuthError as exception:
|
||||
|
||||
@@ -4,7 +4,7 @@ from __future__ import annotations
|
||||
from datetime import datetime, timedelta
|
||||
from typing import TYPE_CHECKING, cast
|
||||
|
||||
from elvia import Elvia
|
||||
from elvia import Elvia, error as ElviaError
|
||||
|
||||
from homeassistant.components.recorder.models import StatisticData, StatisticMetaData
|
||||
from homeassistant.components.recorder.statistics import (
|
||||
@@ -38,11 +38,18 @@ class ElviaImporter:
|
||||
self.client = Elvia(meter_value_token=api_token).meter_value()
|
||||
self.metering_point_id = metering_point_id
|
||||
|
||||
async def _fetch_hourly_data(self, since: datetime) -> list[MeterValueTimeSeries]:
|
||||
async def _fetch_hourly_data(
|
||||
self,
|
||||
since: datetime,
|
||||
until: datetime,
|
||||
) -> list[MeterValueTimeSeries]:
|
||||
"""Fetch hourly data."""
|
||||
LOGGER.debug("Fetching hourly data since %s", since)
|
||||
start_time = since.isoformat()
|
||||
end_time = until.isoformat()
|
||||
LOGGER.debug("Fetching hourly data %s - %s", start_time, end_time)
|
||||
all_data = await self.client.get_meter_values(
|
||||
start_time=since.isoformat(),
|
||||
start_time=start_time,
|
||||
end_time=end_time,
|
||||
metering_point_ids=[self.metering_point_id],
|
||||
)
|
||||
return all_data["meteringpoints"][0]["metervalue"]["timeSeries"]
|
||||
@@ -61,18 +68,37 @@ class ElviaImporter:
|
||||
)
|
||||
|
||||
if not last_stats:
|
||||
# First time we insert 1 years of data (if available)
|
||||
hourly_data = await self._fetch_hourly_data(
|
||||
since=dt_util.now() - timedelta(days=365)
|
||||
)
|
||||
# First time we insert 3 years of data (if available)
|
||||
hourly_data: list[MeterValueTimeSeries] = []
|
||||
until = dt_util.utcnow()
|
||||
for year in (3, 2, 1):
|
||||
try:
|
||||
year_hours = await self._fetch_hourly_data(
|
||||
since=until - timedelta(days=365 * year),
|
||||
until=until - timedelta(days=365 * (year - 1)),
|
||||
)
|
||||
except ElviaError.ElviaException:
|
||||
# This will raise if the contract have no data for the
|
||||
# year, we can safely ignore this
|
||||
continue
|
||||
hourly_data.extend(year_hours)
|
||||
|
||||
if hourly_data is None or len(hourly_data) == 0:
|
||||
LOGGER.error("No data available for the metering point")
|
||||
return
|
||||
last_stats_time = None
|
||||
_sum = 0.0
|
||||
else:
|
||||
hourly_data = await self._fetch_hourly_data(
|
||||
since=dt_util.utc_from_timestamp(last_stats[statistic_id][0]["end"])
|
||||
)
|
||||
try:
|
||||
hourly_data = await self._fetch_hourly_data(
|
||||
since=dt_util.utc_from_timestamp(
|
||||
last_stats[statistic_id][0]["end"]
|
||||
),
|
||||
until=dt_util.utcnow(),
|
||||
)
|
||||
except ElviaError.ElviaException as err:
|
||||
LOGGER.error("Error fetching data: %s", err)
|
||||
return
|
||||
|
||||
if (
|
||||
hourly_data is None
|
||||
|
||||
@@ -83,6 +83,7 @@ class EphEmberThermostat(ClimateEntity):
|
||||
|
||||
_attr_hvac_modes = OPERATION_LIST
|
||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
def __init__(self, ember, zone):
|
||||
"""Initialize the thermostat."""
|
||||
@@ -100,6 +101,9 @@ class EphEmberThermostat(ClimateEntity):
|
||||
if self._hot_water:
|
||||
self._attr_supported_features = ClimateEntityFeature.AUX_HEAT
|
||||
self._attr_target_temperature_step = None
|
||||
self._attr_supported_features |= (
|
||||
ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON
|
||||
)
|
||||
|
||||
@property
|
||||
def current_temperature(self):
|
||||
|
||||
@@ -88,9 +88,9 @@ class EpionSensor(CoordinatorEntity[EpionCoordinator], SensorEntity):
|
||||
super().__init__(coordinator)
|
||||
self._epion_device_id = epion_device_id
|
||||
self.entity_description = description
|
||||
self.unique_id = f"{epion_device_id}_{description.key}"
|
||||
self._attr_unique_id = f"{epion_device_id}_{description.key}"
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, self._epion_device_id)},
|
||||
identifiers={(DOMAIN, epion_device_id)},
|
||||
manufacturer="Epion",
|
||||
name=self.device.get("deviceName"),
|
||||
sw_version=self.device.get("fwVersion"),
|
||||
|
||||
@@ -82,10 +82,14 @@ class ControllerEntity(ClimateEntity):
|
||||
_attr_precision = PRECISION_WHOLE
|
||||
_attr_should_poll = False
|
||||
_attr_supported_features = (
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.FAN_MODE
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
| ClimateEntityFeature.FAN_MODE
|
||||
| ClimateEntityFeature.TURN_OFF
|
||||
| ClimateEntityFeature.TURN_ON
|
||||
)
|
||||
_attr_target_temperature_step = PRECISION_WHOLE
|
||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
def __init__(self, controller: Controller) -> None:
|
||||
"""Initialise ControllerDevice."""
|
||||
|
||||
@@ -137,6 +137,7 @@ class EsphomeClimateEntity(EsphomeEntity[ClimateInfo, ClimateState], ClimateEnti
|
||||
|
||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
_attr_translation_key = "climate"
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
@callback
|
||||
def _on_static_info_update(self, static_info: EntityInfo) -> None:
|
||||
@@ -179,6 +180,8 @@ class EsphomeClimateEntity(EsphomeEntity[ClimateInfo, ClimateState], ClimateEnti
|
||||
features |= ClimateEntityFeature.FAN_MODE
|
||||
if self.swing_modes:
|
||||
features |= ClimateEntityFeature.SWING_MODE
|
||||
if len(self.hvac_modes) > 1 and HVACMode.OFF in self.hvac_modes:
|
||||
features |= ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON
|
||||
self._attr_supported_features = features
|
||||
|
||||
def _get_precision(self) -> float:
|
||||
|
||||
@@ -352,7 +352,6 @@ class ESPHomeManager:
|
||||
if self.voice_assistant_udp_server is not None:
|
||||
_LOGGER.warning("Voice assistant UDP server was not stopped")
|
||||
self.voice_assistant_udp_server.stop()
|
||||
self.voice_assistant_udp_server.close()
|
||||
self.voice_assistant_udp_server = None
|
||||
|
||||
hass = self.hass
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["aioesphomeapi", "noiseprotocol", "bleak_esphome"],
|
||||
"requirements": [
|
||||
"aioesphomeapi==21.0.1",
|
||||
"aioesphomeapi==21.0.2",
|
||||
"esphome-dashboard-api==1.2.3",
|
||||
"bleak-esphome==0.4.1"
|
||||
],
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"""ESPHome voice assistant support."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
@@ -67,7 +68,7 @@ class VoiceAssistantUDPServer(asyncio.DatagramProtocol):
|
||||
"""Receive UDP packets and forward them to the voice assistant."""
|
||||
|
||||
started = False
|
||||
stopped = False
|
||||
stop_requested = False
|
||||
transport: asyncio.DatagramTransport | None = None
|
||||
remote_addr: tuple[str, int] | None = None
|
||||
|
||||
@@ -92,6 +93,11 @@ class VoiceAssistantUDPServer(asyncio.DatagramProtocol):
|
||||
self._tts_done = asyncio.Event()
|
||||
self._tts_task: asyncio.Task | None = None
|
||||
|
||||
@property
|
||||
def is_running(self) -> bool:
|
||||
"""True if the the UDP server is started and hasn't been asked to stop."""
|
||||
return self.started and (not self.stop_requested)
|
||||
|
||||
async def start_server(self) -> int:
|
||||
"""Start accepting connections."""
|
||||
|
||||
@@ -99,7 +105,7 @@ class VoiceAssistantUDPServer(asyncio.DatagramProtocol):
|
||||
"""Accept connection."""
|
||||
if self.started:
|
||||
raise RuntimeError("Can only start once")
|
||||
if self.stopped:
|
||||
if self.stop_requested:
|
||||
raise RuntimeError("No longer accepting connections")
|
||||
|
||||
self.started = True
|
||||
@@ -124,7 +130,7 @@ class VoiceAssistantUDPServer(asyncio.DatagramProtocol):
|
||||
@callback
|
||||
def datagram_received(self, data: bytes, addr: tuple[str, int]) -> None:
|
||||
"""Handle incoming UDP packet."""
|
||||
if not self.started or self.stopped:
|
||||
if not self.is_running:
|
||||
return
|
||||
if self.remote_addr is None:
|
||||
self.remote_addr = addr
|
||||
@@ -142,19 +148,19 @@ class VoiceAssistantUDPServer(asyncio.DatagramProtocol):
|
||||
def stop(self) -> None:
|
||||
"""Stop the receiver."""
|
||||
self.queue.put_nowait(b"")
|
||||
self.started = False
|
||||
self.stopped = True
|
||||
self.close()
|
||||
|
||||
def close(self) -> None:
|
||||
"""Close the receiver."""
|
||||
self.started = False
|
||||
self.stopped = True
|
||||
self.stop_requested = True
|
||||
|
||||
if self.transport is not None:
|
||||
self.transport.close()
|
||||
|
||||
async def _iterate_packets(self) -> AsyncIterable[bytes]:
|
||||
"""Iterate over incoming packets."""
|
||||
if not self.started or self.stopped:
|
||||
if not self.is_running:
|
||||
raise RuntimeError("Not running")
|
||||
|
||||
while data := await self.queue.get():
|
||||
@@ -303,8 +309,11 @@ class VoiceAssistantUDPServer(asyncio.DatagramProtocol):
|
||||
|
||||
async def _send_tts(self, media_id: str) -> None:
|
||||
"""Send TTS audio to device via UDP."""
|
||||
# Always send stream start/end events
|
||||
self.handle_event(VoiceAssistantEventType.VOICE_ASSISTANT_TTS_STREAM_START, {})
|
||||
|
||||
try:
|
||||
if self.transport is None:
|
||||
if (not self.is_running) or (self.transport is None):
|
||||
return
|
||||
|
||||
extension, data = await tts.async_get_media_source_audio(
|
||||
@@ -337,15 +346,11 @@ class VoiceAssistantUDPServer(asyncio.DatagramProtocol):
|
||||
|
||||
_LOGGER.debug("Sending %d bytes of audio", audio_bytes_size)
|
||||
|
||||
self.handle_event(
|
||||
VoiceAssistantEventType.VOICE_ASSISTANT_TTS_STREAM_START, {}
|
||||
)
|
||||
|
||||
bytes_per_sample = stt.AudioBitRates.BITRATE_16 // 8
|
||||
sample_offset = 0
|
||||
samples_left = audio_bytes_size // bytes_per_sample
|
||||
|
||||
while samples_left > 0:
|
||||
while (samples_left > 0) and self.is_running:
|
||||
bytes_offset = sample_offset * bytes_per_sample
|
||||
chunk: bytes = audio_bytes[bytes_offset : bytes_offset + 1024]
|
||||
samples_in_chunk = len(chunk) // bytes_per_sample
|
||||
|
||||
@@ -156,6 +156,7 @@ class EvoClimateEntity(EvoDevice, ClimateEntity):
|
||||
"""Base for an evohome Climate device."""
|
||||
|
||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
@property
|
||||
def hvac_modes(self) -> list[HVACMode]:
|
||||
@@ -190,7 +191,10 @@ class EvoZone(EvoChild, EvoClimateEntity):
|
||||
]
|
||||
|
||||
self._attr_supported_features = (
|
||||
ClimateEntityFeature.PRESET_MODE | ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
ClimateEntityFeature.PRESET_MODE
|
||||
| ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
| ClimateEntityFeature.TURN_OFF
|
||||
| ClimateEntityFeature.TURN_ON
|
||||
)
|
||||
|
||||
async def async_zone_svc_request(self, service: str, data: dict[str, Any]) -> None:
|
||||
@@ -372,6 +376,9 @@ class EvoController(EvoClimateEntity):
|
||||
]
|
||||
if self._attr_preset_modes:
|
||||
self._attr_supported_features = ClimateEntityFeature.PRESET_MODE
|
||||
self._attr_supported_features |= (
|
||||
ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON
|
||||
)
|
||||
|
||||
async def async_tcs_svc_request(self, service: str, data: dict[str, Any]) -> None:
|
||||
"""Process a service request (system mode) for a controller.
|
||||
|
||||
@@ -126,6 +126,8 @@ async def async_setup_entry(
|
||||
class FibaroThermostat(FibaroDevice, ClimateEntity):
|
||||
"""Representation of a Fibaro Thermostat."""
|
||||
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
def __init__(self, fibaro_device: DeviceModel) -> None:
|
||||
"""Initialize the Fibaro device."""
|
||||
super().__init__(fibaro_device)
|
||||
@@ -209,6 +211,11 @@ class FibaroThermostat(FibaroDevice, ClimateEntity):
|
||||
if mode in OPMODES_PRESET:
|
||||
self._attr_preset_modes.append(OPMODES_PRESET[mode])
|
||||
|
||||
if HVACMode.OFF in self._attr_hvac_modes and len(self._attr_hvac_modes) > 1:
|
||||
self._attr_supported_features |= (
|
||||
ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON
|
||||
)
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Call when entity is added to hass."""
|
||||
_LOGGER.debug(
|
||||
|
||||
@@ -69,6 +69,7 @@ class Flexit(ClimateEntity):
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.FAN_MODE
|
||||
)
|
||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
def __init__(
|
||||
self, hub: ModbusHub, modbus_slave: int | None, name: str | None
|
||||
|
||||
@@ -62,13 +62,17 @@ class FlexitClimateEntity(FlexitEntity, ClimateEntity):
|
||||
]
|
||||
|
||||
_attr_supported_features = (
|
||||
ClimateEntityFeature.PRESET_MODE | ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
ClimateEntityFeature.PRESET_MODE
|
||||
| ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
| ClimateEntityFeature.TURN_OFF
|
||||
| ClimateEntityFeature.TURN_ON
|
||||
)
|
||||
|
||||
_attr_target_temperature_step = PRECISION_HALVES
|
||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
_attr_max_temp = MAX_TEMP
|
||||
_attr_min_temp = MIN_TEMP
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
def __init__(self, coordinator: FlexitCoordinator) -> None:
|
||||
"""Initialize the Flexit unit."""
|
||||
|
||||
@@ -7,6 +7,7 @@ from typing import Any
|
||||
|
||||
from aioflo.api import API
|
||||
from aioflo.errors import RequestError
|
||||
from orjson import JSONDecodeError
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
@@ -18,6 +19,8 @@ from .const import DOMAIN as FLO_DOMAIN, LOGGER
|
||||
class FloDeviceDataUpdateCoordinator(DataUpdateCoordinator): # pylint: disable=hass-enforce-coordinator-module
|
||||
"""Flo device object."""
|
||||
|
||||
_failure_count: int = 0
|
||||
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, api_client: API, location_id: str, device_id: str
|
||||
) -> None:
|
||||
@@ -43,8 +46,11 @@ class FloDeviceDataUpdateCoordinator(DataUpdateCoordinator): # pylint: disable=
|
||||
await self.send_presence_ping()
|
||||
await self._update_device()
|
||||
await self._update_consumption_data()
|
||||
except RequestError as error:
|
||||
raise UpdateFailed(error) from error
|
||||
self._failure_count = 0
|
||||
except (RequestError, TimeoutError, JSONDecodeError) as error:
|
||||
self._failure_count += 1
|
||||
if self._failure_count > 3:
|
||||
raise UpdateFailed(error) from error
|
||||
|
||||
@property
|
||||
def location_id(self) -> str:
|
||||
|
||||
@@ -64,10 +64,15 @@ class Device(CoordinatorEntity[FreedomproDataUpdateCoordinator], ClimateEntity):
|
||||
_attr_hvac_modes = SUPPORTED_HVAC_MODES
|
||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
_attr_name = None
|
||||
_attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
_attr_supported_features = (
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
| ClimateEntityFeature.TURN_OFF
|
||||
| ClimateEntityFeature.TURN_ON
|
||||
)
|
||||
_attr_current_temperature = 0
|
||||
_attr_target_temperature = 0
|
||||
_attr_hvac_mode = HVACMode.OFF
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
||||
@@ -80,6 +80,7 @@ class FritzboxThermostat(FritzBoxDeviceEntity, ClimateEntity):
|
||||
| ClimateEntityFeature.TURN_ON
|
||||
)
|
||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
@property
|
||||
def current_temperature(self) -> float:
|
||||
|
||||
@@ -20,5 +20,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
||||
"integration_type": "system",
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["home-assistant-frontend==20240131.0"]
|
||||
"requirements": ["home-assistant-frontend==20240205.0"]
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import logging
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
import voluptuous as vol
|
||||
import yarl
|
||||
|
||||
from homeassistant.components.camera import Camera, CameraEntityFeature
|
||||
@@ -140,6 +141,12 @@ class GenericCamera(Camera):
|
||||
_LOGGER.error("Error parsing template %s: %s", self._still_image_url, err)
|
||||
return self._last_image
|
||||
|
||||
try:
|
||||
vol.Schema(vol.Url())(url)
|
||||
except vol.Invalid as err:
|
||||
_LOGGER.warning("Invalid URL '%s': %s, returning last image", url, err)
|
||||
return self._last_image
|
||||
|
||||
if url == self._last_url and self._limit_refetch:
|
||||
return self._last_image
|
||||
|
||||
|
||||
@@ -178,6 +178,7 @@ class GenericThermostat(ClimateEntity, RestoreEntity):
|
||||
"""Representation of a Generic Thermostat device."""
|
||||
|
||||
_attr_should_poll = False
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -225,7 +226,11 @@ class GenericThermostat(ClimateEntity, RestoreEntity):
|
||||
self._target_temp = target_temp
|
||||
self._attr_temperature_unit = unit
|
||||
self._attr_unique_id = unique_id
|
||||
self._attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
self._attr_supported_features = (
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
| ClimateEntityFeature.TURN_OFF
|
||||
| ClimateEntityFeature.TURN_ON
|
||||
)
|
||||
if len(presets):
|
||||
self._attr_supported_features |= ClimateEntityFeature.PRESET_MODE
|
||||
self._attr_preset_modes = [PRESET_NONE] + list(presets.keys())
|
||||
|
||||
@@ -50,8 +50,12 @@ class GeniusClimateZone(GeniusHeatingZone, ClimateEntity):
|
||||
"""Representation of a Genius Hub climate device."""
|
||||
|
||||
_attr_supported_features = (
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
| ClimateEntityFeature.PRESET_MODE
|
||||
| ClimateEntityFeature.TURN_OFF
|
||||
| ClimateEntityFeature.TURN_ON
|
||||
)
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
def __init__(self, broker, zone) -> None:
|
||||
"""Initialize the climate device."""
|
||||
|
||||
@@ -105,7 +105,8 @@ class GeoJsonLocationEvent(GeolocationEvent):
|
||||
def _update_from_feed(self, feed_entry: GenericFeedEntry) -> None:
|
||||
"""Update the internal state from the provided feed entry."""
|
||||
if feed_entry.properties and "name" in feed_entry.properties:
|
||||
self._attr_name = feed_entry.properties.get("name")
|
||||
# The entry name's type can vary, but our own name must be a string
|
||||
self._attr_name = str(feed_entry.properties["name"])
|
||||
else:
|
||||
self._attr_name = feed_entry.title
|
||||
self._attr_distance = feed_entry.distance_to_home
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/geo_rss_events",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["georss_client", "georss_generic_client"],
|
||||
"requirements": ["georss-generic-client==0.6"]
|
||||
"requirements": ["georss-generic-client==0.8"]
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
"prompt": {
|
||||
"name": "Prompt",
|
||||
"description": "The prompt",
|
||||
"example": "Describe what you see in these images:"
|
||||
"example": "Describe what you see in these images"
|
||||
},
|
||||
"image_filename": {
|
||||
"name": "Image filename",
|
||||
|
||||
@@ -90,5 +90,5 @@
|
||||
"dependencies": ["bluetooth_adapters"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/govee_ble",
|
||||
"iot_class": "local_push",
|
||||
"requirements": ["govee-ble==0.27.3"]
|
||||
"requirements": ["govee-ble==0.31.0"]
|
||||
}
|
||||
|
||||
@@ -113,6 +113,8 @@ class GreeClimateEntity(GreeEntity, ClimateEntity):
|
||||
| ClimateEntityFeature.FAN_MODE
|
||||
| ClimateEntityFeature.PRESET_MODE
|
||||
| ClimateEntityFeature.SWING_MODE
|
||||
| ClimateEntityFeature.TURN_OFF
|
||||
| ClimateEntityFeature.TURN_ON
|
||||
)
|
||||
_attr_target_temperature_step = TARGET_TEMPERATURE_STEP
|
||||
_attr_hvac_modes = [*HVAC_MODES_REVERSE, HVACMode.OFF]
|
||||
@@ -120,6 +122,7 @@ class GreeClimateEntity(GreeEntity, ClimateEntity):
|
||||
_attr_fan_modes = [*FAN_MODES_REVERSE]
|
||||
_attr_swing_modes = SWING_MODES
|
||||
_attr_name = None
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
def __init__(self, coordinator: DeviceDataUpdateCoordinator) -> None:
|
||||
"""Initialize the Gree device."""
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"""Platform allowing several sensors to be grouped into one sensor to provide numeric combinations."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
@@ -13,6 +14,7 @@ from homeassistant.components.input_number import DOMAIN as INPUT_NUMBER_DOMAIN
|
||||
from homeassistant.components.number import DOMAIN as NUMBER_DOMAIN
|
||||
from homeassistant.components.sensor import (
|
||||
CONF_STATE_CLASS,
|
||||
DEVICE_CLASS_UNITS,
|
||||
DEVICE_CLASSES_SCHEMA,
|
||||
DOMAIN,
|
||||
PLATFORM_SCHEMA as PARENT_PLATFORM_SCHEMA,
|
||||
@@ -313,6 +315,7 @@ class SensorGroup(GroupEntity, SensorEntity):
|
||||
self._device_class = device_class
|
||||
self._native_unit_of_measurement = unit_of_measurement
|
||||
self._valid_units: set[str | None] = set()
|
||||
self._can_convert: bool = False
|
||||
self._attr_name = name
|
||||
if name == DEFAULT_NAME:
|
||||
self._attr_name = f"{DEFAULT_NAME} {sensor_type}".capitalize()
|
||||
@@ -352,10 +355,18 @@ class SensorGroup(GroupEntity, SensorEntity):
|
||||
self._valid_units
|
||||
and (uom := state.attributes["unit_of_measurement"])
|
||||
in self._valid_units
|
||||
and self._can_convert is True
|
||||
):
|
||||
numeric_state = UNIT_CONVERTERS[self.device_class].convert(
|
||||
numeric_state, uom, self.native_unit_of_measurement
|
||||
)
|
||||
if (
|
||||
self._valid_units
|
||||
and (uom := state.attributes["unit_of_measurement"])
|
||||
not in self._valid_units
|
||||
):
|
||||
raise HomeAssistantError("Not a valid unit")
|
||||
|
||||
sensor_values.append((entity_id, numeric_state, state))
|
||||
if entity_id in self._state_incorrect:
|
||||
self._state_incorrect.remove(entity_id)
|
||||
@@ -536,8 +547,21 @@ class SensorGroup(GroupEntity, SensorEntity):
|
||||
unit_of_measurements.append(_unit_of_measurement)
|
||||
|
||||
# Ensure only valid unit of measurements for the specific device class can be used
|
||||
if (device_class := self.device_class) in UNIT_CONVERTERS and all(
|
||||
x in UNIT_CONVERTERS[device_class].VALID_UNITS for x in unit_of_measurements
|
||||
if (
|
||||
# Test if uom's in device class is convertible
|
||||
(device_class := self.device_class) in UNIT_CONVERTERS
|
||||
and all(
|
||||
uom in UNIT_CONVERTERS[device_class].VALID_UNITS
|
||||
for uom in unit_of_measurements
|
||||
)
|
||||
) or (
|
||||
# Test if uom's in device class is not convertible
|
||||
device_class
|
||||
and device_class not in UNIT_CONVERTERS
|
||||
and device_class in DEVICE_CLASS_UNITS
|
||||
and all(
|
||||
uom in DEVICE_CLASS_UNITS[device_class] for uom in unit_of_measurements
|
||||
)
|
||||
):
|
||||
async_delete_issue(
|
||||
self.hass, DOMAIN, f"{self.entity_id}_uoms_not_matching_device_class"
|
||||
@@ -546,6 +570,7 @@ class SensorGroup(GroupEntity, SensorEntity):
|
||||
self.hass, DOMAIN, f"{self.entity_id}_uoms_not_matching_no_device_class"
|
||||
)
|
||||
return unit_of_measurements[0]
|
||||
|
||||
if device_class:
|
||||
async_create_issue(
|
||||
self.hass,
|
||||
@@ -559,7 +584,7 @@ class SensorGroup(GroupEntity, SensorEntity):
|
||||
"entity_id": self.entity_id,
|
||||
"device_class": device_class,
|
||||
"source_entities": ", ".join(self._entity_ids),
|
||||
"uoms:": ", ".join(unit_of_measurements),
|
||||
"uoms": ", ".join(unit_of_measurements),
|
||||
},
|
||||
)
|
||||
else:
|
||||
@@ -574,7 +599,7 @@ class SensorGroup(GroupEntity, SensorEntity):
|
||||
translation_placeholders={
|
||||
"entity_id": self.entity_id,
|
||||
"source_entities": ", ".join(self._entity_ids),
|
||||
"uoms:": ", ".join(unit_of_measurements),
|
||||
"uoms": ", ".join(unit_of_measurements),
|
||||
},
|
||||
)
|
||||
return None
|
||||
@@ -587,5 +612,13 @@ class SensorGroup(GroupEntity, SensorEntity):
|
||||
if (
|
||||
device_class := self.device_class
|
||||
) in UNIT_CONVERTERS and self.native_unit_of_measurement:
|
||||
self._can_convert = True
|
||||
return UNIT_CONVERTERS[device_class].VALID_UNITS
|
||||
if (
|
||||
device_class
|
||||
and (device_class) in DEVICE_CLASS_UNITS
|
||||
and self.native_unit_of_measurement
|
||||
):
|
||||
valid_uoms: set = DEVICE_CLASS_UNITS[device_class]
|
||||
return valid_uoms
|
||||
return set()
|
||||
|
||||
@@ -1001,12 +1001,18 @@ class HassioDataUpdateCoordinator(DataUpdateCoordinator): # pylint: disable=has
|
||||
raise_on_entry_error: bool = False,
|
||||
) -> None:
|
||||
"""Refresh data."""
|
||||
if not scheduled:
|
||||
if not scheduled and not raise_on_auth_failed:
|
||||
# Force refreshing updates for non-scheduled updates
|
||||
# If `raise_on_auth_failed` is set, it means this is
|
||||
# the first refresh and we do not want to delay
|
||||
# startup or cause a timeout so we only refresh the
|
||||
# updates if this is not a scheduled refresh and
|
||||
# we are not doing the first refresh.
|
||||
try:
|
||||
await self.hassio.refresh_updates()
|
||||
except HassioAPIError as err:
|
||||
_LOGGER.warning("Error on Supervisor API: %s", err)
|
||||
|
||||
await super()._async_refresh(
|
||||
log_failures, raise_on_auth_failed, scheduled, raise_on_entry_error
|
||||
)
|
||||
|
||||
@@ -459,7 +459,7 @@ class HassIO:
|
||||
|
||||
This method returns a coroutine.
|
||||
"""
|
||||
return self.send_command("/refresh_updates", timeout=None)
|
||||
return self.send_command("/refresh_updates", timeout=300)
|
||||
|
||||
@api_data
|
||||
def retrieve_discovery_messages(self) -> Coroutine:
|
||||
|
||||
@@ -76,7 +76,12 @@ class HeatmiserV3Thermostat(ClimateEntity):
|
||||
"""Representation of a HeatmiserV3 thermostat."""
|
||||
|
||||
_attr_hvac_modes = [HVACMode.HEAT, HVACMode.OFF]
|
||||
_attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
_attr_supported_features = (
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
| ClimateEntityFeature.TURN_OFF
|
||||
| ClimateEntityFeature.TURN_ON
|
||||
)
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
def __init__(self, therm, device, uh1):
|
||||
"""Initialize the thermostat."""
|
||||
|
||||
@@ -144,6 +144,8 @@ class ClimateAehW4a1(ClimateEntity):
|
||||
| ClimateEntityFeature.FAN_MODE
|
||||
| ClimateEntityFeature.SWING_MODE
|
||||
| ClimateEntityFeature.PRESET_MODE
|
||||
| ClimateEntityFeature.TURN_OFF
|
||||
| ClimateEntityFeature.TURN_ON
|
||||
)
|
||||
_attr_fan_modes = FAN_MODES
|
||||
_attr_swing_modes = SWING_MODES
|
||||
@@ -152,6 +154,7 @@ class ClimateAehW4a1(ClimateEntity):
|
||||
_attr_target_temperature_step = 1
|
||||
_previous_state: HVACMode | str | None = None
|
||||
_on: str | None = None
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
def __init__(self, device):
|
||||
"""Initialize the climate device."""
|
||||
|
||||
@@ -92,8 +92,12 @@ class HiveClimateEntity(HiveEntity, ClimateEntity):
|
||||
_attr_hvac_modes = [HVACMode.AUTO, HVACMode.HEAT, HVACMode.OFF]
|
||||
_attr_preset_modes = [PRESET_BOOST, PRESET_NONE]
|
||||
_attr_supported_features = (
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
| ClimateEntityFeature.PRESET_MODE
|
||||
| ClimateEntityFeature.TURN_OFF
|
||||
| ClimateEntityFeature.TURN_ON
|
||||
)
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
def __init__(self, hive_session, hive_device):
|
||||
"""Initialize the Climate device."""
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/holiday",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["holidays==0.41", "babel==2.13.1"]
|
||||
"requirements": ["holidays==0.42", "babel==2.13.1"]
|
||||
}
|
||||
|
||||
@@ -10,10 +10,14 @@ BSH_POWER_ON = "BSH.Common.EnumType.PowerState.On"
|
||||
BSH_POWER_OFF = "BSH.Common.EnumType.PowerState.Off"
|
||||
BSH_POWER_STANDBY = "BSH.Common.EnumType.PowerState.Standby"
|
||||
BSH_ACTIVE_PROGRAM = "BSH.Common.Root.ActiveProgram"
|
||||
BSH_OPERATION_STATE = "BSH.Common.Status.OperationState"
|
||||
BSH_REMOTE_CONTROL_ACTIVATION_STATE = "BSH.Common.Status.RemoteControlActive"
|
||||
BSH_REMOTE_START_ALLOWANCE_STATE = "BSH.Common.Status.RemoteControlStartAllowed"
|
||||
|
||||
BSH_OPERATION_STATE = "BSH.Common.Status.OperationState"
|
||||
BSH_OPERATION_STATE_RUN = "BSH.Common.EnumType.OperationState.Run"
|
||||
BSH_OPERATION_STATE_PAUSE = "BSH.Common.EnumType.OperationState.Pause"
|
||||
BSH_OPERATION_STATE_FINISHED = "BSH.Common.EnumType.OperationState.Finished"
|
||||
|
||||
COOKING_LIGHTING = "Cooking.Common.Setting.Lighting"
|
||||
COOKING_LIGHTING_BRIGHTNESS = "Cooking.Common.Setting.LightingBrightness"
|
||||
|
||||
|
||||
@@ -10,7 +10,14 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
||||
from .const import ATTR_VALUE, BSH_OPERATION_STATE, DOMAIN
|
||||
from .const import (
|
||||
ATTR_VALUE,
|
||||
BSH_OPERATION_STATE,
|
||||
BSH_OPERATION_STATE_FINISHED,
|
||||
BSH_OPERATION_STATE_PAUSE,
|
||||
BSH_OPERATION_STATE_RUN,
|
||||
DOMAIN,
|
||||
)
|
||||
from .entity import HomeConnectEntity
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -69,9 +76,20 @@ class HomeConnectSensor(HomeConnectEntity, SensorEntity):
|
||||
# if the date is supposed to be in the future but we're
|
||||
# already past it, set state to None.
|
||||
self._attr_native_value = None
|
||||
else:
|
||||
elif (
|
||||
BSH_OPERATION_STATE in status
|
||||
and ATTR_VALUE in status[BSH_OPERATION_STATE]
|
||||
and status[BSH_OPERATION_STATE][ATTR_VALUE]
|
||||
in [
|
||||
BSH_OPERATION_STATE_RUN,
|
||||
BSH_OPERATION_STATE_PAUSE,
|
||||
BSH_OPERATION_STATE_FINISHED,
|
||||
]
|
||||
):
|
||||
seconds = self._sign * float(status[self._key][ATTR_VALUE])
|
||||
self._attr_native_value = dt_util.utcnow() + timedelta(seconds=seconds)
|
||||
else:
|
||||
self._attr_native_value = None
|
||||
else:
|
||||
self._attr_native_value = status[self._key].get(ATTR_VALUE)
|
||||
if self._key == BSH_OPERATION_STATE:
|
||||
|
||||
@@ -139,6 +139,7 @@ class HomeKitBaseClimateEntity(HomeKitEntity, ClimateEntity):
|
||||
"""The base HomeKit Controller climate entity."""
|
||||
|
||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
@callback
|
||||
def _async_reconfigure(self) -> None:
|
||||
|
||||
@@ -14,6 +14,6 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/homekit_controller",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["aiohomekit", "commentjson"],
|
||||
"requirements": ["aiohomekit==3.1.3"],
|
||||
"requirements": ["aiohomekit==3.1.4"],
|
||||
"zeroconf": ["_hap._tcp.local.", "_hap._udp.local."]
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user