mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 16:57:53 +00:00
Merge branch 'dev' into esphome_bronze
This commit is contained in:
commit
03f6eda8b1
@ -29,6 +29,8 @@ from .entity import (
|
||||
)
|
||||
from .enum_mapper import EsphomeEnumMapper
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
_ESPHOME_ACP_STATE_TO_HASS_STATE: EsphomeEnumMapper[
|
||||
ESPHomeAlarmControlPanelState, AlarmControlPanelState
|
||||
] = EsphomeEnumMapper(
|
||||
|
@ -47,6 +47,8 @@ from .entry_data import ESPHomeConfigEntry, RuntimeEntryData
|
||||
from .enum_mapper import EsphomeEnumMapper
|
||||
from .ffmpeg_proxy import async_create_proxy_url
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
_VOICE_ASSISTANT_EVENT_TYPES: EsphomeEnumMapper[
|
||||
|
@ -20,6 +20,8 @@ from .const import DOMAIN
|
||||
from .entity import EsphomeAssistEntity, EsphomeEntity, platform_async_setup_entry
|
||||
from .entry_data import ESPHomeConfigEntry
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
|
@ -16,6 +16,8 @@ from .entity import (
|
||||
platform_async_setup_entry,
|
||||
)
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
class EsphomeButton(EsphomeEntity[ButtonInfo, EntityState], ButtonEntity):
|
||||
"""A button implementation for ESPHome."""
|
||||
|
@ -16,6 +16,8 @@ from homeassistant.core import callback
|
||||
|
||||
from .entity import EsphomeEntity, platform_async_setup_entry
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
class EsphomeCamera(Camera, EsphomeEntity[CameraInfo, CameraState]):
|
||||
"""A camera implementation for ESPHome."""
|
||||
|
@ -65,6 +65,8 @@ from .entity import (
|
||||
)
|
||||
from .enum_mapper import EsphomeEnumMapper
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
FAN_QUIET = "quiet"
|
||||
|
||||
|
||||
|
@ -24,6 +24,8 @@ from .entity import (
|
||||
platform_async_setup_entry,
|
||||
)
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
class EsphomeCover(EsphomeEntity[CoverInfo, CoverState], CoverEntity):
|
||||
"""A cover implementation for ESPHome."""
|
||||
|
@ -11,6 +11,8 @@ from homeassistant.components.date import DateEntity
|
||||
|
||||
from .entity import EsphomeEntity, esphome_state_property, platform_async_setup_entry
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
class EsphomeDate(EsphomeEntity[DateInfo, DateState], DateEntity):
|
||||
"""A date implementation for esphome."""
|
||||
|
@ -12,6 +12,8 @@ from homeassistant.util import dt as dt_util
|
||||
|
||||
from .entity import EsphomeEntity, esphome_state_property, platform_async_setup_entry
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
class EsphomeDateTime(EsphomeEntity[DateTimeInfo, DateTimeState], DateTimeEntity):
|
||||
"""A datetime implementation for esphome."""
|
||||
|
@ -12,6 +12,8 @@ from homeassistant.util.enum import try_parse_enum
|
||||
|
||||
from .entity import EsphomeEntity, platform_async_setup_entry
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
class EsphomeEvent(EsphomeEntity[EventInfo, Event], EventEntity):
|
||||
"""An event implementation for ESPHome."""
|
||||
|
@ -30,6 +30,8 @@ from .entity import (
|
||||
)
|
||||
from .enum_mapper import EsphomeEnumMapper
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
ORDERED_NAMED_FAN_SPEEDS = [FanSpeed.LOW, FanSpeed.MEDIUM, FanSpeed.HIGH]
|
||||
|
||||
|
||||
|
@ -38,6 +38,8 @@ from .entity import (
|
||||
platform_async_setup_entry,
|
||||
)
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
FLASH_LENGTHS = {FLASH_SHORT: 2, FLASH_LONG: 10}
|
||||
|
||||
|
||||
|
@ -18,6 +18,8 @@ from .entity import (
|
||||
platform_async_setup_entry,
|
||||
)
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
class EsphomeLock(EsphomeEntity[LockInfo, LockEntityState], LockEntity):
|
||||
"""A lock implementation for ESPHome."""
|
||||
|
@ -44,7 +44,7 @@ from homeassistant.core import (
|
||||
State,
|
||||
callback,
|
||||
)
|
||||
from homeassistant.exceptions import TemplateError
|
||||
from homeassistant.exceptions import HomeAssistantError, TemplateError
|
||||
from homeassistant.helpers import (
|
||||
config_validation as cv,
|
||||
device_registry as dr,
|
||||
@ -827,7 +827,18 @@ def execute_service(
|
||||
entry_data: RuntimeEntryData, service: UserService, call: ServiceCall
|
||||
) -> None:
|
||||
"""Execute a service on a node."""
|
||||
entry_data.client.execute_service(service, call.data)
|
||||
try:
|
||||
entry_data.client.execute_service(service, call.data)
|
||||
except APIConnectionError as err:
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="action_call_failed",
|
||||
translation_placeholders={
|
||||
"call_name": service.name,
|
||||
"device_name": entry_data.name,
|
||||
"error": str(err),
|
||||
},
|
||||
) from err
|
||||
|
||||
|
||||
def build_service_name(device_info: EsphomeDeviceInfo, service: UserService) -> str:
|
||||
|
@ -16,7 +16,7 @@
|
||||
"loggers": ["aioesphomeapi", "noiseprotocol", "bleak_esphome"],
|
||||
"mqtt": ["esphome/discover/#"],
|
||||
"requirements": [
|
||||
"aioesphomeapi==29.10.0",
|
||||
"aioesphomeapi==30.0.1",
|
||||
"esphome-dashboard-api==1.2.3",
|
||||
"bleak-esphome==2.13.1"
|
||||
],
|
||||
|
@ -41,6 +41,8 @@ from .entity import (
|
||||
from .enum_mapper import EsphomeEnumMapper
|
||||
from .ffmpeg_proxy import async_create_proxy_url
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
_STATES: EsphomeEnumMapper[EspMediaPlayerState, MediaPlayerState] = EsphomeEnumMapper(
|
||||
|
@ -23,6 +23,8 @@ from .entity import (
|
||||
)
|
||||
from .enum_mapper import EsphomeEnumMapper
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
NUMBER_MODES: EsphomeEnumMapper[EsphomeNumberMode, NumberMode] = EsphomeEnumMapper(
|
||||
{
|
||||
EsphomeNumberMode.AUTO: NumberMode.AUTO,
|
||||
|
@ -25,6 +25,8 @@ from .entity import (
|
||||
)
|
||||
from .entry_data import ESPHomeConfigEntry, RuntimeEntryData
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
|
@ -29,6 +29,8 @@ from homeassistant.util.enum import try_parse_enum
|
||||
from .entity import EsphomeEntity, platform_async_setup_entry
|
||||
from .enum_mapper import EsphomeEnumMapper
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
|
@ -180,5 +180,10 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
"action_call_failed": {
|
||||
"message": "Failed to execute the action call {call_name} on {device_name}: {error}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,8 @@ from .entity import (
|
||||
platform_async_setup_entry,
|
||||
)
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
class EsphomeSwitch(EsphomeEntity[SwitchInfo, SwitchState], SwitchEntity):
|
||||
"""A switch implementation for ESPHome."""
|
||||
|
@ -17,6 +17,8 @@ from .entity import (
|
||||
)
|
||||
from .enum_mapper import EsphomeEnumMapper
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
TEXT_MODES: EsphomeEnumMapper[EsphomeTextMode, TextMode] = EsphomeEnumMapper(
|
||||
{
|
||||
EsphomeTextMode.TEXT: TextMode.TEXT,
|
||||
|
@ -11,6 +11,8 @@ from homeassistant.components.time import TimeEntity
|
||||
|
||||
from .entity import EsphomeEntity, esphome_state_property, platform_async_setup_entry
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
class EsphomeTime(EsphomeEntity[TimeInfo, TimeState], TimeEntity):
|
||||
"""A time implementation for esphome."""
|
||||
|
@ -38,6 +38,8 @@ from .entity import (
|
||||
)
|
||||
from .entry_data import RuntimeEntryData
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
KEY_UPDATE_LOCK = "esphome_update_lock"
|
||||
|
||||
NO_FEATURES = UpdateEntityFeature(0)
|
||||
|
@ -22,6 +22,8 @@ from .entity import (
|
||||
platform_async_setup_entry,
|
||||
)
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
class EsphomeValve(EsphomeEntity[ValveInfo, ValveState], ValveEntity):
|
||||
"""A valve implementation for ESPHome."""
|
||||
|
@ -197,7 +197,12 @@ class OAuth2FlowHandler(
|
||||
"Error reading primary calendar, make sure Google Calendar API is enabled: %s",
|
||||
err,
|
||||
)
|
||||
return self.async_abort(reason="api_disabled")
|
||||
return self.async_abort(
|
||||
reason="calendar_api_disabled",
|
||||
description_placeholders={
|
||||
"calendar_api_url": "https://console.cloud.google.com/apis/library/calendar-json.googleapis.com"
|
||||
},
|
||||
)
|
||||
except ApiException as err:
|
||||
_LOGGER.error("Error reading primary calendar: %s", err)
|
||||
return self.async_abort(reason="cannot_connect")
|
||||
|
@ -28,7 +28,7 @@
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"timeout_connect": "[%key:common::config_flow::error::timeout_connect%]",
|
||||
"code_expired": "Authentication code expired or credential setup is invalid, please try again.",
|
||||
"api_disabled": "You must enable the Google Calendar API in the Google Cloud Console"
|
||||
"calendar_api_disabled": "You must [enable the Google Calendar API]({calendar_api_url}) in the Google Cloud Console"
|
||||
},
|
||||
"create_entry": {
|
||||
"default": "[%key:common::config_flow::create_entry::authenticated%]"
|
||||
|
@ -18,7 +18,7 @@
|
||||
},
|
||||
"data_description": {
|
||||
"host": "The IP address or hostname of your LaMetric TIME on your network.",
|
||||
"api_key": "You can find this API key in [devices page in your LaMetric developer account](https://developer.lametric.com/user/devices)."
|
||||
"api_key": "You can find this API key in the [devices page in your LaMetric developer account](https://developer.lametric.com/user/devices)."
|
||||
}
|
||||
},
|
||||
"cloud_select_device": {
|
||||
@ -83,8 +83,8 @@
|
||||
"brightness_mode": {
|
||||
"name": "Brightness mode",
|
||||
"state": {
|
||||
"auto": "Automatic",
|
||||
"manual": "Manual"
|
||||
"auto": "[%key:common::state::auto%]",
|
||||
"manual": "[%key:common::state::manual%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
2
requirements_all.txt
generated
2
requirements_all.txt
generated
@ -243,7 +243,7 @@ aioelectricitymaps==0.4.0
|
||||
aioemonitor==1.0.5
|
||||
|
||||
# homeassistant.components.esphome
|
||||
aioesphomeapi==29.10.0
|
||||
aioesphomeapi==30.0.1
|
||||
|
||||
# homeassistant.components.flo
|
||||
aioflo==2021.11.0
|
||||
|
2
requirements_test_all.txt
generated
2
requirements_test_all.txt
generated
@ -231,7 +231,7 @@ aioelectricitymaps==0.4.0
|
||||
aioemonitor==1.0.5
|
||||
|
||||
# homeassistant.components.esphome
|
||||
aioesphomeapi==29.10.0
|
||||
aioesphomeapi==30.0.1
|
||||
|
||||
# homeassistant.components.flo
|
||||
aioflo==2021.11.0
|
||||
|
@ -88,6 +88,7 @@ OSI_APPROVED_LICENSES_SPDX = {
|
||||
"MPL-1.1",
|
||||
"MPL-2.0",
|
||||
"PSF-2.0",
|
||||
"Python-2.0",
|
||||
"Unlicense",
|
||||
"Zlib",
|
||||
"ZPL-2.1",
|
||||
|
@ -106,6 +106,7 @@ async def test_restore_dashboard_storage_skipped_if_addon_uninstalled(
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
assert mock_config_entry.state is ConfigEntryState.LOADED
|
||||
await hass.async_block_till_done() # wait for dashboard setup
|
||||
assert "test-slug is no longer installed" in caplog.text
|
||||
assert not mock_dashboard_api.called
|
||||
|
||||
|
@ -42,6 +42,7 @@ from homeassistant.const import (
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, ServiceCall
|
||||
from homeassistant.data_entry_flow import FlowResultType
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import device_registry as dr, issue_registry as ir
|
||||
from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo
|
||||
from homeassistant.setup import async_setup_component
|
||||
@ -1123,6 +1124,65 @@ async def test_esphome_user_services_ignores_invalid_arg_types(
|
||||
assert not hass.services.has_service(DOMAIN, "with_dash_bad_service")
|
||||
|
||||
|
||||
async def test_esphome_user_service_fails(
|
||||
hass: HomeAssistant,
|
||||
mock_client: APIClient,
|
||||
mock_esphome_device: Callable[
|
||||
[APIClient, list[EntityInfo], list[UserService], list[EntityState]],
|
||||
Awaitable[MockESPHomeDevice],
|
||||
],
|
||||
) -> None:
|
||||
"""Test executing a user service fails due to disconnect."""
|
||||
entity_info = []
|
||||
states = []
|
||||
service1 = UserService(
|
||||
name="simple_service",
|
||||
key=2,
|
||||
args=[
|
||||
UserServiceArg(name="arg1", type=UserServiceArgType.BOOL),
|
||||
],
|
||||
)
|
||||
await mock_esphome_device(
|
||||
mock_client=mock_client,
|
||||
entity_info=entity_info,
|
||||
user_service=[service1],
|
||||
device_info={"name": "with-dash"},
|
||||
states=states,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert hass.services.has_service(DOMAIN, "with_dash_simple_service")
|
||||
|
||||
mock_client.execute_service = Mock(side_effect=APIConnectionError("fail"))
|
||||
with pytest.raises(HomeAssistantError) as exc:
|
||||
await hass.services.async_call(
|
||||
DOMAIN, "with_dash_simple_service", {"arg1": True}, blocking=True
|
||||
)
|
||||
assert exc.value.translation_domain == DOMAIN
|
||||
assert exc.value.translation_key == "action_call_failed"
|
||||
assert exc.value.translation_placeholders == {
|
||||
"call_name": "simple_service",
|
||||
"device_name": "with-dash",
|
||||
"error": "fail",
|
||||
}
|
||||
assert (
|
||||
str(exc.value)
|
||||
== "Failed to execute the action call simple_service on with-dash: fail"
|
||||
)
|
||||
|
||||
mock_client.execute_service.assert_has_calls(
|
||||
[
|
||||
call(
|
||||
UserService(
|
||||
name="simple_service",
|
||||
key=2,
|
||||
args=[UserServiceArg(name="arg1", type=UserServiceArgType.BOOL)],
|
||||
),
|
||||
{"arg1": True},
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
async def test_esphome_user_services_changes(
|
||||
hass: HomeAssistant,
|
||||
mock_client: APIClient,
|
||||
|
@ -570,7 +570,7 @@ async def test_reauth_flow(
|
||||
("primary_calendar_error", "primary_calendar_status", "reason"),
|
||||
[
|
||||
(ClientError(), None, "cannot_connect"),
|
||||
(None, HTTPStatus.FORBIDDEN, "api_disabled"),
|
||||
(None, HTTPStatus.FORBIDDEN, "calendar_api_disabled"),
|
||||
(None, HTTPStatus.SERVICE_UNAVAILABLE, "cannot_connect"),
|
||||
],
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user