Skip to content

Commit

Permalink
Merge branch 'home-assistant:dev' into lektrico2
Browse files Browse the repository at this point in the history
  • Loading branch information
Lektrico authored Mar 22, 2024
2 parents 0dbe36e + abb2170 commit 7532a4c
Show file tree
Hide file tree
Showing 37 changed files with 1,039 additions and 179 deletions.
2 changes: 1 addition & 1 deletion homeassistant/components/axis/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"iot_class": "local_push",
"loggers": ["axis"],
"quality_scale": "platinum",
"requirements": ["axis==56"],
"requirements": ["axis==57"],
"ssdp": [
{
"manufacturer": "AXIS"
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/enphase_envoy/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"documentation": "https://www.home-assistant.io/integrations/enphase_envoy",
"iot_class": "local_polling",
"loggers": ["pyenphase"],
"requirements": ["pyenphase==1.19.2"],
"requirements": ["pyenphase==1.20.0"],
"zeroconf": [
{
"type": "_enphase-envoy._tcp.local."
Expand Down
17 changes: 15 additions & 2 deletions homeassistant/components/generic/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,14 +183,27 @@ async def async_test_still(
except (
TimeoutError,
RequestError,
HTTPStatusError,
TimeoutException,
) as err:
_LOGGER.error("Error getting camera image from %s: %s", url, type(err).__name__)
return {CONF_STILL_IMAGE_URL: "unable_still_load"}, None
except HTTPStatusError as err:
_LOGGER.error(
"Error getting camera image from %s: %s %s",
url,
type(err).__name__,
err.response.text,
)
if err.response.status_code in [401, 403]:
return {CONF_STILL_IMAGE_URL: "unable_still_load_auth"}, None
if err.response.status_code in [404]:
return {CONF_STILL_IMAGE_URL: "unable_still_load_not_found"}, None
if err.response.status_code in [500, 503]:
return {CONF_STILL_IMAGE_URL: "unable_still_load_server_error"}, None
return {CONF_STILL_IMAGE_URL: "unable_still_load"}, None

if not image:
return {CONF_STILL_IMAGE_URL: "unable_still_load"}, None
return {CONF_STILL_IMAGE_URL: "unable_still_load_no_image"}, None
fmt = get_image_type(image)
_LOGGER.debug(
"Still image at '%s' detected format: %s",
Expand Down
8 changes: 8 additions & 0 deletions homeassistant/components/generic/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
"unknown": "[%key:common::config_flow::error::unknown%]",
"already_exists": "A camera with these URL settings already exists.",
"unable_still_load": "Unable to load valid image from still image URL (e.g. invalid host, URL or authentication failure). Review log for more info.",
"unable_still_load_auth": "Unable to load valid image from still image URL: The camera may require a user name and password, or they are not correct.",
"unable_still_load_not_found": "Unable to load valid image from still image URL: The URL was not found on the server.",
"unable_still_load_server_error": "Unable to load valid image from still image URL: The camera replied with a server error.",
"unable_still_load_no_image": "Unable to load valid image from still image URL: No image was returned.",
"no_still_image_or_stream_url": "You must specify at least a still image or stream URL",
"invalid_still_image": "URL did not return a valid still image",
"malformed_url": "Malformed URL",
Expand Down Expand Up @@ -73,6 +77,10 @@
"unknown": "[%key:common::config_flow::error::unknown%]",
"already_exists": "[%key:component::generic::config::error::already_exists%]",
"unable_still_load": "[%key:component::generic::config::error::unable_still_load%]",
"unable_still_load_auth": "[%key:component::generic::config::error::unable_still_load_auth%]",
"unable_still_load_not_found": "[%key:component::generic::config::error::unable_still_load_not_found%]",
"unable_still_load_server_error": "[%key:component::generic::config::error::unable_still_load_server_error%]",
"unable_still_load_no_image": "[%key:component::generic::config::error::unable_still_load_no_image%]",
"no_still_image_or_stream_url": "[%key:component::generic::config::error::no_still_image_or_stream_url%]",
"invalid_still_image": "[%key:component::generic::config::error::invalid_still_image%]",
"malformed_url": "[%key:component::generic::config::error::malformed_url%]",
Expand Down
24 changes: 19 additions & 5 deletions homeassistant/components/homekit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -550,8 +550,13 @@ def __init__(
self._reset_lock = asyncio.Lock()
self._cancel_reload_dispatcher: CALLBACK_TYPE | None = None

def setup(self, async_zeroconf_instance: AsyncZeroconf, uuid: str) -> None:
"""Set up bridge and accessory driver."""
def setup(self, async_zeroconf_instance: AsyncZeroconf, uuid: str) -> bool:
"""Set up bridge and accessory driver.
Returns True if data was loaded from disk
Returns False if the persistent data was not loaded
"""
assert self.iid_storage is not None
persist_file = get_persist_fullpath_for_entry_id(self.hass, self._entry_id)
self.driver = HomeDriver(
Expand All @@ -573,6 +578,9 @@ def setup(self, async_zeroconf_instance: AsyncZeroconf, uuid: str) -> None:
# as pyhap uses a random one until state is restored
if os.path.exists(persist_file):
self.driver.load()
return True

return False

async def async_reset_accessories(self, entity_ids: Iterable[str]) -> None:
"""Reset the accessory to load the latest configuration."""
Expand Down Expand Up @@ -842,16 +850,22 @@ async def async_start(self, *args: Any) -> None:
# Avoid gather here since it will be I/O bound anyways
await self.aid_storage.async_initialize()
await self.iid_storage.async_initialize()
await self.hass.async_add_executor_job(self.setup, async_zc_instance, uuid)
loaded_from_disk = await self.hass.async_add_executor_job(
self.setup, async_zc_instance, uuid
)
assert self.driver is not None

if not await self._async_create_accessories():
return
self._async_register_bridge()
_LOGGER.debug("Driver start for %s", self._name)
await self.driver.async_start()
async with self.hass.data[PERSIST_LOCK_DATA]:
await self.hass.async_add_executor_job(self.driver.persist)
if not loaded_from_disk:
# If the state was not loaded from disk, it means this is the
# first time the bridge is ever starting up. In this case, we
# need to make sure its persisted to disk.
async with self.hass.data[PERSIST_LOCK_DATA]:
await self.hass.async_add_executor_job(self.driver.persist)
self.status = STATUS_RUNNING

if self.driver.state.paired:
Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/husqvarna_automower/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
Platform.BINARY_SENSOR,
Platform.DEVICE_TRACKER,
Platform.LAWN_MOWER,
Platform.SELECT,
Platform.SENSOR,
Platform.SWITCH,
]
Expand Down
5 changes: 5 additions & 0 deletions homeassistant/components/husqvarna_automower/icons.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
"default": "mdi:debug-step-into"
}
},
"select": {
"headlight_mode": {
"default": "mdi:car-light-high"
}
},
"sensor": {
"number_of_charging_cycles": {
"default": "mdi:battery-sync-outline"
Expand Down
70 changes: 70 additions & 0 deletions homeassistant/components/husqvarna_automower/select.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
"""Creates a select entity for the headlight of the mower."""

import logging

from aioautomower.exceptions import ApiException
from aioautomower.model import HeadlightModes

from homeassistant.components.select import SelectEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from .const import DOMAIN
from .coordinator import AutomowerDataUpdateCoordinator
from .entity import AutomowerControlEntity

_LOGGER = logging.getLogger(__name__)


HEADLIGHT_MODES: list = [
HeadlightModes.ALWAYS_OFF.lower(),
HeadlightModes.ALWAYS_ON.lower(),
HeadlightModes.EVENING_AND_NIGHT.lower(),
HeadlightModes.EVENING_ONLY.lower(),
]


async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up select platform."""
coordinator: AutomowerDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
async_add_entities(
AutomowerSelectEntity(mower_id, coordinator)
for mower_id in coordinator.data
if coordinator.data[mower_id].capabilities.headlights
)


class AutomowerSelectEntity(AutomowerControlEntity, SelectEntity):
"""Defining the headlight mode entity."""

_attr_options = HEADLIGHT_MODES
_attr_entity_category = EntityCategory.CONFIG
_attr_translation_key = "headlight_mode"

def __init__(
self,
mower_id: str,
coordinator: AutomowerDataUpdateCoordinator,
) -> None:
"""Set up select platform."""
super().__init__(mower_id, coordinator)
self._attr_unique_id = f"{mower_id}_headlight_mode"

@property
def current_option(self) -> str:
"""Return the current option for the entity."""
return self.mower_attributes.headlight.mode.lower()

async def async_select_option(self, option: str) -> None:
"""Change the selected option."""
try:
await self.coordinator.api.set_headlight_mode(self.mower_id, option.upper())
except ApiException as exception:
raise HomeAssistantError(
f"Command couldn't be sent to the command queue: {exception}"
) from exception
17 changes: 14 additions & 3 deletions homeassistant/components/husqvarna_automower/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,15 @@
"name": "Returning to dock"
}
},
"switch": {
"enable_schedule": {
"name": "Enable schedule"
"select": {
"headlight_mode": {
"name": "Headlight mode",
"state": {
"always_on": "Always on",
"always_off": "Always off",
"evening_only": "Evening only",
"evening_and_night": "Evening and night"
}
}
},
"sensor": {
Expand Down Expand Up @@ -79,6 +85,11 @@
"demo": "Demo"
}
}
},
"switch": {
"enable_schedule": {
"name": "Enable schedule"
}
}
}
}
11 changes: 10 additions & 1 deletion homeassistant/components/mobile_app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,16 @@
from homeassistant.helpers.storage import Store
from homeassistant.helpers.typing import ConfigType

from . import websocket_api
# Pre-import the platforms so they get loaded when the integration
# is imported as they are almost always going to be loaded and its
# cheaper to import them all at once.
from . import ( # noqa: F401
binary_sensor as binary_sensor_pre_import,
device_tracker as device_tracker_pre_import,
notify as notify_pre_import,
sensor as sensor_pre_import,
websocket_api,
)
from .const import (
ATTR_DEVICE_NAME,
ATTR_MANUFACTURER,
Expand Down
62 changes: 53 additions & 9 deletions homeassistant/components/panel_iframe/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@

import voluptuous as vol

from homeassistant.components import frontend
from homeassistant.components import lovelace
from homeassistant.components.lovelace import dashboard
from homeassistant.const import CONF_ICON, CONF_URL
from homeassistant.core import HomeAssistant
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
from homeassistant.helpers.storage import Store
from homeassistant.helpers.typing import ConfigType

DOMAIN = "panel_iframe"
Expand Down Expand Up @@ -37,18 +40,59 @@
extra=vol.ALLOW_EXTRA,
)

STORAGE_KEY = DOMAIN
STORAGE_VERSION_MAJOR = 1


async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the iFrame frontend panels."""
async_create_issue(
hass,
DOMAIN,
"deprecated_yaml",
breaks_in_ha_version="2024.10.0",
is_fixable=False,
is_persistent=False,
issue_domain=DOMAIN,
severity=IssueSeverity.WARNING,
translation_key="deprecated_yaml",
translation_placeholders={
"domain": DOMAIN,
"integration_title": "iframe Panel",
},
)

store: Store[dict[str, bool]] = Store(
hass,
STORAGE_VERSION_MAJOR,
STORAGE_KEY,
)
data = await store.async_load()
if data:
return True

dashboards_collection: dashboard.DashboardsCollection = hass.data[lovelace.DOMAIN][
"dashboards_collection"
]

for url_path, info in config[DOMAIN].items():
frontend.async_register_built_in_panel(
hass,
"iframe",
info.get(CONF_TITLE),
info.get(CONF_ICON),
url_path,
{"url": info[CONF_URL]},
require_admin=info[CONF_REQUIRE_ADMIN],
dashboard_create_data = {
lovelace.CONF_ALLOW_SINGLE_WORD: True,
lovelace.CONF_URL_PATH: url_path,
}
for key in (CONF_ICON, CONF_REQUIRE_ADMIN, CONF_TITLE):
if key in info:
dashboard_create_data[key] = info[key]

await dashboards_collection.async_create_item(dashboard_create_data)

dashboard_store: dashboard.LovelaceStorage = hass.data[lovelace.DOMAIN][
"dashboards"
][url_path]
await dashboard_store.async_save(
{"strategy": {"type": "iframe", "url": info[CONF_URL]}}
)

await store.async_save({"migrated": True})

return True
2 changes: 1 addition & 1 deletion homeassistant/components/panel_iframe/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"domain": "panel_iframe",
"name": "iframe Panel",
"codeowners": ["@home-assistant/frontend"],
"dependencies": ["frontend"],
"dependencies": ["frontend", "lovelace"],
"documentation": "https://www.home-assistant.io/integrations/panel_iframe",
"quality_scale": "internal"
}
8 changes: 8 additions & 0 deletions homeassistant/components/panel_iframe/strings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"issues": {
"deprecated_yaml": {
"title": "The {integration_title} YAML configuration is being removed",
"description": "Configuring {integration_title} using YAML is being removed.\n\nYour existing YAML configuration has been imported into the UI automatically as a regular dashboard.\n\nRemove the `{domain}` configuration from your configuration.yaml file and restart Home Assistant to fix this issue."
}
}
}
2 changes: 2 additions & 0 deletions homeassistant/components/shelly/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
get_block_device_sleep_period,
get_coap_context,
get_device_entry_gen,
get_http_port,
get_rpc_device_wakeup_period,
get_ws_context,
)
Expand Down Expand Up @@ -249,6 +250,7 @@ async def _async_setup_rpc_entry(hass: HomeAssistant, entry: ConfigEntry) -> boo
entry.data.get(CONF_USERNAME),
entry.data.get(CONF_PASSWORD),
device_mac=entry.unique_id,
port=get_http_port(entry.data),
)

ws_context = await get_ws_context(hass)
Expand Down
Loading

0 comments on commit 7532a4c

Please sign in to comment.