Add availability to command_line (#105300)

* Add availability to command_line

* Add tests

* freezer
This commit is contained in:
G Johansson 2024-01-15 18:20:34 +01:00 committed by GitHub
parent 6a9fdaae7a
commit 749ef45727
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 225 additions and 5 deletions

View File

@ -55,6 +55,7 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity_platform import async_get_platforms from homeassistant.helpers.entity_platform import async_get_platforms
from homeassistant.helpers.reload import async_integration_yaml_config from homeassistant.helpers.reload import async_integration_yaml_config
from homeassistant.helpers.service import async_register_admin_service from homeassistant.helpers.service import async_register_admin_service
from homeassistant.helpers.trigger_template_entity import CONF_AVAILABILITY
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
from .const import CONF_COMMAND_TIMEOUT, DEFAULT_TIMEOUT, DOMAIN from .const import CONF_COMMAND_TIMEOUT, DEFAULT_TIMEOUT, DOMAIN
@ -90,6 +91,7 @@ BINARY_SENSOR_SCHEMA = vol.Schema(
vol.Optional( vol.Optional(
CONF_SCAN_INTERVAL, default=BINARY_SENSOR_DEFAULT_SCAN_INTERVAL CONF_SCAN_INTERVAL, default=BINARY_SENSOR_DEFAULT_SCAN_INTERVAL
): vol.All(cv.time_period, cv.positive_timedelta), ): vol.All(cv.time_period, cv.positive_timedelta),
vol.Optional(CONF_AVAILABILITY): cv.template,
} }
) )
COVER_SCHEMA = vol.Schema( COVER_SCHEMA = vol.Schema(
@ -105,6 +107,7 @@ COVER_SCHEMA = vol.Schema(
vol.Optional(CONF_SCAN_INTERVAL, default=COVER_DEFAULT_SCAN_INTERVAL): vol.All( vol.Optional(CONF_SCAN_INTERVAL, default=COVER_DEFAULT_SCAN_INTERVAL): vol.All(
cv.time_period, cv.positive_timedelta cv.time_period, cv.positive_timedelta
), ),
vol.Optional(CONF_AVAILABILITY): cv.template,
} }
) )
NOTIFY_SCHEMA = vol.Schema( NOTIFY_SCHEMA = vol.Schema(
@ -129,6 +132,7 @@ SENSOR_SCHEMA = vol.Schema(
vol.Optional(CONF_SCAN_INTERVAL, default=SENSOR_DEFAULT_SCAN_INTERVAL): vol.All( vol.Optional(CONF_SCAN_INTERVAL, default=SENSOR_DEFAULT_SCAN_INTERVAL): vol.All(
cv.time_period, cv.positive_timedelta cv.time_period, cv.positive_timedelta
), ),
vol.Optional(CONF_AVAILABILITY): cv.template,
} }
) )
SWITCH_SCHEMA = vol.Schema( SWITCH_SCHEMA = vol.Schema(
@ -144,6 +148,7 @@ SWITCH_SCHEMA = vol.Schema(
vol.Optional(CONF_SCAN_INTERVAL, default=SWITCH_DEFAULT_SCAN_INTERVAL): vol.All( vol.Optional(CONF_SCAN_INTERVAL, default=SWITCH_DEFAULT_SCAN_INTERVAL): vol.All(
cv.time_period, cv.positive_timedelta cv.time_period, cv.positive_timedelta
), ),
vol.Optional(CONF_AVAILABILITY): cv.template,
} }
) )
COMBINED_SCHEMA = vol.Schema( COMBINED_SCHEMA = vol.Schema(

View File

@ -24,7 +24,10 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.template import Template from homeassistant.helpers.template import Template
from homeassistant.helpers.trigger_template_entity import ManualTriggerEntity from homeassistant.helpers.trigger_template_entity import (
CONF_AVAILABILITY,
ManualTriggerEntity,
)
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util import dt as dt_util from homeassistant.util import dt as dt_util
@ -63,6 +66,7 @@ async def async_setup_platform(
scan_interval: timedelta = binary_sensor_config.get( scan_interval: timedelta = binary_sensor_config.get(
CONF_SCAN_INTERVAL, SCAN_INTERVAL CONF_SCAN_INTERVAL, SCAN_INTERVAL
) )
availability: Template | None = binary_sensor_config.get(CONF_AVAILABILITY)
if value_template is not None: if value_template is not None:
value_template.hass = hass value_template.hass = hass
data = CommandSensorData(hass, command, command_timeout) data = CommandSensorData(hass, command, command_timeout)
@ -72,6 +76,7 @@ async def async_setup_platform(
CONF_NAME: Template(name, hass), CONF_NAME: Template(name, hass),
CONF_DEVICE_CLASS: device_class, CONF_DEVICE_CLASS: device_class,
CONF_ICON: icon, CONF_ICON: icon,
CONF_AVAILABILITY: availability,
} }
async_add_entities( async_add_entities(

View File

@ -20,7 +20,10 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.template import Template from homeassistant.helpers.template import Template
from homeassistant.helpers.trigger_template_entity import ManualTriggerEntity from homeassistant.helpers.trigger_template_entity import (
CONF_AVAILABILITY,
ManualTriggerEntity,
)
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util import dt as dt_util, slugify from homeassistant.util import dt as dt_util, slugify
@ -50,6 +53,7 @@ async def async_setup_platform(
trigger_entity_config = { trigger_entity_config = {
CONF_UNIQUE_ID: device_config.get(CONF_UNIQUE_ID), CONF_UNIQUE_ID: device_config.get(CONF_UNIQUE_ID),
CONF_NAME: Template(device_config.get(CONF_NAME, device_name), hass), CONF_NAME: Template(device_config.get(CONF_NAME, device_name), hass),
CONF_AVAILABILITY: device_config.get(CONF_AVAILABILITY),
} }
covers.append( covers.append(

View File

@ -20,7 +20,10 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.template import Template from homeassistant.helpers.template import Template
from homeassistant.helpers.trigger_template_entity import ManualTriggerEntity from homeassistant.helpers.trigger_template_entity import (
CONF_AVAILABILITY,
ManualTriggerEntity,
)
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util import dt as dt_util, slugify from homeassistant.util import dt as dt_util, slugify
@ -48,6 +51,7 @@ async def async_setup_platform(
CONF_UNIQUE_ID: device_config.get(CONF_UNIQUE_ID), CONF_UNIQUE_ID: device_config.get(CONF_UNIQUE_ID),
CONF_NAME: Template(device_config.get(CONF_NAME, object_id), hass), CONF_NAME: Template(device_config.get(CONF_NAME, object_id), hass),
CONF_ICON: device_config.get(CONF_ICON), CONF_ICON: device_config.get(CONF_ICON),
CONF_AVAILABILITY: device_config.get(CONF_AVAILABILITY),
} }
value_template: Template | None = device_config.get(CONF_VALUE_TEMPLATE) value_template: Template | None = device_config.get(CONF_VALUE_TEMPLATE)

View File

@ -6,6 +6,7 @@ from datetime import timedelta
from typing import Any from typing import Any
from unittest.mock import patch from unittest.mock import patch
from freezegun.api import FrozenDateTimeFactory
import pytest import pytest
from homeassistant import setup from homeassistant import setup
@ -15,7 +16,7 @@ from homeassistant.components.homeassistant import (
DOMAIN as HA_DOMAIN, DOMAIN as HA_DOMAIN,
SERVICE_UPDATE_ENTITY, SERVICE_UPDATE_ENTITY,
) )
from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON, STATE_UNAVAILABLE
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 as dt_util from homeassistant.util import dt as dt_util
@ -289,3 +290,53 @@ async def test_updating_manually(
) )
await hass.async_block_till_done() await hass.async_block_till_done()
assert called assert called
@pytest.mark.parametrize(
"get_config",
[
{
"command_line": [
{
"binary_sensor": {
"name": "Test",
"command": "echo 10",
"payload_on": "1.0",
"payload_off": "0",
"value_template": "{{ value | multiply(0.1) }}",
"availability": '{{ states("sensor.input1")=="on" }}',
}
}
]
}
],
)
async def test_availability(
hass: HomeAssistant,
load_yaml_integration: None,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test availability."""
hass.states.async_set("sensor.input1", "on")
freezer.tick(timedelta(minutes=1))
async_fire_time_changed(hass)
await hass.async_block_till_done()
entity_state = hass.states.get("binary_sensor.test")
assert entity_state
assert entity_state.state == STATE_ON
hass.states.async_set("sensor.input1", "off")
await hass.async_block_till_done()
with patch(
"homeassistant.components.command_line.utils.subprocess.check_output",
return_value=b"0",
):
freezer.tick(timedelta(minutes=1))
async_fire_time_changed(hass)
await hass.async_block_till_done()
entity_state = hass.states.get("binary_sensor.test")
assert entity_state
assert entity_state.state == STATE_UNAVAILABLE

View File

@ -7,6 +7,7 @@ import os
import tempfile import tempfile
from unittest.mock import patch from unittest.mock import patch
from freezegun.api import FrozenDateTimeFactory
import pytest import pytest
from homeassistant import setup from homeassistant import setup
@ -22,6 +23,8 @@ from homeassistant.const import (
SERVICE_CLOSE_COVER, SERVICE_CLOSE_COVER,
SERVICE_OPEN_COVER, SERVICE_OPEN_COVER,
SERVICE_STOP_COVER, SERVICE_STOP_COVER,
STATE_OPEN,
STATE_UNAVAILABLE,
) )
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
@ -340,3 +343,50 @@ async def test_updating_manually(
) )
await hass.async_block_till_done() await hass.async_block_till_done()
assert called assert called
@pytest.mark.parametrize(
"get_config",
[
{
"command_line": [
{
"cover": {
"command_state": "echo 10",
"name": "Test",
"availability": '{{ states("sensor.input1")=="on" }}',
},
}
]
}
],
)
async def test_availability(
hass: HomeAssistant,
load_yaml_integration: None,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test availability."""
hass.states.async_set("sensor.input1", "on")
freezer.tick(timedelta(minutes=1))
async_fire_time_changed(hass)
await hass.async_block_till_done()
entity_state = hass.states.get("cover.test")
assert entity_state
assert entity_state.state == STATE_OPEN
hass.states.async_set("sensor.input1", "off")
await hass.async_block_till_done()
with patch(
"homeassistant.components.command_line.utils.subprocess.check_output",
return_value=b"50\n",
):
freezer.tick(timedelta(minutes=1))
async_fire_time_changed(hass)
await hass.async_block_till_done()
entity_state = hass.states.get("cover.test")
assert entity_state
assert entity_state.state == STATE_UNAVAILABLE

View File

@ -7,6 +7,7 @@ import subprocess
from typing import Any from typing import Any
from unittest.mock import patch from unittest.mock import patch
from freezegun.api import FrozenDateTimeFactory
import pytest import pytest
from homeassistant import setup from homeassistant import setup
@ -16,7 +17,7 @@ from homeassistant.components.homeassistant import (
DOMAIN as HA_DOMAIN, DOMAIN as HA_DOMAIN,
SERVICE_UPDATE_ENTITY, SERVICE_UPDATE_ENTITY,
) )
from homeassistant.const import ATTR_ENTITY_ID, STATE_UNKNOWN from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE, STATE_UNKNOWN
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 as dt_util from homeassistant.util import dt as dt_util
@ -708,3 +709,52 @@ async def test_template_not_error_when_data_is_none(
"Template variable error: 'None' has no attribute 'split' when rendering" "Template variable error: 'None' has no attribute 'split' when rendering"
not in caplog.text not in caplog.text
) )
@pytest.mark.parametrize(
"get_config",
[
{
"command_line": [
{
"sensor": {
"name": "Test",
"command": "echo January 17, 2022",
"device_class": "date",
"value_template": "{{ strptime(value, '%B %d, %Y').strftime('%Y-%m-%d') }}",
"availability": '{{ states("sensor.input1")=="on" }}',
}
}
]
}
],
)
async def test_availability(
hass: HomeAssistant,
load_yaml_integration: None,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test availability."""
hass.states.async_set("sensor.input1", "on")
freezer.tick(timedelta(minutes=1))
async_fire_time_changed(hass)
await hass.async_block_till_done()
entity_state = hass.states.get("sensor.test")
assert entity_state
assert entity_state.state == "2022-01-17"
hass.states.async_set("sensor.input1", "off")
await hass.async_block_till_done()
with patch(
"homeassistant.components.command_line.utils.subprocess.check_output",
return_value=b"January 17, 2022",
):
freezer.tick(timedelta(minutes=1))
async_fire_time_changed(hass)
await hass.async_block_till_done()
entity_state = hass.states.get("sensor.test")
assert entity_state
assert entity_state.state == STATE_UNAVAILABLE

View File

@ -9,6 +9,7 @@ import subprocess
import tempfile import tempfile
from unittest.mock import patch from unittest.mock import patch
from freezegun.api import FrozenDateTimeFactory
import pytest import pytest
from homeassistant import setup from homeassistant import setup
@ -25,6 +26,7 @@ from homeassistant.const import (
SERVICE_TURN_ON, SERVICE_TURN_ON,
STATE_OFF, STATE_OFF,
STATE_ON, STATE_ON,
STATE_UNAVAILABLE,
) )
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
@ -710,3 +712,52 @@ async def test_updating_manually(
) )
await hass.async_block_till_done() await hass.async_block_till_done()
assert called assert called
@pytest.mark.parametrize(
"get_config",
[
{
"command_line": [
{
"switch": {
"command_state": "echo 1",
"command_on": "echo 2",
"command_off": "echo 3",
"name": "Test",
"availability": '{{ states("sensor.input1")=="on" }}',
},
}
]
}
],
)
async def test_availability(
hass: HomeAssistant,
load_yaml_integration: None,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test availability."""
hass.states.async_set("sensor.input1", "on")
freezer.tick(timedelta(minutes=1))
async_fire_time_changed(hass)
await hass.async_block_till_done()
entity_state = hass.states.get("switch.test")
assert entity_state
assert entity_state.state == STATE_ON
hass.states.async_set("sensor.input1", "off")
await hass.async_block_till_done()
with patch(
"homeassistant.components.command_line.utils.subprocess.check_output",
return_value=b"50\n",
):
freezer.tick(timedelta(minutes=1))
async_fire_time_changed(hass)
await hass.async_block_till_done()
entity_state = hass.states.get("switch.test")
assert entity_state
assert entity_state.state == STATE_UNAVAILABLE