1
mirror of https://github.com/home-assistant/core synced 2024-07-27 18:58:57 +02:00

Add chart service to LaMetric (#80554)

This commit is contained in:
Franck Nijhof 2022-10-19 03:36:19 +02:00 committed by GitHub
parent ddba653158
commit e3919babb2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 187 additions and 37 deletions

View File

@ -19,10 +19,12 @@ LOGGER = logging.getLogger(__package__)
SCAN_INTERVAL = timedelta(seconds=30) SCAN_INTERVAL = timedelta(seconds=30)
CONF_CYCLES: Final = "cycles" CONF_CYCLES: Final = "cycles"
CONF_DATA: Final = "data"
CONF_ICON_TYPE: Final = "icon_type" CONF_ICON_TYPE: Final = "icon_type"
CONF_LIFETIME: Final = "lifetime" CONF_LIFETIME: Final = "lifetime"
CONF_MESSAGE: Final = "message"
CONF_PRIORITY: Final = "priority" CONF_PRIORITY: Final = "priority"
CONF_SOUND: Final = "sound" CONF_SOUND: Final = "sound"
CONF_MESSAGE: Final = "message"
SERVICE_MESSAGE: Final = "message" SERVICE_MESSAGE: Final = "message"
SERVICE_CHART: Final = "chart"

View File

@ -1,6 +1,11 @@
"""Support for LaMetric time services.""" """Support for LaMetric time services."""
from __future__ import annotations
from collections.abc import Sequence
from demetriek import ( from demetriek import (
AlarmSound, AlarmSound,
Chart,
LaMetricError, LaMetricError,
Model, Model,
Notification, Notification,
@ -19,20 +24,21 @@ from homeassistant.helpers import config_validation as cv
from .const import ( from .const import (
CONF_CYCLES, CONF_CYCLES,
CONF_DATA,
CONF_ICON_TYPE, CONF_ICON_TYPE,
CONF_MESSAGE, CONF_MESSAGE,
CONF_PRIORITY, CONF_PRIORITY,
CONF_SOUND, CONF_SOUND,
DOMAIN, DOMAIN,
SERVICE_CHART,
SERVICE_MESSAGE, SERVICE_MESSAGE,
) )
from .coordinator import LaMetricDataUpdateCoordinator from .coordinator import LaMetricDataUpdateCoordinator
from .helpers import async_get_coordinator_by_device_id 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_DEVICE_ID): cv.string,
vol.Required(CONF_MESSAGE): cv.string,
vol.Optional(CONF_CYCLES, default=1): cv.positive_int, vol.Optional(CONF_CYCLES, default=1): cv.positive_int,
vol.Optional(CONF_ICON_TYPE, default=NotificationIconType.NONE): vol.Coerce( vol.Optional(CONF_ICON_TYPE, default=NotificationIconType.NONE): vol.Coerce(
NotificationIconType NotificationIconType
@ -43,34 +49,73 @@ SERVICE_MESSAGE_SCHEMA = vol.Schema(
vol.Optional(CONF_SOUND): vol.Any( vol.Optional(CONF_SOUND): vol.Any(
vol.Coerce(AlarmSound), vol.Coerce(NotificationSound) 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, 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 @callback
def async_setup_services(hass: HomeAssistant) -> None: def async_setup_services(hass: HomeAssistant) -> None:
"""Set up services for the LaMetric integration.""" """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.""" """Send a message to a LaMetric device."""
coordinator = async_get_coordinator_by_device_id( coordinator = async_get_coordinator_by_device_id(
hass, call.data[CONF_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( hass.services.async_register(
DOMAIN, DOMAIN,
SERVICE_MESSAGE, SERVICE_MESSAGE,
_async_service_text, _async_service_message,
schema=SERVICE_MESSAGE_SCHEMA, schema=SERVICE_MESSAGE_SCHEMA,
) )
async def async_service_text( async def async_send_notification(
coordinator: LaMetricDataUpdateCoordinator, call: ServiceCall coordinator: LaMetricDataUpdateCoordinator,
call: ServiceCall,
frames: Sequence[Chart | Simple],
) -> None: ) -> None:
"""Send a message to an LaMetric device.""" """Send a notification to an LaMetric device."""
sound = None sound = None
if CONF_SOUND in call.data: if CONF_SOUND in call.data:
sound = Sound(id=call.data[CONF_SOUND], category=None) 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]), icon_type=NotificationIconType(call.data[CONF_ICON_TYPE]),
priority=NotificationPriority(call.data.get(CONF_PRIORITY)), priority=NotificationPriority(call.data.get(CONF_PRIORITY)),
model=Model( model=Model(
frames=[ frames=frames,
Simple(
icon=call.data.get(CONF_ICON),
text=call.data[CONF_MESSAGE],
)
],
cycles=call.data[CONF_CYCLES], cycles=call.data[CONF_CYCLES],
sound=sound, sound=sound,
), ),

View File

@ -1,29 +1,22 @@
message: chart:
name: Display a message name: Display a chart
description: Display a message with an optional icon on a LaMetric device. description: Display a chart on a LaMetric device.
fields: fields:
device_id: device_id: &device_id
name: Device name: Device
description: The LaMetric device to display the message on. description: The LaMetric device to display the chart on.
required: true required: true
selector: selector:
device: device:
integration: lametric integration: lametric
message: data:
name: Message name: Data
description: The message to display. description: The list of data points in the chart
required: true required: true
example: "[1,2,3,4,5,4,3,2,1]"
selector: selector:
text: object:
icon: sound: &sound
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:
name: Sound name: Sound
description: The notification sound to play. description: The notification sound to play.
required: false required: false
@ -126,7 +119,7 @@ message:
value: "wind" value: "wind"
- label: "Wind short" - label: "Wind short"
value: "wind_short" value: "wind_short"
cycles: cycles: &cycles
name: Cycles name: Cycles
description: >- description: >-
The number of times to display the message. When set to 0, the message The number of times to display the message. When set to 0, the message
@ -138,7 +131,7 @@ message:
min: 0 min: 0
max: 10 max: 10
mode: slider mode: slider
icon_type: icon_type: &icon_type
name: Icon type name: Icon type
description: >- description: >-
The type of icon to display, indicating the nature of the notification. The type of icon to display, indicating the nature of the notification.
@ -154,7 +147,7 @@ message:
value: "info" value: "info"
- label: "Alert" - label: "Alert"
value: "alert" value: "alert"
priority: priority: &priority
name: Priority name: Priority
description: >- description: >-
The priority of the notification. When the device is running in The priority of the notification. When the device is running in
@ -172,3 +165,27 @@ message:
value: "warning" value: "warning"
- label: "Critical" - label: "Critical"
value: "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

View File

@ -2,6 +2,7 @@
from unittest.mock import MagicMock from unittest.mock import MagicMock
from demetriek import ( from demetriek import (
Chart,
LaMetricError, LaMetricError,
Notification, Notification,
NotificationIconType, NotificationIconType,
@ -14,11 +15,13 @@ import pytest
from homeassistant.components.lametric.const import ( from homeassistant.components.lametric.const import (
CONF_CYCLES, CONF_CYCLES,
CONF_DATA,
CONF_ICON_TYPE, CONF_ICON_TYPE,
CONF_MESSAGE, CONF_MESSAGE,
CONF_PRIORITY, CONF_PRIORITY,
CONF_SOUND, CONF_SOUND,
DOMAIN, DOMAIN,
SERVICE_CHART,
SERVICE_MESSAGE, SERVICE_MESSAGE,
) )
from homeassistant.const import CONF_DEVICE_ID, CONF_ICON 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 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( async def test_service_message(
hass: HomeAssistant, hass: HomeAssistant,
init_integration: MockConfigEntry, init_integration: MockConfigEntry,
mock_lametric: MagicMock, mock_lametric: MagicMock,
) -> None: ) -> None:
"""Test the LaMetric text service.""" """Test the LaMetric message service."""
entity_registry = er.async_get(hass) entity_registry = er.async_get(hass)
entry = entity_registry.async_get("button.frenck_s_lametric_next_app") entry = entity_registry.async_get("button.frenck_s_lametric_next_app")