mirror of
https://github.com/home-assistant/core.git
synced 2026-02-06 23:34:51 +01:00
Compare commits
190 Commits
lg_infrare
...
PIRUnoccup
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1cdbe596fe | ||
|
|
a9d52bfbe7 | ||
|
|
6eed1f9961 | ||
|
|
149607ab17 | ||
|
|
279b5be357 | ||
|
|
82b93e788b | ||
|
|
555813f84f | ||
|
|
ecf1b4e591 | ||
|
|
e17a9f12a1 | ||
|
|
e8f05f5291 | ||
|
|
a5a76e9268 | ||
|
|
edc3fb47b2 | ||
|
|
f1e514a70a | ||
|
|
5632baca5b | ||
|
|
78f9bad706 | ||
|
|
f2d4319366 | ||
|
|
3eb8d64381 | ||
|
|
818ce549d9 | ||
|
|
db7800d170 | ||
|
|
bc1c24efb1 | ||
|
|
5ad632c34a | ||
|
|
65f95e5c4b | ||
|
|
bb406594d1 | ||
|
|
b7a7b7bc63 | ||
|
|
1c59d846e3 | ||
|
|
3b40bb7d28 | ||
|
|
a171e17097 | ||
|
|
c881d96d2f | ||
|
|
f1a99a2d65 | ||
|
|
d02adabe5d | ||
|
|
286730165d | ||
|
|
95a58252cf | ||
|
|
bf6643643b | ||
|
|
8d780d6712 | ||
|
|
576c7227c6 | ||
|
|
915d375f0a | ||
|
|
e9487a81a7 | ||
|
|
0a2fe01b66 | ||
|
|
9de9bde7d8 | ||
|
|
fbc91d3d3d | ||
|
|
47672614df | ||
|
|
8c01c4a155 | ||
|
|
5dc7f8bfe3 | ||
|
|
cc01d15d74 | ||
|
|
5c980e8d97 | ||
|
|
c01e3beb2e | ||
|
|
a5b16e3694 | ||
|
|
866cd52ada | ||
|
|
2d308aaa20 | ||
|
|
0456eb54ee | ||
|
|
ce6fced6a4 | ||
|
|
fc56f52c74 | ||
|
|
f7e65eeece | ||
|
|
8c94de4a9c | ||
|
|
46971c1c82 | ||
|
|
fb5c3c7eb6 | ||
|
|
ea42237444 | ||
|
|
2a76c2678e | ||
|
|
72b6e5fabe | ||
|
|
f739fc1f55 | ||
|
|
aecfca5020 | ||
|
|
f024ae442f | ||
|
|
07a9aad4a4 | ||
|
|
22ab58077e | ||
|
|
4b666688c9 | ||
|
|
d118332366 | ||
|
|
9f32e0da14 | ||
|
|
1cef223a06 | ||
|
|
29da1233f3 | ||
|
|
a5b3d22058 | ||
|
|
d37e958a0b | ||
|
|
0498ac7364 | ||
|
|
67bdeb9945 | ||
|
|
a227307387 | ||
|
|
0e0309cabf | ||
|
|
fd2dfc83c6 | ||
|
|
9e736891c4 | ||
|
|
fbabf0dcb8 | ||
|
|
7128791152 | ||
|
|
94456b5bc3 | ||
|
|
2105c6b177 | ||
|
|
34156f79e8 | ||
|
|
bb1a2530f5 | ||
|
|
06613746f9 | ||
|
|
98ca948afe | ||
|
|
fa58fe5f4e | ||
|
|
46f230c487 | ||
|
|
13a987aba3 | ||
|
|
9cef323581 | ||
|
|
7ea7576188 | ||
|
|
f8abbfd42b | ||
|
|
5cd1821bc9 | ||
|
|
2ef7f26ffb | ||
|
|
184bea49e2 | ||
|
|
c853fb2068 | ||
|
|
79e0a93e48 | ||
|
|
3867c1d7d1 | ||
|
|
b9b6b050cc | ||
|
|
d960736b3d | ||
|
|
3e8923f105 | ||
|
|
17cca3e69d | ||
|
|
12714c489f | ||
|
|
f788d61b4a | ||
|
|
5c726af00b | ||
|
|
d1d207fbb2 | ||
|
|
6c7f8df7f7 | ||
|
|
6f8c9b1504 | ||
|
|
4f9aedbc84 | ||
|
|
afa0f572ce | ||
|
|
a6a1b9ddbd | ||
|
|
c1f5b4593f | ||
|
|
f1de4dc1cc | ||
|
|
4ae0d9a9c6 | ||
|
|
fcd0b579cf | ||
|
|
52fb0343e4 | ||
|
|
1050b4580a | ||
|
|
344c42172e | ||
|
|
93cc0fd7f1 | ||
|
|
05fe636b55 | ||
|
|
f22467d099 | ||
|
|
4bc3899b32 | ||
|
|
fc4d6bf5f1 | ||
|
|
8ed0672a8f | ||
|
|
282e347a1b | ||
|
|
1bfb02b440 | ||
|
|
71b03bd9ae | ||
|
|
cbd69822eb | ||
|
|
db900f4dd2 | ||
|
|
a707e695bc | ||
|
|
4feceac205 | ||
|
|
10c20faaca | ||
|
|
abcd512401 | ||
|
|
3fdaaecd0f | ||
|
|
fdf8edf474 | ||
|
|
47e1a98bee | ||
|
|
2d8572b943 | ||
|
|
660cfdbd50 | ||
|
|
4208595da6 | ||
|
|
b6b2d2fc6f | ||
|
|
6c4c632848 | ||
|
|
78cf62176f | ||
|
|
df971c7a42 | ||
|
|
1fcabb7f2d | ||
|
|
9fb60c9ea2 | ||
|
|
9c11a4646f | ||
|
|
b036a78776 | ||
|
|
60bb3cb704 | ||
|
|
0e770958ac | ||
|
|
2a54c71b6c | ||
|
|
50463291ab | ||
|
|
43cc34042a | ||
|
|
a02244ccda | ||
|
|
a739619121 | ||
|
|
5db97a5f1c | ||
|
|
804ba9c9cc | ||
|
|
5ecbcea946 | ||
|
|
11be2b6289 | ||
|
|
eefae0307b | ||
|
|
d397ee28ea | ||
|
|
02c821128e | ||
|
|
71dc15d45f | ||
|
|
1078387b22 | ||
|
|
35fab27d15 | ||
|
|
915dc7a908 | ||
|
|
e5a9738983 | ||
|
|
2ff73219a2 | ||
|
|
5dc1270ed1 | ||
|
|
9e95ad5a85 | ||
|
|
9a5d4610f7 | ||
|
|
41c524fce4 | ||
|
|
5f9fa95554 | ||
|
|
6950be8ea9 | ||
|
|
c5a8bf64d0 | ||
|
|
a2b9a6e9df | ||
|
|
a0c567f0da | ||
|
|
c7feafdde6 | ||
|
|
e1e74b0aeb | ||
|
|
673411ef97 | ||
|
|
f7e5af7cb1 | ||
|
|
0ee56ce708 | ||
|
|
f93a176398 | ||
|
|
cd2394bc12 | ||
|
|
5c20b8eaff | ||
|
|
4bd499d3a6 | ||
|
|
8a53b94c5a | ||
|
|
d5aff326e3 | ||
|
|
22f66abbe7 | ||
|
|
f635228b1f | ||
|
|
4c708c143d | ||
|
|
3369459d41 |
1
.github/workflows/builder.yml
vendored
1
.github/workflows/builder.yml
vendored
@@ -235,6 +235,7 @@ jobs:
|
||||
build-args: |
|
||||
BUILD_FROM=${{ steps.vars.outputs.base_image }}
|
||||
tags: ghcr.io/home-assistant/${{ matrix.arch }}-homeassistant:${{ needs.init.outputs.version }}
|
||||
outputs: type=image,push=true,compression=zstd,compression-level=9,force-compression=true,oci-mediatypes=true
|
||||
labels: |
|
||||
io.hass.arch=${{ matrix.arch }}
|
||||
io.hass.version=${{ needs.init.outputs.version }}
|
||||
|
||||
4
.github/workflows/codeql.yml
vendored
4
.github/workflows/codeql.yml
vendored
@@ -24,11 +24,11 @@ jobs:
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@6bc82e05fd0ea64601dd4b465378bbcf57de0314 # v4.32.1
|
||||
uses: github/codeql-action/init@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v4.32.2
|
||||
with:
|
||||
languages: python
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@6bc82e05fd0ea64601dd4b465378bbcf57de0314 # v4.32.1
|
||||
uses: github/codeql-action/analyze@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v4.32.2
|
||||
with:
|
||||
category: "/language:python"
|
||||
|
||||
@@ -221,6 +221,7 @@ homeassistant.components.generic_hygrostat.*
|
||||
homeassistant.components.generic_thermostat.*
|
||||
homeassistant.components.geo_location.*
|
||||
homeassistant.components.geocaching.*
|
||||
homeassistant.components.ghost.*
|
||||
homeassistant.components.gios.*
|
||||
homeassistant.components.github.*
|
||||
homeassistant.components.glances.*
|
||||
@@ -282,7 +283,6 @@ homeassistant.components.imgw_pib.*
|
||||
homeassistant.components.immich.*
|
||||
homeassistant.components.incomfort.*
|
||||
homeassistant.components.inels.*
|
||||
homeassistant.components.infrared.*
|
||||
homeassistant.components.input_button.*
|
||||
homeassistant.components.input_select.*
|
||||
homeassistant.components.input_text.*
|
||||
@@ -315,7 +315,6 @@ homeassistant.components.ld2410_ble.*
|
||||
homeassistant.components.led_ble.*
|
||||
homeassistant.components.lektrico.*
|
||||
homeassistant.components.letpot.*
|
||||
homeassistant.components.lg_infrared.*
|
||||
homeassistant.components.libre_hardware_monitor.*
|
||||
homeassistant.components.lidarr.*
|
||||
homeassistant.components.lifx.*
|
||||
@@ -437,6 +436,7 @@ homeassistant.components.raspberry_pi.*
|
||||
homeassistant.components.rdw.*
|
||||
homeassistant.components.recollect_waste.*
|
||||
homeassistant.components.recorder.*
|
||||
homeassistant.components.redgtech.*
|
||||
homeassistant.components.remember_the_milk.*
|
||||
homeassistant.components.remote.*
|
||||
homeassistant.components.remote_calendar.*
|
||||
|
||||
6
CODEOWNERS
generated
6
CODEOWNERS
generated
@@ -595,6 +595,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/geonetnz_quakes/ @exxamalte
|
||||
/homeassistant/components/geonetnz_volcano/ @exxamalte
|
||||
/tests/components/geonetnz_volcano/ @exxamalte
|
||||
/homeassistant/components/ghost/ @johnonolan
|
||||
/tests/components/ghost/ @johnonolan
|
||||
/homeassistant/components/gios/ @bieniu
|
||||
/tests/components/gios/ @bieniu
|
||||
/homeassistant/components/github/ @timmo001 @ludeeus
|
||||
@@ -782,8 +784,6 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/inels/ @epdevlab
|
||||
/homeassistant/components/influxdb/ @mdegat01
|
||||
/tests/components/influxdb/ @mdegat01
|
||||
/homeassistant/components/infrared/ @home-assistant/core
|
||||
/tests/components/infrared/ @home-assistant/core
|
||||
/homeassistant/components/inkbird/ @bdraco
|
||||
/tests/components/inkbird/ @bdraco
|
||||
/homeassistant/components/input_boolean/ @home-assistant/core
|
||||
@@ -1357,6 +1357,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/recorder/ @home-assistant/core
|
||||
/homeassistant/components/recovery_mode/ @home-assistant/core
|
||||
/tests/components/recovery_mode/ @home-assistant/core
|
||||
/homeassistant/components/redgtech/ @jonhsady @luan-nvg
|
||||
/tests/components/redgtech/ @jonhsady @luan-nvg
|
||||
/homeassistant/components/refoss/ @ashionky
|
||||
/tests/components/refoss/ @ashionky
|
||||
/homeassistant/components/rehlko/ @bdraco @peterager
|
||||
|
||||
@@ -64,7 +64,7 @@ class AbodeFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
else:
|
||||
errors = {"base": "cannot_connect"}
|
||||
|
||||
except (ConnectTimeout, HTTPError):
|
||||
except ConnectTimeout, HTTPError:
|
||||
errors = {"base": "cannot_connect"}
|
||||
|
||||
if errors:
|
||||
|
||||
@@ -99,7 +99,7 @@ class AbodeLight(AbodeDevice, LightEntity):
|
||||
return _hs
|
||||
|
||||
@property
|
||||
def color_mode(self) -> ColorMode | None:
|
||||
def color_mode(self) -> ColorMode:
|
||||
"""Return the color mode of the light."""
|
||||
if self._device.is_dimmable and self._device.is_color_capable:
|
||||
if self.hs_color is not None:
|
||||
@@ -110,7 +110,7 @@ class AbodeLight(AbodeDevice, LightEntity):
|
||||
return ColorMode.ONOFF
|
||||
|
||||
@property
|
||||
def supported_color_modes(self) -> set[ColorMode] | None:
|
||||
def supported_color_modes(self) -> set[ColorMode]:
|
||||
"""Flag supported color modes."""
|
||||
if self._device.is_dimmable and self._device.is_color_capable:
|
||||
return {ColorMode.COLOR_TEMP, ColorMode.HS}
|
||||
|
||||
@@ -43,7 +43,7 @@ class AccuWeatherFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
longitude=user_input[CONF_LONGITUDE],
|
||||
)
|
||||
await accuweather.async_get_location()
|
||||
except (ApiError, ClientConnectorError, TimeoutError, ClientError):
|
||||
except ApiError, ClientConnectorError, TimeoutError, ClientError:
|
||||
errors["base"] = "cannot_connect"
|
||||
except InvalidApiKeyError:
|
||||
errors[CONF_API_KEY] = "invalid_api_key"
|
||||
@@ -104,7 +104,7 @@ class AccuWeatherFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
longitude=self._longitude,
|
||||
)
|
||||
await accuweather.async_get_location()
|
||||
except (ApiError, ClientConnectorError, TimeoutError, ClientError):
|
||||
except ApiError, ClientConnectorError, TimeoutError, ClientError:
|
||||
errors["base"] = "cannot_connect"
|
||||
except InvalidApiKeyError:
|
||||
errors["base"] = "invalid_api_key"
|
||||
|
||||
@@ -29,30 +29,42 @@ SWITCHES: tuple[ActronAirSwitchEntityDescription, ...] = (
|
||||
key="away_mode",
|
||||
translation_key="away_mode",
|
||||
is_on_fn=lambda coordinator: coordinator.data.user_aircon_settings.away_mode,
|
||||
set_fn=lambda coordinator,
|
||||
enabled: coordinator.data.user_aircon_settings.set_away_mode(enabled),
|
||||
set_fn=lambda coordinator, enabled: (
|
||||
coordinator.data.user_aircon_settings.set_away_mode(enabled)
|
||||
),
|
||||
),
|
||||
ActronAirSwitchEntityDescription(
|
||||
key="continuous_fan",
|
||||
translation_key="continuous_fan",
|
||||
is_on_fn=lambda coordinator: coordinator.data.user_aircon_settings.continuous_fan_enabled,
|
||||
set_fn=lambda coordinator,
|
||||
enabled: coordinator.data.user_aircon_settings.set_continuous_mode(enabled),
|
||||
is_on_fn=lambda coordinator: (
|
||||
coordinator.data.user_aircon_settings.continuous_fan_enabled
|
||||
),
|
||||
set_fn=lambda coordinator, enabled: (
|
||||
coordinator.data.user_aircon_settings.set_continuous_mode(enabled)
|
||||
),
|
||||
),
|
||||
ActronAirSwitchEntityDescription(
|
||||
key="quiet_mode",
|
||||
translation_key="quiet_mode",
|
||||
is_on_fn=lambda coordinator: coordinator.data.user_aircon_settings.quiet_mode_enabled,
|
||||
set_fn=lambda coordinator,
|
||||
enabled: coordinator.data.user_aircon_settings.set_quiet_mode(enabled),
|
||||
is_on_fn=lambda coordinator: (
|
||||
coordinator.data.user_aircon_settings.quiet_mode_enabled
|
||||
),
|
||||
set_fn=lambda coordinator, enabled: (
|
||||
coordinator.data.user_aircon_settings.set_quiet_mode(enabled)
|
||||
),
|
||||
),
|
||||
ActronAirSwitchEntityDescription(
|
||||
key="turbo_mode",
|
||||
translation_key="turbo_mode",
|
||||
is_on_fn=lambda coordinator: coordinator.data.user_aircon_settings.turbo_enabled,
|
||||
set_fn=lambda coordinator,
|
||||
enabled: coordinator.data.user_aircon_settings.set_turbo_mode(enabled),
|
||||
is_supported_fn=lambda coordinator: coordinator.data.user_aircon_settings.turbo_supported,
|
||||
is_on_fn=lambda coordinator: (
|
||||
coordinator.data.user_aircon_settings.turbo_enabled
|
||||
),
|
||||
set_fn=lambda coordinator, enabled: (
|
||||
coordinator.data.user_aircon_settings.set_turbo_mode(enabled)
|
||||
),
|
||||
is_supported_fn=lambda coordinator: (
|
||||
coordinator.data.user_aircon_settings.turbo_supported
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -133,8 +133,9 @@ CONTROL_ENTITIES: tuple[AirGradientSelectEntityDescription, ...] = (
|
||||
value_fn=lambda config: _get_value(
|
||||
config.co2_automatic_baseline_calibration_days, ABC_DAYS
|
||||
),
|
||||
set_value_fn=lambda client,
|
||||
value: client.set_co2_automatic_baseline_calibration(int(value)),
|
||||
set_value_fn=lambda client, value: (
|
||||
client.set_co2_automatic_baseline_calibration(int(value))
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ class AirobotButton(AirobotEntity, ButtonEntity):
|
||||
"""Handle the button press."""
|
||||
try:
|
||||
await self.entity_description.press_fn(self.coordinator)
|
||||
except (AirobotConnectionError, AirobotTimeoutError):
|
||||
except AirobotConnectionError, AirobotTimeoutError:
|
||||
# Connection errors during reboot are expected as device restarts
|
||||
pass
|
||||
except AirobotError as err:
|
||||
|
||||
@@ -114,7 +114,7 @@ class AirOSConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
AirOSDeviceConnectionError,
|
||||
):
|
||||
self.errors["base"] = "cannot_connect"
|
||||
except (AirOSConnectionAuthenticationError, AirOSDataMissingError):
|
||||
except AirOSConnectionAuthenticationError, AirOSDataMissingError:
|
||||
self.errors["base"] = "invalid_auth"
|
||||
except AirOSKeyDataMissingError:
|
||||
self.errors["base"] = "key_data_missing"
|
||||
|
||||
@@ -130,7 +130,7 @@ class AirVisualFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
|
||||
try:
|
||||
await coro
|
||||
except (InvalidKeyError, KeyExpiredError, UnauthorizedError):
|
||||
except InvalidKeyError, KeyExpiredError, UnauthorizedError:
|
||||
errors[CONF_API_KEY] = "invalid_api_key"
|
||||
except NotFoundError:
|
||||
errors[CONF_CITY] = "location_not_found"
|
||||
|
||||
@@ -100,7 +100,7 @@ class AirZoneCloudConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
|
||||
try:
|
||||
await self.airzone.login()
|
||||
except (AirzoneCloudError, LoginError):
|
||||
except AirzoneCloudError, LoginError:
|
||||
errors["base"] = "cannot_connect"
|
||||
else:
|
||||
return await self.async_step_inst_pick()
|
||||
|
||||
@@ -123,7 +123,7 @@ class Auth:
|
||||
allow_redirects=True,
|
||||
)
|
||||
|
||||
except (TimeoutError, aiohttp.ClientError):
|
||||
except TimeoutError, aiohttp.ClientError:
|
||||
_LOGGER.error("Timeout calling LWA to get auth token")
|
||||
return None
|
||||
|
||||
|
||||
@@ -358,7 +358,7 @@ async def async_send_changereport_message(
|
||||
"""
|
||||
try:
|
||||
token = await config.async_get_access_token()
|
||||
except (RequireRelink, NoTokenAvailable):
|
||||
except RequireRelink, NoTokenAvailable:
|
||||
await config.set_authorized(False)
|
||||
_LOGGER.error(
|
||||
"Error when sending ChangeReport to Alexa, could not get access token"
|
||||
@@ -392,7 +392,7 @@ async def async_send_changereport_message(
|
||||
allow_redirects=True,
|
||||
)
|
||||
|
||||
except (TimeoutError, aiohttp.ClientError):
|
||||
except TimeoutError, aiohttp.ClientError:
|
||||
_LOGGER.error("Timeout sending report to Alexa for %s", alexa_entity.entity_id)
|
||||
return
|
||||
|
||||
@@ -549,7 +549,7 @@ async def async_send_doorbell_event_message(
|
||||
allow_redirects=True,
|
||||
)
|
||||
|
||||
except (TimeoutError, aiohttp.ClientError):
|
||||
except TimeoutError, aiohttp.ClientError:
|
||||
_LOGGER.error("Timeout sending report to Alexa for %s", alexa_entity.entity_id)
|
||||
return
|
||||
|
||||
|
||||
@@ -90,6 +90,9 @@
|
||||
"cannot_retrieve_data_with_error": {
|
||||
"message": "Error retrieving data: {error}"
|
||||
},
|
||||
"config_entry_not_found": {
|
||||
"message": "Config entry not found: {device_id}"
|
||||
},
|
||||
"device_serial_number_missing": {
|
||||
"message": "Device serial number missing: {device_id}"
|
||||
},
|
||||
|
||||
@@ -59,13 +59,15 @@ async def async_setup_entry(
|
||||
|
||||
coordinator = entry.runtime_data
|
||||
|
||||
# Replace unique id for "DND" switch and remove from Speaker Group
|
||||
await async_update_unique_id(
|
||||
hass, coordinator, SWITCH_DOMAIN, "do_not_disturb", "dnd"
|
||||
)
|
||||
# DND keys
|
||||
old_key = "do_not_disturb"
|
||||
new_key = "dnd"
|
||||
|
||||
# Remove DND switch from virtual groups
|
||||
await async_remove_dnd_from_virtual_group(hass, coordinator)
|
||||
# Remove old DND switch from virtual groups
|
||||
await async_remove_dnd_from_virtual_group(hass, coordinator, old_key)
|
||||
|
||||
# Replace unique id for DND switch
|
||||
await async_update_unique_id(hass, coordinator, SWITCH_DOMAIN, old_key, new_key)
|
||||
|
||||
known_devices: set[str] = set()
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ def alexa_api_call[_T: AmazonEntity, **_P](
|
||||
async def async_update_unique_id(
|
||||
hass: HomeAssistant,
|
||||
coordinator: AmazonDevicesCoordinator,
|
||||
domain: str,
|
||||
platform: str,
|
||||
old_key: str,
|
||||
new_key: str,
|
||||
) -> None:
|
||||
@@ -63,7 +63,9 @@ async def async_update_unique_id(
|
||||
|
||||
for serial_num in coordinator.data:
|
||||
unique_id = f"{serial_num}-{old_key}"
|
||||
if entity_id := entity_registry.async_get_entity_id(domain, DOMAIN, unique_id):
|
||||
if entity_id := entity_registry.async_get_entity_id(
|
||||
DOMAIN, platform, unique_id
|
||||
):
|
||||
_LOGGER.debug("Updating unique_id for %s", entity_id)
|
||||
new_unique_id = unique_id.replace(old_key, new_key)
|
||||
|
||||
@@ -74,12 +76,13 @@ async def async_update_unique_id(
|
||||
async def async_remove_dnd_from_virtual_group(
|
||||
hass: HomeAssistant,
|
||||
coordinator: AmazonDevicesCoordinator,
|
||||
key: str,
|
||||
) -> None:
|
||||
"""Remove entity DND from virtual group."""
|
||||
entity_registry = er.async_get(hass)
|
||||
|
||||
for serial_num in coordinator.data:
|
||||
unique_id = f"{serial_num}-do_not_disturb"
|
||||
unique_id = f"{serial_num}-{key}"
|
||||
entity_id = entity_registry.async_get_entity_id(
|
||||
DOMAIN, SWITCH_DOMAIN, unique_id
|
||||
)
|
||||
@@ -104,7 +107,7 @@ async def async_remove_unsupported_notification_sensors(
|
||||
):
|
||||
unique_id = f"{serial_num}-{notification_key}"
|
||||
entity_id = entity_registry.async_get_entity_id(
|
||||
domain=SENSOR_DOMAIN, platform=DOMAIN, unique_id=unique_id
|
||||
DOMAIN, SENSOR_DOMAIN, unique_id=unique_id
|
||||
)
|
||||
is_unsupported = not coordinator.data[serial_num].notifications_supported
|
||||
|
||||
|
||||
@@ -77,9 +77,11 @@ class AmbientNetworkConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
# Filter out indoor stations
|
||||
self._stations = dict(
|
||||
filter(
|
||||
lambda item: not item[1]
|
||||
.get(API_STATION_INFO, {})
|
||||
.get(API_STATION_INDOOR, False),
|
||||
lambda item: (
|
||||
not item[1]
|
||||
.get(API_STATION_INFO, {})
|
||||
.get(API_STATION_INDOOR, False)
|
||||
),
|
||||
self._stations.items(),
|
||||
)
|
||||
)
|
||||
@@ -113,7 +115,7 @@ class AmbientNetworkConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id=CONF_USER, data_schema=schema, errors=errors if errors else {}
|
||||
step_id=CONF_USER, data_schema=schema, errors=errors or {}
|
||||
)
|
||||
|
||||
async def async_step_station(
|
||||
|
||||
@@ -31,7 +31,7 @@ class AmbientStationFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
data_schema=self.data_schema,
|
||||
errors=errors if errors else {},
|
||||
errors=errors or {},
|
||||
)
|
||||
|
||||
async def async_step_user(
|
||||
|
||||
@@ -26,10 +26,9 @@ from homeassistant.const import (
|
||||
UnitOfVolumetricFlux,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity import EntityDescription
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from . import AmbientStation, AmbientStationConfigEntry
|
||||
from . import AmbientStationConfigEntry
|
||||
from .const import ATTR_LAST_DATA, TYPE_SOLARRADIATION, TYPE_SOLARRADIATION_LX
|
||||
from .entity import AmbientWeatherEntity
|
||||
|
||||
@@ -683,22 +682,6 @@ async def async_setup_entry(
|
||||
class AmbientWeatherSensor(AmbientWeatherEntity, SensorEntity):
|
||||
"""Define an Ambient sensor."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
ambient: AmbientStation,
|
||||
mac_address: str,
|
||||
station_name: str,
|
||||
description: EntityDescription,
|
||||
) -> None:
|
||||
"""Initialize the sensor."""
|
||||
super().__init__(ambient, mac_address, station_name, description)
|
||||
|
||||
if description.key == TYPE_SOLARRADIATION_LX:
|
||||
# Since TYPE_SOLARRADIATION and TYPE_SOLARRADIATION_LX will have the same
|
||||
# name in the UI, we influence the entity ID of TYPE_SOLARRADIATION_LX here
|
||||
# to differentiate them:
|
||||
self.entity_id = f"sensor.{station_name}_solar_rad_lx"
|
||||
|
||||
@callback
|
||||
def update_from_latest_data(self) -> None:
|
||||
"""Fetch new state data for the sensor."""
|
||||
|
||||
@@ -93,7 +93,7 @@ class AndroidTVRemoteConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
self._abort_if_unique_id_configured(updates={CONF_HOST: self.host})
|
||||
try:
|
||||
return await self._async_start_pair()
|
||||
except (CannotConnect, ConnectionClosed):
|
||||
except CannotConnect, ConnectionClosed:
|
||||
errors["base"] = "cannot_connect"
|
||||
else:
|
||||
user_input = {}
|
||||
@@ -135,7 +135,7 @@ class AndroidTVRemoteConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
# Attempt to pair again.
|
||||
try:
|
||||
return await self._async_start_pair()
|
||||
except (CannotConnect, ConnectionClosed):
|
||||
except CannotConnect, ConnectionClosed:
|
||||
# Device doesn't respond to the specified host. Abort.
|
||||
# If we are in the user flow we could go back to the user step to allow
|
||||
# them to enter a new IP address but we cannot do that for the zeroconf
|
||||
@@ -203,7 +203,7 @@ class AndroidTVRemoteConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
if user_input is not None:
|
||||
try:
|
||||
return await self._async_start_pair()
|
||||
except (CannotConnect, ConnectionClosed):
|
||||
except CannotConnect, ConnectionClosed:
|
||||
# Device became network unreachable after discovery.
|
||||
# Abort and let discovery find it again later.
|
||||
return self.async_abort(reason="cannot_connect")
|
||||
@@ -229,7 +229,7 @@ class AndroidTVRemoteConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
if user_input is not None:
|
||||
try:
|
||||
return await self._async_start_pair()
|
||||
except (CannotConnect, ConnectionClosed):
|
||||
except CannotConnect, ConnectionClosed:
|
||||
# Device is network unreachable. Abort.
|
||||
errors["base"] = "cannot_connect"
|
||||
return self.async_show_form(
|
||||
@@ -264,7 +264,7 @@ class AndroidTVRemoteOptionsFlowHandler(OptionsFlowWithReload):
|
||||
@callback
|
||||
def _save_config(self, data: dict[str, Any]) -> ConfigFlowResult:
|
||||
"""Save the updated options."""
|
||||
new_data = {k: v for k, v in data.items() if k not in [CONF_APPS]}
|
||||
new_data = {k: v for k, v in data.items() if k != CONF_APPS}
|
||||
if self._apps:
|
||||
new_data[CONF_APPS] = self._apps
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ async def validate_account(auth: MSOB2CAuth, account_number: str) -> str | MSOB2
|
||||
_aw = AnglianWater(authenticator=auth)
|
||||
try:
|
||||
await _aw.validate_smart_meter(account_number)
|
||||
except (InvalidAccountIdError, SmartMeterUnavailableError):
|
||||
except InvalidAccountIdError, SmartMeterUnavailableError:
|
||||
return "smart_meter_unavailable"
|
||||
return auth
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ from homeassistant.helpers.selector import (
|
||||
SelectOptionDict,
|
||||
SelectSelector,
|
||||
SelectSelectorConfig,
|
||||
SelectSelectorMode,
|
||||
TemplateSelector,
|
||||
)
|
||||
from homeassistant.helpers.typing import VolDictType
|
||||
@@ -47,6 +48,7 @@ from .const import (
|
||||
CONF_RECOMMENDED,
|
||||
CONF_TEMPERATURE,
|
||||
CONF_THINKING_BUDGET,
|
||||
CONF_THINKING_EFFORT,
|
||||
CONF_WEB_SEARCH,
|
||||
CONF_WEB_SEARCH_CITY,
|
||||
CONF_WEB_SEARCH_COUNTRY,
|
||||
@@ -58,6 +60,7 @@ from .const import (
|
||||
DEFAULT_AI_TASK_NAME,
|
||||
DEFAULT_CONVERSATION_NAME,
|
||||
DOMAIN,
|
||||
NON_ADAPTIVE_THINKING_MODELS,
|
||||
NON_THINKING_MODELS,
|
||||
WEB_SEARCH_UNSUPPORTED_MODELS,
|
||||
)
|
||||
@@ -111,6 +114,7 @@ async def get_model_list(client: anthropic.AsyncAnthropic) -> list[SelectOptionD
|
||||
"claude-3-5-haiku-20241022",
|
||||
"claude-3-opus-20240229",
|
||||
)
|
||||
and model_info.id[-2:-1] != "-"
|
||||
else model_info.id
|
||||
)
|
||||
if short_form.search(model_alias):
|
||||
@@ -354,7 +358,9 @@ class ConversationSubentryFlowHandler(ConfigSubentryFlow):
|
||||
|
||||
model = self.options[CONF_CHAT_MODEL]
|
||||
|
||||
if not model.startswith(tuple(NON_THINKING_MODELS)):
|
||||
if not model.startswith(tuple(NON_THINKING_MODELS)) and model.startswith(
|
||||
tuple(NON_ADAPTIVE_THINKING_MODELS)
|
||||
):
|
||||
step_schema[
|
||||
vol.Optional(
|
||||
CONF_THINKING_BUDGET, default=DEFAULT[CONF_THINKING_BUDGET]
|
||||
@@ -371,6 +377,22 @@ class ConversationSubentryFlowHandler(ConfigSubentryFlow):
|
||||
else:
|
||||
self.options.pop(CONF_THINKING_BUDGET, None)
|
||||
|
||||
if not model.startswith(tuple(NON_ADAPTIVE_THINKING_MODELS)):
|
||||
step_schema[
|
||||
vol.Optional(
|
||||
CONF_THINKING_EFFORT,
|
||||
default=DEFAULT[CONF_THINKING_EFFORT],
|
||||
)
|
||||
] = SelectSelector(
|
||||
SelectSelectorConfig(
|
||||
options=["none", "low", "medium", "high", "max"],
|
||||
translation_key=CONF_THINKING_EFFORT,
|
||||
mode=SelectSelectorMode.DROPDOWN,
|
||||
)
|
||||
)
|
||||
else:
|
||||
self.options.pop(CONF_THINKING_EFFORT, None)
|
||||
|
||||
if not model.startswith(tuple(WEB_SEARCH_UNSUPPORTED_MODELS)):
|
||||
step_schema.update(
|
||||
{
|
||||
|
||||
@@ -14,6 +14,7 @@ CONF_CHAT_MODEL = "chat_model"
|
||||
CONF_MAX_TOKENS = "max_tokens"
|
||||
CONF_TEMPERATURE = "temperature"
|
||||
CONF_THINKING_BUDGET = "thinking_budget"
|
||||
CONF_THINKING_EFFORT = "thinking_effort"
|
||||
CONF_WEB_SEARCH = "web_search"
|
||||
CONF_WEB_SEARCH_USER_LOCATION = "user_location"
|
||||
CONF_WEB_SEARCH_MAX_USES = "web_search_max_uses"
|
||||
@@ -29,6 +30,7 @@ DEFAULT = {
|
||||
CONF_MAX_TOKENS: 3000,
|
||||
CONF_TEMPERATURE: 1.0,
|
||||
CONF_THINKING_BUDGET: 0,
|
||||
CONF_THINKING_EFFORT: "low",
|
||||
CONF_WEB_SEARCH: False,
|
||||
CONF_WEB_SEARCH_USER_LOCATION: False,
|
||||
CONF_WEB_SEARCH_MAX_USES: 5,
|
||||
@@ -42,6 +44,18 @@ NON_THINKING_MODELS = [
|
||||
"claude-3-haiku",
|
||||
]
|
||||
|
||||
NON_ADAPTIVE_THINKING_MODELS = [
|
||||
"claude-opus-4-5",
|
||||
"claude-sonnet-4-5",
|
||||
"claude-haiku-4-5",
|
||||
"claude-opus-4-1",
|
||||
"claude-opus-4-0",
|
||||
"claude-opus-4-20250514",
|
||||
"claude-sonnet-4-0",
|
||||
"claude-sonnet-4-20250514",
|
||||
"claude-3",
|
||||
]
|
||||
|
||||
WEB_SEARCH_UNSUPPORTED_MODELS = [
|
||||
"claude-3-haiku",
|
||||
"claude-3-opus",
|
||||
|
||||
@@ -23,6 +23,7 @@ from anthropic.types import (
|
||||
MessageDeltaUsage,
|
||||
MessageParam,
|
||||
MessageStreamEvent,
|
||||
OutputConfigParam,
|
||||
RawContentBlockDeltaEvent,
|
||||
RawContentBlockStartEvent,
|
||||
RawContentBlockStopEvent,
|
||||
@@ -41,6 +42,7 @@ from anthropic.types import (
|
||||
TextDelta,
|
||||
ThinkingBlock,
|
||||
ThinkingBlockParam,
|
||||
ThinkingConfigAdaptiveParam,
|
||||
ThinkingConfigDisabledParam,
|
||||
ThinkingConfigEnabledParam,
|
||||
ThinkingDelta,
|
||||
@@ -78,6 +80,7 @@ from .const import (
|
||||
CONF_MAX_TOKENS,
|
||||
CONF_TEMPERATURE,
|
||||
CONF_THINKING_BUDGET,
|
||||
CONF_THINKING_EFFORT,
|
||||
CONF_WEB_SEARCH,
|
||||
CONF_WEB_SEARCH_CITY,
|
||||
CONF_WEB_SEARCH_COUNTRY,
|
||||
@@ -89,6 +92,7 @@ from .const import (
|
||||
DOMAIN,
|
||||
LOGGER,
|
||||
MIN_THINKING_BUDGET,
|
||||
NON_ADAPTIVE_THINKING_MODELS,
|
||||
NON_THINKING_MODELS,
|
||||
)
|
||||
|
||||
@@ -622,21 +626,34 @@ class AnthropicBaseLLMEntity(Entity):
|
||||
stream=True,
|
||||
)
|
||||
|
||||
thinking_budget = options.get(
|
||||
CONF_THINKING_BUDGET, DEFAULT[CONF_THINKING_BUDGET]
|
||||
)
|
||||
if (
|
||||
not model.startswith(tuple(NON_THINKING_MODELS))
|
||||
and thinking_budget >= MIN_THINKING_BUDGET
|
||||
):
|
||||
model_args["thinking"] = ThinkingConfigEnabledParam(
|
||||
type="enabled", budget_tokens=thinking_budget
|
||||
if not model.startswith(tuple(NON_ADAPTIVE_THINKING_MODELS)):
|
||||
thinking_effort = options.get(
|
||||
CONF_THINKING_EFFORT, DEFAULT[CONF_THINKING_EFFORT]
|
||||
)
|
||||
if thinking_effort != "none":
|
||||
model_args["thinking"] = ThinkingConfigAdaptiveParam(type="adaptive")
|
||||
model_args["output_config"] = OutputConfigParam(effort=thinking_effort)
|
||||
else:
|
||||
model_args["thinking"] = ThinkingConfigDisabledParam(type="disabled")
|
||||
model_args["temperature"] = options.get(
|
||||
CONF_TEMPERATURE, DEFAULT[CONF_TEMPERATURE]
|
||||
)
|
||||
else:
|
||||
model_args["thinking"] = ThinkingConfigDisabledParam(type="disabled")
|
||||
model_args["temperature"] = options.get(
|
||||
CONF_TEMPERATURE, DEFAULT[CONF_TEMPERATURE]
|
||||
thinking_budget = options.get(
|
||||
CONF_THINKING_BUDGET, DEFAULT[CONF_THINKING_BUDGET]
|
||||
)
|
||||
if (
|
||||
not model.startswith(tuple(NON_THINKING_MODELS))
|
||||
and thinking_budget >= MIN_THINKING_BUDGET
|
||||
):
|
||||
model_args["thinking"] = ThinkingConfigEnabledParam(
|
||||
type="enabled", budget_tokens=thinking_budget
|
||||
)
|
||||
else:
|
||||
model_args["thinking"] = ThinkingConfigDisabledParam(type="disabled")
|
||||
model_args["temperature"] = options.get(
|
||||
CONF_TEMPERATURE, DEFAULT[CONF_TEMPERATURE]
|
||||
)
|
||||
|
||||
tools: list[ToolUnionParam] = []
|
||||
if chat_log.llm_api:
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/anthropic",
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"requirements": ["anthropic==0.75.0"]
|
||||
"requirements": ["anthropic==0.78.0"]
|
||||
}
|
||||
|
||||
@@ -47,12 +47,14 @@
|
||||
"model": {
|
||||
"data": {
|
||||
"thinking_budget": "[%key:component::anthropic::config_subentries::conversation::step::model::data::thinking_budget%]",
|
||||
"thinking_effort": "[%key:component::anthropic::config_subentries::conversation::step::model::data::thinking_effort%]",
|
||||
"user_location": "[%key:component::anthropic::config_subentries::conversation::step::model::data::user_location%]",
|
||||
"web_search": "[%key:component::anthropic::config_subentries::conversation::step::model::data::web_search%]",
|
||||
"web_search_max_uses": "[%key:component::anthropic::config_subentries::conversation::step::model::data::web_search_max_uses%]"
|
||||
},
|
||||
"data_description": {
|
||||
"thinking_budget": "[%key:component::anthropic::config_subentries::conversation::step::model::data_description::thinking_budget%]",
|
||||
"thinking_effort": "[%key:component::anthropic::config_subentries::conversation::step::model::data_description::thinking_effort%]",
|
||||
"user_location": "[%key:component::anthropic::config_subentries::conversation::step::model::data_description::user_location%]",
|
||||
"web_search": "[%key:component::anthropic::config_subentries::conversation::step::model::data_description::web_search%]",
|
||||
"web_search_max_uses": "[%key:component::anthropic::config_subentries::conversation::step::model::data_description::web_search_max_uses%]"
|
||||
@@ -95,12 +97,14 @@
|
||||
"model": {
|
||||
"data": {
|
||||
"thinking_budget": "Thinking budget",
|
||||
"thinking_effort": "Thinking effort",
|
||||
"user_location": "Include home location",
|
||||
"web_search": "Enable web search",
|
||||
"web_search_max_uses": "Maximum web searches"
|
||||
},
|
||||
"data_description": {
|
||||
"thinking_budget": "The number of tokens the model can use to think about the response out of the total maximum number of tokens. Set to 1024 or greater to enable extended thinking.",
|
||||
"thinking_effort": "Control how many tokens Claude uses when responding, trading off between response thoroughness and token efficiency",
|
||||
"user_location": "Localize search results based on home location",
|
||||
"web_search": "The web search tool gives Claude direct access to real-time web content, allowing it to answer questions with up-to-date information beyond its knowledge cutoff",
|
||||
"web_search_max_uses": "Limit the number of searches performed per response"
|
||||
@@ -125,5 +129,16 @@
|
||||
},
|
||||
"title": "Model deprecated"
|
||||
}
|
||||
},
|
||||
"selector": {
|
||||
"thinking_effort": {
|
||||
"options": {
|
||||
"high": "[%key:common::state::high%]",
|
||||
"low": "[%key:common::state::low%]",
|
||||
"max": "Max",
|
||||
"medium": "[%key:common::state::medium%]",
|
||||
"none": "None"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ class ConfigFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
try:
|
||||
async with asyncio.timeout(CONNECTION_TIMEOUT):
|
||||
data = APCUPSdData(await aioapcaccess.request_status(host, port))
|
||||
except (OSError, asyncio.IncompleteReadError, TimeoutError):
|
||||
except OSError, asyncio.IncompleteReadError, TimeoutError:
|
||||
errors = {"base": "cannot_connect"}
|
||||
return self.async_show_form(
|
||||
step_id="user", data_schema=_SCHEMA, errors=errors
|
||||
@@ -77,7 +77,7 @@ class ConfigFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
try:
|
||||
async with asyncio.timeout(CONNECTION_TIMEOUT):
|
||||
data = APCUPSdData(await aioapcaccess.request_status(host, port))
|
||||
except (OSError, asyncio.IncompleteReadError, TimeoutError):
|
||||
except OSError, asyncio.IncompleteReadError, TimeoutError:
|
||||
errors = {"base": "cannot_connect"}
|
||||
return self.async_show_form(
|
||||
step_id="reconfigure", data_schema=_SCHEMA, errors=errors
|
||||
|
||||
@@ -547,7 +547,7 @@ class APCUPSdSensor(APCUPSdEntity, SensorEntity):
|
||||
|
||||
try:
|
||||
self._attr_native_value = dateutil.parser.parse(data)
|
||||
except (dateutil.parser.ParserError, OverflowError):
|
||||
except dateutil.parser.ParserError, OverflowError:
|
||||
# If parsing fails we should mark it as unknown, with a log for further debugging.
|
||||
_LOGGER.warning('Failed to parse date for %s: "%s"', key, data)
|
||||
self._attr_native_value = None
|
||||
|
||||
@@ -2,15 +2,16 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pyatv.const import KeyboardFocusState
|
||||
from pyatv.const import FeatureName, FeatureState, KeyboardFocusState
|
||||
from pyatv.interface import AppleTV, KeyboardListener
|
||||
|
||||
from homeassistant.components.binary_sensor import BinarySensorEntity
|
||||
from homeassistant.const import CONF_NAME
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from . import AppleTvConfigEntry
|
||||
from . import SIGNAL_CONNECTED, AppleTvConfigEntry
|
||||
from .entity import AppleTVEntity
|
||||
|
||||
|
||||
@@ -21,10 +22,22 @@ async def async_setup_entry(
|
||||
) -> None:
|
||||
"""Load Apple TV binary sensor based on a config entry."""
|
||||
# apple_tv config entries always have a unique id
|
||||
assert config_entry.unique_id is not None
|
||||
name: str = config_entry.data[CONF_NAME]
|
||||
manager = config_entry.runtime_data
|
||||
async_add_entities([AppleTVKeyboardFocused(name, config_entry.unique_id, manager)])
|
||||
cb: CALLBACK_TYPE
|
||||
|
||||
def setup_entities(atv: AppleTV) -> None:
|
||||
if atv.features.in_state(FeatureState.Available, FeatureName.TextFocusState):
|
||||
assert config_entry.unique_id is not None
|
||||
name: str = config_entry.data[CONF_NAME]
|
||||
async_add_entities(
|
||||
[AppleTVKeyboardFocused(name, config_entry.unique_id, manager)]
|
||||
)
|
||||
cb()
|
||||
|
||||
cb = async_dispatcher_connect(
|
||||
hass, f"{SIGNAL_CONNECTED}_{config_entry.unique_id}", setup_entities
|
||||
)
|
||||
config_entry.async_on_unload(cb)
|
||||
|
||||
|
||||
class AppleTVKeyboardFocused(AppleTVEntity, BinarySensorEntity, KeyboardListener):
|
||||
|
||||
@@ -181,9 +181,9 @@ async def async_import_client_credential(
|
||||
CONF_DOMAIN: domain,
|
||||
CONF_CLIENT_ID: credential.client_id,
|
||||
CONF_CLIENT_SECRET: credential.client_secret,
|
||||
CONF_AUTH_DOMAIN: auth_domain if auth_domain else domain,
|
||||
CONF_AUTH_DOMAIN: auth_domain or domain,
|
||||
}
|
||||
item[CONF_NAME] = credential.name if credential.name else DEFAULT_IMPORT_NAME
|
||||
item[CONF_NAME] = credential.name or DEFAULT_IMPORT_NAME
|
||||
await hass.data[DATA_COMPONENT].async_import_item(item)
|
||||
|
||||
|
||||
|
||||
@@ -168,7 +168,7 @@ class AprilaireCoordinator(BaseDataUpdateCoordinatorProtocol):
|
||||
|
||||
name = data.get(Attribute.NAME) if data else None
|
||||
|
||||
return name if name else "Aprilaire"
|
||||
return name or "Aprilaire"
|
||||
|
||||
def get_hw_version(self, data: dict[str, Any]) -> str:
|
||||
"""Get the hardware version."""
|
||||
|
||||
@@ -41,7 +41,7 @@ class APsystemsLocalAPIFlow(ConfigFlow, domain=DOMAIN):
|
||||
)
|
||||
try:
|
||||
device_info = await api.get_device_info()
|
||||
except (TimeoutError, ClientConnectionError):
|
||||
except TimeoutError, ClientConnectionError:
|
||||
errors["base"] = "cannot_connect"
|
||||
else:
|
||||
await self.async_set_unique_id(device_info.deviceId)
|
||||
|
||||
@@ -64,7 +64,7 @@ class ApSystemsDataCoordinator(DataUpdateCoordinator[ApSystemsSensorData]):
|
||||
async def _async_setup(self) -> None:
|
||||
try:
|
||||
device_info = await self.api.get_device_info()
|
||||
except (ConnectionError, TimeoutError):
|
||||
except ConnectionError, TimeoutError:
|
||||
raise UpdateFailed from None
|
||||
self.api.max_power = device_info.maxPower
|
||||
self.api.min_power = device_info.minPower
|
||||
|
||||
@@ -49,7 +49,7 @@ class ApSystemsMaxOutputNumber(ApSystemsEntity, NumberEntity):
|
||||
"""Set the state with the value fetched from the inverter."""
|
||||
try:
|
||||
status = await self._api.get_max_power()
|
||||
except (TimeoutError, ClientConnectorError):
|
||||
except TimeoutError, ClientConnectorError:
|
||||
self._attr_available = False
|
||||
else:
|
||||
self._attr_available = True
|
||||
|
||||
@@ -43,7 +43,7 @@ class ApSystemsInverterSwitch(ApSystemsEntity, SwitchEntity):
|
||||
"""Update switch status and availability."""
|
||||
try:
|
||||
status = await self._api.get_device_power_status()
|
||||
except (TimeoutError, ClientConnectionError, InverterReturnedError):
|
||||
except TimeoutError, ClientConnectionError, InverterReturnedError:
|
||||
self._attr_available = False
|
||||
else:
|
||||
self._attr_available = True
|
||||
|
||||
@@ -56,7 +56,7 @@ class AquaCellConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
refresh_token = await api.authenticate(
|
||||
user_input[CONF_EMAIL], user_input[CONF_PASSWORD]
|
||||
)
|
||||
except (ApiException, TimeoutError):
|
||||
except ApiException, TimeoutError:
|
||||
errors["base"] = "cannot_connect"
|
||||
except AuthenticationFailed:
|
||||
errors["base"] = "invalid_auth"
|
||||
|
||||
@@ -94,7 +94,7 @@ def _retry[_SharpAquosTVDeviceT: SharpAquosTVDevice, **_P](
|
||||
try:
|
||||
func(obj, *args, **kwargs)
|
||||
break
|
||||
except (OSError, TypeError, ValueError):
|
||||
except OSError, TypeError, ValueError:
|
||||
update_retries -= 1
|
||||
if update_retries == 0:
|
||||
obj.set_state(MediaPlayerState.OFF)
|
||||
|
||||
@@ -201,5 +201,5 @@ class ArwnSensor(SensorEntity):
|
||||
ev: dict[str, Any] = {}
|
||||
ev.update(event)
|
||||
self._attr_extra_state_attributes = ev
|
||||
self._attr_native_value = ev.get(self._state_key, None)
|
||||
self._attr_native_value = ev.get(self._state_key)
|
||||
self.async_write_ha_state()
|
||||
|
||||
@@ -969,7 +969,7 @@ class PipelineRun:
|
||||
metadata,
|
||||
self._speech_to_text_stream(audio_stream=stream, stt_vad=stt_vad),
|
||||
)
|
||||
except (asyncio.CancelledError, TimeoutError):
|
||||
except asyncio.CancelledError, TimeoutError:
|
||||
raise # expected
|
||||
except hass_nabucasa.auth.Unauthenticated as src_error:
|
||||
raise SpeechToTextError(
|
||||
|
||||
@@ -189,7 +189,7 @@ class AsusWrtFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
try:
|
||||
await api.async_connect()
|
||||
|
||||
except (AsusRouterError, OSError):
|
||||
except AsusRouterError, OSError:
|
||||
_LOGGER.error(
|
||||
"Error connecting to the AsusWrt router at %s using protocol %s",
|
||||
host,
|
||||
|
||||
@@ -51,5 +51,5 @@ class AtagConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
data_schema=vol.Schema(DATA_SCHEMA),
|
||||
errors=errors if errors else {},
|
||||
errors=errors or {},
|
||||
)
|
||||
|
||||
@@ -304,7 +304,7 @@ async def _try_async_validate_config_item(
|
||||
"""Validate config item."""
|
||||
try:
|
||||
return await _async_validate_config_item(hass, config, False, True)
|
||||
except (vol.Invalid, HomeAssistantError):
|
||||
except vol.Invalid, HomeAssistantError:
|
||||
return None
|
||||
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ async def async_get_config_entry_diagnostics(
|
||||
account_data["allowed"], TO_REDACT_ACCOUNT_DATA_ALLOWED
|
||||
)
|
||||
|
||||
except (AttributeError, TypeError, ValueError, KeyError):
|
||||
except AttributeError, TypeError, ValueError, KeyError:
|
||||
bucket_info = {"name": "unknown", "id": "unknown"}
|
||||
account_data = {"error": "Failed to retrieve detailed account information"}
|
||||
|
||||
|
||||
@@ -145,7 +145,7 @@ class BeoConfigFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
async with self._client:
|
||||
try:
|
||||
await self._client.get_beolink_self(_request_timeout=3)
|
||||
except (ClientConnectorError, TimeoutError):
|
||||
except ClientConnectorError, TimeoutError:
|
||||
return self.async_abort(reason="invalid_address")
|
||||
|
||||
self._model = discovery_info.hostname[:-16].replace("-", " ")
|
||||
|
||||
@@ -80,18 +80,16 @@ SWITCHES = (
|
||||
key=PLUG_AND_CHARGE,
|
||||
translation_key=PLUG_AND_CHARGE,
|
||||
function=set_plug_and_charge,
|
||||
turn_on_off_fn=lambda evse_id, connector: (
|
||||
update_on_value_and_activity(PLUG_AND_CHARGE, evse_id, connector)
|
||||
turn_on_off_fn=lambda evse_id, connector: update_on_value_and_activity(
|
||||
PLUG_AND_CHARGE, evse_id, connector
|
||||
),
|
||||
),
|
||||
BlueCurrentSwitchEntityDescription(
|
||||
key=LINKED_CHARGE_CARDS,
|
||||
translation_key=LINKED_CHARGE_CARDS,
|
||||
function=set_linked_charge_cards,
|
||||
turn_on_off_fn=lambda evse_id, connector: (
|
||||
update_on_value_and_activity(
|
||||
PUBLIC_CHARGING, evse_id, connector, reverse_is_on=True
|
||||
)
|
||||
turn_on_off_fn=lambda evse_id, connector: update_on_value_and_activity(
|
||||
PUBLIC_CHARGING, evse_id, connector, reverse_is_on=True
|
||||
),
|
||||
),
|
||||
BlueCurrentSwitchEntityDescription(
|
||||
|
||||
@@ -148,8 +148,10 @@ SENSOR_TYPES: tuple[BMWBinarySensorEntityDescription, ...] = (
|
||||
device_class=BinarySensorDeviceClass.LOCK,
|
||||
# device class lock: On means unlocked, Off means locked
|
||||
# Possible values: LOCKED, SECURED, SELECTIVE_LOCKED, UNLOCKED
|
||||
value_fn=lambda v: v.doors_and_windows.door_lock_state
|
||||
not in {LockState.LOCKED, LockState.SECURED},
|
||||
value_fn=lambda v: (
|
||||
v.doors_and_windows.door_lock_state
|
||||
not in {LockState.LOCKED, LockState.SECURED}
|
||||
),
|
||||
attr_fn=lambda v, u: {
|
||||
"door_lock_state": v.doors_and_windows.door_lock_state.value
|
||||
},
|
||||
@@ -189,9 +191,11 @@ SENSOR_TYPES: tuple[BMWBinarySensorEntityDescription, ...] = (
|
||||
BMWBinarySensorEntityDescription(
|
||||
key="is_pre_entry_climatization_enabled",
|
||||
translation_key="is_pre_entry_climatization_enabled",
|
||||
value_fn=lambda v: v.charging_profile.is_pre_entry_climatization_enabled
|
||||
if v.charging_profile
|
||||
else False,
|
||||
value_fn=lambda v: (
|
||||
v.charging_profile.is_pre_entry_climatization_enabled
|
||||
if v.charging_profile
|
||||
else False
|
||||
),
|
||||
is_available=lambda v: v.has_electric_drivetrain,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -40,7 +40,9 @@ BUTTON_TYPES: tuple[BMWButtonEntityDescription, ...] = (
|
||||
BMWButtonEntityDescription(
|
||||
key="light_flash",
|
||||
translation_key="light_flash",
|
||||
remote_function=lambda vehicle: vehicle.remote_services.trigger_remote_light_flash(),
|
||||
remote_function=lambda vehicle: (
|
||||
vehicle.remote_services.trigger_remote_light_flash()
|
||||
),
|
||||
),
|
||||
BMWButtonEntityDescription(
|
||||
key="sound_horn",
|
||||
@@ -50,18 +52,24 @@ BUTTON_TYPES: tuple[BMWButtonEntityDescription, ...] = (
|
||||
BMWButtonEntityDescription(
|
||||
key="activate_air_conditioning",
|
||||
translation_key="activate_air_conditioning",
|
||||
remote_function=lambda vehicle: vehicle.remote_services.trigger_remote_air_conditioning(),
|
||||
remote_function=lambda vehicle: (
|
||||
vehicle.remote_services.trigger_remote_air_conditioning()
|
||||
),
|
||||
),
|
||||
BMWButtonEntityDescription(
|
||||
key="deactivate_air_conditioning",
|
||||
translation_key="deactivate_air_conditioning",
|
||||
remote_function=lambda vehicle: vehicle.remote_services.trigger_remote_air_conditioning_stop(),
|
||||
remote_function=lambda vehicle: (
|
||||
vehicle.remote_services.trigger_remote_air_conditioning_stop()
|
||||
),
|
||||
is_available=lambda vehicle: vehicle.is_remote_climate_stop_enabled,
|
||||
),
|
||||
BMWButtonEntityDescription(
|
||||
key="find_vehicle",
|
||||
translation_key="find_vehicle",
|
||||
remote_function=lambda vehicle: vehicle.remote_services.trigger_remote_vehicle_finder(),
|
||||
remote_function=lambda vehicle: (
|
||||
vehicle.remote_services.trigger_remote_vehicle_finder()
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -50,7 +50,9 @@ NUMBER_TYPES: list[BMWSwitchEntityDescription] = [
|
||||
is_available=lambda v: v.is_remote_climate_stop_enabled,
|
||||
value_fn=lambda v: v.climate.is_climate_on,
|
||||
remote_service_on=lambda v: v.remote_services.trigger_remote_air_conditioning(),
|
||||
remote_service_off=lambda v: v.remote_services.trigger_remote_air_conditioning_stop(),
|
||||
remote_service_off=lambda v: (
|
||||
v.remote_services.trigger_remote_air_conditioning_stop()
|
||||
),
|
||||
),
|
||||
BMWSwitchEntityDescription(
|
||||
key="charging",
|
||||
|
||||
@@ -200,7 +200,7 @@ class BraviaTVCoordinator(DataUpdateCoordinator[None]):
|
||||
"device": self.config_entry.title,
|
||||
},
|
||||
) from err
|
||||
except (BraviaConnectionError, BraviaConnectionTimeout, BraviaTurnedOff):
|
||||
except BraviaConnectionError, BraviaConnectionTimeout, BraviaTurnedOff:
|
||||
self.is_on = False
|
||||
self.connected = False
|
||||
_LOGGER.debug(
|
||||
|
||||
@@ -63,9 +63,9 @@ SENSOR_DESCRIPTIONS: tuple[BringSensorEntityDescription, ...] = (
|
||||
key=BringSensor.LIST_LANGUAGE,
|
||||
translation_key=BringSensor.LIST_LANGUAGE,
|
||||
value_fn=(
|
||||
lambda lst, settings: x.lower()
|
||||
if (x := list_language(lst.lst.listUuid, settings))
|
||||
else None
|
||||
lambda lst, settings: (
|
||||
x.lower() if (x := list_language(lst.lst.listUuid, settings)) else None
|
||||
)
|
||||
),
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
options=[x.lower() for x in BRING_SUPPORTED_LOCALES],
|
||||
|
||||
@@ -173,7 +173,7 @@ class BroadlinkDevice[_ApiT: blk.Device = blk.Device]:
|
||||
request = partial(function, *args, **kwargs)
|
||||
try:
|
||||
return await self.hass.async_add_executor_job(request)
|
||||
except (AuthorizationError, ConnectionClosedError):
|
||||
except AuthorizationError, ConnectionClosedError:
|
||||
if not await self.async_auth():
|
||||
raise
|
||||
return await self.hass.async_add_executor_job(request)
|
||||
|
||||
@@ -337,7 +337,7 @@ class BroadlinkRemote(BroadlinkEntity, RemoteEntity, RestoreEntity):
|
||||
await asyncio.sleep(1)
|
||||
try:
|
||||
code = await device.async_request(device.api.check_data)
|
||||
except (ReadError, StorageError):
|
||||
except ReadError, StorageError:
|
||||
continue
|
||||
return b64encode(code).decode("utf8")
|
||||
|
||||
@@ -413,7 +413,7 @@ class BroadlinkRemote(BroadlinkEntity, RemoteEntity, RestoreEntity):
|
||||
await asyncio.sleep(1)
|
||||
try:
|
||||
code = await device.async_request(device.api.check_data)
|
||||
except (ReadError, StorageError):
|
||||
except ReadError, StorageError:
|
||||
continue
|
||||
return b64encode(code).decode("utf8")
|
||||
|
||||
|
||||
@@ -127,7 +127,7 @@ class BrotherConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
model, serial = await validate_input(self.hass, user_input)
|
||||
except InvalidHost:
|
||||
errors[CONF_HOST] = "wrong_host"
|
||||
except (ConnectionError, TimeoutError):
|
||||
except ConnectionError, TimeoutError:
|
||||
errors["base"] = "cannot_connect"
|
||||
except SnmpError:
|
||||
errors["base"] = "snmp_error"
|
||||
@@ -163,7 +163,7 @@ class BrotherConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
await self.brother.async_update()
|
||||
except UnsupportedModelError:
|
||||
return self.async_abort(reason="unsupported_model")
|
||||
except (ConnectionError, SnmpError, TimeoutError):
|
||||
except ConnectionError, SnmpError, TimeoutError:
|
||||
return self.async_abort(reason="cannot_connect")
|
||||
|
||||
# Check if already configured
|
||||
@@ -211,7 +211,7 @@ class BrotherConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
await validate_input(self.hass, user_input, entry.unique_id)
|
||||
except InvalidHost:
|
||||
errors[CONF_HOST] = "wrong_host"
|
||||
except (ConnectionError, TimeoutError):
|
||||
except ConnectionError, TimeoutError:
|
||||
errors["base"] = "cannot_connect"
|
||||
except SnmpError:
|
||||
errors["base"] = "snmp_error"
|
||||
|
||||
@@ -81,7 +81,7 @@ def get_event_types_by_event_class(event_class: str) -> set[str]:
|
||||
but if there is only one button then it will be
|
||||
button without a number postfix.
|
||||
"""
|
||||
return EVENT_TYPES_BY_EVENT_CLASS.get(event_class.split("_")[0], set())
|
||||
return EVENT_TYPES_BY_EVENT_CLASS.get(event_class.split("_", maxsplit=1)[0], set())
|
||||
|
||||
|
||||
async def async_validate_trigger_config(
|
||||
|
||||
@@ -199,7 +199,7 @@ class BrData:
|
||||
"""Return the temperature, or None."""
|
||||
try:
|
||||
return float(self.data.get(TEMPERATURE))
|
||||
except (ValueError, TypeError):
|
||||
except ValueError, TypeError:
|
||||
return None
|
||||
|
||||
@property
|
||||
@@ -207,7 +207,7 @@ class BrData:
|
||||
"""Return the feeltemperature, or None."""
|
||||
try:
|
||||
return float(self.data.get(FEELTEMPERATURE))
|
||||
except (ValueError, TypeError):
|
||||
except ValueError, TypeError:
|
||||
return None
|
||||
|
||||
@property
|
||||
@@ -215,7 +215,7 @@ class BrData:
|
||||
"""Return the pressure, or None."""
|
||||
try:
|
||||
return float(self.data.get(PRESSURE))
|
||||
except (ValueError, TypeError):
|
||||
except ValueError, TypeError:
|
||||
return None
|
||||
|
||||
@property
|
||||
@@ -223,7 +223,7 @@ class BrData:
|
||||
"""Return the humidity, or None."""
|
||||
try:
|
||||
return int(self.data.get(HUMIDITY))
|
||||
except (ValueError, TypeError):
|
||||
except ValueError, TypeError:
|
||||
return None
|
||||
|
||||
@property
|
||||
@@ -231,7 +231,7 @@ class BrData:
|
||||
"""Return the visibility, or None."""
|
||||
try:
|
||||
return int(self.data.get(VISIBILITY))
|
||||
except (ValueError, TypeError):
|
||||
except ValueError, TypeError:
|
||||
return None
|
||||
|
||||
@property
|
||||
@@ -239,7 +239,7 @@ class BrData:
|
||||
"""Return the windgust, or None."""
|
||||
try:
|
||||
return float(self.data.get(WINDGUST))
|
||||
except (ValueError, TypeError):
|
||||
except ValueError, TypeError:
|
||||
return None
|
||||
|
||||
@property
|
||||
@@ -247,7 +247,7 @@ class BrData:
|
||||
"""Return the windspeed, or None."""
|
||||
try:
|
||||
return float(self.data.get(WINDSPEED))
|
||||
except (ValueError, TypeError):
|
||||
except ValueError, TypeError:
|
||||
return None
|
||||
|
||||
@property
|
||||
@@ -255,7 +255,7 @@ class BrData:
|
||||
"""Return the wind bearing, or None."""
|
||||
try:
|
||||
return int(self.data.get(WINDAZIMUTH))
|
||||
except (ValueError, TypeError):
|
||||
except ValueError, TypeError:
|
||||
return None
|
||||
|
||||
@property
|
||||
|
||||
@@ -691,7 +691,7 @@ class CalendarEventView(http.HomeAssistantView):
|
||||
try:
|
||||
start_date = dt_util.parse_datetime(start)
|
||||
end_date = dt_util.parse_datetime(end)
|
||||
except (ValueError, AttributeError):
|
||||
except ValueError, AttributeError:
|
||||
return web.Response(status=HTTPStatus.BAD_REQUEST)
|
||||
if start_date is None or end_date is None:
|
||||
return web.Response(status=HTTPStatus.BAD_REQUEST)
|
||||
|
||||
@@ -58,14 +58,14 @@
|
||||
"name": "Enable motion detection"
|
||||
},
|
||||
"play_stream": {
|
||||
"description": "Plays the camera stream on a supported media player.",
|
||||
"description": "Plays a camera stream on a supported media player.",
|
||||
"fields": {
|
||||
"format": {
|
||||
"description": "Stream format supported by the media player.",
|
||||
"name": "Format"
|
||||
},
|
||||
"media_player": {
|
||||
"description": "Media players to stream to.",
|
||||
"description": "Media player to stream to.",
|
||||
"name": "Media player"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -71,7 +71,7 @@ class CanaryConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
await self.hass.async_add_executor_job(
|
||||
validate_input, self.hass, user_input
|
||||
)
|
||||
except (ConnectTimeout, HTTPError):
|
||||
except ConnectTimeout, HTTPError:
|
||||
errors["base"] = "cannot_connect"
|
||||
except Exception:
|
||||
_LOGGER.exception("Unexpected exception")
|
||||
|
||||
@@ -373,7 +373,7 @@ class CloudAlexaConfig(alexa_config.AbstractConfig):
|
||||
if self.should_report_state:
|
||||
try:
|
||||
await self.async_enable_proactive_mode()
|
||||
except (alexa_errors.NoTokenAvailable, alexa_errors.RequireRelink):
|
||||
except alexa_errors.NoTokenAvailable, alexa_errors.RequireRelink:
|
||||
await self.set_authorized(False)
|
||||
else:
|
||||
await self.async_disable_proactive_mode()
|
||||
|
||||
@@ -187,7 +187,7 @@ class CloudClient(Interface):
|
||||
err,
|
||||
)
|
||||
async_call_later(self._hass, 30, enable_alexa_job)
|
||||
except (alexa_errors.NoTokenAvailable, alexa_errors.RequireRelink):
|
||||
except alexa_errors.NoTokenAvailable, alexa_errors.RequireRelink:
|
||||
pass
|
||||
|
||||
enable_alexa_job = HassJob(enable_alexa, cancel_on_shutdown=True)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import base64
|
||||
from collections.abc import AsyncGenerator, Callable, Iterable
|
||||
from enum import Enum
|
||||
from enum import StrEnum
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
@@ -59,7 +59,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
_MAX_TOOL_ITERATIONS = 10
|
||||
|
||||
|
||||
class ResponseItemType(str, Enum):
|
||||
class ResponseItemType(StrEnum):
|
||||
"""Response item types."""
|
||||
|
||||
FUNCTION_CALL = "function_call"
|
||||
|
||||
@@ -779,7 +779,7 @@ async def websocket_update_prefs(
|
||||
msg["id"], "alexa_timeout", "Timeout validating Alexa access token."
|
||||
)
|
||||
return
|
||||
except (alexa_errors.NoTokenAvailable, alexa_errors.RequireRelink):
|
||||
except alexa_errors.NoTokenAvailable, alexa_errors.RequireRelink:
|
||||
connection.send_error(
|
||||
msg["id"],
|
||||
"alexa_relink",
|
||||
|
||||
@@ -13,6 +13,6 @@
|
||||
"integration_type": "system",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["acme", "hass_nabucasa", "snitun"],
|
||||
"requirements": ["hass-nabucasa==1.12.0", "openai==2.15.0"],
|
||||
"requirements": ["hass-nabucasa==1.13.0", "openai==2.15.0"],
|
||||
"single_config_entry": true
|
||||
}
|
||||
|
||||
@@ -196,44 +196,46 @@ class R2BackupAgent(BackupAgent):
|
||||
)
|
||||
upload_id = multipart_upload["UploadId"]
|
||||
try:
|
||||
parts = []
|
||||
parts: list[dict[str, Any]] = []
|
||||
part_number = 1
|
||||
buffer_size = 0 # bytes
|
||||
buffer: list[bytes] = []
|
||||
buffer = bytearray() # bytes buffer to store the data
|
||||
|
||||
stream = await open_stream()
|
||||
async for chunk in stream:
|
||||
buffer_size += len(chunk)
|
||||
buffer.append(chunk)
|
||||
buffer.extend(chunk)
|
||||
|
||||
# upload parts of exactly MULTIPART_MIN_PART_SIZE_BYTES to ensure
|
||||
# all non-trailing parts have the same size (required by S3/R2)
|
||||
while len(buffer) >= MULTIPART_MIN_PART_SIZE_BYTES:
|
||||
part_data = bytes(buffer[:MULTIPART_MIN_PART_SIZE_BYTES])
|
||||
del buffer[:MULTIPART_MIN_PART_SIZE_BYTES]
|
||||
|
||||
# If buffer size meets minimum part size, upload it as a part
|
||||
if buffer_size >= MULTIPART_MIN_PART_SIZE_BYTES:
|
||||
_LOGGER.debug(
|
||||
"Uploading part number %d, size %d", part_number, buffer_size
|
||||
"Uploading part number %d, size %d",
|
||||
part_number,
|
||||
len(part_data),
|
||||
)
|
||||
part = await self._client.upload_part(
|
||||
Bucket=self._bucket,
|
||||
Key=self._with_prefix(tar_filename),
|
||||
PartNumber=part_number,
|
||||
UploadId=upload_id,
|
||||
Body=b"".join(buffer),
|
||||
Body=part_data,
|
||||
)
|
||||
parts.append({"PartNumber": part_number, "ETag": part["ETag"]})
|
||||
part_number += 1
|
||||
buffer_size = 0
|
||||
buffer = []
|
||||
|
||||
# Upload the final buffer as the last part (no minimum size requirement)
|
||||
if buffer:
|
||||
_LOGGER.debug(
|
||||
"Uploading final part number %d, size %d", part_number, buffer_size
|
||||
"Uploading final part number %d, size %d", part_number, len(buffer)
|
||||
)
|
||||
part = await self._client.upload_part(
|
||||
Bucket=self._bucket,
|
||||
Key=self._with_prefix(tar_filename),
|
||||
PartNumber=part_number,
|
||||
UploadId=upload_id,
|
||||
Body=b"".join(buffer),
|
||||
Body=bytes(buffer),
|
||||
)
|
||||
parts.append({"PartNumber": part_number, "ETag": part["ETag"]})
|
||||
|
||||
|
||||
@@ -126,5 +126,5 @@ class ComedHourlyPricingSensor(SensorEntity):
|
||||
|
||||
except (TimeoutError, aiohttp.ClientError) as err:
|
||||
_LOGGER.error("Could not get data from ComEd API: %s", err)
|
||||
except (ValueError, KeyError):
|
||||
except ValueError, KeyError:
|
||||
_LOGGER.warning("Could not update status for %s", self.name)
|
||||
|
||||
@@ -186,7 +186,7 @@ class CompensationSensor(SensorEntity):
|
||||
y_value = self._poly(x_value)
|
||||
self._attr_native_value = round(y_value, self._precision)
|
||||
|
||||
except (ValueError, TypeError):
|
||||
except ValueError, TypeError:
|
||||
self._attr_native_value = None
|
||||
if self._source_attribute:
|
||||
_LOGGER.warning(
|
||||
|
||||
@@ -421,7 +421,7 @@ def config_entries_flow_subscribe(
|
||||
config_entries.SOURCE_USER,
|
||||
)
|
||||
]
|
||||
except (ValueError, TypeError):
|
||||
except ValueError, TypeError:
|
||||
# If we can't serialize, we'll filter out unserializable flows
|
||||
serialized_flows = []
|
||||
for flw in hass.config_entries.flow.async_progress():
|
||||
@@ -434,7 +434,7 @@ def config_entries_flow_subscribe(
|
||||
serialized_flows.append(
|
||||
json_bytes({"type": None, "flow_id": flw["flow_id"], "flow": flw})
|
||||
)
|
||||
except (ValueError, TypeError):
|
||||
except ValueError, TypeError:
|
||||
_LOGGER.error(
|
||||
"Unable to serialize to JSON. Bad data found at %s",
|
||||
format_unserializable_data(
|
||||
|
||||
@@ -38,6 +38,8 @@ CONTROL4_CURRENT_TEMPERATURE = "TEMPERATURE_F"
|
||||
CONTROL4_HUMIDITY = "HUMIDITY"
|
||||
CONTROL4_COOL_SETPOINT = "COOL_SETPOINT_F"
|
||||
CONTROL4_HEAT_SETPOINT = "HEAT_SETPOINT_F"
|
||||
CONTROL4_FAN_MODE = "FAN_MODE"
|
||||
CONTROL4_FAN_MODES_LIST = "FAN_MODES_LIST"
|
||||
|
||||
VARIABLES_OF_INTEREST = {
|
||||
CONTROL4_HVAC_STATE,
|
||||
@@ -46,6 +48,8 @@ VARIABLES_OF_INTEREST = {
|
||||
CONTROL4_HUMIDITY,
|
||||
CONTROL4_COOL_SETPOINT,
|
||||
CONTROL4_HEAT_SETPOINT,
|
||||
CONTROL4_FAN_MODE,
|
||||
CONTROL4_FAN_MODES_LIST,
|
||||
}
|
||||
|
||||
# Map Control4 HVAC modes to Home Assistant
|
||||
@@ -153,12 +157,7 @@ class Control4Climate(Control4Entity, ClimateEntity):
|
||||
|
||||
_attr_has_entity_name = True
|
||||
_attr_temperature_unit = UnitOfTemperature.FAHRENHEIT
|
||||
_attr_supported_features = (
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
| ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
|
||||
| ClimateEntityFeature.TURN_ON
|
||||
| ClimateEntityFeature.TURN_OFF
|
||||
)
|
||||
_attr_translation_key = "thermostat"
|
||||
_attr_hvac_modes = [HVACMode.OFF, HVACMode.HEAT, HVACMode.COOL, HVACMode.HEAT_COOL]
|
||||
|
||||
def __init__(
|
||||
@@ -201,6 +200,19 @@ class Control4Climate(Control4Entity, ClimateEntity):
|
||||
"""Return the thermostat data from the coordinator."""
|
||||
return self.coordinator.data.get(self._idx)
|
||||
|
||||
@property
|
||||
def supported_features(self) -> ClimateEntityFeature:
|
||||
"""Return the list of supported features."""
|
||||
features = (
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
| ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
|
||||
| ClimateEntityFeature.TURN_ON
|
||||
| ClimateEntityFeature.TURN_OFF
|
||||
)
|
||||
if self.fan_modes:
|
||||
features |= ClimateEntityFeature.FAN_MODE
|
||||
return features
|
||||
|
||||
@property
|
||||
def current_temperature(self) -> float | None:
|
||||
"""Return the current temperature."""
|
||||
@@ -275,6 +287,28 @@ class Control4Climate(Control4Entity, ClimateEntity):
|
||||
return data.get(CONTROL4_HEAT_SETPOINT)
|
||||
return None
|
||||
|
||||
@property
|
||||
def fan_mode(self) -> str | None:
|
||||
"""Return the current fan mode."""
|
||||
data = self._thermostat_data
|
||||
if data is None:
|
||||
return None
|
||||
c4_fan_mode = data.get(CONTROL4_FAN_MODE)
|
||||
if c4_fan_mode is None:
|
||||
return None
|
||||
return c4_fan_mode.lower()
|
||||
|
||||
@property
|
||||
def fan_modes(self) -> list[str] | None:
|
||||
"""Return the list of available fan modes."""
|
||||
data = self._thermostat_data
|
||||
if data is None:
|
||||
return None
|
||||
modes = data.get(CONTROL4_FAN_MODES_LIST)
|
||||
if not modes:
|
||||
return None
|
||||
return [m.strip().lower() for m in modes.split(",") if m.strip()]
|
||||
|
||||
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
||||
"""Set new target HVAC mode."""
|
||||
c4_hvac_mode = HA_TO_C4_HVAC_MODE[hvac_mode]
|
||||
@@ -303,3 +337,9 @@ class Control4Climate(Control4Entity, ClimateEntity):
|
||||
await c4_climate.setHeatSetpointF(temp)
|
||||
|
||||
await self.coordinator.async_request_refresh()
|
||||
|
||||
async def async_set_fan_mode(self, fan_mode: str) -> None:
|
||||
"""Set new target fan mode."""
|
||||
c4_climate = self._create_api_object()
|
||||
await c4_climate.setFanMode(fan_mode.title())
|
||||
await self.coordinator.async_request_refresh()
|
||||
|
||||
@@ -74,7 +74,7 @@ class Control4ConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
director_bearer_token = (
|
||||
await account.getDirectorBearerToken(controller_unique_id)
|
||||
)["token"]
|
||||
except (BadCredentials, Unauthorized):
|
||||
except BadCredentials, Unauthorized:
|
||||
errors["base"] = "invalid_auth"
|
||||
return errors, data, description_placeholders
|
||||
except NotFound:
|
||||
@@ -97,7 +97,7 @@ class Control4ConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
except Unauthorized:
|
||||
errors["base"] = "director_auth_failed"
|
||||
return errors, data, description_placeholders
|
||||
except (ClientError, TimeoutError):
|
||||
except ClientError, TimeoutError:
|
||||
errors["base"] = "cannot_connect"
|
||||
description_placeholders["host"] = host
|
||||
return errors, data, description_placeholders
|
||||
|
||||
15
homeassistant/components/control4/icons.json
Normal file
15
homeassistant/components/control4/icons.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"entity": {
|
||||
"climate": {
|
||||
"thermostat": {
|
||||
"state_attributes": {
|
||||
"fan_mode": {
|
||||
"state": {
|
||||
"circulate": "mdi:fan-clock"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,19 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"climate": {
|
||||
"thermostat": {
|
||||
"state_attributes": {
|
||||
"fan_mode": {
|
||||
"state": {
|
||||
"circulate": "Circulate"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"step": {
|
||||
"init": {
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/conversation",
|
||||
"integration_type": "entity",
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["hassil==3.5.0", "home-assistant-intents==2026.2.3"]
|
||||
"requirements": ["hassil==3.5.0", "home-assistant-intents==2026.1.28"]
|
||||
}
|
||||
|
||||
@@ -48,9 +48,11 @@ SENSOR_DESCRIPTIONS: tuple[CookidooSensorEntityDescription, ...] = (
|
||||
key=CookidooSensor.SUBSCRIPTION,
|
||||
translation_key=CookidooSensor.SUBSCRIPTION,
|
||||
value_fn=(
|
||||
lambda data: SUBSCRIPTION_MAP[data.subscription.type]
|
||||
if data.subscription
|
||||
else SUBSCRIPTION_MAP["NONE"]
|
||||
lambda data: (
|
||||
SUBSCRIPTION_MAP[data.subscription.type]
|
||||
if data.subscription
|
||||
else SUBSCRIPTION_MAP["NONE"]
|
||||
)
|
||||
),
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
options=list(SUBSCRIPTION_MAP.values()),
|
||||
@@ -60,9 +62,11 @@ SENSOR_DESCRIPTIONS: tuple[CookidooSensorEntityDescription, ...] = (
|
||||
key=CookidooSensor.EXPIRES,
|
||||
translation_key=CookidooSensor.EXPIRES,
|
||||
value_fn=(
|
||||
lambda data: dt_util.parse_datetime(data.subscription.expires)
|
||||
if data.subscription
|
||||
else None
|
||||
lambda data: (
|
||||
dt_util.parse_datetime(data.subscription.expires)
|
||||
if data.subscription
|
||||
else None
|
||||
)
|
||||
),
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
device_class=SensorDeviceClass.TIMESTAMP,
|
||||
|
||||
@@ -93,7 +93,7 @@ class FlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
password=password,
|
||||
ssl_context=client_context_no_verify(),
|
||||
)
|
||||
except (TimeoutError, ClientError):
|
||||
except TimeoutError, ClientError:
|
||||
self.host = None
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
|
||||
@@ -142,7 +142,7 @@ async def validate_datadog_connection(
|
||||
try:
|
||||
client = DogStatsd(user_input[CONF_HOST], user_input[CONF_PORT])
|
||||
await hass.async_add_executor_job(client.increment, "connection_test")
|
||||
except (OSError, ValueError):
|
||||
except OSError, ValueError:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
@@ -100,7 +100,7 @@ class DeconzFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
async with asyncio.timeout(10):
|
||||
self.bridges = await deconz_discovery(session)
|
||||
|
||||
except (TimeoutError, ResponseError):
|
||||
except TimeoutError, ResponseError:
|
||||
self.bridges = []
|
||||
|
||||
if LOGGER.isEnabledFor(logging.DEBUG):
|
||||
@@ -158,7 +158,7 @@ class DeconzFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
except LinkButtonNotPressed:
|
||||
errors["base"] = "linking_not_possible"
|
||||
|
||||
except (ResponseError, RequestError, TimeoutError):
|
||||
except ResponseError, RequestError, TimeoutError:
|
||||
errors["base"] = "no_key"
|
||||
|
||||
else:
|
||||
|
||||
@@ -87,7 +87,7 @@ class DelugeFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
)
|
||||
try:
|
||||
await self.hass.async_add_executor_job(api.connect)
|
||||
except (ConnectionRefusedError, TimeoutError, SSLError):
|
||||
except ConnectionRefusedError, TimeoutError, SSLError:
|
||||
return "cannot_connect"
|
||||
except Exception as ex:
|
||||
_LOGGER.exception("Unexpected error")
|
||||
|
||||
@@ -46,7 +46,6 @@ async def async_setup_entry(
|
||||
async_add_entities(
|
||||
[
|
||||
DemoLight(
|
||||
available=True,
|
||||
effect_list=LIGHT_EFFECT_LIST,
|
||||
effect=LIGHT_EFFECT_LIST[0],
|
||||
translation_key="bed_light",
|
||||
@@ -55,21 +54,18 @@ async def async_setup_entry(
|
||||
unique_id="light_1",
|
||||
),
|
||||
DemoLight(
|
||||
available=True,
|
||||
ct=LIGHT_TEMPS[1],
|
||||
device_name="Ceiling Lights",
|
||||
state=True,
|
||||
unique_id="light_2",
|
||||
),
|
||||
DemoLight(
|
||||
available=True,
|
||||
hs_color=LIGHT_COLORS[1],
|
||||
device_name="Kitchen Lights",
|
||||
state=True,
|
||||
unique_id="light_3",
|
||||
),
|
||||
DemoLight(
|
||||
available=True,
|
||||
ct=LIGHT_TEMPS[1],
|
||||
device_name="Office RGBW Lights",
|
||||
rgbw_color=(255, 0, 0, 255),
|
||||
@@ -78,7 +74,6 @@ async def async_setup_entry(
|
||||
unique_id="light_4",
|
||||
),
|
||||
DemoLight(
|
||||
available=True,
|
||||
device_name="Living Room RGBWW Lights",
|
||||
rgbww_color=(255, 0, 0, 255, 0),
|
||||
state=True,
|
||||
@@ -86,7 +81,6 @@ async def async_setup_entry(
|
||||
unique_id="light_5",
|
||||
),
|
||||
DemoLight(
|
||||
available=True,
|
||||
device_name="Entrance Color + White Lights",
|
||||
hs_color=LIGHT_COLORS[1],
|
||||
state=True,
|
||||
@@ -112,7 +106,6 @@ class DemoLight(LightEntity):
|
||||
unique_id: str,
|
||||
device_name: str,
|
||||
state: bool,
|
||||
available: bool = False,
|
||||
brightness: int = 180,
|
||||
ct: int | None = None,
|
||||
effect_list: list[str] | None = None,
|
||||
@@ -125,128 +118,72 @@ class DemoLight(LightEntity):
|
||||
) -> None:
|
||||
"""Initialize the light."""
|
||||
self._attr_translation_key = translation_key
|
||||
self._available = True
|
||||
self._brightness = brightness
|
||||
self._ct = ct or random.choice(LIGHT_TEMPS)
|
||||
self._effect = effect
|
||||
self._effect_list = effect_list
|
||||
self._hs_color = hs_color
|
||||
self._rgbw_color = rgbw_color
|
||||
self._rgbww_color = rgbww_color
|
||||
self._state = state
|
||||
self._unique_id = unique_id
|
||||
self._attr_brightness = brightness
|
||||
self._attr_color_temp_kelvin = ct or random.choice(LIGHT_TEMPS)
|
||||
self._attr_effect = effect
|
||||
self._attr_effect_list = effect_list
|
||||
self._attr_hs_color = hs_color
|
||||
self._attr_rgbw_color = rgbw_color
|
||||
self._attr_rgbww_color = rgbww_color
|
||||
self._attr_is_on = state
|
||||
self._attr_unique_id = unique_id
|
||||
if hs_color:
|
||||
self._color_mode = ColorMode.HS
|
||||
self._attr_color_mode = ColorMode.HS
|
||||
elif rgbw_color:
|
||||
self._color_mode = ColorMode.RGBW
|
||||
self._attr_color_mode = ColorMode.RGBW
|
||||
elif rgbww_color:
|
||||
self._color_mode = ColorMode.RGBWW
|
||||
self._attr_color_mode = ColorMode.RGBWW
|
||||
else:
|
||||
self._color_mode = ColorMode.COLOR_TEMP
|
||||
self._attr_color_mode = ColorMode.COLOR_TEMP
|
||||
if not supported_color_modes:
|
||||
supported_color_modes = SUPPORT_DEMO
|
||||
self._color_modes = supported_color_modes
|
||||
if self._effect_list is not None:
|
||||
self._attr_supported_color_modes = supported_color_modes
|
||||
if self._attr_effect_list is not None:
|
||||
self._attr_supported_features |= LightEntityFeature.EFFECT
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={
|
||||
# Serial numbers are unique identifiers within a specific domain
|
||||
(DOMAIN, self.unique_id)
|
||||
(DOMAIN, unique_id)
|
||||
},
|
||||
name=device_name,
|
||||
)
|
||||
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
"""Return unique ID for light."""
|
||||
return self._unique_id
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return availability."""
|
||||
# This demo light is always available, but well-behaving components
|
||||
# should implement this to inform Home Assistant accordingly.
|
||||
return self._available
|
||||
|
||||
@property
|
||||
def brightness(self) -> int:
|
||||
"""Return the brightness of this light between 0..255."""
|
||||
return self._brightness
|
||||
|
||||
@property
|
||||
def color_mode(self) -> ColorMode | None:
|
||||
"""Return the color mode of the light."""
|
||||
return self._color_mode
|
||||
|
||||
@property
|
||||
def hs_color(self) -> tuple[int, int] | None:
|
||||
"""Return the hs color value."""
|
||||
return self._hs_color
|
||||
|
||||
@property
|
||||
def rgbw_color(self) -> tuple[int, int, int, int] | None:
|
||||
"""Return the rgbw color value."""
|
||||
return self._rgbw_color
|
||||
|
||||
@property
|
||||
def rgbww_color(self) -> tuple[int, int, int, int, int] | None:
|
||||
"""Return the rgbww color value."""
|
||||
return self._rgbww_color
|
||||
|
||||
@property
|
||||
def color_temp_kelvin(self) -> int | None:
|
||||
"""Return the color temperature value in Kelvin."""
|
||||
return self._ct
|
||||
|
||||
@property
|
||||
def effect_list(self) -> list[str] | None:
|
||||
"""Return the list of supported effects."""
|
||||
return self._effect_list
|
||||
|
||||
@property
|
||||
def effect(self) -> str | None:
|
||||
"""Return the current effect."""
|
||||
return self._effect
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return true if light is on."""
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def supported_color_modes(self) -> set[ColorMode]:
|
||||
"""Flag supported color modes."""
|
||||
return self._color_modes
|
||||
return True
|
||||
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn the light on."""
|
||||
self._state = True
|
||||
self._attr_is_on = True
|
||||
|
||||
if ATTR_BRIGHTNESS in kwargs:
|
||||
self._brightness = kwargs[ATTR_BRIGHTNESS]
|
||||
self._attr_brightness = kwargs[ATTR_BRIGHTNESS]
|
||||
|
||||
if ATTR_COLOR_TEMP_KELVIN in kwargs:
|
||||
self._color_mode = ColorMode.COLOR_TEMP
|
||||
self._ct = kwargs[ATTR_COLOR_TEMP_KELVIN]
|
||||
self._attr_color_mode = ColorMode.COLOR_TEMP
|
||||
self._attr_color_temp_kelvin = kwargs[ATTR_COLOR_TEMP_KELVIN]
|
||||
|
||||
if ATTR_EFFECT in kwargs:
|
||||
self._effect = kwargs[ATTR_EFFECT]
|
||||
self._attr_effect = kwargs[ATTR_EFFECT]
|
||||
|
||||
if ATTR_HS_COLOR in kwargs:
|
||||
self._color_mode = ColorMode.HS
|
||||
self._hs_color = kwargs[ATTR_HS_COLOR]
|
||||
self._attr_color_mode = ColorMode.HS
|
||||
self._attr_hs_color = kwargs[ATTR_HS_COLOR]
|
||||
|
||||
if ATTR_RGBW_COLOR in kwargs:
|
||||
self._color_mode = ColorMode.RGBW
|
||||
self._rgbw_color = kwargs[ATTR_RGBW_COLOR]
|
||||
self._attr_color_mode = ColorMode.RGBW
|
||||
self._attr_rgbw_color = kwargs[ATTR_RGBW_COLOR]
|
||||
|
||||
if ATTR_RGBWW_COLOR in kwargs:
|
||||
self._color_mode = ColorMode.RGBWW
|
||||
self._rgbww_color = kwargs[ATTR_RGBWW_COLOR]
|
||||
self._attr_color_mode = ColorMode.RGBWW
|
||||
self._attr_rgbww_color = kwargs[ATTR_RGBWW_COLOR]
|
||||
|
||||
if ATTR_WHITE in kwargs:
|
||||
self._color_mode = ColorMode.WHITE
|
||||
self._brightness = kwargs[ATTR_WHITE]
|
||||
self._attr_color_mode = ColorMode.WHITE
|
||||
self._attr_brightness = kwargs[ATTR_WHITE]
|
||||
|
||||
# As we have disabled polling, we need to inform
|
||||
# Home Assistant about updates in our state ourselves.
|
||||
@@ -254,7 +191,7 @@ class DemoLight(LightEntity):
|
||||
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn the light off."""
|
||||
self._state = False
|
||||
self._attr_is_on = False
|
||||
|
||||
# As we have disabled polling, we need to inform
|
||||
# Home Assistant about updates in our state ourselves.
|
||||
|
||||
@@ -199,7 +199,7 @@ class DenonAvrFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
|
||||
try:
|
||||
success = await connect_denonavr.async_connect_receiver()
|
||||
except (AvrNetworkError, AvrTimoutError):
|
||||
except AvrNetworkError, AvrTimoutError:
|
||||
success = False
|
||||
if not success:
|
||||
return self.async_abort(reason="cannot_connect")
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["denonavr"],
|
||||
"requirements": ["denonavr==1.3.1"],
|
||||
"requirements": ["denonavr==1.3.2"],
|
||||
"ssdp": [
|
||||
{
|
||||
"deviceType": "urn:schemas-upnp-org:device:MediaRenderer:1",
|
||||
|
||||
@@ -17,6 +17,7 @@ from denonavr.const import (
|
||||
STATE_ON,
|
||||
STATE_PAUSED,
|
||||
STATE_PLAYING,
|
||||
STATE_STOPPED,
|
||||
)
|
||||
from denonavr.exceptions import (
|
||||
AvrCommandError,
|
||||
@@ -69,6 +70,7 @@ SUPPORT_MEDIA_MODES = (
|
||||
| MediaPlayerEntityFeature.NEXT_TRACK
|
||||
| MediaPlayerEntityFeature.VOLUME_SET
|
||||
| MediaPlayerEntityFeature.PLAY
|
||||
| MediaPlayerEntityFeature.STOP
|
||||
)
|
||||
|
||||
SCAN_INTERVAL = timedelta(seconds=10)
|
||||
@@ -96,6 +98,7 @@ DENON_STATE_MAPPING = {
|
||||
STATE_OFF: MediaPlayerState.OFF,
|
||||
STATE_PLAYING: MediaPlayerState.PLAYING,
|
||||
STATE_PAUSED: MediaPlayerState.PAUSED,
|
||||
STATE_STOPPED: MediaPlayerState.IDLE,
|
||||
}
|
||||
|
||||
|
||||
@@ -404,6 +407,11 @@ class DenonDevice(MediaPlayerEntity):
|
||||
"""Send pause command."""
|
||||
await self._receiver.async_pause()
|
||||
|
||||
@async_log_errors
|
||||
async def async_media_stop(self) -> None:
|
||||
"""Send stop command."""
|
||||
await self._receiver.async_stop()
|
||||
|
||||
@async_log_errors
|
||||
async def async_media_previous_track(self) -> None:
|
||||
"""Send previous track command."""
|
||||
|
||||
@@ -104,7 +104,7 @@ PLATFORM_SCHEMA = SENSOR_PLATFORM_SCHEMA.extend(
|
||||
def _is_decimal_state(state: str) -> bool:
|
||||
try:
|
||||
Decimal(state)
|
||||
except (InvalidOperation, TypeError):
|
||||
except InvalidOperation, TypeError:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
@@ -220,8 +220,8 @@ class DerivativeSensor(RestoreSensor, SensorEntity):
|
||||
if max_sub_interval is None or max_sub_interval.total_seconds() == 0
|
||||
else max_sub_interval
|
||||
)
|
||||
self._cancel_max_sub_interval_exceeded_callback: CALLBACK_TYPE = (
|
||||
lambda *args: None
|
||||
self._cancel_max_sub_interval_exceeded_callback: CALLBACK_TYPE = lambda *args: (
|
||||
None
|
||||
)
|
||||
|
||||
def _derive_and_set_attributes_from_state(self, source_state: State | None) -> None:
|
||||
@@ -306,7 +306,7 @@ class DerivativeSensor(RestoreSensor, SensorEntity):
|
||||
Decimal(restored_data.native_value), # type: ignore[arg-type]
|
||||
self._round_digits,
|
||||
)
|
||||
except (InvalidOperation, TypeError):
|
||||
except InvalidOperation, TypeError:
|
||||
self._attr_native_value = None
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
|
||||
@@ -86,11 +86,7 @@ class DevialetMediaPlayerEntity(
|
||||
self._attr_media_position_updated_at = (
|
||||
self.coordinator.client.position_updated_at
|
||||
)
|
||||
self._attr_media_title = (
|
||||
self.coordinator.client.media_title
|
||||
if self.coordinator.client.media_title
|
||||
else self.source
|
||||
)
|
||||
self._attr_media_title = self.coordinator.client.media_title or self.source
|
||||
self.async_write_ha_state()
|
||||
|
||||
@property
|
||||
|
||||
@@ -311,7 +311,7 @@ async def _async_get_device_automation_capabilities(
|
||||
|
||||
try:
|
||||
capabilities = await getattr(platform, function_name)(hass, automation)
|
||||
except (EntityNotFound, InvalidDeviceAutomationConfig):
|
||||
except EntityNotFound, InvalidDeviceAutomationConfig:
|
||||
return {}
|
||||
|
||||
capabilities = capabilities.copy()
|
||||
|
||||
@@ -895,7 +895,7 @@ class Device(RestoreEntity):
|
||||
try:
|
||||
self.gps = float(gps[0]), float(gps[1])
|
||||
self.gps_accuracy = gps_accuracy or 0
|
||||
except (ValueError, TypeError, IndexError):
|
||||
except ValueError, TypeError, IndexError:
|
||||
self.gps = None
|
||||
self.gps_accuracy = 0
|
||||
LOGGER.warning("Could not parse gps value for %s: %s", self.dev_id, gps)
|
||||
|
||||
@@ -87,7 +87,7 @@ async def _async_try_connect(token: str) -> tuple[str | None, nextcord.AppInfo |
|
||||
info = await discord_bot.application_info()
|
||||
except nextcord.LoginFailure:
|
||||
return "invalid_auth", None
|
||||
except (ClientConnectorError, nextcord.HTTPException, nextcord.NotFound):
|
||||
except ClientConnectorError, nextcord.HTTPException, nextcord.NotFound:
|
||||
return "cannot_connect", None
|
||||
except Exception:
|
||||
_LOGGER.exception("Unexpected exception")
|
||||
|
||||
@@ -72,7 +72,7 @@ class DiscovergyConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
httpx_client=get_async_client(self.hass),
|
||||
authentication=BasicAuth(),
|
||||
).meters()
|
||||
except (discovergyError.HTTPError, discovergyError.DiscovergyClientError):
|
||||
except discovergyError.HTTPError, discovergyError.DiscovergyClientError:
|
||||
errors["base"] = "cannot_connect"
|
||||
except discovergyError.InvalidLogin:
|
||||
errors["base"] = "invalid_auth"
|
||||
|
||||
@@ -260,7 +260,7 @@ class DlnaDmrEntity(MediaPlayerEntity):
|
||||
try:
|
||||
bootid_str = info.ssdp_headers[ssdp.ATTR_SSDP_BOOTID]
|
||||
bootid: int | None = int(bootid_str, 10)
|
||||
except (KeyError, ValueError):
|
||||
except KeyError, ValueError:
|
||||
bootid = None
|
||||
|
||||
if change == ssdp.SsdpChange.UPDATE:
|
||||
|
||||
@@ -234,7 +234,7 @@ class DmsDeviceSource:
|
||||
try:
|
||||
bootid_str = info.ssdp_headers[ssdp.ATTR_SSDP_BOOTID]
|
||||
bootid: int | None = int(bootid_str, 10)
|
||||
except (KeyError, ValueError):
|
||||
except KeyError, ValueError:
|
||||
bootid = None
|
||||
|
||||
if change == ssdp.SsdpChange.UPDATE:
|
||||
@@ -245,7 +245,7 @@ class DmsDeviceSource:
|
||||
try:
|
||||
next_bootid_str = info.ssdp_headers[ssdp.ATTR_SSDP_NEXTBOOTID]
|
||||
self._bootid = int(next_bootid_str, 10)
|
||||
except (KeyError, ValueError):
|
||||
except KeyError, ValueError:
|
||||
pass
|
||||
# Nothing left to do until ssdp:alive comes through
|
||||
return
|
||||
@@ -567,7 +567,7 @@ class DmsDeviceSource:
|
||||
# can_play is False).
|
||||
try:
|
||||
child_count = int(item.child_count)
|
||||
except (AttributeError, TypeError, ValueError):
|
||||
except AttributeError, TypeError, ValueError:
|
||||
child_count = 0
|
||||
can_expand = (
|
||||
bool(children) or child_count > 0 or isinstance(item, didl_lite.Container)
|
||||
|
||||
@@ -36,8 +36,10 @@ BINARY_SENSOR_DESCRIPTIONS = (
|
||||
key="security_locked",
|
||||
translation_key="deadbolt",
|
||||
device_class=BinarySensorDeviceClass.LOCK,
|
||||
is_on=lambda state: state.unlock_status
|
||||
not in (UnlockStatus.SECURITY_LOCKED, UnlockStatus.UNLOCKED_SECURITY_LOCKED),
|
||||
is_on=lambda state: (
|
||||
state.unlock_status
|
||||
not in (UnlockStatus.SECURITY_LOCKED, UnlockStatus.UNLOCKED_SECURITY_LOCKED)
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ class Dremel3DPrinterConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
|
||||
try:
|
||||
api = await self.hass.async_add_executor_job(Dremel3DPrinter, host)
|
||||
except (ConnectTimeout, HTTPError, JSONDecodeError):
|
||||
except ConnectTimeout, HTTPError, JSONDecodeError:
|
||||
errors = {"base": "cannot_connect"}
|
||||
except Exception: # noqa: BLE001
|
||||
LOGGER.exception("An unknown error has occurred")
|
||||
|
||||
@@ -120,7 +120,7 @@ class DSMRConnection:
|
||||
|
||||
try:
|
||||
transport, protocol = await asyncio.create_task(reader_factory())
|
||||
except (serial.SerialException, OSError):
|
||||
except serial.SerialException, OSError:
|
||||
LOGGER.exception("Error connecting to DSMR")
|
||||
return False
|
||||
|
||||
|
||||
@@ -837,7 +837,7 @@ async def async_setup_entry(
|
||||
# throttle reconnect attempts
|
||||
await asyncio.sleep(DEFAULT_RECONNECT_INTERVAL)
|
||||
|
||||
except (serial.SerialException, OSError):
|
||||
except serial.SerialException, OSError:
|
||||
# Log any error while establishing connection and drop to retry
|
||||
# connection wait
|
||||
LOGGER.exception("Error connecting to DSMR")
|
||||
|
||||
@@ -44,7 +44,7 @@ class DukeEnergyConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
auth = await api.authenticate()
|
||||
except ClientResponseError as e:
|
||||
errors["base"] = "invalid_auth" if e.status == 404 else "cannot_connect"
|
||||
except (ClientError, TimeoutError):
|
||||
except ClientError, TimeoutError:
|
||||
errors["base"] = "cannot_connect"
|
||||
except Exception:
|
||||
_LOGGER.exception("Unexpected exception")
|
||||
|
||||
@@ -214,7 +214,7 @@ class DukeEnergyCoordinator(DataUpdateCoordinator[None]):
|
||||
# Make sure we don't go back too far
|
||||
if end_step < start:
|
||||
break
|
||||
except (TimeoutError, ClientError):
|
||||
except TimeoutError, ClientError:
|
||||
# ClientError is raised when there is no more data for the range
|
||||
break
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import date, datetime
|
||||
from enum import Enum
|
||||
from enum import StrEnum
|
||||
from functools import partial
|
||||
from typing import Final
|
||||
|
||||
@@ -47,7 +47,7 @@ SERVICE_SCHEMA: Final = vol.Schema(
|
||||
)
|
||||
|
||||
|
||||
class PriceType(str, Enum):
|
||||
class PriceType(StrEnum):
|
||||
"""Type of price."""
|
||||
|
||||
ENERGY_USAGE = "energy_usage"
|
||||
|
||||
@@ -68,7 +68,7 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
|
||||
try:
|
||||
ebusdpy.init(server_address)
|
||||
except (TimeoutError, OSError):
|
||||
except TimeoutError, OSError:
|
||||
return False
|
||||
hass.data[EBUSD_DATA] = EbusdData(server_address, circuit)
|
||||
sensor_config = {
|
||||
|
||||
@@ -619,9 +619,7 @@ class Thermostat(ClimateEntity):
|
||||
return [
|
||||
{
|
||||
"id": device.id,
|
||||
"name_by_user": device.name_by_user
|
||||
if device.name_by_user
|
||||
else device.name,
|
||||
"name_by_user": device.name_by_user or device.name,
|
||||
}
|
||||
for device in device_registry.devices.values()
|
||||
for sensor_info in sensors_info
|
||||
@@ -924,7 +922,7 @@ class Thermostat(ClimateEntity):
|
||||
sensor_names = self._sensors_in_preset_mode(preset_mode)
|
||||
return sorted(
|
||||
[
|
||||
device.name_by_user if device.name_by_user else device.name
|
||||
device.name_by_user or device.name
|
||||
for device in device_registry.devices.values()
|
||||
for sensor_name in sensor_names
|
||||
if device.name == sensor_name
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user