Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
9e022ad
Quality fixes for Jewish Calendar (#148689)
tsvi Jul 14, 2025
f08d1e5
Fix adding a work area in Husqvarna Automower (#148358)
Thomas55555 Jul 14, 2025
f680e99
Add support for Broadlink A2 air quality sensor (#142203)
kanshurichard Jul 14, 2025
92bb1f2
Do not add utility_meter config entry to source device (#148735)
emontnemery Jul 14, 2025
57f89dd
Do not add trend config entry to source device (#148733)
emontnemery Jul 14, 2025
7df0016
Do not add threshold config entry to source device (#148732)
emontnemery Jul 14, 2025
254f766
Do not add history_stats config entry to source device (#148729)
emontnemery Jul 14, 2025
1a1e9e9
Add test for combining state change and state report listeners (#148721)
emontnemery Jul 14, 2025
e35f7b1
Do not add generic_hygrostat config entry to source device (#148727)
emontnemery Jul 14, 2025
3ae9ea3
Do not add generic_thermostat config entry to source device (#148728)
emontnemery Jul 14, 2025
c27a67d
Do not add integration config entry to source device (#148730)
emontnemery Jul 14, 2025
124931b
TTS to always stream when available (#148695)
balloob Jul 14, 2025
8421ca7
Add assumed optimistic state to template select (#148513)
Petro31 Jul 14, 2025
1753baf
Add method to track entity state changes from target selectors (#148086)
abmantis Jul 14, 2025
c935686
Add add-on discovery flow to pyLoad integration (#148494)
tr4nt0r Jul 14, 2025
0729b3a
Change hass.data storage to runtime.data for Squeezebox (#146482)
peteS-UK Jul 14, 2025
ed4a23d
Override connect method in RecorderPool (#148490)
emontnemery Jul 14, 2025
1ef0754
Fix for ignored devices issue #137114 (#146562)
sbidy Jul 14, 2025
9068a09
Add Stookwijzer forecast service (#138392)
fwestenberg Jul 14, 2025
d42d270
Bump Huum to version 0.8.0 (#148763)
vincentwolsink Jul 14, 2025
c08c402
Add switches for HmIPW-DRI16, HmIPW-DRI32, HmIPW-DRS4, HmIPW-DRS8 (#1…
hahn-th Jul 14, 2025
9e3a78b
Bump pySmartThings to 3.2.8 (#148761)
joostlek Jul 14, 2025
80eb4fb
Replace asyncio.iscoroutinefunction (#148738)
cdce8p Jul 14, 2025
5ec9c4e
Add PS Vita support to PlayStation Network integration (#148186)
tr4nt0r Jul 14, 2025
37ae476
Add Zeroconf support for bsblan integration (#146137)
liudger Jul 14, 2025
6664135
Add Uptime Kuma integration (#146393)
tr4nt0r Jul 14, 2025
f65fa38
Add reconfigure flow for KNX (#145067)
farmio Jul 14, 2025
c476500
Fix Shelly `n_current` sensor removal condition (#148740)
bieniu Jul 14, 2025
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 @@ -535,6 +535,7 @@ homeassistant.components.unifiprotect.*
homeassistant.components.upcloud.*
homeassistant.components.update.*
homeassistant.components.uptime.*
homeassistant.components.uptime_kuma.*
homeassistant.components.uptimerobot.*
homeassistant.components.usb.*
homeassistant.components.uvc.*
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.

1 change: 1 addition & 0 deletions homeassistant/components/broadlink/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
Platform.SELECT: {"HYS"},
Platform.SENSOR: {
"A1",
"A2",
"MP1S",
"RM4MINI",
"RM4PRO",
Expand Down
19 changes: 19 additions & 0 deletions homeassistant/components/broadlink/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
PERCENTAGE,
UnitOfElectricCurrent,
UnitOfElectricPotential,
Expand All @@ -34,6 +35,24 @@
key="air_quality",
device_class=SensorDeviceClass.AQI,
),
SensorEntityDescription(
key="pm10",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
device_class=SensorDeviceClass.PM10,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key="pm2_5",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
device_class=SensorDeviceClass.PM25,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key="pm1",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
device_class=SensorDeviceClass.PM1,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key="humidity",
native_unit_of_measurement=PERCENTAGE,
Expand Down
11 changes: 11 additions & 0 deletions homeassistant/components/broadlink/updater.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ def get_update_manager(device: BroadlinkDevice[_ApiT]) -> BroadlinkUpdateManager
"""Return an update manager for a given Broadlink device."""
update_managers: dict[str, type[BroadlinkUpdateManager]] = {
"A1": BroadlinkA1UpdateManager,
"A2": BroadlinkA2UpdateManager,
"BG1": BroadlinkBG1UpdateManager,
"HYS": BroadlinkThermostatUpdateManager,
"LB1": BroadlinkLB1UpdateManager,
Expand Down Expand Up @@ -118,6 +119,16 @@ async def async_fetch_data(self) -> dict[str, Any]:
return await self.device.async_request(self.device.api.check_sensors_raw)


class BroadlinkA2UpdateManager(BroadlinkUpdateManager[blk.a2]):
"""Manages updates for Broadlink A2 devices."""

SCAN_INTERVAL = timedelta(seconds=10)

async def async_fetch_data(self) -> dict[str, Any]:
"""Fetch data from the device."""
return await self.device.async_request(self.device.api.check_sensors_raw)


class BroadlinkMP1UpdateManager(BroadlinkUpdateManager[blk.mp1]):
"""Manages updates for Broadlink MP1 devices."""

Expand Down
144 changes: 130 additions & 14 deletions homeassistant/components/bsblan/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from homeassistant.core import callback
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.device_registry import format_mac
from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo

from .const import CONF_PASSKEY, DEFAULT_PORT, DOMAIN

Expand All @@ -21,12 +22,15 @@ class BSBLANFlowHandler(ConfigFlow, domain=DOMAIN):

VERSION = 1

host: str
port: int
mac: str
passkey: str | None = None
username: str | None = None
password: str | None = None
def __init__(self) -> None:
"""Initialize BSBLan flow."""
self.host: str | None = None
self.port: int = DEFAULT_PORT
self.mac: str | None = None
self.passkey: str | None = None
self.username: str | None = None
self.password: str | None = None
self._auth_required = True

async def async_step_user(
self, user_input: dict[str, Any] | None = None
Expand All @@ -41,9 +45,111 @@ async def async_step_user(
self.username = user_input.get(CONF_USERNAME)
self.password = user_input.get(CONF_PASSWORD)

return await self._validate_and_create()

async def async_step_zeroconf(
self, discovery_info: ZeroconfServiceInfo
) -> ConfigFlowResult:
"""Handle Zeroconf discovery."""

self.host = str(discovery_info.ip_address)
self.port = discovery_info.port or DEFAULT_PORT

# Get MAC from properties
self.mac = discovery_info.properties.get("mac")

# If MAC was found in zeroconf, use it immediately
if self.mac:
await self.async_set_unique_id(format_mac(self.mac))
self._abort_if_unique_id_configured(
updates={
CONF_HOST: self.host,
CONF_PORT: self.port,
}
)
else:
# MAC not available from zeroconf - check for existing host/port first
self._async_abort_entries_match(
{CONF_HOST: self.host, CONF_PORT: self.port}
)

# Try to get device info without authentication to minimize discovery popup
config = BSBLANConfig(host=self.host, port=self.port)
session = async_get_clientsession(self.hass)
bsblan = BSBLAN(config, session)
try:
device = await bsblan.device()
except BSBLANError:
# Device requires authentication - proceed to discovery confirm
self.mac = None
else:
self.mac = device.MAC

# Got MAC without auth - set unique ID and check for existing device
await self.async_set_unique_id(format_mac(self.mac))
self._abort_if_unique_id_configured(
updates={
CONF_HOST: self.host,
CONF_PORT: self.port,
}
)
# No auth needed, so we can proceed to a confirmation step without fields
self._auth_required = False

# Proceed to get credentials
self.context["title_placeholders"] = {"name": f"BSBLAN {self.host}"}
return await self.async_step_discovery_confirm()

async def async_step_discovery_confirm(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle getting credentials for discovered device."""
if user_input is None:
data_schema = vol.Schema(
{
vol.Optional(CONF_PASSKEY): str,
vol.Optional(CONF_USERNAME): str,
vol.Optional(CONF_PASSWORD): str,
}
)
if not self._auth_required:
data_schema = vol.Schema({})

return self.async_show_form(
step_id="discovery_confirm",
data_schema=data_schema,
description_placeholders={"host": str(self.host)},
)

if not self._auth_required:
return self._async_create_entry()

self.passkey = user_input.get(CONF_PASSKEY)
self.username = user_input.get(CONF_USERNAME)
self.password = user_input.get(CONF_PASSWORD)

return await self._validate_and_create(is_discovery=True)

async def _validate_and_create(
self, is_discovery: bool = False
) -> ConfigFlowResult:
"""Validate device connection and create entry."""
try:
await self._get_bsblan_info()
await self._get_bsblan_info(is_discovery=is_discovery)
except BSBLANError:
if is_discovery:
return self.async_show_form(
step_id="discovery_confirm",
data_schema=vol.Schema(
{
vol.Optional(CONF_PASSKEY): str,
vol.Optional(CONF_USERNAME): str,
vol.Optional(CONF_PASSWORD): str,
}
),
errors={"base": "cannot_connect"},
description_placeholders={"host": str(self.host)},
)
return self._show_setup_form({"base": "cannot_connect"})

return self._async_create_entry()
Expand All @@ -67,6 +173,7 @@ def _show_setup_form(self, errors: dict | None = None) -> ConfigFlowResult:

@callback
def _async_create_entry(self) -> ConfigFlowResult:
"""Create the config entry."""
return self.async_create_entry(
title=format_mac(self.mac),
data={
Expand All @@ -78,8 +185,10 @@ def _async_create_entry(self) -> ConfigFlowResult:
},
)

async def _get_bsblan_info(self, raise_on_progress: bool = True) -> None:
"""Get device information from an BSBLAN device."""
async def _get_bsblan_info(
self, raise_on_progress: bool = True, is_discovery: bool = False
) -> None:
"""Get device information from a BSBLAN device."""
config = BSBLANConfig(
host=self.host,
passkey=self.passkey,
Expand All @@ -90,11 +199,18 @@ async def _get_bsblan_info(self, raise_on_progress: bool = True) -> None:
session = async_get_clientsession(self.hass)
bsblan = BSBLAN(config, session)
device = await bsblan.device()
self.mac = device.MAC

await self.async_set_unique_id(
format_mac(self.mac), raise_on_progress=raise_on_progress
)
retrieved_mac = device.MAC

# Handle unique ID assignment based on whether MAC was available from zeroconf
if not self.mac:
# MAC wasn't available from zeroconf, now we have it from API
self.mac = retrieved_mac
await self.async_set_unique_id(
format_mac(self.mac), raise_on_progress=raise_on_progress
)

# Always allow updating host/port for both user and discovery flows
# This ensures connectivity is maintained when devices change IP addresses
self._abort_if_unique_id_configured(
updates={
CONF_HOST: self.host,
Expand Down
8 changes: 7 additions & 1 deletion homeassistant/components/bsblan/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,11 @@
"integration_type": "device",
"iot_class": "local_polling",
"loggers": ["bsblan"],
"requirements": ["python-bsblan==2.1.0"]
"requirements": ["python-bsblan==2.1.0"],
"zeroconf": [
{
"type": "_http._tcp.local.",
"name": "bsb-lan*"
}
]
}
2 changes: 2 additions & 0 deletions homeassistant/components/bsblan/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
from .coordinator import BSBLanCoordinatorData
from .entity import BSBLanEntity

PARALLEL_UPDATES = 1


@dataclass(frozen=True, kw_only=True)
class BSBLanSensorEntityDescription(SensorEntityDescription):
Expand Down
20 changes: 19 additions & 1 deletion homeassistant/components/bsblan/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,25 @@
"password": "[%key:common::config_flow::data::password%]"
},
"data_description": {
"host": "The hostname or IP address of your BSB-Lan device."
"host": "The hostname or IP address of your BSB-Lan device.",
"port": "The port number of your BSB-Lan device.",
"passkey": "The passkey for your BSB-Lan device.",
"username": "The username for your BSB-Lan device.",
"password": "The password for your BSB-Lan device."
}
},
"discovery_confirm": {
"title": "BSB-Lan device discovered",
"description": "A BSB-Lan device was discovered at {host}. Please provide credentials if required.",
"data": {
"passkey": "[%key:component::bsblan::config::step::user::data::passkey%]",
"username": "[%key:common::config_flow::data::username%]",
"password": "[%key:common::config_flow::data::password%]"
},
"data_description": {
"passkey": "[%key:component::bsblan::config::step::user::data_description::passkey%]",
"username": "[%key:component::bsblan::config::step::user::data_description::username%]",
"password": "[%key:component::bsblan::config::step::user::data_description::password%]"
}
}
},
Expand Down
Loading
Loading