From e3919babb226d572c768527ec745557b758e2321 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 19 Oct 2022 03:36:19 +0200 Subject: [PATCH] Add chart service to LaMetric (#80554) --- homeassistant/components/lametric/const.py | 4 +- homeassistant/components/lametric/services.py | 68 +++++++++++--- .../components/lametric/services.yaml | 59 +++++++----- tests/components/lametric/test_services.py | 93 ++++++++++++++++++- 4 files changed, 187 insertions(+), 37 deletions(-) diff --git a/homeassistant/components/lametric/const.py b/homeassistant/components/lametric/const.py index 4c6ea64c835d..4f9472b24f46 100644 --- a/homeassistant/components/lametric/const.py +++ b/homeassistant/components/lametric/const.py @@ -19,10 +19,12 @@ LOGGER = logging.getLogger(__package__) SCAN_INTERVAL = timedelta(seconds=30) CONF_CYCLES: Final = "cycles" +CONF_DATA: Final = "data" CONF_ICON_TYPE: Final = "icon_type" CONF_LIFETIME: Final = "lifetime" +CONF_MESSAGE: Final = "message" CONF_PRIORITY: Final = "priority" CONF_SOUND: Final = "sound" -CONF_MESSAGE: Final = "message" SERVICE_MESSAGE: Final = "message" +SERVICE_CHART: Final = "chart" diff --git a/homeassistant/components/lametric/services.py b/homeassistant/components/lametric/services.py index be8dea5d7bff..2cbdfff6fd8f 100644 --- a/homeassistant/components/lametric/services.py +++ b/homeassistant/components/lametric/services.py @@ -1,6 +1,11 @@ """Support for LaMetric time services.""" +from __future__ import annotations + +from collections.abc import Sequence + from demetriek import ( AlarmSound, + Chart, LaMetricError, Model, Notification, @@ -19,20 +24,21 @@ from homeassistant.helpers import config_validation as cv from .const import ( CONF_CYCLES, + CONF_DATA, CONF_ICON_TYPE, CONF_MESSAGE, CONF_PRIORITY, CONF_SOUND, DOMAIN, + SERVICE_CHART, SERVICE_MESSAGE, ) from .coordinator import LaMetricDataUpdateCoordinator from .helpers import async_get_coordinator_by_device_id -SERVICE_MESSAGE_SCHEMA = vol.Schema( +SERVICE_BASE_SCHEMA = vol.Schema( { vol.Required(CONF_DEVICE_ID): cv.string, - vol.Required(CONF_MESSAGE): cv.string, vol.Optional(CONF_CYCLES, default=1): cv.positive_int, vol.Optional(CONF_ICON_TYPE, default=NotificationIconType.NONE): vol.Coerce( NotificationIconType @@ -43,34 +49,73 @@ SERVICE_MESSAGE_SCHEMA = vol.Schema( vol.Optional(CONF_SOUND): vol.Any( vol.Coerce(AlarmSound), vol.Coerce(NotificationSound) ), + } +) + +SERVICE_MESSAGE_SCHEMA = SERVICE_BASE_SCHEMA.extend( + { + vol.Required(CONF_MESSAGE): cv.string, vol.Optional(CONF_ICON): cv.string, } ) +SERVICE_CHART_SCHEMA = SERVICE_BASE_SCHEMA.extend( + { + vol.Required(CONF_DATA): vol.All(cv.ensure_list, [vol.Coerce(int)]), + } +) + @callback def async_setup_services(hass: HomeAssistant) -> None: """Set up services for the LaMetric integration.""" - async def _async_service_text(call: ServiceCall) -> None: + async def _async_service_chart(call: ServiceCall) -> None: + """Send a chart to a LaMetric device.""" + coordinator = async_get_coordinator_by_device_id( + hass, call.data[CONF_DEVICE_ID] + ) + await async_send_notification( + coordinator, call, [Chart(data=call.data[CONF_DATA])] + ) + + async def _async_service_message(call: ServiceCall) -> None: """Send a message to a LaMetric device.""" coordinator = async_get_coordinator_by_device_id( hass, call.data[CONF_DEVICE_ID] ) - await async_service_text(coordinator, call) + await async_send_notification( + coordinator, + call, + [ + Simple( + icon=call.data.get(CONF_ICON), + text=call.data[CONF_MESSAGE], + ) + ], + ) + + hass.services.async_register( + DOMAIN, + SERVICE_CHART, + _async_service_chart, + schema=SERVICE_CHART_SCHEMA, + ) hass.services.async_register( DOMAIN, SERVICE_MESSAGE, - _async_service_text, + _async_service_message, schema=SERVICE_MESSAGE_SCHEMA, ) -async def async_service_text( - coordinator: LaMetricDataUpdateCoordinator, call: ServiceCall +async def async_send_notification( + coordinator: LaMetricDataUpdateCoordinator, + call: ServiceCall, + frames: Sequence[Chart | Simple], ) -> None: - """Send a message to an LaMetric device.""" + """Send a notification to an LaMetric device.""" sound = None if CONF_SOUND in call.data: sound = Sound(id=call.data[CONF_SOUND], category=None) @@ -79,12 +124,7 @@ async def async_service_text( icon_type=NotificationIconType(call.data[CONF_ICON_TYPE]), priority=NotificationPriority(call.data.get(CONF_PRIORITY)), model=Model( - frames=[ - Simple( - icon=call.data.get(CONF_ICON), - text=call.data[CONF_MESSAGE], - ) - ], + frames=frames, cycles=call.data[CONF_CYCLES], sound=sound, ), diff --git a/homeassistant/components/lametric/services.yaml b/homeassistant/components/lametric/services.yaml index 28cf982b4cd5..5e8db5f7da4e 100644 --- a/homeassistant/components/lametric/services.yaml +++ b/homeassistant/components/lametric/services.yaml @@ -1,29 +1,22 @@ -message: - name: Display a message - description: Display a message with an optional icon on a LaMetric device. +chart: + name: Display a chart + description: Display a chart on a LaMetric device. fields: - device_id: + device_id: &device_id name: Device - description: The LaMetric device to display the message on. + description: The LaMetric device to display the chart on. required: true selector: device: integration: lametric - message: - name: Message - description: The message to display. + data: + name: Data + description: The list of data points in the chart required: true + example: "[1,2,3,4,5,4,3,2,1]" selector: - text: - icon: - name: Icon - description: >- - The ID number of the icon or animation to display. List of all icons - and their IDs can be found at: https://developer.lametric.com/icons - required: false - selector: - text: - sound: + object: + sound: &sound name: Sound description: The notification sound to play. required: false @@ -126,7 +119,7 @@ message: value: "wind" - label: "Wind short" value: "wind_short" - cycles: + cycles: &cycles name: Cycles description: >- The number of times to display the message. When set to 0, the message @@ -138,7 +131,7 @@ message: min: 0 max: 10 mode: slider - icon_type: + icon_type: &icon_type name: Icon type description: >- The type of icon to display, indicating the nature of the notification. @@ -154,7 +147,7 @@ message: value: "info" - label: "Alert" value: "alert" - priority: + priority: &priority name: Priority description: >- The priority of the notification. When the device is running in @@ -172,3 +165,27 @@ message: value: "warning" - label: "Critical" value: "critical" + +message: + name: Display a message + description: Display a message with an optional icon on a LaMetric device. + fields: + device_id: *device_id + message: + name: Message + description: The message to display. + required: true + selector: + text: + icon: + name: Icon + description: >- + The ID number of the icon or animation to display. List of all icons + and their IDs can be found at: https://developer.lametric.com/icons + required: false + selector: + text: + sound: *sound + cycles: *cycles + icon_type: *icon_type + priority: *priority diff --git a/tests/components/lametric/test_services.py b/tests/components/lametric/test_services.py index 50427755769a..4792db266f88 100644 --- a/tests/components/lametric/test_services.py +++ b/tests/components/lametric/test_services.py @@ -2,6 +2,7 @@ from unittest.mock import MagicMock from demetriek import ( + Chart, LaMetricError, Notification, NotificationIconType, @@ -14,11 +15,13 @@ import pytest from homeassistant.components.lametric.const import ( CONF_CYCLES, + CONF_DATA, CONF_ICON_TYPE, CONF_MESSAGE, CONF_PRIORITY, CONF_SOUND, DOMAIN, + SERVICE_CHART, SERVICE_MESSAGE, ) from homeassistant.const import CONF_DEVICE_ID, CONF_ICON @@ -29,12 +32,100 @@ from homeassistant.helpers import entity_registry as er from tests.common import MockConfigEntry +async def test_service_chart( + hass: HomeAssistant, + init_integration: MockConfigEntry, + mock_lametric: MagicMock, +) -> None: + """Test the LaMetric chart service.""" + entity_registry = er.async_get(hass) + + entry = entity_registry.async_get("button.frenck_s_lametric_next_app") + assert entry + assert entry.device_id + + await hass.services.async_call( + DOMAIN, + SERVICE_CHART, + { + CONF_DEVICE_ID: entry.device_id, + CONF_DATA: [1, 2, 3, 4, 5, 4, 3, 2, 1], + }, + blocking=True, + ) + + assert len(mock_lametric.notify.mock_calls) == 1 + + notification: Notification = mock_lametric.notify.mock_calls[0][2]["notification"] + assert notification.icon_type is NotificationIconType.NONE + assert notification.life_time is None + assert notification.model.cycles == 1 + assert notification.model.sound is None + assert notification.notification_id is None + assert notification.notification_type is None + assert notification.priority is NotificationPriority.INFO + + assert len(notification.model.frames) == 1 + frame = notification.model.frames[0] + assert type(frame) is Chart + assert frame.data == [1, 2, 3, 4, 5, 4, 3, 2, 1] + + await hass.services.async_call( + DOMAIN, + SERVICE_CHART, + { + CONF_DATA: [1, 2, 3, 4, 5, 4, 3, 2, 1], + CONF_DEVICE_ID: entry.device_id, + CONF_CYCLES: 3, + CONF_ICON_TYPE: "info", + CONF_PRIORITY: "critical", + CONF_SOUND: "cat", + }, + blocking=True, + ) + + assert len(mock_lametric.notify.mock_calls) == 2 + + notification: Notification = mock_lametric.notify.mock_calls[1][2]["notification"] + assert notification.icon_type is NotificationIconType.INFO + assert notification.life_time is None + assert notification.model.cycles == 3 + assert notification.model.sound is not None + assert notification.model.sound.category is NotificationSoundCategory.NOTIFICATIONS + assert notification.model.sound.sound is NotificationSound.CAT + assert notification.model.sound.repeat == 1 + assert notification.notification_id is None + assert notification.notification_type is None + assert notification.priority is NotificationPriority.CRITICAL + + assert len(notification.model.frames) == 1 + frame = notification.model.frames[0] + assert type(frame) is Chart + assert frame.data == [1, 2, 3, 4, 5, 4, 3, 2, 1] + + mock_lametric.notify.side_effect = LaMetricError + with pytest.raises( + HomeAssistantError, match="Could not send LaMetric notification" + ): + await hass.services.async_call( + DOMAIN, + SERVICE_CHART, + { + CONF_DEVICE_ID: entry.device_id, + CONF_DATA: [1, 2, 3, 4, 5], + }, + blocking=True, + ) + + assert len(mock_lametric.notify.mock_calls) == 3 + + async def test_service_message( hass: HomeAssistant, init_integration: MockConfigEntry, mock_lametric: MagicMock, ) -> None: - """Test the LaMetric text service.""" + """Test the LaMetric message service.""" entity_registry = er.async_get(hass) entry = entity_registry.async_get("button.frenck_s_lametric_next_app")