Add unique_id configuration variable to command_line integration (#58596)

This commit is contained in:
Gabriel Rauter 2022-01-03 11:44:47 +01:00 committed by GitHub
parent 545b10a711
commit d26275011a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 209 additions and 4 deletions

View File

@ -16,6 +16,7 @@ from homeassistant.const import (
CONF_NAME, CONF_NAME,
CONF_PAYLOAD_OFF, CONF_PAYLOAD_OFF,
CONF_PAYLOAD_ON, CONF_PAYLOAD_ON,
CONF_UNIQUE_ID,
CONF_VALUE_TEMPLATE, CONF_VALUE_TEMPLATE,
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
@ -43,6 +44,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template, vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_COMMAND_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, vol.Optional(CONF_COMMAND_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
vol.Optional(CONF_UNIQUE_ID): cv.string,
} }
) )
@ -64,6 +66,7 @@ def setup_platform(
device_class = config.get(CONF_DEVICE_CLASS) device_class = config.get(CONF_DEVICE_CLASS)
value_template = config.get(CONF_VALUE_TEMPLATE) value_template = config.get(CONF_VALUE_TEMPLATE)
command_timeout = config.get(CONF_COMMAND_TIMEOUT) command_timeout = config.get(CONF_COMMAND_TIMEOUT)
unique_id = config.get(CONF_UNIQUE_ID)
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)
@ -71,7 +74,14 @@ def setup_platform(
add_entities( add_entities(
[ [
CommandBinarySensor( CommandBinarySensor(
hass, data, name, device_class, payload_on, payload_off, value_template hass,
data,
name,
device_class,
payload_on,
payload_off,
value_template,
unique_id,
) )
], ],
True, True,
@ -82,7 +92,15 @@ class CommandBinarySensor(BinarySensorEntity):
"""Representation of a command line binary sensor.""" """Representation of a command line binary sensor."""
def __init__( def __init__(
self, hass, data, name, device_class, payload_on, payload_off, value_template self,
hass,
data,
name,
device_class,
payload_on,
payload_off,
value_template,
unique_id,
): ):
"""Initialize the Command line binary sensor.""" """Initialize the Command line binary sensor."""
self._hass = hass self._hass = hass
@ -93,6 +111,7 @@ class CommandBinarySensor(BinarySensorEntity):
self._payload_on = payload_on self._payload_on = payload_on
self._payload_off = payload_off self._payload_off = payload_off
self._value_template = value_template self._value_template = value_template
self._attr_unique_id = unique_id
@property @property
def name(self): def name(self):

View File

@ -13,6 +13,7 @@ from homeassistant.const import (
CONF_COMMAND_STOP, CONF_COMMAND_STOP,
CONF_COVERS, CONF_COVERS,
CONF_FRIENDLY_NAME, CONF_FRIENDLY_NAME,
CONF_UNIQUE_ID,
CONF_VALUE_TEMPLATE, CONF_VALUE_TEMPLATE,
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
@ -35,6 +36,7 @@ COVER_SCHEMA = vol.Schema(
vol.Optional(CONF_FRIENDLY_NAME): cv.string, vol.Optional(CONF_FRIENDLY_NAME): cv.string,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template, vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_COMMAND_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, vol.Optional(CONF_COMMAND_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
vol.Optional(CONF_UNIQUE_ID): cv.string,
} }
) )
@ -71,6 +73,7 @@ def setup_platform(
device_config.get(CONF_COMMAND_STATE), device_config.get(CONF_COMMAND_STATE),
value_template, value_template,
device_config[CONF_COMMAND_TIMEOUT], device_config[CONF_COMMAND_TIMEOUT],
device_config.get(CONF_UNIQUE_ID),
) )
) )
@ -94,6 +97,7 @@ class CommandCover(CoverEntity):
command_state, command_state,
value_template, value_template,
timeout, timeout,
unique_id,
): ):
"""Initialize the cover.""" """Initialize the cover."""
self._hass = hass self._hass = hass
@ -105,6 +109,7 @@ class CommandCover(CoverEntity):
self._command_state = command_state self._command_state = command_state
self._value_template = value_template self._value_template = value_template
self._timeout = timeout self._timeout = timeout
self._attr_unique_id = unique_id
def _move_cover(self, command): def _move_cover(self, command):
"""Execute the actual commands.""" """Execute the actual commands."""

View File

@ -12,6 +12,7 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity
from homeassistant.const import ( from homeassistant.const import (
CONF_COMMAND, CONF_COMMAND,
CONF_NAME, CONF_NAME,
CONF_UNIQUE_ID,
CONF_UNIT_OF_MEASUREMENT, CONF_UNIT_OF_MEASUREMENT,
CONF_VALUE_TEMPLATE, CONF_VALUE_TEMPLATE,
STATE_UNKNOWN, STATE_UNKNOWN,
@ -43,6 +44,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
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,
} }
) )
@ -62,13 +64,19 @@ def setup_platform(
unit = config.get(CONF_UNIT_OF_MEASUREMENT) unit = config.get(CONF_UNIT_OF_MEASUREMENT)
value_template = config.get(CONF_VALUE_TEMPLATE) value_template = config.get(CONF_VALUE_TEMPLATE)
command_timeout = config.get(CONF_COMMAND_TIMEOUT) command_timeout = config.get(CONF_COMMAND_TIMEOUT)
unique_id = config.get(CONF_UNIQUE_ID)
if value_template is not None: if value_template is not None:
value_template.hass = hass value_template.hass = hass
json_attributes = config.get(CONF_JSON_ATTRIBUTES) json_attributes = config.get(CONF_JSON_ATTRIBUTES)
data = CommandSensorData(hass, command, command_timeout) data = CommandSensorData(hass, command, command_timeout)
add_entities( add_entities(
[CommandSensor(hass, data, name, unit, value_template, json_attributes)], True [
CommandSensor(
hass, data, name, unit, value_template, json_attributes, unique_id
)
],
True,
) )
@ -76,7 +84,14 @@ class CommandSensor(SensorEntity):
"""Representation of a sensor that is using shell commands.""" """Representation of a sensor that is using shell commands."""
def __init__( def __init__(
self, hass, data, name, unit_of_measurement, value_template, json_attributes self,
hass,
data,
name,
unit_of_measurement,
value_template,
json_attributes,
unique_id,
): ):
"""Initialize the sensor.""" """Initialize the sensor."""
self._hass = hass self._hass = hass
@ -87,6 +102,7 @@ class CommandSensor(SensorEntity):
self._state = None self._state = None
self._unit_of_measurement = unit_of_measurement self._unit_of_measurement = unit_of_measurement
self._value_template = value_template self._value_template = value_template
self._attr_unique_id = unique_id
@property @property
def name(self): def name(self):

View File

@ -17,6 +17,7 @@ from homeassistant.const import (
CONF_FRIENDLY_NAME, CONF_FRIENDLY_NAME,
CONF_ICON_TEMPLATE, CONF_ICON_TEMPLATE,
CONF_SWITCHES, CONF_SWITCHES,
CONF_UNIQUE_ID,
CONF_VALUE_TEMPLATE, CONF_VALUE_TEMPLATE,
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
@ -39,6 +40,7 @@ SWITCH_SCHEMA = vol.Schema(
vol.Optional(CONF_VALUE_TEMPLATE): cv.template, vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_ICON_TEMPLATE): cv.template, vol.Optional(CONF_ICON_TEMPLATE): cv.template,
vol.Optional(CONF_COMMAND_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, vol.Optional(CONF_COMMAND_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
vol.Optional(CONF_UNIQUE_ID): cv.string,
} }
) )
@ -81,6 +83,7 @@ def setup_platform(
icon_template, icon_template,
value_template, value_template,
device_config[CONF_COMMAND_TIMEOUT], device_config[CONF_COMMAND_TIMEOUT],
device_config.get(CONF_UNIQUE_ID),
) )
) )
@ -105,6 +108,7 @@ class CommandSwitch(SwitchEntity):
icon_template, icon_template,
value_template, value_template,
timeout, timeout,
unique_id,
): ):
"""Initialize the switch.""" """Initialize the switch."""
self._hass = hass self._hass = hass
@ -117,6 +121,7 @@ class CommandSwitch(SwitchEntity):
self._icon_template = icon_template self._icon_template = icon_template
self._value_template = value_template self._value_template = value_template
self._timeout = timeout self._timeout = timeout
self._attr_unique_id = unique_id
def _switch(self, command): def _switch(self, command):
"""Execute the actual commands.""" """Execute the actual commands."""

View File

@ -7,6 +7,7 @@ from homeassistant import setup
from homeassistant.components.binary_sensor import DOMAIN from homeassistant.components.binary_sensor import DOMAIN
from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.const import STATE_OFF, STATE_ON
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry
async def setup_test_entity(hass: HomeAssistant, config_dict: dict[str, Any]) -> None: async def setup_test_entity(hass: HomeAssistant, config_dict: dict[str, Any]) -> None:
@ -65,3 +66,47 @@ async def test_sensor_off(hass: HomeAssistant) -> None:
) )
entity_state = hass.states.get("binary_sensor.test") entity_state = hass.states.get("binary_sensor.test")
assert entity_state.state == STATE_OFF assert entity_state.state == STATE_OFF
async def test_unique_id(hass):
"""Test unique_id option and if it only creates one binary sensor per id."""
assert await setup.async_setup_component(
hass,
DOMAIN,
{
DOMAIN: [
{
"platform": "command_line",
"unique_id": "unique",
"command": "echo 0",
},
{
"platform": "command_line",
"unique_id": "not-so-unique-anymore",
"command": "echo 1",
},
{
"platform": "command_line",
"unique_id": "not-so-unique-anymore",
"command": "echo 2",
},
]
},
)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 2
ent_reg = entity_registry.async_get(hass)
assert len(ent_reg.entities) == 2
assert (
ent_reg.async_get_entity_id("binary_sensor", "command_line", "unique")
is not None
)
assert (
ent_reg.async_get_entity_id(
"binary_sensor", "command_line", "not-so-unique-anymore"
)
is not None
)

View File

@ -16,6 +16,7 @@ from homeassistant.const import (
SERVICE_STOP_COVER, SERVICE_STOP_COVER,
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
from tests.common import async_fire_time_changed, get_fixture_path from tests.common import async_fire_time_changed, get_fixture_path
@ -160,3 +161,41 @@ async def test_move_cover_failure(caplog: Any, hass: HomeAssistant) -> None:
DOMAIN, SERVICE_OPEN_COVER, {ATTR_ENTITY_ID: "cover.test"}, blocking=True DOMAIN, SERVICE_OPEN_COVER, {ATTR_ENTITY_ID: "cover.test"}, blocking=True
) )
assert "Command failed" in caplog.text assert "Command failed" in caplog.text
async def test_unique_id(hass):
"""Test unique_id option and if it only creates one cover per id."""
await setup_test_entity(
hass,
{
"unique": {
"command_open": "echo open",
"command_close": "echo close",
"command_stop": "echo stop",
"unique_id": "unique",
},
"not_unique_1": {
"command_open": "echo open",
"command_close": "echo close",
"command_stop": "echo stop",
"unique_id": "not-so-unique-anymore",
},
"not_unique_2": {
"command_open": "echo open",
"command_close": "echo close",
"command_stop": "echo stop",
"unique_id": "not-so-unique-anymore",
},
},
)
assert len(hass.states.async_all()) == 2
ent_reg = entity_registry.async_get(hass)
assert len(ent_reg.entities) == 2
assert ent_reg.async_get_entity_id("cover", "command_line", "unique") is not None
assert (
ent_reg.async_get_entity_id("cover", "command_line", "not-so-unique-anymore")
is not None
)

View File

@ -7,6 +7,7 @@ from unittest.mock import patch
from homeassistant import setup from homeassistant import setup
from homeassistant.components.sensor import DOMAIN from homeassistant.components.sensor import DOMAIN
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry
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:
@ -223,3 +224,42 @@ async def test_update_with_unnecessary_json_attrs(caplog, hass: HomeAssistant) -
assert entity_state.attributes["key"] == "some_json_value" assert entity_state.attributes["key"] == "some_json_value"
assert entity_state.attributes["another_key"] == "another_json_value" assert entity_state.attributes["another_key"] == "another_json_value"
assert "key_three" not in entity_state.attributes assert "key_three" not in entity_state.attributes
async def test_unique_id(hass):
"""Test unique_id option and if it only creates one sensor per id."""
assert await setup.async_setup_component(
hass,
DOMAIN,
{
DOMAIN: [
{
"platform": "command_line",
"unique_id": "unique",
"command": "echo 0",
},
{
"platform": "command_line",
"unique_id": "not-so-unique-anymore",
"command": "echo 1",
},
{
"platform": "command_line",
"unique_id": "not-so-unique-anymore",
"command": "echo 2",
},
]
},
)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 2
ent_reg = entity_registry.async_get(hass)
assert len(ent_reg.entities) == 2
assert ent_reg.async_get_entity_id("sensor", "command_line", "unique") is not None
assert (
ent_reg.async_get_entity_id("sensor", "command_line", "not-so-unique-anymore")
is not None
)

View File

@ -18,6 +18,7 @@ from homeassistant.const import (
STATE_ON, STATE_ON,
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
from tests.common import async_fire_time_changed from tests.common import async_fire_time_changed
@ -376,3 +377,38 @@ async def test_no_switches(caplog: Any, hass: HomeAssistant) -> None:
await setup_test_entity(hass, {}) await setup_test_entity(hass, {})
assert "No switches" in caplog.text assert "No switches" in caplog.text
async def test_unique_id(hass):
"""Test unique_id option and if it only creates one switch per id."""
await setup_test_entity(
hass,
{
"unique": {
"command_on": "echo on",
"command_off": "echo off",
"unique_id": "unique",
},
"not_unique_1": {
"command_on": "echo on",
"command_off": "echo off",
"unique_id": "not-so-unique-anymore",
},
"not_unique_2": {
"command_on": "echo on",
"command_off": "echo off",
"unique_id": "not-so-unique-anymore",
},
},
)
assert len(hass.states.async_all()) == 2
ent_reg = entity_registry.async_get(hass)
assert len(ent_reg.entities) == 2
assert ent_reg.async_get_entity_id("switch", "command_line", "unique") is not None
assert (
ent_reg.async_get_entity_id("switch", "command_line", "not-so-unique-anymore")
is not None
)