1
0
mirror of https://github.com/home-assistant/core.git synced 2026-02-14 23:28:42 +00:00

feat: add info skills to alexa devices (#162097)

This commit is contained in:
jameson_uk
2026-02-11 10:23:07 +00:00
committed by GitHub
parent 3f9e7d1dba
commit 95a1ceb080
7 changed files with 282 additions and 1 deletions

View File

@@ -29,3 +29,24 @@ COUNTRY_DOMAINS = {
CATEGORY_SENSORS = "sensors"
CATEGORY_NOTIFICATIONS = "notifications"
# Map service translation keys to Alexa API
INFO_SKILLS_MAPPING = {
"calendar_today": "Alexa.Calendar.PlayToday",
"calendar_tomorrow": "Alexa.Calendar.PlayTomorrow",
"calendar_next": "Alexa.Calendar.PlayNext",
"date": "Alexa.Date.Play",
"time": "Alexa.Time.Play",
"national_news": "Alexa.News.NationalNews",
"flash_briefing": "Alexa.FlashBriefing.Play",
"traffic": "Alexa.Traffic.Play",
"weather": "Alexa.Weather.Play",
"cleanup": "Alexa.CleanUp.Play",
"good_morning": "Alexa.GoodMorning.Play",
"sing_song": "Alexa.SingASong.Play",
"fun_fact": "Alexa.FunFact.Play",
"tell_joke": "Alexa.Joke.Play",
"tell_story": "Alexa.TellStory.Play",
"im_home": "Alexa.ImHome.Play",
"goodnight": "Alexa.GoodNight.Play",
}

View File

@@ -1,5 +1,8 @@
{
"services": {
"send_info_skill": {
"service": "mdi:information"
},
"send_sound": {
"service": "mdi:cast-audio"
},

View File

@@ -1,5 +1,6 @@
"""Support for services."""
from aioamazondevices.const.metadata import ALEXA_INFO_SKILLS
from aioamazondevices.const.sounds import SOUNDS_LIST
import voluptuous as vol
@@ -9,13 +10,15 @@ from homeassistant.core import HomeAssistant, ServiceCall, callback
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import config_validation as cv, device_registry as dr
from .const import DOMAIN
from .const import DOMAIN, INFO_SKILLS_MAPPING
from .coordinator import AmazonConfigEntry
ATTR_TEXT_COMMAND = "text_command"
ATTR_SOUND = "sound"
ATTR_INFO_SKILL = "info_skill"
SERVICE_TEXT_COMMAND = "send_text_command"
SERVICE_SOUND_NOTIFICATION = "send_sound"
SERVICE_INFO_SKILL = "send_info_skill"
SCHEMA_SOUND_SERVICE = vol.Schema(
{
@@ -29,6 +32,12 @@ SCHEMA_CUSTOM_COMMAND = vol.Schema(
vol.Required(ATTR_DEVICE_ID): cv.string,
}
)
SCHEMA_INFO_SKILL = vol.Schema(
{
vol.Required(ATTR_INFO_SKILL): cv.string,
vol.Required(ATTR_DEVICE_ID): cv.string,
}
)
@callback
@@ -86,6 +95,17 @@ async def _async_execute_action(call: ServiceCall, attribute: str) -> None:
await coordinator.api.call_alexa_text_command(
coordinator.data[device.serial_number], value
)
elif attribute == ATTR_INFO_SKILL:
info_skill = INFO_SKILLS_MAPPING.get(value)
if info_skill not in ALEXA_INFO_SKILLS:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="invalid_info_skill_value",
translation_placeholders={"info_skill": value},
)
await coordinator.api.call_alexa_info_skill(
coordinator.data[device.serial_number], value
)
async def async_send_sound_notification(call: ServiceCall) -> None:
@@ -98,6 +118,11 @@ async def async_send_text_command(call: ServiceCall) -> None:
await _async_execute_action(call, ATTR_TEXT_COMMAND)
async def async_send_info_skill(call: ServiceCall) -> None:
"""Send an info skill command to a AmazonDevice."""
await _async_execute_action(call, ATTR_INFO_SKILL)
@callback
def async_setup_services(hass: HomeAssistant) -> None:
"""Set up the services for the Amazon Devices integration."""
@@ -112,5 +137,10 @@ def async_setup_services(hass: HomeAssistant) -> None:
async_send_text_command,
SCHEMA_CUSTOM_COMMAND,
),
(
SERVICE_INFO_SKILL,
async_send_info_skill,
SCHEMA_INFO_SKILL,
),
):
hass.services.async_register(DOMAIN, service_name, method, schema=schema)

View File

@@ -67,3 +67,36 @@ send_sound:
- squeaky_12
- zap_01
translation_key: sound
send_info_skill:
fields:
device_id:
required: true
selector:
device:
integration: alexa_devices
info_skill:
required: true
example: date
default: date
selector:
select:
options:
- calendar_today
- calendar_tomorrow
- calendar_next
- date
- time
- national_news
- flash_briefing
- traffic
- weather
- cleanup
- good_morning
- sing_song
- fun_fact
- tell_joke
- tell_story
- im_home
- goodnight
translation_key: info_skill

View File

@@ -102,11 +102,35 @@
"invalid_device_id": {
"message": "Invalid device ID specified: {device_id}"
},
"invalid_info_skill_value": {
"message": "Invalid info skill {info_skill} specified"
},
"invalid_sound_value": {
"message": "Invalid sound {sound} specified"
}
},
"selector": {
"info_skill": {
"options": {
"calendar_next": "Calendar: Next event",
"calendar_today": "Calendar: Today's Calendar",
"calendar_tomorrow": "Calendar: Tomorrow's Calendar",
"cleanup": "Encourage me to clean up",
"date": "Date",
"flash_briefing": "Flash Briefing",
"fun_fact": "Tell me a fun fact",
"good_morning": "Good morning",
"goodnight": "Wish me a good night",
"im_home": "Welcome me home",
"national_news": "National News",
"sing_song": "Sing a song",
"tell_joke": "Tell me a joke",
"tell_story": "Tell me a story",
"time": "Time",
"traffic": "Traffic",
"weather": "Weather"
}
},
"sound": {
"options": {
"air_horn_03": "Air horn",
@@ -154,6 +178,20 @@
}
},
"services": {
"send_info_skill": {
"description": "Sends an info skill command to a device",
"fields": {
"device_id": {
"description": "[%key:component::alexa_devices::common::device_id_description%]",
"name": "Device"
},
"info_skill": {
"description": "The info skill command to send.",
"name": "Alexa info skill command"
}
},
"name": "Send info skill command"
},
"send_sound": {
"description": "Sends a sound to a device",
"fields": {

View File

@@ -1,4 +1,71 @@
# serializer version: 1
# name: test_info_skill_service
_Call(
tuple(
dict({
'account_name': 'Echo Test',
'capabilities': list([
'AUDIO_PLAYER',
'MICROPHONE',
]),
'device_cluster_members': list([
'echo_test_serial_number',
]),
'device_family': 'mine',
'device_owner_customer_id': 'amazon_ower_id',
'device_type': 'echo',
'endpoint_id': 'G1234567890123456789012345678A',
'entity_id': '11111111-2222-3333-4444-555555555555',
'household_device': False,
'notifications': dict({
'Alarm': dict({
'label': 'Morning Alarm',
'next_occurrence': datetime.datetime(2023, 10, 1, 7, 0, tzinfo=datetime.timezone.utc),
'status': 'ON',
'type': 'Alarm',
}),
'Reminder': dict({
'label': 'Take out the trash',
'next_occurrence': None,
'status': 'ON',
'type': 'Reminder',
}),
'Timer': dict({
'label': '',
'next_occurrence': None,
'status': 'OFF',
'type': 'Timer',
}),
}),
'notifications_supported': True,
'online': True,
'sensors': dict({
'dnd': dict({
'error': False,
'error_msg': None,
'error_type': None,
'name': 'dnd',
'scale': None,
'value': False,
}),
'temperature': dict({
'error': False,
'error_msg': None,
'error_type': None,
'name': 'temperature',
'scale': 'CELSIUS',
'value': '22.5',
}),
}),
'serial_number': 'echo_test_serial_number',
'software_version': 'echo_test_software_version',
}),
'tell_joke',
),
dict({
}),
)
# ---
# name: test_send_sound_service
_Call(
tuple(

View File

@@ -7,8 +7,10 @@ from syrupy.assertion import SnapshotAssertion
from homeassistant.components.alexa_devices.const import DOMAIN
from homeassistant.components.alexa_devices.services import (
ATTR_INFO_SKILL,
ATTR_SOUND,
ATTR_TEXT_COMMAND,
SERVICE_INFO_SKILL,
SERVICE_SOUND_NOTIFICATION,
SERVICE_TEXT_COMMAND,
)
@@ -35,6 +37,37 @@ async def test_setup_services(
assert (services := hass.services.async_services_for_domain(DOMAIN))
assert SERVICE_TEXT_COMMAND in services
assert SERVICE_SOUND_NOTIFICATION in services
assert SERVICE_INFO_SKILL in services
async def test_info_skill_service(
hass: HomeAssistant,
device_registry: dr.DeviceRegistry,
mock_amazon_devices_client: AsyncMock,
mock_config_entry: MockConfigEntry,
snapshot: SnapshotAssertion,
) -> None:
"""Test info skill service."""
await setup_integration(hass, mock_config_entry)
device_entry = device_registry.async_get_device(
identifiers={(DOMAIN, TEST_DEVICE_1_SN)}
)
assert device_entry
await hass.services.async_call(
DOMAIN,
SERVICE_INFO_SKILL,
{
ATTR_INFO_SKILL: "tell_joke",
ATTR_DEVICE_ID: device_entry.id,
},
blocking=True,
)
assert mock_amazon_devices_client.call_alexa_info_skill.call_count == 1
assert mock_amazon_devices_client.call_alexa_info_skill.call_args == snapshot
async def test_send_sound_service(
@@ -153,6 +186,62 @@ async def test_invalid_parameters(
assert exc_info.value.translation_placeholders == translation_placeholders
@pytest.mark.parametrize(
("info_skill", "device_id", "translation_key", "translation_placeholders"),
[
(
"tell_joke",
"fake_device_id",
"invalid_device_id",
{"device_id": "fake_device_id"},
),
(
"wrong_info_skill_name",
TEST_DEVICE_1_ID,
"invalid_info_skill_value",
{
"info_skill": "wrong_info_skill_name",
},
),
],
)
async def test_invalid_info_skillparameters(
hass: HomeAssistant,
mock_amazon_devices_client: AsyncMock,
mock_config_entry: MockConfigEntry,
info_skill: str,
device_id: str,
translation_key: str,
translation_placeholders: dict[str, str],
) -> None:
"""Test invalid info skill service parameters."""
device_entry = dr.DeviceEntry(
id=TEST_DEVICE_1_ID, identifiers={(DOMAIN, TEST_DEVICE_1_SN)}
)
mock_device_registry(
hass,
{device_entry.id: device_entry},
)
await setup_integration(hass, mock_config_entry)
# Call Service
with pytest.raises(ServiceValidationError) as exc_info:
await hass.services.async_call(
DOMAIN,
SERVICE_INFO_SKILL,
{
ATTR_INFO_SKILL: info_skill,
ATTR_DEVICE_ID: device_id,
},
blocking=True,
)
assert exc_info.value.translation_domain == DOMAIN
assert exc_info.value.translation_key == translation_key
assert exc_info.value.translation_placeholders == translation_placeholders
async def test_config_entry_not_loaded(
hass: HomeAssistant,
device_registry: dr.DeviceRegistry,