mirror of
https://github.com/home-assistant/core
synced 2024-10-05 17:38:03 +00:00
Refactor Rest Sensor with ManualTriggerEntity (#97396)
* ManualTriggerEntity for rest sensor * add availability test * review comments * last fixes
This commit is contained in:
parent
82ade574d8
commit
aacb8aecfc
|
@ -76,6 +76,7 @@ SENSOR_SCHEMA = {
|
||||||
vol.Optional(CONF_JSON_ATTRS_PATH): cv.string,
|
vol.Optional(CONF_JSON_ATTRS_PATH): cv.string,
|
||||||
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
|
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
|
||||||
vol.Optional(CONF_FORCE_UPDATE, default=DEFAULT_FORCE_UPDATE): cv.boolean,
|
vol.Optional(CONF_FORCE_UPDATE, default=DEFAULT_FORCE_UPDATE): cv.boolean,
|
||||||
|
vol.Optional(CONF_AVAILABILITY): cv.template,
|
||||||
}
|
}
|
||||||
|
|
||||||
BINARY_SENSOR_SCHEMA = {
|
BINARY_SENSOR_SCHEMA = {
|
||||||
|
|
|
@ -3,28 +3,40 @@ from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import ssl
|
import ssl
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from jsonpath import jsonpath
|
from jsonpath import jsonpath
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.sensor import (
|
from homeassistant.components.sensor import (
|
||||||
|
CONF_STATE_CLASS,
|
||||||
DOMAIN as SENSOR_DOMAIN,
|
DOMAIN as SENSOR_DOMAIN,
|
||||||
PLATFORM_SCHEMA,
|
PLATFORM_SCHEMA,
|
||||||
SensorDeviceClass,
|
SensorDeviceClass,
|
||||||
|
SensorEntity,
|
||||||
)
|
)
|
||||||
from homeassistant.components.sensor.helpers import async_parse_date_datetime
|
from homeassistant.components.sensor.helpers import async_parse_date_datetime
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
|
CONF_DEVICE_CLASS,
|
||||||
CONF_FORCE_UPDATE,
|
CONF_FORCE_UPDATE,
|
||||||
|
CONF_ICON,
|
||||||
|
CONF_NAME,
|
||||||
CONF_RESOURCE,
|
CONF_RESOURCE,
|
||||||
CONF_RESOURCE_TEMPLATE,
|
CONF_RESOURCE_TEMPLATE,
|
||||||
CONF_UNIQUE_ID,
|
CONF_UNIQUE_ID,
|
||||||
|
CONF_UNIT_OF_MEASUREMENT,
|
||||||
CONF_VALUE_TEMPLATE,
|
CONF_VALUE_TEMPLATE,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import PlatformNotReady
|
from homeassistant.exceptions import PlatformNotReady
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.template_entity import TemplateSensor
|
from homeassistant.helpers.template import Template
|
||||||
|
from homeassistant.helpers.template_entity import (
|
||||||
|
CONF_AVAILABILITY,
|
||||||
|
CONF_PICTURE,
|
||||||
|
ManualTriggerSensorEntity,
|
||||||
|
)
|
||||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||||
from homeassistant.util.json import json_loads
|
from homeassistant.util.json import json_loads
|
||||||
|
@ -43,6 +55,16 @@ PLATFORM_SCHEMA = vol.All(
|
||||||
cv.has_at_least_one_key(CONF_RESOURCE, CONF_RESOURCE_TEMPLATE), PLATFORM_SCHEMA
|
cv.has_at_least_one_key(CONF_RESOURCE, CONF_RESOURCE_TEMPLATE), PLATFORM_SCHEMA
|
||||||
)
|
)
|
||||||
|
|
||||||
|
TRIGGER_ENTITY_OPTIONS = (
|
||||||
|
CONF_AVAILABILITY,
|
||||||
|
CONF_DEVICE_CLASS,
|
||||||
|
CONF_ICON,
|
||||||
|
CONF_PICTURE,
|
||||||
|
CONF_UNIQUE_ID,
|
||||||
|
CONF_STATE_CLASS,
|
||||||
|
CONF_UNIT_OF_MEASUREMENT,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_platform(
|
async def async_setup_platform(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
|
@ -75,7 +97,14 @@ async def async_setup_platform(
|
||||||
raise PlatformNotReady from rest.last_exception
|
raise PlatformNotReady from rest.last_exception
|
||||||
raise PlatformNotReady
|
raise PlatformNotReady
|
||||||
|
|
||||||
unique_id: str | None = conf.get(CONF_UNIQUE_ID)
|
name = conf.get(CONF_NAME) or Template(DEFAULT_SENSOR_NAME, hass)
|
||||||
|
|
||||||
|
trigger_entity_config = {CONF_NAME: name}
|
||||||
|
|
||||||
|
for key in TRIGGER_ENTITY_OPTIONS:
|
||||||
|
if key not in conf:
|
||||||
|
continue
|
||||||
|
trigger_entity_config[key] = conf[key]
|
||||||
|
|
||||||
async_add_entities(
|
async_add_entities(
|
||||||
[
|
[
|
||||||
|
@ -84,13 +113,13 @@ async def async_setup_platform(
|
||||||
coordinator,
|
coordinator,
|
||||||
rest,
|
rest,
|
||||||
conf,
|
conf,
|
||||||
unique_id,
|
trigger_entity_config,
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class RestSensor(RestEntity, TemplateSensor):
|
class RestSensor(ManualTriggerSensorEntity, RestEntity, SensorEntity):
|
||||||
"""Implementation of a REST sensor."""
|
"""Implementation of a REST sensor."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
|
@ -99,9 +128,10 @@ class RestSensor(RestEntity, TemplateSensor):
|
||||||
coordinator: DataUpdateCoordinator[None] | None,
|
coordinator: DataUpdateCoordinator[None] | None,
|
||||||
rest: RestData,
|
rest: RestData,
|
||||||
config: ConfigType,
|
config: ConfigType,
|
||||||
unique_id: str | None,
|
trigger_entity_config: ConfigType,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the REST sensor."""
|
"""Initialize the REST sensor."""
|
||||||
|
ManualTriggerSensorEntity.__init__(self, hass, trigger_entity_config)
|
||||||
RestEntity.__init__(
|
RestEntity.__init__(
|
||||||
self,
|
self,
|
||||||
coordinator,
|
coordinator,
|
||||||
|
@ -109,25 +139,30 @@ class RestSensor(RestEntity, TemplateSensor):
|
||||||
config.get(CONF_RESOURCE_TEMPLATE),
|
config.get(CONF_RESOURCE_TEMPLATE),
|
||||||
config[CONF_FORCE_UPDATE],
|
config[CONF_FORCE_UPDATE],
|
||||||
)
|
)
|
||||||
TemplateSensor.__init__(
|
|
||||||
self,
|
|
||||||
hass,
|
|
||||||
config=config,
|
|
||||||
fallback_name=DEFAULT_SENSOR_NAME,
|
|
||||||
unique_id=unique_id,
|
|
||||||
)
|
|
||||||
self._value_template = config.get(CONF_VALUE_TEMPLATE)
|
self._value_template = config.get(CONF_VALUE_TEMPLATE)
|
||||||
if (value_template := self._value_template) is not None:
|
if (value_template := self._value_template) is not None:
|
||||||
value_template.hass = hass
|
value_template.hass = hass
|
||||||
self._json_attrs = config.get(CONF_JSON_ATTRS)
|
self._json_attrs = config.get(CONF_JSON_ATTRS)
|
||||||
self._json_attrs_path = config.get(CONF_JSON_ATTRS_PATH)
|
self._json_attrs_path = config.get(CONF_JSON_ATTRS_PATH)
|
||||||
|
self._attr_extra_state_attributes = {}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available(self) -> bool:
|
||||||
|
"""Return if entity is available."""
|
||||||
|
available1 = RestEntity.available.fget(self) # type: ignore[attr-defined]
|
||||||
|
available2 = ManualTriggerSensorEntity.available.fget(self) # type: ignore[attr-defined]
|
||||||
|
return bool(available1 and available2)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def extra_state_attributes(self) -> dict[str, Any]:
|
||||||
|
"""Return extra attributes."""
|
||||||
|
return dict(self._attr_extra_state_attributes)
|
||||||
|
|
||||||
def _update_from_rest_data(self) -> None:
|
def _update_from_rest_data(self) -> None:
|
||||||
"""Update state from the rest data."""
|
"""Update state from the rest data."""
|
||||||
value = self.rest.data_without_xml()
|
value = self.rest.data_without_xml()
|
||||||
|
|
||||||
if self._json_attrs:
|
if self._json_attrs:
|
||||||
self._attr_extra_state_attributes = {}
|
|
||||||
if value:
|
if value:
|
||||||
try:
|
try:
|
||||||
json_dict = json_loads(value)
|
json_dict = json_loads(value)
|
||||||
|
@ -155,6 +190,8 @@ class RestSensor(RestEntity, TemplateSensor):
|
||||||
else:
|
else:
|
||||||
_LOGGER.warning("Empty reply found when expecting JSON data")
|
_LOGGER.warning("Empty reply found when expecting JSON data")
|
||||||
|
|
||||||
|
raw_value = value
|
||||||
|
|
||||||
if value is not None and self._value_template is not None:
|
if value is not None and self._value_template is not None:
|
||||||
value = self._value_template.async_render_with_possible_json_value(
|
value = self._value_template.async_render_with_possible_json_value(
|
||||||
value, None
|
value, None
|
||||||
|
@ -165,8 +202,13 @@ class RestSensor(RestEntity, TemplateSensor):
|
||||||
SensorDeviceClass.TIMESTAMP,
|
SensorDeviceClass.TIMESTAMP,
|
||||||
):
|
):
|
||||||
self._attr_native_value = value
|
self._attr_native_value = value
|
||||||
|
self._process_manual_data(raw_value)
|
||||||
|
self.async_write_ha_state()
|
||||||
return
|
return
|
||||||
|
|
||||||
self._attr_native_value = async_parse_date_datetime(
|
self._attr_native_value = async_parse_date_datetime(
|
||||||
value, self.entity_id, self.device_class
|
value, self.entity_id, self.device_class
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self._process_manual_data(raw_value)
|
||||||
|
self.async_write_ha_state()
|
||||||
|
|
|
@ -653,3 +653,17 @@ class ManualTriggerEntity(TriggerBaseEntity):
|
||||||
variables = {"this": this, **(run_variables or {})}
|
variables = {"this": this, **(run_variables or {})}
|
||||||
|
|
||||||
self._render_templates(variables)
|
self._render_templates(variables)
|
||||||
|
|
||||||
|
|
||||||
|
class ManualTriggerSensorEntity(ManualTriggerEntity):
|
||||||
|
"""Template entity based on manual trigger data for sensor."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config: dict,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize the sensor entity."""
|
||||||
|
ManualTriggerEntity.__init__(self, hass, config)
|
||||||
|
self._attr_native_unit_of_measurement = config.get(CONF_UNIT_OF_MEASUREMENT)
|
||||||
|
self._attr_state_class = config.get(CONF_STATE_CLASS)
|
||||||
|
|
|
@ -23,6 +23,7 @@ from homeassistant.const import (
|
||||||
ATTR_UNIT_OF_MEASUREMENT,
|
ATTR_UNIT_OF_MEASUREMENT,
|
||||||
CONTENT_TYPE_JSON,
|
CONTENT_TYPE_JSON,
|
||||||
SERVICE_RELOAD,
|
SERVICE_RELOAD,
|
||||||
|
STATE_UNAVAILABLE,
|
||||||
STATE_UNKNOWN,
|
STATE_UNKNOWN,
|
||||||
UnitOfInformation,
|
UnitOfInformation,
|
||||||
UnitOfTemperature,
|
UnitOfTemperature,
|
||||||
|
@ -1018,3 +1019,27 @@ async def test_entity_config(hass: HomeAssistant) -> None:
|
||||||
"state_class": "measurement",
|
"state_class": "measurement",
|
||||||
"unit_of_measurement": "°C",
|
"unit_of_measurement": "°C",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@respx.mock
|
||||||
|
async def test_availability_in_config(hass: HomeAssistant) -> None:
|
||||||
|
"""Test entity configuration."""
|
||||||
|
|
||||||
|
config = {
|
||||||
|
SENSOR_DOMAIN: {
|
||||||
|
# REST configuration
|
||||||
|
"platform": DOMAIN,
|
||||||
|
"method": "GET",
|
||||||
|
"resource": "http://localhost",
|
||||||
|
# Entity configuration
|
||||||
|
"availability": "{{value==1}}",
|
||||||
|
"name": "{{'REST' + ' ' + 'Sensor'}}",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
respx.get("http://localhost").respond(status_code=HTTPStatus.OK, text="123")
|
||||||
|
assert await async_setup_component(hass, SENSOR_DOMAIN, config)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get("sensor.rest_sensor")
|
||||||
|
assert state.state == STATE_UNAVAILABLE
|
||||||
|
|
Loading…
Reference in a new issue