mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 16:57:53 +00:00
Add metered PDU dynamic outlet sensors to NUT (#140179)
* Add metered PDU dynamic outlet sensors * Make deep copy and improve efficiency of loops * Improve performance by creating new dict Co-authored-by: J. Nick Koston <nick+github@koston.org> * Remove unused import copy * Use outlet name (if available) in friendly name and remove as separate sensor --------- Co-authored-by: J. Nick Koston <nick+github@koston.org>
This commit is contained in:
parent
84c6fa256c
commit
2571725eb9
@ -67,6 +67,21 @@
|
||||
"input_voltage_status": {
|
||||
"default": "mdi:information-outline"
|
||||
},
|
||||
"outlet_number_current": {
|
||||
"default": "mdi:gauge"
|
||||
},
|
||||
"outlet_number_current_status": {
|
||||
"default": "mdi:information-outline"
|
||||
},
|
||||
"outlet_number_desc": {
|
||||
"default": "mdi:information-outline"
|
||||
},
|
||||
"outlet_number_power": {
|
||||
"default": "mdi:gauge"
|
||||
},
|
||||
"outlet_number_realpower": {
|
||||
"default": "mdi:gauge"
|
||||
},
|
||||
"outlet_voltage": {
|
||||
"default": "mdi:gauge"
|
||||
},
|
||||
|
@ -1029,6 +1029,7 @@ async def async_setup_entry(
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the NUT sensors."""
|
||||
valid_sensor_types: dict[str, SensorEntityDescription]
|
||||
|
||||
pynut_data = config_entry.runtime_data
|
||||
coordinator = pynut_data.coordinator
|
||||
@ -1036,20 +1037,75 @@ async def async_setup_entry(
|
||||
unique_id = pynut_data.unique_id
|
||||
status = coordinator.data
|
||||
|
||||
resources = [sensor_id for sensor_id in SENSOR_TYPES if sensor_id in status]
|
||||
# Display status is a special case that falls back to the status value
|
||||
# of the UPS instead.
|
||||
if KEY_STATUS in resources:
|
||||
resources.append(KEY_STATUS_DISPLAY)
|
||||
# Dynamically add outlet sensors to valid sensors dictionary
|
||||
if (num_outlets := status.get("outlet.count")) is not None:
|
||||
additional_sensor_types: dict[str, SensorEntityDescription] = {}
|
||||
for outlet_num in range(1, int(num_outlets) + 1):
|
||||
outlet_num_str: str = str(outlet_num)
|
||||
outlet_name: str = (
|
||||
status.get(f"outlet.{outlet_num_str}.name") or outlet_num_str
|
||||
)
|
||||
additional_sensor_types |= {
|
||||
f"outlet.{outlet_num_str}.current": SensorEntityDescription(
|
||||
key=f"outlet.{outlet_num_str}.current",
|
||||
translation_key="outlet_number_current",
|
||||
translation_placeholders={"outlet_name": outlet_name},
|
||||
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
||||
device_class=SensorDeviceClass.CURRENT,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
f"outlet.{outlet_num_str}.current_status": SensorEntityDescription(
|
||||
key=f"outlet.{outlet_num_str}.current_status",
|
||||
translation_key="outlet_number_current_status",
|
||||
translation_placeholders={"outlet_name": outlet_name},
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
f"outlet.{outlet_num_str}.desc": SensorEntityDescription(
|
||||
key=f"outlet.{outlet_num_str}.desc",
|
||||
translation_key="outlet_number_desc",
|
||||
translation_placeholders={"outlet_name": outlet_name},
|
||||
),
|
||||
f"outlet.{outlet_num_str}.power": SensorEntityDescription(
|
||||
key=f"outlet.{outlet_num_str}.power",
|
||||
translation_key="outlet_number_power",
|
||||
translation_placeholders={"outlet_name": outlet_name},
|
||||
native_unit_of_measurement=UnitOfApparentPower.VOLT_AMPERE,
|
||||
device_class=SensorDeviceClass.APPARENT_POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
f"outlet.{outlet_num_str}.realpower": SensorEntityDescription(
|
||||
key=f"outlet.{outlet_num_str}.realpower",
|
||||
translation_key="outlet_number_realpower",
|
||||
translation_placeholders={"outlet_name": outlet_name},
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
}
|
||||
|
||||
valid_sensor_types = {**SENSOR_TYPES, **additional_sensor_types}
|
||||
else:
|
||||
valid_sensor_types = SENSOR_TYPES
|
||||
|
||||
# If device reports ambient sensors are not present, then remove
|
||||
if status.get(AMBIENT_PRESENT) == "no":
|
||||
resources = [item for item in resources if item not in AMBIENT_SENSORS]
|
||||
has_ambient_sensors: bool = status.get(AMBIENT_PRESENT) != "no"
|
||||
resources = [
|
||||
sensor_id
|
||||
for sensor_id in valid_sensor_types
|
||||
if sensor_id in status
|
||||
and (has_ambient_sensors or sensor_id not in AMBIENT_SENSORS)
|
||||
]
|
||||
|
||||
# Display status is a special case that falls back to the status value
|
||||
# of the UPS instead.
|
||||
if KEY_STATUS in status:
|
||||
resources.append(KEY_STATUS_DISPLAY)
|
||||
|
||||
async_add_entities(
|
||||
NUTSensor(
|
||||
coordinator,
|
||||
SENSOR_TYPES[sensor_type],
|
||||
valid_sensor_types[sensor_type],
|
||||
data,
|
||||
unique_id,
|
||||
)
|
||||
|
@ -157,6 +157,13 @@
|
||||
"input_l1_n_voltage": { "name": "Input L1 voltage" },
|
||||
"input_l2_n_voltage": { "name": "Input L2 voltage" },
|
||||
"input_l3_n_voltage": { "name": "Input L3 voltage" },
|
||||
"outlet_number_current": { "name": "Outlet {outlet_name} current" },
|
||||
"outlet_number_current_status": {
|
||||
"name": "Outlet {outlet_name} current status"
|
||||
},
|
||||
"outlet_number_desc": { "name": "Outlet {outlet_name} description" },
|
||||
"outlet_number_power": { "name": "Outlet {outlet_name} power" },
|
||||
"outlet_number_realpower": { "name": "Outlet {outlet_name} real power" },
|
||||
"outlet_voltage": { "name": "Outlet voltage" },
|
||||
"output_current": { "name": "Output current" },
|
||||
"output_current_nominal": { "name": "Nominal output current" },
|
||||
|
@ -12,6 +12,7 @@ from homeassistant.const import (
|
||||
CONF_RESOURCES,
|
||||
PERCENTAGE,
|
||||
STATE_UNKNOWN,
|
||||
UnitOfElectricCurrent,
|
||||
UnitOfElectricPotential,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
@ -103,7 +104,7 @@ async def test_ups_devices_with_unique_ids(
|
||||
[
|
||||
(
|
||||
"EATON-EPDU-G3",
|
||||
"EATON_ePDU MA 00U-C IN: TYPE 00A 0P OUT: 00xTYPE_A000A00000_",
|
||||
"EATON_ePDU MA 00U-C IN: TYPE 00A 0P OUT: 00xTYPE_A000A00000",
|
||||
),
|
||||
],
|
||||
)
|
||||
@ -115,11 +116,13 @@ async def test_pdu_devices_with_unique_ids(
|
||||
) -> None:
|
||||
"""Test creation of device sensors with unique ids."""
|
||||
|
||||
await _test_sensor_and_attributes(
|
||||
await async_init_integration(hass, model)
|
||||
|
||||
_test_sensor_and_attributes(
|
||||
hass,
|
||||
entity_registry,
|
||||
model,
|
||||
unique_id=f"{unique_id_base}input.voltage",
|
||||
unique_id=f"{unique_id_base}_input.voltage",
|
||||
device_id="sensor.ups1_input_voltage",
|
||||
state_value="122.91",
|
||||
expected_attributes={
|
||||
@ -130,11 +133,11 @@ async def test_pdu_devices_with_unique_ids(
|
||||
},
|
||||
)
|
||||
|
||||
await _test_sensor_and_attributes(
|
||||
_test_sensor_and_attributes(
|
||||
hass,
|
||||
entity_registry,
|
||||
model,
|
||||
unique_id=f"{unique_id_base}ambient.humidity.status",
|
||||
unique_id=f"{unique_id_base}_ambient.humidity.status",
|
||||
device_id="sensor.ups1_ambient_humidity_status",
|
||||
state_value="good",
|
||||
expected_attributes={
|
||||
@ -143,11 +146,11 @@ async def test_pdu_devices_with_unique_ids(
|
||||
},
|
||||
)
|
||||
|
||||
await _test_sensor_and_attributes(
|
||||
_test_sensor_and_attributes(
|
||||
hass,
|
||||
entity_registry,
|
||||
model,
|
||||
unique_id=f"{unique_id_base}ambient.temperature.status",
|
||||
unique_id=f"{unique_id_base}_ambient.temperature.status",
|
||||
device_id="sensor.ups1_ambient_temperature_status",
|
||||
state_value="good",
|
||||
expected_attributes={
|
||||
@ -248,7 +251,7 @@ async def test_stale_options(
|
||||
[
|
||||
(
|
||||
"EATON-EPDU-G3-AMBIENT-NOT-PRESENT",
|
||||
"EATON_ePDU MA 00U-C IN: TYPE 00A 0P OUT: 00xTYPE_A000A00000_",
|
||||
"EATON_ePDU MA 00U-C IN: TYPE 00A 0P OUT: 00xTYPE_A000A00000",
|
||||
),
|
||||
],
|
||||
)
|
||||
@ -273,3 +276,57 @@ async def test_pdu_devices_ambient_not_present(
|
||||
|
||||
entry = entity_registry.async_get("sensor.ups1_ambient_temperature_status")
|
||||
assert not entry
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("model", "unique_id_base"),
|
||||
[
|
||||
(
|
||||
"EATON-EPDU-G3",
|
||||
"EATON_ePDU MA 00U-C IN: TYPE 00A 0P OUT: 00xTYPE_A000A00000",
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_pdu_dynamic_outlets(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
model: str,
|
||||
unique_id_base: str,
|
||||
) -> None:
|
||||
"""Test for dynamically created outlet sensors."""
|
||||
|
||||
await async_init_integration(hass, model)
|
||||
|
||||
_test_sensor_and_attributes(
|
||||
hass,
|
||||
entity_registry,
|
||||
model,
|
||||
unique_id=f"{unique_id_base}_outlet.1.current",
|
||||
device_id="sensor.ups1_outlet_a1_current",
|
||||
state_value="0",
|
||||
expected_attributes={
|
||||
"device_class": SensorDeviceClass.CURRENT,
|
||||
"friendly_name": "Ups1 Outlet A1 current",
|
||||
"unit_of_measurement": UnitOfElectricCurrent.AMPERE,
|
||||
},
|
||||
)
|
||||
|
||||
_test_sensor_and_attributes(
|
||||
hass,
|
||||
entity_registry,
|
||||
model,
|
||||
unique_id=f"{unique_id_base}_outlet.24.current",
|
||||
device_id="sensor.ups1_outlet_a24_current",
|
||||
state_value="0.19",
|
||||
expected_attributes={
|
||||
"device_class": SensorDeviceClass.CURRENT,
|
||||
"friendly_name": "Ups1 Outlet A24 current",
|
||||
"unit_of_measurement": UnitOfElectricCurrent.AMPERE,
|
||||
},
|
||||
)
|
||||
|
||||
entry = entity_registry.async_get("sensor.ups1_outlet_25_current")
|
||||
assert not entry
|
||||
|
||||
entry = entity_registry.async_get("sensor.ups1_outlet_a25_current")
|
||||
assert not entry
|
||||
|
@ -82,7 +82,7 @@ async def async_init_integration(
|
||||
return entry
|
||||
|
||||
|
||||
async def _test_sensor_and_attributes(
|
||||
def _test_sensor_and_attributes(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
model: str,
|
||||
@ -91,9 +91,8 @@ async def _test_sensor_and_attributes(
|
||||
state_value: str,
|
||||
expected_attributes: dict,
|
||||
) -> None:
|
||||
"""Test creation of device sensors with unique ids."""
|
||||
"""Test all of the sensor entry attributes."""
|
||||
|
||||
await async_init_integration(hass, model)
|
||||
entry = entity_registry.async_get(device_id)
|
||||
assert entry
|
||||
assert entry.unique_id == unique_id
|
||||
|
Loading…
x
Reference in New Issue
Block a user