Refactor Command line sensor to inherit TemplateSensor (#81222)

* Refactor sensor

* Remove not needed

* block until done

* reset test

* test sensor

* Add time
This commit is contained in:
G Johansson 2023-03-13 17:23:25 +01:00 committed by GitHub
parent a7396af4bb
commit 02389960ce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 72 additions and 18 deletions

View File

@ -8,9 +8,16 @@ import logging
import voluptuous as vol import voluptuous as vol
from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.components.sensor import (
CONF_STATE_CLASS,
DEVICE_CLASSES_SCHEMA,
PLATFORM_SCHEMA,
STATE_CLASSES_SCHEMA,
SensorEntity,
)
from homeassistant.const import ( from homeassistant.const import (
CONF_COMMAND, CONF_COMMAND,
CONF_DEVICE_CLASS,
CONF_NAME, CONF_NAME,
CONF_UNIQUE_ID, CONF_UNIQUE_ID,
CONF_UNIT_OF_MEASUREMENT, CONF_UNIT_OF_MEASUREMENT,
@ -21,8 +28,12 @@ from homeassistant.core import HomeAssistant
from homeassistant.exceptions import TemplateError from homeassistant.exceptions import TemplateError
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.reload import setup_reload_service from homeassistant.helpers.reload import async_setup_reload_service
from homeassistant.helpers.template import Template from homeassistant.helpers.template import Template
from homeassistant.helpers.template_entity import (
TEMPLATE_SENSOR_BASE_SCHEMA,
TemplateSensor,
)
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from . import check_output_or_log from . import check_output_or_log
@ -45,19 +56,25 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string, vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template, vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_UNIQUE_ID): cv.string, vol.Optional(CONF_UNIQUE_ID): cv.string,
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_STATE_CLASS): STATE_CLASSES_SCHEMA,
} }
) )
def setup_platform( async def async_setup_platform(
hass: HomeAssistant, hass: HomeAssistant,
config: ConfigType, config: ConfigType,
add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None, discovery_info: DiscoveryInfoType | None = None,
) -> None: ) -> None:
"""Set up the Command Sensor.""" """Set up the Command Sensor."""
setup_reload_service(hass, DOMAIN, PLATFORMS) await async_setup_reload_service(hass, DOMAIN, PLATFORMS)
sensor_config = vol.Schema(
TEMPLATE_SENSOR_BASE_SCHEMA.schema, extra=vol.REMOVE_EXTRA
)(config)
name: str = config[CONF_NAME] name: str = config[CONF_NAME]
command: str = config[CONF_COMMAND] command: str = config[CONF_COMMAND]
@ -70,17 +87,30 @@ def setup_platform(
json_attributes: list[str] | None = config.get(CONF_JSON_ATTRIBUTES) json_attributes: list[str] | None = config.get(CONF_JSON_ATTRIBUTES)
data = CommandSensorData(hass, command, command_timeout) data = CommandSensorData(hass, command, command_timeout)
add_entities( async_add_entities(
[CommandSensor(data, name, unit, value_template, json_attributes, unique_id)], [
CommandSensor(
hass,
sensor_config,
data,
name,
unit,
value_template,
json_attributes,
unique_id,
)
],
True, True,
) )
class CommandSensor(SensorEntity): class CommandSensor(TemplateSensor, SensorEntity):
"""Representation of a sensor that is using shell commands.""" """Representation of a sensor that is using shell commands."""
def __init__( def __init__(
self, self,
hass: HomeAssistant,
config: ConfigType,
data: CommandSensorData, data: CommandSensorData,
name: str, name: str,
unit_of_measurement: str | None, unit_of_measurement: str | None,
@ -89,18 +119,22 @@ class CommandSensor(SensorEntity):
unique_id: str | None, unique_id: str | None,
) -> None: ) -> None:
"""Initialize the sensor.""" """Initialize the sensor."""
TemplateSensor.__init__(
self,
hass,
config=config,
fallback_name=name,
unique_id=unique_id,
)
self.data = data self.data = data
self._attr_extra_state_attributes = {} self._attr_extra_state_attributes = {}
self._json_attributes = json_attributes self._json_attributes = json_attributes
self._attr_name = name
self._attr_native_value = None self._attr_native_value = None
self._attr_native_unit_of_measurement = unit_of_measurement
self._value_template = value_template self._value_template = value_template
self._attr_unique_id = unique_id
def update(self) -> None: async def async_update(self) -> None:
"""Get the latest data and updates the state.""" """Get the latest data and updates the state."""
self.data.update() await self.hass.async_add_executor_job(self.data.update)
value = self.data.value value = self.data.value
if self._json_attributes: if self._json_attributes:
@ -124,10 +158,10 @@ class CommandSensor(SensorEntity):
if value is None: if value is None:
value = STATE_UNKNOWN value = STATE_UNKNOWN
elif self._value_template is not None: elif self._value_template is not None:
self._attr_native_value = ( self._attr_native_value = await self.hass.async_add_executor_job(
self._value_template.render_with_possible_json_value( self._value_template.render_with_possible_json_value,
value, STATE_UNKNOWN value,
) STATE_UNKNOWN,
) )
else: else:
self._attr_native_value = value self._attr_native_value = value

View File

@ -1,6 +1,7 @@
"""The tests for the Command line sensor platform.""" """The tests for the Command line sensor platform."""
from __future__ import annotations from __future__ import annotations
from datetime import timedelta
from typing import Any from typing import Any
from unittest.mock import patch from unittest.mock import patch
@ -10,6 +11,9 @@ from homeassistant import setup
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er from homeassistant.helpers import entity_registry as er
from homeassistant.util import dt
from tests.common import async_fire_time_changed
async def setup_test_entities(hass: HomeAssistant, config_dict: dict[str, Any]) -> None: async def setup_test_entities(hass: HomeAssistant, config_dict: dict[str, Any]) -> None:
@ -67,6 +71,14 @@ async def test_template_render(hass: HomeAssistant) -> None:
"command": "echo {{ states.sensor.input_sensor.state }}", "command": "echo {{ states.sensor.input_sensor.state }}",
}, },
) )
# Give time for template to load
async_fire_time_changed(
hass,
dt.utcnow() + timedelta(minutes=1),
)
await hass.async_block_till_done()
entity_state = hass.states.get("sensor.test") entity_state = hass.states.get("sensor.test")
assert entity_state assert entity_state
assert entity_state.state == "sensor_value" assert entity_state.state == "sensor_value"
@ -86,7 +98,15 @@ async def test_template_render_with_quote(hass: HomeAssistant) -> None:
}, },
) )
check_output.assert_called_once_with( # Give time for template to load
async_fire_time_changed(
hass,
dt.utcnow() + timedelta(minutes=1),
)
await hass.async_block_till_done()
assert len(check_output.mock_calls) == 2
check_output.assert_called_with(
'echo "sensor_value" "3 4"', 'echo "sensor_value" "3 4"',
shell=True, # nosec # shell by design shell=True, # nosec # shell by design
timeout=15, timeout=15,