Add mysensors text platform (#84667)

Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
Martin Hjelmare 2022-12-30 00:01:03 +01:00 committed by GitHub
parent c0da80b567
commit 5d4216d648
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 178 additions and 2 deletions

View File

@ -135,6 +135,7 @@ SWITCH_TYPES: dict[SensorType, set[ValueType]] = {
"S_WATER_QUALITY": {"V_STATUS"},
}
TEXT_TYPES: dict[SensorType, set[ValueType]] = {"S_INFO": {"V_TEXT"}}
PLATFORM_TYPES: dict[Platform, dict[SensorType, set[ValueType]]] = {
Platform.BINARY_SENSOR: BINARY_SENSOR_TYPES,
@ -145,6 +146,7 @@ PLATFORM_TYPES: dict[Platform, dict[SensorType, set[ValueType]]] = {
Platform.NOTIFY: NOTIFY_TYPES,
Platform.SENSOR: SENSOR_TYPES,
Platform.SWITCH: SWITCH_TYPES,
Platform.TEXT: TEXT_TYPES,
}
FLAT_PLATFORM_TYPES: dict[tuple[str, SensorType], set[ValueType]] = {

View File

@ -6,10 +6,12 @@ from typing import Any, cast
from homeassistant.components.notify import ATTR_TARGET, BaseNotificationService
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util import slugify
from .. import mysensors
from .const import DevId, DiscoveryInfo
from .const import DOMAIN, DevId, DiscoveryInfo
async def async_get_service(
@ -63,6 +65,7 @@ class MySensorsNotificationService(BaseNotificationService):
] = mysensors.get_mysensors_devices(
hass, Platform.NOTIFY
) # type: ignore[assignment]
self.hass = hass
async def async_send_message(self, message: str = "", **kwargs: Any) -> None:
"""Send a message to a user."""
@ -73,5 +76,25 @@ class MySensorsNotificationService(BaseNotificationService):
if target_devices is None or device.name in target_devices
]
placeholders = {
"alternate_service": "text.set_value",
"deprecated_service": f"notify.{self._service_name}",
"alternate_target": str(
[f"text.{slugify(device.name)}" for device in devices]
),
}
async_create_issue(
self.hass,
DOMAIN,
"deprecated_notify_service",
breaks_in_ha_version="2023.4.0",
is_fixable=True,
is_persistent=True,
severity=IssueSeverity.WARNING,
translation_key="deprecated_service",
translation_placeholders=placeholders,
)
for device in devices:
device.send_msg(message)

View File

@ -83,5 +83,18 @@
"port_out_of_range": "Port number must be at least 1 and at most 65535",
"unknown": "[%key:common::config_flow::error::unknown%]"
}
},
"issues": {
"deprecated_service": {
"title": "The {deprecated_service} service will be removed",
"fix_flow": {
"step": {
"confirm": {
"title": "The {deprecated_service} service will be removed",
"description": "Update any automations or scripts that use this service to instead use the `{alternate_service}` service with a target entity ID of `{alternate_target}`."
}
}
}
}
}
}

View File

@ -0,0 +1,60 @@
"""Provide a text platform for MySensors."""
from __future__ import annotations
from homeassistant.components.text import TextEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .. import mysensors
from .const import MYSENSORS_DISCOVERY, DiscoveryInfo
from .device import MySensorsEntity
from .helpers import on_unload
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up this platform for a specific ConfigEntry(==Gateway)."""
@callback
def async_discover(discovery_info: DiscoveryInfo) -> None:
"""Discover and add a MySensors text entity."""
mysensors.setup_mysensors_platform(
hass,
Platform.TEXT,
discovery_info,
MySensorsText,
async_add_entities=async_add_entities,
)
on_unload(
hass,
config_entry.entry_id,
async_dispatcher_connect(
hass,
MYSENSORS_DISCOVERY.format(config_entry.entry_id, Platform.TEXT),
async_discover,
),
)
class MySensorsText(MySensorsEntity, TextEntity):
"""Representation of the value of a MySensors Text child node."""
_attr_native_max = 25
@property
def native_value(self) -> str | None:
"""Return the value reported by the text."""
return self._values.get(self.value_type)
async def async_set_value(self, value: str) -> None:
"""Change the value."""
self.gateway.set_child_value(
self.node_id, self.child_id, self.value_type, value, ack=1
)

View File

@ -83,5 +83,18 @@
"description": "Choose connection method to the gateway"
}
}
},
"issues": {
"deprecated_service": {
"fix_flow": {
"step": {
"confirm": {
"description": "Update any automations or scripts that use this service to instead use the `{alternate_service}` service with a target entity ID of `{alternate_target}`.",
"title": "The {deprecated_service} service will be removed"
}
}
},
"title": "The {deprecated_service} service will be removed"
}
}
}
}

View File

@ -0,0 +1,65 @@
"""Provide tests for mysensors text platform."""
from __future__ import annotations
from collections.abc import Callable
from unittest.mock import MagicMock, call
from mysensors.sensor import Sensor
import pytest
from homeassistant.components.text import (
ATTR_VALUE,
DOMAIN as TEXT_DOMAIN,
SERVICE_SET_VALUE,
)
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.core import HomeAssistant
async def test_text_node(
hass: HomeAssistant,
text_node: Sensor,
receive_message: Callable[[str], None],
transport_write: MagicMock,
) -> None:
"""Test a text node."""
entity_id = "text.text_node_1_1"
state = hass.states.get(entity_id)
assert state
assert state.state == "test"
await hass.services.async_call(
TEXT_DOMAIN,
SERVICE_SET_VALUE,
{ATTR_ENTITY_ID: entity_id, ATTR_VALUE: "Hello World"},
blocking=True,
)
assert transport_write.call_count == 1
assert transport_write.call_args == call("1;1;1;1;47;Hello World\n")
receive_message("1;1;1;0;47;Hello World\n")
await hass.async_block_till_done()
state = hass.states.get(entity_id)
assert state
assert state.state == "Hello World"
transport_write.reset_mock()
value = "12345678123456781234567812"
with pytest.raises(ValueError) as err:
await hass.services.async_call(
TEXT_DOMAIN,
SERVICE_SET_VALUE,
{ATTR_ENTITY_ID: entity_id, ATTR_VALUE: value},
blocking=True,
)
assert str(err.value) == (
f"Value {value} for Text Node 1 1 is too long (maximum length 25)"
)