Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
39 changes: 37 additions & 2 deletions homeassistant/components/airos/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,18 @@

from airos.airos8 import AirOS8

from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, Platform
from homeassistant.const import (
CONF_HOST,
CONF_PASSWORD,
CONF_SSL,
CONF_USERNAME,
CONF_VERIFY_SSL,
Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession

from .const import DEFAULT_SSL, DEFAULT_VERIFY_SSL, SECTION_ADVANCED_SETTINGS
from .coordinator import AirOSConfigEntry, AirOSDataUpdateCoordinator

_PLATFORMS: list[Platform] = [
Expand All @@ -21,13 +29,16 @@ async def async_setup_entry(hass: HomeAssistant, entry: AirOSConfigEntry) -> boo

# By default airOS 8 comes with self-signed SSL certificates,
# with no option in the web UI to change or upload a custom certificate.
session = async_get_clientsession(hass, verify_ssl=False)
session = async_get_clientsession(
hass, verify_ssl=entry.data[SECTION_ADVANCED_SETTINGS][CONF_VERIFY_SSL]
)

airos_device = AirOS8(
host=entry.data[CONF_HOST],
username=entry.data[CONF_USERNAME],
password=entry.data[CONF_PASSWORD],
session=session,
use_ssl=entry.data[SECTION_ADVANCED_SETTINGS][CONF_SSL],
)

coordinator = AirOSDataUpdateCoordinator(hass, entry, airos_device)
Expand All @@ -40,6 +51,30 @@ async def async_setup_entry(hass: HomeAssistant, entry: AirOSConfigEntry) -> boo
return True


async def async_migrate_entry(hass: HomeAssistant, entry: AirOSConfigEntry) -> bool:
"""Migrate old config entry."""

if entry.version > 1:
# This means the user has downgraded from a future version
return False

if entry.version == 1 and entry.minor_version == 1:
new_data = {**entry.data}
advanced_data = {
CONF_SSL: DEFAULT_SSL,
CONF_VERIFY_SSL: DEFAULT_VERIFY_SSL,
}
new_data[SECTION_ADVANCED_SETTINGS] = advanced_data

hass.config_entries.async_update_entry(
entry,
data=new_data,
minor_version=2,
)

return True


async def async_unload_entry(hass: HomeAssistant, entry: AirOSConfigEntry) -> bool:
"""Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, _PLATFORMS)
27 changes: 24 additions & 3 deletions homeassistant/components/airos/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,17 @@
import voluptuous as vol

from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.const import (
CONF_HOST,
CONF_PASSWORD,
CONF_SSL,
CONF_USERNAME,
CONF_VERIFY_SSL,
)
from homeassistant.data_entry_flow import section
from homeassistant.helpers.aiohttp_client import async_get_clientsession

from .const import DOMAIN
from .const import DEFAULT_SSL, DEFAULT_VERIFY_SSL, DOMAIN, SECTION_ADVANCED_SETTINGS
from .coordinator import AirOS8

_LOGGER = logging.getLogger(__name__)
Expand All @@ -28,6 +35,15 @@
vol.Required(CONF_HOST): str,
vol.Required(CONF_USERNAME, default="ubnt"): str,
vol.Required(CONF_PASSWORD): str,
vol.Required(SECTION_ADVANCED_SETTINGS): section(
vol.Schema(
{
vol.Required(CONF_SSL, default=DEFAULT_SSL): bool,
vol.Required(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): bool,
}
),
{"collapsed": True},
),
}
)

Expand All @@ -36,6 +52,7 @@ class AirOSConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Ubiquiti airOS."""

VERSION = 1
MINOR_VERSION = 2

async def async_step_user(
self,
Expand All @@ -46,13 +63,17 @@ async def async_step_user(
if user_input is not None:
# By default airOS 8 comes with self-signed SSL certificates,
# with no option in the web UI to change or upload a custom certificate.
session = async_get_clientsession(self.hass, verify_ssl=False)
session = async_get_clientsession(
self.hass,
verify_ssl=user_input[SECTION_ADVANCED_SETTINGS][CONF_VERIFY_SSL],
)

airos_device = AirOS8(
host=user_input[CONF_HOST],
username=user_input[CONF_USERNAME],
password=user_input[CONF_PASSWORD],
session=session,
use_ssl=user_input[SECTION_ADVANCED_SETTINGS][CONF_SSL],
)
try:
await airos_device.login()
Expand Down
5 changes: 5 additions & 0 deletions homeassistant/components/airos/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,8 @@
SCAN_INTERVAL = timedelta(minutes=1)

MANUFACTURER = "Ubiquiti"

DEFAULT_VERIFY_SSL = False
DEFAULT_SSL = True

SECTION_ADVANCED_SETTINGS = "advanced_settings"
11 changes: 8 additions & 3 deletions homeassistant/components/airos/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

from __future__ import annotations

from homeassistant.const import CONF_HOST
from homeassistant.const import CONF_HOST, CONF_SSL
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity

from .const import DOMAIN, MANUFACTURER
from .const import DOMAIN, MANUFACTURER, SECTION_ADVANCED_SETTINGS
from .coordinator import AirOSDataUpdateCoordinator


Expand All @@ -20,9 +20,14 @@ def __init__(self, coordinator: AirOSDataUpdateCoordinator) -> None:
super().__init__(coordinator)

airos_data = self.coordinator.data
url_schema = (
"https"
if coordinator.config_entry.data[SECTION_ADVANCED_SETTINGS][CONF_SSL]
else "http"
)

configuration_url: str | None = (
f"https://{coordinator.config_entry.data[CONF_HOST]}"
f"{url_schema}://{coordinator.config_entry.data[CONF_HOST]}"
)

self._attr_device_info = DeviceInfo(
Expand Down
12 changes: 12 additions & 0 deletions homeassistant/components/airos/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,18 @@
"host": "IP address or hostname of the airOS device",
"username": "Administrator username for the airOS device, normally 'ubnt'",
"password": "Password configured through the UISP app or web interface"
},
"sections": {
"advanced_settings": {
"data": {
"ssl": "Use HTTPS",
"verify_ssl": "[%key:common::config_flow::data::verify_ssl%]"
},
"data_description": {
"ssl": "Whether the connection should be encrypted (required for most devices)",
"verify_ssl": "Whether the certificate should be verified when using HTTPS. This should be off for self-signed certificates"
}
}
}
}
},
Expand Down
61 changes: 43 additions & 18 deletions homeassistant/components/assist_pipeline/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -1308,7 +1308,9 @@ async def tts_input_stream_generator() -> AsyncGenerator[str]:
# instead of a full response.
all_targets_in_satellite_area = (
self._get_all_targets_in_satellite_area(
conversation_result.response, self._device_id
conversation_result.response,
self._satellite_id,
self._device_id,
)
)

Expand Down Expand Up @@ -1337,39 +1339,62 @@ async def tts_input_stream_generator() -> AsyncGenerator[str]:
return (speech, all_targets_in_satellite_area)

def _get_all_targets_in_satellite_area(
self, intent_response: intent.IntentResponse, device_id: str | None
self,
intent_response: intent.IntentResponse,
satellite_id: str | None,
device_id: str | None,
) -> bool:
"""Return true if all targeted entities were in the same area as the device."""
if (
(intent_response.response_type != intent.IntentResponseType.ACTION_DONE)
or (not intent_response.matched_states)
or (not device_id)
intent_response.response_type != intent.IntentResponseType.ACTION_DONE
or not intent_response.matched_states
):
return False

entity_registry = er.async_get(self.hass)
device_registry = dr.async_get(self.hass)

if (not (device := device_registry.async_get(device_id))) or (
not device.area_id
area_id: str | None = None

if (
satellite_id is not None
and (target_entity_entry := entity_registry.async_get(satellite_id))
is not None
):
return False
area_id = target_entity_entry.area_id
device_id = target_entity_entry.device_id

if area_id is None:
if device_id is None:
return False

device_entry = device_registry.async_get(device_id)
if device_entry is None:
return False

area_id = device_entry.area_id
if area_id is None:
return False

entity_registry = er.async_get(self.hass)
for state in intent_response.matched_states:
entity = entity_registry.async_get(state.entity_id)
if not entity:
target_entity_entry = entity_registry.async_get(state.entity_id)
if target_entity_entry is None:
return False

if (entity_area_id := entity.area_id) is None:
if (entity.device_id is None) or (
(entity_device := device_registry.async_get(entity.device_id))
is None
):
target_area_id = target_entity_entry.area_id
if target_area_id is None:
if target_entity_entry.device_id is None:
return False

target_device_entry = device_registry.async_get(
target_entity_entry.device_id
)
if target_device_entry is None:
return False

entity_area_id = entity_device.area_id
target_area_id = target_device_entry.area_id

if entity_area_id != device.area_id:
if target_area_id != area_id:
return False

return True
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/frontend/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@
"documentation": "https://www.home-assistant.io/integrations/frontend",
"integration_type": "system",
"quality_scale": "internal",
"requirements": ["home-assistant-frontend==20250925.1"]
"requirements": ["home-assistant-frontend==20250926.0"]
}
2 changes: 1 addition & 1 deletion homeassistant/components/lamarzocco/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: LaMarzoccoConfigEntry) -
)

coordinators = LaMarzoccoRuntimeData(
LaMarzoccoConfigUpdateCoordinator(hass, entry, device),
LaMarzoccoConfigUpdateCoordinator(hass, entry, device, cloud_client),
LaMarzoccoSettingsUpdateCoordinator(hass, entry, device),
LaMarzoccoScheduleUpdateCoordinator(hass, entry, device),
LaMarzoccoStatisticsUpdateCoordinator(hass, entry, device),
Expand Down
12 changes: 10 additions & 2 deletions homeassistant/components/lamarzocco/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import logging
from typing import Any

from pylamarzocco import LaMarzoccoMachine
from pylamarzocco import LaMarzoccoCloudClient, LaMarzoccoMachine
from pylamarzocco.exceptions import AuthFail, RequestNotSuccessful

from homeassistant.config_entries import ConfigEntry
Expand All @@ -19,7 +19,7 @@

from .const import DOMAIN

SCAN_INTERVAL = timedelta(seconds=15)
SCAN_INTERVAL = timedelta(seconds=60)
SETTINGS_UPDATE_INTERVAL = timedelta(hours=8)
SCHEDULE_UPDATE_INTERVAL = timedelta(minutes=30)
STATISTICS_UPDATE_INTERVAL = timedelta(minutes=15)
Expand Down Expand Up @@ -51,6 +51,7 @@ def __init__(
hass: HomeAssistant,
entry: LaMarzoccoConfigEntry,
device: LaMarzoccoMachine,
cloud_client: LaMarzoccoCloudClient | None = None,
) -> None:
"""Initialize coordinator."""
super().__init__(
Expand All @@ -61,6 +62,7 @@ def __init__(
update_interval=self._default_update_interval,
)
self.device = device
self.cloud_client = cloud_client

async def _async_update_data(self) -> None:
"""Do the data update."""
Expand All @@ -85,11 +87,17 @@ async def _internal_async_update_data(self) -> None:
class LaMarzoccoConfigUpdateCoordinator(LaMarzoccoUpdateCoordinator):
"""Class to handle fetching data from the La Marzocco API centrally."""

cloud_client: LaMarzoccoCloudClient

async def _internal_async_update_data(self) -> None:
"""Fetch data from API endpoint."""

# ensure token stays valid; does nothing if token is still valid
await self.cloud_client.async_get_access_token()

if self.device.websocket.connected:
return

await self.device.get_dashboard()
_LOGGER.debug("Current status: %s", self.device.dashboard.to_dict())

Expand Down
3 changes: 2 additions & 1 deletion homeassistant/components/letpot/number.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
NumberEntityDescription,
NumberMode,
)
from homeassistant.const import PRECISION_WHOLE, EntityCategory
from homeassistant.const import PRECISION_WHOLE, EntityCategory, UnitOfTime
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback

Expand Down Expand Up @@ -72,6 +72,7 @@ class LetPotNumberEntityDescription(LetPotEntityDescription, NumberEntityDescrip
LetPotNumberEntityDescription(
key="plant_days",
translation_key="plant_days",
native_unit_of_measurement=UnitOfTime.DAYS,
value_fn=lambda coordinator: coordinator.data.plant_days,
set_value_fn=(
lambda device_client, serial, value: device_client.set_plant_days(
Expand Down
3 changes: 1 addition & 2 deletions homeassistant/components/letpot/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,7 @@
"name": "Light brightness"
},
"plant_days": {
"name": "Plants age",
"unit_of_measurement": "days"
"name": "Plants age"
}
},
"select": {
Expand Down
Loading
Loading