Improve Shelly RPC entity naming (#125415)

* Fix default names for cover entities

* Drop component index if only one component exists

* Improve doc strings

* Use more consistent naming

* Typo

* Revert removing index 0 from entity names

* Improve names for RGB(W) lights
This commit is contained in:
Maciej Bieniek 2024-09-15 11:29:26 +02:00 committed by GitHub
parent d292f2b9b4
commit 6906ee0e48
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 82 additions and 37 deletions

View File

@ -1066,7 +1066,7 @@ RPC_SENSORS: Final = {
"analoginput": RpcSensorDescription(
key="input",
sub_key="percent",
name="Analog input",
name="analog",
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
removal_condition=lambda config, _, key: (
@ -1076,7 +1076,7 @@ RPC_SENSORS: Final = {
"analoginput_xpercent": RpcSensorDescription(
key="input",
sub_key="xpercent",
name="Analog value",
name="analog value",
removal_condition=lambda config, status, key: (
config[key]["type"] != "analog"
or config[key]["enable"] is False
@ -1087,7 +1087,7 @@ RPC_SENSORS: Final = {
"pulse_counter": RpcSensorDescription(
key="input",
sub_key="counts",
name="Pulse counter",
name="pulse counter",
native_unit_of_measurement="pulse",
state_class=SensorStateClass.TOTAL,
value=lambda status, _: status["total"],
@ -1098,7 +1098,7 @@ RPC_SENSORS: Final = {
"counter_value": RpcSensorDescription(
key="input",
sub_key="counts",
name="Counter value",
name="counter value",
value=lambda status, _: status["xtotal"],
removal_condition=lambda config, status, key: (
config[key]["type"] != "count"
@ -1110,7 +1110,7 @@ RPC_SENSORS: Final = {
"counter_frequency": RpcSensorDescription(
key="input",
sub_key="freq",
name="Pulse counter frequency",
name="pulse counter frequency",
native_unit_of_measurement=UnitOfFrequency.HERTZ,
state_class=SensorStateClass.MEASUREMENT,
removal_condition=lambda config, _, key: (
@ -1120,7 +1120,7 @@ RPC_SENSORS: Final = {
"counter_frequency_value": RpcSensorDescription(
key="input",
sub_key="xfreq",
name="Pulse counter frequency value",
name="pulse counter frequency value",
removal_condition=lambda config, status, key: (
config[key]["type"] != "count"
or config[key]["enable"] is False

View File

@ -319,15 +319,19 @@ def get_rpc_channel_name(device: RpcDevice, key: str) -> str:
device_name = device.name
entity_name: str | None = None
if key in device.config:
entity_name = device.config[key].get("name", device_name)
entity_name = device.config[key].get("name")
if entity_name is None:
if key.startswith(("input:", "light:", "switch:")):
return f"{device_name} {key.replace(':', '_')}"
channel = key.split(":")[0]
channel_id = key.split(":")[-1]
if key.startswith(("cover:", "input:", "light:", "switch:", "thermostat:")):
return f"{device_name} {channel.title()} {channel_id}"
if key.startswith(("rgb:", "rgbw:")):
return f"{device_name} {channel.upper()} light {channel_id}"
if key.startswith("em1"):
return f"{device_name} EM{key.split(':')[-1]}"
return f"{device_name} EM{channel_id}"
if key.startswith(("boolean:", "enum:", "number:", "text:")):
return key.replace(":", " ").title()
return f"{channel.title()} {channel_id}"
return device_name
return entity_name

View File

@ -609,23 +609,25 @@ async def test_rpc_climate_hvac_mode(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Test climate hvac mode service."""
entity_id = "climate.test_name_thermostat_0"
await init_integration(hass, 2, model=MODEL_WALL_DISPLAY)
state = hass.states.get(ENTITY_ID)
state = hass.states.get(entity_id)
assert state.state == HVACMode.HEAT
assert state.attributes[ATTR_TEMPERATURE] == 23
assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 12.3
assert state.attributes[ATTR_HVAC_ACTION] == HVACAction.HEATING
assert state.attributes[ATTR_CURRENT_HUMIDITY] == 44.4
entry = entity_registry.async_get(ENTITY_ID)
entry = entity_registry.async_get(entity_id)
assert entry
assert entry.unique_id == "123456789ABC-thermostat:0"
monkeypatch.setitem(mock_rpc_device.status["thermostat:0"], "output", False)
mock_rpc_device.mock_update()
state = hass.states.get(ENTITY_ID)
state = hass.states.get(entity_id)
assert state.attributes[ATTR_HVAC_ACTION] == HVACAction.IDLE
assert state.attributes[ATTR_CURRENT_HUMIDITY] == 44.4
@ -633,7 +635,7 @@ async def test_rpc_climate_hvac_mode(
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_HVAC_MODE,
{ATTR_ENTITY_ID: ENTITY_ID, ATTR_HVAC_MODE: HVACMode.OFF},
{ATTR_ENTITY_ID: entity_id, ATTR_HVAC_MODE: HVACMode.OFF},
blocking=True,
)
mock_rpc_device.mock_update()
@ -641,7 +643,7 @@ async def test_rpc_climate_hvac_mode(
mock_rpc_device.call_rpc.assert_called_once_with(
"Thermostat.SetConfig", {"config": {"id": 0, "enable": False}}
)
state = hass.states.get(ENTITY_ID)
state = hass.states.get(entity_id)
assert state.state == HVACMode.OFF
@ -652,20 +654,21 @@ async def test_rpc_climate_without_humidity(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Test climate entity without the humidity value."""
entity_id = "climate.test_name_thermostat_0"
new_status = deepcopy(mock_rpc_device.status)
new_status.pop("humidity:0")
monkeypatch.setattr(mock_rpc_device, "status", new_status)
await init_integration(hass, 2, model=MODEL_WALL_DISPLAY)
state = hass.states.get(ENTITY_ID)
state = hass.states.get(entity_id)
assert state.state == HVACMode.HEAT
assert state.attributes[ATTR_TEMPERATURE] == 23
assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 12.3
assert state.attributes[ATTR_HVAC_ACTION] == HVACAction.HEATING
assert ATTR_CURRENT_HUMIDITY not in state.attributes
entry = entity_registry.async_get(ENTITY_ID)
entry = entity_registry.async_get(entity_id)
assert entry
assert entry.unique_id == "123456789ABC-thermostat:0"
@ -674,9 +677,11 @@ async def test_rpc_climate_set_temperature(
hass: HomeAssistant, mock_rpc_device: Mock, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Test climate set target temperature."""
entity_id = "climate.test_name_thermostat_0"
await init_integration(hass, 2, model=MODEL_WALL_DISPLAY)
state = hass.states.get(ENTITY_ID)
state = hass.states.get(entity_id)
assert state.attributes[ATTR_TEMPERATURE] == 23
# test set temperature without target temperature
@ -684,7 +689,7 @@ async def test_rpc_climate_set_temperature(
CLIMATE_DOMAIN,
SERVICE_SET_TEMPERATURE,
{
ATTR_ENTITY_ID: ENTITY_ID,
ATTR_ENTITY_ID: entity_id,
ATTR_TARGET_TEMP_LOW: 20,
ATTR_TARGET_TEMP_HIGH: 30,
},
@ -696,7 +701,7 @@ async def test_rpc_climate_set_temperature(
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_TEMPERATURE,
{ATTR_ENTITY_ID: ENTITY_ID, ATTR_TEMPERATURE: 28},
{ATTR_ENTITY_ID: entity_id, ATTR_TEMPERATURE: 28},
blocking=True,
)
mock_rpc_device.mock_update()
@ -704,7 +709,7 @@ async def test_rpc_climate_set_temperature(
mock_rpc_device.call_rpc.assert_called_once_with(
"Thermostat.SetConfig", {"config": {"id": 0, "target_C": 28}}
)
state = hass.states.get(ENTITY_ID)
state = hass.states.get(entity_id)
assert state.attributes[ATTR_TEMPERATURE] == 28
@ -712,13 +717,14 @@ async def test_rpc_climate_hvac_mode_cool(
hass: HomeAssistant, mock_rpc_device: Mock, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Test climate with hvac mode cooling."""
entity_id = "climate.test_name_thermostat_0"
new_config = deepcopy(mock_rpc_device.config)
new_config["thermostat:0"]["type"] = "cooling"
monkeypatch.setattr(mock_rpc_device, "config", new_config)
await init_integration(hass, 2, model=MODEL_WALL_DISPLAY)
state = hass.states.get(ENTITY_ID)
state = hass.states.get(entity_id)
assert state.state == HVACMode.COOL
assert state.attributes[ATTR_HVAC_ACTION] == HVACAction.COOLING
@ -730,7 +736,7 @@ async def test_wall_display_thermostat_mode(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Test Wall Display in thermostat mode."""
climate_entity_id = "climate.test_name"
climate_entity_id = "climate.test_name_thermostat_0"
switch_entity_id = "switch.test_switch_0"
await init_integration(hass, 2, model=MODEL_WALL_DISPLAY)
@ -757,7 +763,7 @@ async def test_wall_display_thermostat_mode_external_actuator(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Test Wall Display in thermostat mode with an external actuator."""
climate_entity_id = "climate.test_name"
climate_entity_id = "climate.test_name_thermostat_0"
switch_entity_id = "switch.test_switch_0"
new_status = deepcopy(mock_rpc_device.status)

View File

@ -729,14 +729,14 @@ async def test_rpc_analog_input_sensors(
await init_integration(hass, 2)
entity_id = f"{SENSOR_DOMAIN}.test_name_analog_input"
entity_id = f"{SENSOR_DOMAIN}.test_name_input_1_analog"
assert hass.states.get(entity_id).state == "89"
entry = entity_registry.async_get(entity_id)
assert entry
assert entry.unique_id == "123456789ABC-input:1-analoginput"
entity_id = f"{SENSOR_DOMAIN}.test_name_analog_value"
entity_id = f"{SENSOR_DOMAIN}.test_name_input_1_analog_value"
state = hass.states.get(entity_id)
assert state
assert state.state == "8.9"
@ -757,10 +757,10 @@ async def test_rpc_disabled_analog_input_sensors(
await init_integration(hass, 2)
entity_id = f"{SENSOR_DOMAIN}.test_name_analog_input"
entity_id = f"{SENSOR_DOMAIN}.test_name_input_1_analog"
assert hass.states.get(entity_id) is None
entity_id = f"{SENSOR_DOMAIN}.test_name_analog_value"
entity_id = f"{SENSOR_DOMAIN}.test_name_input_1_analog_value"
assert hass.states.get(entity_id) is None
@ -777,10 +777,10 @@ async def test_rpc_disabled_xpercent(
)
await init_integration(hass, 2)
entity_id = f"{SENSOR_DOMAIN}.test_name_analog_input"
entity_id = f"{SENSOR_DOMAIN}.test_name_input_1_analog"
assert hass.states.get(entity_id).state == "89"
entity_id = f"{SENSOR_DOMAIN}.test_name_analog_value"
entity_id = f"{SENSOR_DOMAIN}.test_name_input_1_analog_value"
assert hass.states.get(entity_id) is None
@ -1293,7 +1293,7 @@ async def test_rpc_rgbw_sensors(
await init_integration(hass, 2)
entity_id = "sensor.test_name_power"
entity_id = f"sensor.test_name_{light_type}_light_0_power"
state = hass.states.get(entity_id)
assert state
@ -1304,7 +1304,7 @@ async def test_rpc_rgbw_sensors(
assert entry
assert entry.unique_id == f"123456789ABC-{light_type}:0-power_{light_type}"
entity_id = "sensor.test_name_energy"
entity_id = f"sensor.test_name_{light_type}_light_0_energy"
state = hass.states.get(entity_id)
assert state
@ -1315,7 +1315,7 @@ async def test_rpc_rgbw_sensors(
assert entry
assert entry.unique_id == f"123456789ABC-{light_type}:0-energy_{light_type}"
entity_id = "sensor.test_name_current"
entity_id = f"sensor.test_name_{light_type}_light_0_current"
state = hass.states.get(entity_id)
assert state
@ -1328,7 +1328,7 @@ async def test_rpc_rgbw_sensors(
assert entry
assert entry.unique_id == f"123456789ABC-{light_type}:0-current_{light_type}"
entity_id = "sensor.test_name_voltage"
entity_id = f"sensor.test_name_{light_type}_light_0_voltage"
state = hass.states.get(entity_id)
assert state
@ -1341,7 +1341,7 @@ async def test_rpc_rgbw_sensors(
assert entry
assert entry.unique_id == f"123456789ABC-{light_type}:0-voltage_{light_type}"
entity_id = "sensor.test_name_device_temperature"
entity_id = f"sensor.test_name_{light_type}_light_0_device_temperature"
state = hass.states.get(entity_id)
assert state

View File

@ -236,7 +236,42 @@ async def test_get_block_input_triggers(
async def test_get_rpc_channel_name(mock_rpc_device: Mock) -> None:
"""Test get RPC channel name."""
assert get_rpc_channel_name(mock_rpc_device, "input:0") == "Test name input 0"
assert get_rpc_channel_name(mock_rpc_device, "input:3") == "Test name input_3"
assert get_rpc_channel_name(mock_rpc_device, "input:3") == "Test name Input 3"
@pytest.mark.parametrize(
("component", "expected"),
[
("cover", "Cover"),
("input", "Input"),
("light", "Light"),
("rgb", "RGB light"),
("rgbw", "RGBW light"),
("switch", "Switch"),
("thermostat", "Thermostat"),
],
)
async def test_get_rpc_channel_name_multiple_components(
mock_rpc_device: Mock,
monkeypatch: pytest.MonkeyPatch,
component: str,
expected: str,
) -> None:
"""Test get RPC channel name when there is more components of the same type."""
config = {
f"{component}:0": {"name": None},
f"{component}:1": {"name": None},
}
monkeypatch.setattr(mock_rpc_device, "config", config)
assert (
get_rpc_channel_name(mock_rpc_device, f"{component}:0")
== f"Test name {expected} 0"
)
assert (
get_rpc_channel_name(mock_rpc_device, f"{component}:1")
== f"Test name {expected} 1"
)
async def test_get_rpc_input_triggers(