From db31afe019c0737e37c7afa72fa4117957e7bf82 Mon Sep 17 00:00:00 2001 From: Yuxin Wang Date: Sun, 10 Mar 2024 18:56:25 -0400 Subject: [PATCH] Migrate APCUPSD to has entity name (#112997) * Properly set entity names for APCUPSD * Add test cases to prevent future regressions * Fix tests due to the updated entity IDs * Prettify code * Remove redundant translation key --- .../components/apcupsd/binary_sensor.py | 3 +- homeassistant/components/apcupsd/sensor.py | 86 ++------ homeassistant/components/apcupsd/strings.json | 204 ++++++++++++++++++ .../components/apcupsd/test_binary_sensor.py | 11 +- tests/components/apcupsd/test_init.py | 57 ++++- tests/components/apcupsd/test_sensor.py | 91 +++++--- 6 files changed, 344 insertions(+), 108 deletions(-) diff --git a/homeassistant/components/apcupsd/binary_sensor.py b/homeassistant/components/apcupsd/binary_sensor.py index ea0308f5450..bc214e56d80 100644 --- a/homeassistant/components/apcupsd/binary_sensor.py +++ b/homeassistant/components/apcupsd/binary_sensor.py @@ -20,7 +20,6 @@ from .coordinator import APCUPSdCoordinator _LOGGER = logging.getLogger(__name__) _DESCRIPTION = BinarySensorEntityDescription( key="statflag", - name="UPS Online Status", translation_key="online_status", ) # The bit in STATFLAG that indicates the online status of the APC UPS. @@ -46,6 +45,8 @@ async def async_setup_entry( class OnlineStatus(CoordinatorEntity[APCUPSdCoordinator], BinarySensorEntity): """Representation of a UPS online status.""" + _attr_has_entity_name = True + def __init__( self, coordinator: APCUPSdCoordinator, diff --git a/homeassistant/components/apcupsd/sensor.py b/homeassistant/components/apcupsd/sensor.py index 8162653abb3..008171cfe3c 100644 --- a/homeassistant/components/apcupsd/sensor.py +++ b/homeassistant/components/apcupsd/sensor.py @@ -34,11 +34,10 @@ SENSORS: dict[str, SensorEntityDescription] = { "alarmdel": SensorEntityDescription( key="alarmdel", translation_key="alarm_delay", - name="UPS Alarm Delay", ), "ambtemp": SensorEntityDescription( key="ambtemp", - name="UPS Ambient Temperature", + translation_key="ambient_temperature", native_unit_of_measurement=UnitOfTemperature.CELSIUS, device_class=SensorDeviceClass.TEMPERATURE, state_class=SensorStateClass.MEASUREMENT, @@ -46,40 +45,34 @@ SENSORS: dict[str, SensorEntityDescription] = { "apc": SensorEntityDescription( key="apc", translation_key="apc_status", - name="UPS Status Data", entity_registry_enabled_default=False, ), "apcmodel": SensorEntityDescription( key="apcmodel", translation_key="apc_model", - name="UPS Model", entity_registry_enabled_default=False, ), "badbatts": SensorEntityDescription( key="badbatts", translation_key="bad_batteries", - name="UPS Bad Batteries", ), "battdate": SensorEntityDescription( key="battdate", translation_key="battery_replacement_date", - name="UPS Battery Replaced", ), "battstat": SensorEntityDescription( key="battstat", translation_key="battery_status", - name="UPS Battery Status", ), "battv": SensorEntityDescription( key="battv", - name="UPS Battery Voltage", + translation_key="battery_voltage", native_unit_of_measurement=UnitOfElectricPotential.VOLT, device_class=SensorDeviceClass.VOLTAGE, state_class=SensorStateClass.MEASUREMENT, ), "bcharge": SensorEntityDescription( key="bcharge", - name="UPS Battery", native_unit_of_measurement=PERCENTAGE, device_class=SensorDeviceClass.BATTERY, state_class=SensorStateClass.MEASUREMENT, @@ -87,86 +80,74 @@ SENSORS: dict[str, SensorEntityDescription] = { "cable": SensorEntityDescription( key="cable", translation_key="cable_type", - name="UPS Cable Type", entity_registry_enabled_default=False, ), "cumonbatt": SensorEntityDescription( key="cumonbatt", translation_key="total_time_on_battery", - name="UPS Total Time on Battery", state_class=SensorStateClass.TOTAL_INCREASING, ), "date": SensorEntityDescription( key="date", translation_key="date", - name="UPS Status Date", entity_registry_enabled_default=False, ), "dipsw": SensorEntityDescription( key="dipsw", translation_key="dip_switch_settings", - name="UPS Dip Switch Settings", ), "dlowbatt": SensorEntityDescription( key="dlowbatt", translation_key="low_battery_signal", - name="UPS Low Battery Signal", ), "driver": SensorEntityDescription( key="driver", translation_key="driver", - name="UPS Driver", entity_registry_enabled_default=False, ), "dshutd": SensorEntityDescription( key="dshutd", translation_key="shutdown_delay", - name="UPS Shutdown Delay", ), "dwake": SensorEntityDescription( key="dwake", translation_key="wake_delay", - name="UPS Wake Delay", ), "end apc": SensorEntityDescription( key="end apc", translation_key="date_and_time", - name="UPS Date and Time", entity_registry_enabled_default=False, ), "extbatts": SensorEntityDescription( key="extbatts", translation_key="external_batteries", - name="UPS External Batteries", ), "firmware": SensorEntityDescription( key="firmware", translation_key="firmware_version", - name="UPS Firmware Version", entity_registry_enabled_default=False, ), "hitrans": SensorEntityDescription( key="hitrans", - name="UPS Transfer High", + translation_key="transfer_high", native_unit_of_measurement=UnitOfElectricPotential.VOLT, device_class=SensorDeviceClass.VOLTAGE, ), "hostname": SensorEntityDescription( key="hostname", translation_key="hostname", - name="UPS Hostname", entity_registry_enabled_default=False, ), "humidity": SensorEntityDescription( key="humidity", - name="UPS Ambient Humidity", + translation_key="humidity", native_unit_of_measurement=PERCENTAGE, device_class=SensorDeviceClass.HUMIDITY, state_class=SensorStateClass.MEASUREMENT, ), "itemp": SensorEntityDescription( key="itemp", - name="UPS Internal Temperature", + translation_key="internal_temperature", native_unit_of_measurement=UnitOfTemperature.CELSIUS, device_class=SensorDeviceClass.TEMPERATURE, state_class=SensorStateClass.MEASUREMENT, @@ -174,29 +155,26 @@ SENSORS: dict[str, SensorEntityDescription] = { "laststest": SensorEntityDescription( key="laststest", translation_key="last_self_test", - name="UPS Last Self Test", ), "lastxfer": SensorEntityDescription( key="lastxfer", translation_key="last_transfer", - name="UPS Last Transfer", entity_registry_enabled_default=False, ), "linefail": SensorEntityDescription( key="linefail", translation_key="line_failure", - name="UPS Input Voltage Status", ), "linefreq": SensorEntityDescription( key="linefreq", - name="UPS Line Frequency", + translation_key="line_frequency", native_unit_of_measurement=UnitOfFrequency.HERTZ, device_class=SensorDeviceClass.FREQUENCY, state_class=SensorStateClass.MEASUREMENT, ), "linev": SensorEntityDescription( key="linev", - name="UPS Input Voltage", + translation_key="line_voltage", native_unit_of_measurement=UnitOfElectricPotential.VOLT, device_class=SensorDeviceClass.VOLTAGE, state_class=SensorStateClass.MEASUREMENT, @@ -204,113 +182,104 @@ SENSORS: dict[str, SensorEntityDescription] = { "loadpct": SensorEntityDescription( key="loadpct", translation_key="load_capacity", - name="UPS Load", native_unit_of_measurement=PERCENTAGE, state_class=SensorStateClass.MEASUREMENT, ), "loadapnt": SensorEntityDescription( key="loadapnt", translation_key="apparent_power", - name="UPS Load Apparent Power", native_unit_of_measurement=PERCENTAGE, ), "lotrans": SensorEntityDescription( key="lotrans", - name="UPS Transfer Low", + translation_key="transfer_low", native_unit_of_measurement=UnitOfElectricPotential.VOLT, device_class=SensorDeviceClass.VOLTAGE, ), "mandate": SensorEntityDescription( key="mandate", translation_key="manufacture_date", - name="UPS Manufacture Date", entity_registry_enabled_default=False, ), "masterupd": SensorEntityDescription( key="masterupd", translation_key="master_update", - name="UPS Master Update", ), "maxlinev": SensorEntityDescription( key="maxlinev", - name="UPS Input Voltage High", + translation_key="input_voltage_high", native_unit_of_measurement=UnitOfElectricPotential.VOLT, device_class=SensorDeviceClass.VOLTAGE, ), "maxtime": SensorEntityDescription( key="maxtime", translation_key="max_time", - name="UPS Battery Timeout", ), "mbattchg": SensorEntityDescription( key="mbattchg", translation_key="max_battery_charge", - name="UPS Battery Shutdown", native_unit_of_measurement=PERCENTAGE, ), "minlinev": SensorEntityDescription( key="minlinev", - name="UPS Input Voltage Low", + translation_key="input_voltage_low", native_unit_of_measurement=UnitOfElectricPotential.VOLT, device_class=SensorDeviceClass.VOLTAGE, ), "mintimel": SensorEntityDescription( key="mintimel", translation_key="min_time", - name="UPS Shutdown Time", ), "model": SensorEntityDescription( key="model", translation_key="model", - name="UPS Model", entity_registry_enabled_default=False, ), "nombattv": SensorEntityDescription( key="nombattv", - name="UPS Battery Nominal Voltage", + translation_key="battery_nominal_voltage", native_unit_of_measurement=UnitOfElectricPotential.VOLT, device_class=SensorDeviceClass.VOLTAGE, ), "nominv": SensorEntityDescription( key="nominv", - name="UPS Nominal Input Voltage", + translation_key="nominal_input_voltage", native_unit_of_measurement=UnitOfElectricPotential.VOLT, device_class=SensorDeviceClass.VOLTAGE, ), "nomoutv": SensorEntityDescription( key="nomoutv", - name="UPS Nominal Output Voltage", + translation_key="nominal_output_voltage", native_unit_of_measurement=UnitOfElectricPotential.VOLT, device_class=SensorDeviceClass.VOLTAGE, ), "nompower": SensorEntityDescription( key="nompower", - name="UPS Nominal Output Power", + translation_key="nominal_output_power", native_unit_of_measurement=UnitOfPower.WATT, device_class=SensorDeviceClass.POWER, ), "nomapnt": SensorEntityDescription( key="nomapnt", - name="UPS Nominal Apparent Power", + translation_key="nominal_apparent_power", native_unit_of_measurement=UnitOfApparentPower.VOLT_AMPERE, device_class=SensorDeviceClass.APPARENT_POWER, ), "numxfers": SensorEntityDescription( key="numxfers", translation_key="transfer_count", - name="UPS Transfer Count", state_class=SensorStateClass.TOTAL_INCREASING, ), "outcurnt": SensorEntityDescription( key="outcurnt", - name="UPS Output Current", + translation_key="output_current", native_unit_of_measurement=UnitOfElectricCurrent.AMPERE, device_class=SensorDeviceClass.CURRENT, state_class=SensorStateClass.MEASUREMENT, ), "outputv": SensorEntityDescription( key="outputv", - name="UPS Output Voltage", + translation_key="output_voltage", native_unit_of_measurement=UnitOfElectricPotential.VOLT, device_class=SensorDeviceClass.VOLTAGE, state_class=SensorStateClass.MEASUREMENT, @@ -318,108 +287,89 @@ SENSORS: dict[str, SensorEntityDescription] = { "reg1": SensorEntityDescription( key="reg1", translation_key="register_1_fault", - name="UPS Register 1 Fault", entity_registry_enabled_default=False, ), "reg2": SensorEntityDescription( key="reg2", translation_key="register_2_fault", - name="UPS Register 2 Fault", entity_registry_enabled_default=False, ), "reg3": SensorEntityDescription( key="reg3", translation_key="register_3_fault", - name="UPS Register 3 Fault", entity_registry_enabled_default=False, ), "retpct": SensorEntityDescription( key="retpct", translation_key="restore_capacity", - name="UPS Restore Requirement", native_unit_of_measurement=PERCENTAGE, ), "selftest": SensorEntityDescription( key="selftest", translation_key="self_test_result", - name="UPS Self Test result", ), "sense": SensorEntityDescription( key="sense", translation_key="sensitivity", - name="UPS Sensitivity", entity_registry_enabled_default=False, ), "serialno": SensorEntityDescription( key="serialno", translation_key="serial_number", - name="UPS Serial Number", entity_registry_enabled_default=False, ), "starttime": SensorEntityDescription( key="starttime", translation_key="startup_time", - name="UPS Startup Time", ), "statflag": SensorEntityDescription( key="statflag", translation_key="online_status", - name="UPS Status Flag", entity_registry_enabled_default=False, ), "status": SensorEntityDescription( key="status", translation_key="status", - name="UPS Status", ), "stesti": SensorEntityDescription( key="stesti", translation_key="self_test_interval", - name="UPS Self Test Interval", ), "timeleft": SensorEntityDescription( key="timeleft", translation_key="time_left", - name="UPS Time Left", state_class=SensorStateClass.MEASUREMENT, ), "tonbatt": SensorEntityDescription( key="tonbatt", translation_key="time_on_battery", - name="UPS Time on Battery", state_class=SensorStateClass.TOTAL_INCREASING, ), "upsmode": SensorEntityDescription( key="upsmode", translation_key="ups_mode", - name="UPS Mode", ), "upsname": SensorEntityDescription( key="upsname", translation_key="ups_name", - name="UPS Name", entity_registry_enabled_default=False, ), "version": SensorEntityDescription( key="version", translation_key="version", - name="UPS Daemon Info", entity_registry_enabled_default=False, ), "xoffbat": SensorEntityDescription( key="xoffbat", translation_key="transfer_from_battery", - name="UPS Transfer from Battery", ), "xoffbatt": SensorEntityDescription( key="xoffbatt", translation_key="transfer_from_battery", - name="UPS Transfer from Battery", ), "xonbatt": SensorEntityDescription( key="xonbatt", translation_key="transfer_to_battery", - name="UPS Transfer to Battery", ), } @@ -486,6 +436,8 @@ def infer_unit(value: str) -> tuple[str, str | None]: class APCUPSdSensor(CoordinatorEntity[APCUPSdCoordinator], SensorEntity): """Representation of a sensor entity for APCUPSd status values.""" + _attr_has_entity_name = True + def __init__( self, coordinator: APCUPSdCoordinator, diff --git a/homeassistant/components/apcupsd/strings.json b/homeassistant/components/apcupsd/strings.json index c7ebf8a0a3b..15ffb928f00 100644 --- a/homeassistant/components/apcupsd/strings.json +++ b/homeassistant/components/apcupsd/strings.json @@ -16,5 +16,209 @@ "description": "Enter the host and port on which the apcupsd NIS is being served." } } + }, + "entity": { + "binary_sensor": { + "online_status": { + "name": "Online status" + } + }, + "sensor": { + "alarm_delay": { + "name": "Alarm delay" + }, + "ambient_temperature": { + "name": "Ambient temperature" + }, + "apc_status": { + "name": "Status data" + }, + "apc_model": { + "name": "Model" + }, + "bad_batteries": { + "name": "Bad batteries" + }, + "battery_replacement_date": { + "name": "Battery replaced" + }, + "battery_status": { + "name": "Battery status" + }, + "battery_voltage": { + "name": "Battery voltage" + }, + "cable_type": { + "name": "Cable type" + }, + "total_time_on_battery": { + "name": "Total time on battery" + }, + "date": { + "name": "Status date" + }, + "dip_switch_settings": { + "name": "Dip switch settings" + }, + "low_battery_signal": { + "name": "Low battery signal" + }, + "driver": { + "name": "Driver" + }, + "shutdown_delay": { + "name": "Shutdown delay" + }, + "wake_delay": { + "name": "Wake delay" + }, + "date_and_time": { + "name": "Date and time" + }, + "external_batteries": { + "name": "External batteries" + }, + "firmware_version": { + "name": "Firmware version" + }, + "transfer_high": { + "name": "Transfer high" + }, + "hostname": { + "name": "Hostname" + }, + "humidity": { + "name": "Ambient humidity" + }, + "internal_temperature": { + "name": "Internal temperature" + }, + "last_self_test": { + "name": "Last self test" + }, + "last_transfer": { + "name": "Last transfer" + }, + "line_failure": { + "name": "Input voltage status" + }, + "line_frequency": { + "name": "Line frequency" + }, + "line_voltage": { + "name": "Input voltage" + }, + "load_capacity": { + "name": "Load" + }, + "apparent_power": { + "name": "Load apparent power" + }, + "transfer_low": { + "name": "Transfer low" + }, + "manufacture_date": { + "name": "Manufacture date" + }, + "master_update": { + "name": "Master update" + }, + "input_voltage_high": { + "name": "Input voltage high" + }, + "max_time": { + "name": "Battery timeout" + }, + "max_battery_charge": { + "name": "Battery shutdown" + }, + "input_voltage_low": { + "name": "Input voltage low" + }, + "min_time": { + "name": "Shutdown time" + }, + "model": { + "name": "Model" + }, + "battery_nominal_voltage": { + "name": "Battery nominal voltage" + }, + "nominal_input_voltage": { + "name": "Nominal input voltage" + }, + "nominal_output_voltage": { + "name": "Nominal output voltage" + }, + "nominal_output_power": { + "name": "Nominal output power" + }, + "nominal_apparent_power": { + "name": "Nominal apparent power" + }, + "transfer_count": { + "name": "Transfer count" + }, + "output_current": { + "name": "Output current" + }, + "output_voltage": { + "name": "Output voltage" + }, + "register_1_fault": { + "name": "Register 1 fault" + }, + "register_2_fault": { + "name": "Register 2 fault" + }, + "register_3_fault": { + "name": "Register 3 fault" + }, + "restore_capacity": { + "name": "Restore requirement" + }, + "self_test_result": { + "name": "Self test result" + }, + "sensitivity": { + "name": "Sensitivity" + }, + "serial_number": { + "name": "Serial number" + }, + "startup_time": { + "name": "Startup time" + }, + "online_status": { + "name": "Status flag" + }, + "status": { + "name": "Status" + }, + "self_test_interval": { + "name": "Self test interval" + }, + "time_left": { + "name": "Time left" + }, + "time_on_battery": { + "name": "Time on battery" + }, + "ups_mode": { + "name": "Mode" + }, + "ups_name": { + "name": "Name" + }, + "version": { + "name": "Daemon version" + }, + "transfer_from_battery": { + "name": "Transfer from battery" + }, + "transfer_to_battery": { + "name": "Transfer to battery" + } + } } } diff --git a/tests/components/apcupsd/test_binary_sensor.py b/tests/components/apcupsd/test_binary_sensor.py index 21d972ec0ea..7616a960b21 100644 --- a/tests/components/apcupsd/test_binary_sensor.py +++ b/tests/components/apcupsd/test_binary_sensor.py @@ -2,6 +2,7 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er +from homeassistant.util import slugify from . import MOCK_STATUS, async_init_integration @@ -12,12 +13,13 @@ async def test_binary_sensor( """Test states of binary sensor.""" await async_init_integration(hass, status=MOCK_STATUS) - state = hass.states.get("binary_sensor.ups_online_status") + device_slug, serialno = slugify(MOCK_STATUS["UPSNAME"]), MOCK_STATUS["SERIALNO"] + state = hass.states.get(f"binary_sensor.{device_slug}_online_status") assert state assert state.state == "on" - entry = entity_registry.async_get("binary_sensor.ups_online_status") + entry = entity_registry.async_get(f"binary_sensor.{device_slug}_online_status") assert entry - assert entry.unique_id == "XXXXXXXXXXXX_statflag" + assert entry.unique_id == f"{serialno}_statflag" async def test_no_binary_sensor(hass: HomeAssistant) -> None: @@ -26,5 +28,6 @@ async def test_no_binary_sensor(hass: HomeAssistant) -> None: status.pop("STATFLAG") await async_init_integration(hass, status=status) - state = hass.states.get("binary_sensor.ups_online_status") + device_slug = slugify(MOCK_STATUS["UPSNAME"]) + state = hass.states.get(f"binary_sensor.{device_slug}_online_status") assert state is None diff --git a/tests/components/apcupsd/test_init.py b/tests/components/apcupsd/test_init.py index e6db0adc4c7..4a579cdb899 100644 --- a/tests/components/apcupsd/test_init.py +++ b/tests/components/apcupsd/test_init.py @@ -12,23 +12,41 @@ from homeassistant.config_entries import SOURCE_USER, ConfigEntryState from homeassistant.const import STATE_UNAVAILABLE from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr -from homeassistant.util import utcnow +from homeassistant.util import slugify, utcnow from . import CONF_DATA, MOCK_MINIMAL_STATUS, MOCK_STATUS, async_init_integration from tests.common import MockConfigEntry, async_fire_time_changed -@pytest.mark.parametrize("status", (MOCK_STATUS, MOCK_MINIMAL_STATUS)) +@pytest.mark.parametrize( + "status", + ( + # Contains "SERIALNO" and "UPSNAME" fields. + # We should create devices for the entities and prefix their IDs with "MyUPS". + MOCK_STATUS, + # Contains "SERIALNO" but no "UPSNAME" field. + # We should create devices for the entities and prefix their IDs with default "APC UPS". + MOCK_MINIMAL_STATUS | {"SERIALNO": "XXXX"}, + # Does not contain either "SERIALNO" field. + # We should _not_ create devices for the entities and their IDs will not have prefixes. + MOCK_MINIMAL_STATUS, + ), +) async def test_async_setup_entry(hass: HomeAssistant, status: OrderedDict) -> None: """Test a successful setup entry.""" # Minimal status does not contain "SERIALNO" field, which is used to determine the # unique ID of this integration. But, the integration should work fine without it. + # In such a case, the device will not be added either await async_init_integration(hass, status=status) + prefix = "" + if "SERIALNO" in status: + prefix = slugify(status.get("UPSNAME", "APC UPS")) + "_" + # Verify successful setup by querying the status sensor. - state = hass.states.get("binary_sensor.ups_online_status") - assert state is not None + state = hass.states.get(f"binary_sensor.{prefix}online_status") + assert state assert state.state != STATE_UNAVAILABLE assert state.state == "on" @@ -93,12 +111,32 @@ async def test_multiple_integrations(hass: HomeAssistant) -> None: assert len(hass.config_entries.async_entries(DOMAIN)) == 2 assert all(entry.state is ConfigEntryState.LOADED for entry in entries) - state1 = hass.states.get("sensor.ups_load") - state2 = hass.states.get("sensor.ups_load_2") + # Since the two UPS device names are the same, we will have to add a "_2" suffix. + device_slug = slugify(MOCK_STATUS["UPSNAME"]) + state1 = hass.states.get(f"sensor.{device_slug}_load") + state2 = hass.states.get(f"sensor.{device_slug}_load_2") assert state1 is not None and state2 is not None assert state1.state != state2.state +async def test_multiple_integrations_different_devices(hass: HomeAssistant) -> None: + """Test successful setup for multiple entries with different device names.""" + status1 = MOCK_STATUS | {"SERIALNO": "XXXXX1", "UPSNAME": "MyUPS1"} + status2 = MOCK_STATUS | {"SERIALNO": "XXXXX2", "UPSNAME": "MyUPS2"} + entries = ( + await async_init_integration(hass, host="test1", status=status1), + await async_init_integration(hass, host="test2", status=status2), + ) + + assert len(hass.config_entries.async_entries(DOMAIN)) == 2 + assert all(entry.state is ConfigEntryState.LOADED for entry in entries) + + # The device names are different, so they are prefixed differently. + state1 = hass.states.get("sensor.myups1_load") + state2 = hass.states.get("sensor.myups2_load") + assert state1 is not None and state2 is not None + + @pytest.mark.parametrize( "error", (OSError(), asyncio.IncompleteReadError(partial=b"", expected=0)), @@ -154,7 +192,8 @@ async def test_availability(hass: HomeAssistant) -> None: """Ensure that we mark the entity's availability properly when network is down / back up.""" await async_init_integration(hass) - state = hass.states.get("sensor.ups_load") + device_slug = slugify(MOCK_STATUS["UPSNAME"]) + state = hass.states.get(f"sensor.{device_slug}_load") assert state assert state.state != STATE_UNAVAILABLE assert pytest.approx(float(state.state)) == 14.0 @@ -167,7 +206,7 @@ async def test_availability(hass: HomeAssistant) -> None: await hass.async_block_till_done() # Sensors should be marked as unavailable. - state = hass.states.get("sensor.ups_load") + state = hass.states.get(f"sensor.{device_slug}_load") assert state assert state.state == STATE_UNAVAILABLE @@ -179,7 +218,7 @@ async def test_availability(hass: HomeAssistant) -> None: await hass.async_block_till_done() # Sensors should be online now with the new value. - state = hass.states.get("sensor.ups_load") + state = hass.states.get(f"sensor.{device_slug}_load") assert state assert state.state != STATE_UNAVAILABLE assert pytest.approx(float(state.state)) == 15.0 diff --git a/tests/components/apcupsd/test_sensor.py b/tests/components/apcupsd/test_sensor.py index 24aae1d3937..77987e455c7 100644 --- a/tests/components/apcupsd/test_sensor.py +++ b/tests/components/apcupsd/test_sensor.py @@ -22,6 +22,7 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component +from homeassistant.util import slugify from homeassistant.util.dt import utcnow from . import MOCK_STATUS, async_init_integration @@ -32,17 +33,18 @@ from tests.common import async_fire_time_changed async def test_sensor(hass: HomeAssistant, entity_registry: er.EntityRegistry) -> None: """Test states of sensor.""" await async_init_integration(hass, status=MOCK_STATUS) + device_slug, serialno = slugify(MOCK_STATUS["UPSNAME"]), MOCK_STATUS["SERIALNO"] # Test a representative string sensor. - state = hass.states.get("sensor.ups_mode") + state = hass.states.get(f"sensor.{device_slug}_mode") assert state assert state.state == "Stand Alone" - entry = entity_registry.async_get("sensor.ups_mode") + entry = entity_registry.async_get(f"sensor.{device_slug}_mode") assert entry - assert entry.unique_id == "XXXXXXXXXXXX_upsmode" + assert entry.unique_id == f"{serialno}_upsmode" # Test two representative voltage sensors. - state = hass.states.get("sensor.ups_input_voltage") + state = hass.states.get(f"sensor.{device_slug}_input_voltage") assert state assert state.state == "124.0" assert ( @@ -50,11 +52,11 @@ async def test_sensor(hass: HomeAssistant, entity_registry: er.EntityRegistry) - ) assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.MEASUREMENT assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.VOLTAGE - entry = entity_registry.async_get("sensor.ups_input_voltage") + entry = entity_registry.async_get(f"sensor.{device_slug}_input_voltage") assert entry - assert entry.unique_id == "XXXXXXXXXXXX_linev" + assert entry.unique_id == f"{serialno}_linev" - state = hass.states.get("sensor.ups_battery_voltage") + state = hass.states.get(f"sensor.{device_slug}_battery_voltage") assert state assert state.state == "13.7" assert ( @@ -62,38 +64,59 @@ async def test_sensor(hass: HomeAssistant, entity_registry: er.EntityRegistry) - ) assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.MEASUREMENT assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.VOLTAGE - entry = entity_registry.async_get("sensor.ups_battery_voltage") + entry = entity_registry.async_get(f"sensor.{device_slug}_battery_voltage") assert entry - assert entry.unique_id == "XXXXXXXXXXXX_battv" + assert entry.unique_id == f"{serialno}_battv" - # test a representative time sensor. - state = hass.states.get("sensor.ups_self_test_interval") + # Test a representative time sensor. + state = hass.states.get(f"sensor.{device_slug}_self_test_interval") assert state assert state.state == "7" assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfTime.DAYS - entry = entity_registry.async_get("sensor.ups_self_test_interval") + entry = entity_registry.async_get(f"sensor.{device_slug}_self_test_interval") assert entry - assert entry.unique_id == "XXXXXXXXXXXX_stesti" + assert entry.unique_id == f"{serialno}_stesti" # Test a representative percentage sensor. - state = hass.states.get("sensor.ups_load") + state = hass.states.get(f"sensor.{device_slug}_load") assert state assert state.state == "14.0" assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == PERCENTAGE assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.MEASUREMENT - entry = entity_registry.async_get("sensor.ups_load") + entry = entity_registry.async_get(f"sensor.{device_slug}_load") assert entry - assert entry.unique_id == "XXXXXXXXXXXX_loadpct" + assert entry.unique_id == f"{serialno}_loadpct" # Test a representative wattage sensor. - state = hass.states.get("sensor.ups_nominal_output_power") + state = hass.states.get(f"sensor.{device_slug}_nominal_output_power") assert state assert state.state == "330" assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfPower.WATT assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.POWER - entry = entity_registry.async_get("sensor.ups_nominal_output_power") + entry = entity_registry.async_get(f"sensor.{device_slug}_nominal_output_power") assert entry - assert entry.unique_id == "XXXXXXXXXXXX_nompower" + assert entry.unique_id == f"{serialno}_nompower" + + +async def test_sensor_name(hass: HomeAssistant) -> None: + """Test if sensor name follows the recommended entity naming scheme. + + See https://developers.home-assistant.io/docs/core/entity/#entity-naming for more details. + """ + await async_init_integration(hass, status=MOCK_STATUS) + + all_states = hass.states.async_all() + assert len(all_states) != 0 + + device_name = MOCK_STATUS["UPSNAME"] + for state in all_states: + # Friendly name must start with the device name. + friendly_name = state.name + assert friendly_name.startswith(device_name) + + # Entity names should start with a capital letter, the rest of the words are lower case. + entity_name = friendly_name.removeprefix(device_name).strip() + assert entity_name == entity_name.capitalize() async def test_sensor_disabled( @@ -102,10 +125,11 @@ async def test_sensor_disabled( """Test sensor disabled by default.""" await async_init_integration(hass) + device_slug, serialno = slugify(MOCK_STATUS["UPSNAME"]), MOCK_STATUS["SERIALNO"] # Test a representative integration-disabled sensor. - entry = entity_registry.async_get("sensor.ups_model") + entry = entity_registry.async_get(f"sensor.{device_slug}_model") assert entry.disabled - assert entry.unique_id == "XXXXXXXXXXXX_model" + assert entry.unique_id == f"{serialno}_model" assert entry.disabled_by is er.RegistryEntryDisabler.INTEGRATION # Test enabling entity. @@ -121,7 +145,8 @@ async def test_state_update(hass: HomeAssistant) -> None: """Ensure the sensor state changes after updating the data.""" await async_init_integration(hass) - state = hass.states.get("sensor.ups_load") + device_slug = slugify(MOCK_STATUS["UPSNAME"]) + state = hass.states.get(f"sensor.{device_slug}_load") assert state assert state.state != STATE_UNAVAILABLE assert state.state == "14.0" @@ -132,7 +157,7 @@ async def test_state_update(hass: HomeAssistant) -> None: async_fire_time_changed(hass, future) await hass.async_block_till_done() - state = hass.states.get("sensor.ups_load") + state = hass.states.get(f"sensor.{device_slug}_load") assert state assert state.state != STATE_UNAVAILABLE assert state.state == "15.0" @@ -142,8 +167,9 @@ async def test_manual_update_entity(hass: HomeAssistant) -> None: """Test manual update entity via service homeassistant/update_entity.""" await async_init_integration(hass) + device_slug = slugify(MOCK_STATUS["UPSNAME"]) # Assert the initial state of sensor.ups_load. - state = hass.states.get("sensor.ups_load") + state = hass.states.get(f"sensor.{device_slug}_load") assert state assert state.state != STATE_UNAVAILABLE assert state.state == "14.0" @@ -163,7 +189,12 @@ async def test_manual_update_entity(hass: HomeAssistant) -> None: await hass.services.async_call( "homeassistant", "update_entity", - {ATTR_ENTITY_ID: ["sensor.ups_load", "sensor.ups_battery"]}, + { + ATTR_ENTITY_ID: [ + f"sensor.{device_slug}_load", + f"sensor.{device_slug}_battery", + ] + }, blocking=True, ) # Even if we requested updates for two entities, our integration should smartly @@ -171,7 +202,7 @@ async def test_manual_update_entity(hass: HomeAssistant) -> None: assert mock_request_status.call_count == 1 # The new state should be effective. - state = hass.states.get("sensor.ups_load") + state = hass.states.get(f"sensor.{device_slug}_load") assert state assert state.state != STATE_UNAVAILABLE assert state.state == "15.0" @@ -184,6 +215,7 @@ async def test_multiple_manual_update_entity(hass: HomeAssistant) -> None: """ await async_init_integration(hass) + device_slug = slugify(MOCK_STATUS["UPSNAME"]) # Setup HASS for calling the update_entity service. await async_setup_component(hass, "homeassistant", {}) @@ -196,7 +228,12 @@ async def test_multiple_manual_update_entity(hass: HomeAssistant) -> None: await hass.services.async_call( "homeassistant", "update_entity", - {ATTR_ENTITY_ID: ["sensor.ups_load", "sensor.ups_input_voltage"]}, + { + ATTR_ENTITY_ID: [ + f"sensor.{device_slug}_load", + f"sensor.{device_slug}_input_voltage", + ] + }, blocking=True, ) assert mock_request_status.call_count == 1