Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
8e2c2c3
Add Bitvis Power Hub integration (#165457)
MandusBorjesson Sep 10, 2026
fae77cc
Bump aioesphomeapi to 46.4.0 (#181856)
bdraco Sep 10, 2026
ddcdfbf
Add support for ESPHome device-initiated connections (#180961)
bdraco Sep 10, 2026
825f0f4
Bump reolink_aio to 0.21.16 (#181861)
starkillerOG Sep 10, 2026
2baa552
Bump PyViCare to 2.62.1 (#181830)
lackas Sep 10, 2026
bd98f69
Use snapshot_platform helper in avea light tests (#181822)
epenet Sep 10, 2026
7e94b9a
Use snapshot_platform helper in adax climate tests (#181820)
epenet Sep 10, 2026
432ebad
Stream usage prediction events instead of loading them into memory (#…
balloob Sep 10, 2026
b3c2b5d
Add property is_composite_device to DeviceEntry base class (#181780)
emontnemery Sep 10, 2026
5ffb0d1
Wolflink: Add quality scale (#174502)
EnjoyingM Sep 10, 2026
a9337d2
Add coroutines to Zonneplan's coordinator (#181106)
erwindouna Sep 10, 2026
fc78d26
Add outdoor temperature to MELCloud Home (#181216)
erwindouna Sep 10, 2026
3ea011d
Improve error handling in Airly integration (#181718)
bieniu Sep 10, 2026
ed25ae3
Add Midea Water Heater platform (#180693)
rokam Sep 10, 2026
b934734
Mark Hikvision binary sensors unavailable when the event stream drops…
ptarjan Sep 10, 2026
2a2babc
Add Hikvision event platform for smart event detection targets (#180841)
ptarjan Sep 10, 2026
ea1a91a
Move service registration to async_setup in modbus (#181845)
epenet Sep 10, 2026
7b2dc02
Log device availability transitions in Midea (#181785)
chemelli74 Sep 10, 2026
8c0c04c
Add action exceptions to Midea (#181778)
chemelli74 Sep 10, 2026
e59059d
Bump pyimouapi to 1.4.0 (#181707)
Imou-OpenPlatform Sep 10, 2026
9485e2c
Add charging mode select to Silla Prism (#181424)
ebaschiera Sep 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .strict-typing
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ homeassistant.components.bayesian.*
homeassistant.components.besen.*
homeassistant.components.binary_sensor.*
homeassistant.components.bitcoin.*
homeassistant.components.bitvis.*
homeassistant.components.blockchain.*
homeassistant.components.blue_current.*
homeassistant.components.blueprint.*
Expand Down
2 changes: 2 additions & 0 deletions CODEOWNERS

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 19 additions & 4 deletions homeassistant/components/airly/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

from asyncio import timeout
from http import HTTPStatus
import logging
from typing import Any, override

from aiohttp import ClientSession
from aiohttp import ClientConnectorError, ClientSession
from airly import Airly
from airly.exceptions import AirlyError
import voluptuous as vol
Expand All @@ -14,11 +15,18 @@
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession

from .const import CONF_USE_NEAREST, DEFAULT_NAME, DOMAIN, NO_AIRLY_SENSORS
from .const import (
CONF_USE_NEAREST,
DEFAULT_NAME,
DEFAULT_TIMEOUT,
DOMAIN,
NO_AIRLY_SENSORS,
)

DESCRIPTION_PLACEHOLDERS = {
"developer_registration_url": "https://developer.airly.eu/register",
}
_LOGGER = logging.getLogger(__name__)


class AirlyFlowHandler(ConfigFlow, domain=DOMAIN):
Expand Down Expand Up @@ -59,8 +67,15 @@ async def async_step_user(
except AirlyError as err:
if err.status_code == HTTPStatus.UNAUTHORIZED:
errors["base"] = "invalid_api_key"
if err.status_code == HTTPStatus.NOT_FOUND:
elif err.status_code == HTTPStatus.NOT_FOUND:
errors["base"] = "wrong_location"
else:
errors["base"] = "unknown"
except ClientConnectorError, TimeoutError:
errors["base"] = "cannot_connect"
except Exception:
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
else:
if not location_point_valid:
if not location_nearest_valid:
Expand Down Expand Up @@ -106,7 +121,7 @@ async def check_location(
measurements = airly.create_measurements_session_point(
latitude=latitude, longitude=longitude
)
async with timeout(10):
async with timeout(DEFAULT_TIMEOUT):
await measurements.update()

current = measurements.current
Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/airly/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@
URL = "https://airly.org/map/#{latitude},{longitude}"

DEFAULT_NAME: Final = "Airly"
DEFAULT_TIMEOUT = 10
23 changes: 12 additions & 11 deletions homeassistant/components/airly/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
ATTR_API_CAQI,
ATTR_API_CAQI_DESCRIPTION,
ATTR_API_CAQI_LEVEL,
DEFAULT_TIMEOUT,
DOMAIN,
MAX_UPDATE_INTERVAL,
MIN_UPDATE_INTERVAL,
Expand Down Expand Up @@ -103,18 +104,18 @@ async def _async_update_data(self) -> dict[str, str | float | int]:
measurements = self.airly.create_measurements_session_point(
self.latitude, self.longitude
)
async with timeout(20):
try:
try:
async with timeout(DEFAULT_TIMEOUT):
await measurements.update()
except (AirlyError, ClientConnectorError) as error:
raise UpdateFailed(
translation_domain=DOMAIN,
translation_key="update_error",
translation_placeholders={
"entry": self.config_entry.title,
"error": repr(error),
},
) from error
except (AirlyError, ClientConnectorError, TimeoutError) as error:
raise UpdateFailed(
translation_domain=DOMAIN,
translation_key="update_error",
translation_placeholders={
"entry": self.config_entry.title,
"error": repr(error),
},
) from error

_LOGGER.debug(
"Requests remaining: %s/%s",
Expand Down
2 changes: 2 additions & 0 deletions homeassistant/components/airly/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
"wrong_location": "[%key:component::airly::config::error::wrong_location%]"
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_api_key": "[%key:common::config_flow::error::invalid_api_key%]",
"unknown": "[%key:common::config_flow::error::unknown%]",
"wrong_location": "No Airly measuring stations in this area."
},
"step": {
Expand Down
56 changes: 56 additions & 0 deletions homeassistant/components/bitvis/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"""The Bitvis Power Hub integration."""

from typing import TYPE_CHECKING

from homeassistant.const import CONF_PORT, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.typing import ConfigType

from .const import DATA_LISTENER_REGISTRY, DOMAIN
from .coordinator import (
BitvisConfigEntry,
BitvisDataUpdateCoordinator,
async_get_listener_registry,
)

_PLATFORMS: list[Platform] = [Platform.SENSOR]

CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)


async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the Bitvis Power Hub integration."""
async_get_listener_registry(hass)
return True


async def async_setup_entry(hass: HomeAssistant, entry: BitvisConfigEntry) -> bool:
"""Set up Bitvis Power Hub from a config entry."""
async_get_listener_registry(hass)
if TYPE_CHECKING:
assert entry.unique_id is not None
coordinator = BitvisDataUpdateCoordinator(
hass,
entry,
entry.data[CONF_PORT],
entry.unique_id,
)

await coordinator.async_config_entry_first_refresh()

entry.runtime_data = coordinator

await hass.config_entries.async_forward_entry_setups(entry, _PLATFORMS)

return True


async def async_unload_entry(hass: HomeAssistant, entry: BitvisConfigEntry) -> bool:
"""Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, _PLATFORMS)
if unload_ok:
await entry.runtime_data.async_stop()
if not hass.config_entries.async_loaded_entries(DOMAIN):
hass.data.pop(DATA_LISTENER_REGISTRY, None)
return unload_ok
Loading
Loading