Add ambient temperature and humidity status sensors to NUT (#124181)

Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
tdfountain 2025-02-24 06:09:43 -08:00 committed by GitHub
parent 5025e31129
commit 51a881f3b5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 724 additions and 9 deletions

4
CODEOWNERS generated
View File

@ -1051,8 +1051,8 @@ build.json @home-assistant/supervisor
/tests/components/numato/ @clssn /tests/components/numato/ @clssn
/homeassistant/components/number/ @home-assistant/core @Shulyaka /homeassistant/components/number/ @home-assistant/core @Shulyaka
/tests/components/number/ @home-assistant/core @Shulyaka /tests/components/number/ @home-assistant/core @Shulyaka
/homeassistant/components/nut/ @bdraco @ollo69 @pestevez /homeassistant/components/nut/ @bdraco @ollo69 @pestevez @tdfountain
/tests/components/nut/ @bdraco @ollo69 @pestevez /tests/components/nut/ @bdraco @ollo69 @pestevez @tdfountain
/homeassistant/components/nws/ @MatthewFlamm @kamiyo /homeassistant/components/nws/ @MatthewFlamm @kamiyo
/tests/components/nws/ @MatthewFlamm @kamiyo /tests/components/nws/ @MatthewFlamm @kamiyo
/homeassistant/components/nyt_games/ @joostlek /homeassistant/components/nyt_games/ @joostlek

View File

@ -39,8 +39,8 @@ async def async_get_config_entry_diagnostics(
hass_device = device_registry.async_get_device( hass_device = device_registry.async_get_device(
identifiers={(DOMAIN, hass_data.unique_id)} identifiers={(DOMAIN, hass_data.unique_id)}
) )
if not hass_device: # Device is always created
return data assert hass_device is not None
data["device"] = { data["device"] = {
**attr.asdict(hass_device), **attr.asdict(hass_device),

View File

@ -1,6 +1,12 @@
{ {
"entity": { "entity": {
"sensor": { "sensor": {
"ambient_humidity_status": {
"default": "mdi:information-outline"
},
"ambient_temperature_status": {
"default": "mdi:information-outline"
},
"battery_alarm_threshold": { "battery_alarm_threshold": {
"default": "mdi:information-outline" "default": "mdi:information-outline"
}, },

View File

@ -1,7 +1,7 @@
{ {
"domain": "nut", "domain": "nut",
"name": "Network UPS Tools (NUT)", "name": "Network UPS Tools (NUT)",
"codeowners": ["@bdraco", "@ollo69", "@pestevez"], "codeowners": ["@bdraco", "@ollo69", "@pestevez", "@tdfountain"],
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/nut", "documentation": "https://www.home-assistant.io/integrations/nut",
"integration_type": "device", "integration_type": "device",

View File

@ -46,8 +46,17 @@ NUT_DEV_INFO_TO_DEV_INFO: dict[str, str] = {
"serial": ATTR_SERIAL_NUMBER, "serial": ATTR_SERIAL_NUMBER,
} }
AMBIENT_THRESHOLD_STATUS_OPTIONS = [
"good",
"warning-low",
"critical-low",
"warning-high",
"critical-high",
]
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
SENSOR_TYPES: Final[dict[str, SensorEntityDescription]] = { SENSOR_TYPES: Final[dict[str, SensorEntityDescription]] = {
"ups.status.display": SensorEntityDescription( "ups.status.display": SensorEntityDescription(
key="ups.status.display", key="ups.status.display",
@ -930,6 +939,13 @@ SENSOR_TYPES: Final[dict[str, SensorEntityDescription]] = {
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
), ),
"ambient.humidity.status": SensorEntityDescription(
key="ambient.humidity.status",
translation_key="ambient_humidity_status",
device_class=SensorDeviceClass.ENUM,
options=AMBIENT_THRESHOLD_STATUS_OPTIONS,
entity_category=EntityCategory.DIAGNOSTIC,
),
"ambient.temperature": SensorEntityDescription( "ambient.temperature": SensorEntityDescription(
key="ambient.temperature", key="ambient.temperature",
translation_key="ambient_temperature", translation_key="ambient_temperature",
@ -938,6 +954,13 @@ SENSOR_TYPES: Final[dict[str, SensorEntityDescription]] = {
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
), ),
"ambient.temperature.status": SensorEntityDescription(
key="ambient.temperature.status",
translation_key="ambient_temperature_status",
device_class=SensorDeviceClass.ENUM,
options=AMBIENT_THRESHOLD_STATUS_OPTIONS,
entity_category=EntityCategory.DIAGNOSTIC,
),
"watts": SensorEntityDescription( "watts": SensorEntityDescription(
key="watts", key="watts",
translation_key="watts", translation_key="watts",

View File

@ -80,7 +80,9 @@
"entity": { "entity": {
"sensor": { "sensor": {
"ambient_humidity": { "name": "Ambient humidity" }, "ambient_humidity": { "name": "Ambient humidity" },
"ambient_humidity_status": { "name": "Ambient humidity status" },
"ambient_temperature": { "name": "Ambient temperature" }, "ambient_temperature": { "name": "Ambient temperature" },
"ambient_temperature_status": { "name": "Ambient temperature status" },
"battery_alarm_threshold": { "name": "Battery alarm threshold" }, "battery_alarm_threshold": { "name": "Battery alarm threshold" },
"battery_capacity": { "name": "Battery capacity" }, "battery_capacity": { "name": "Battery capacity" },
"battery_charge": { "name": "Battery charge" }, "battery_charge": { "name": "Battery charge" },

View File

@ -0,0 +1,5 @@
"""NUT session fixtures."""
import pytest
pytest.register_assert_rewrite("tests.components.nut.util")

View File

@ -0,0 +1,539 @@
{
"ambient.contacts.1.status": "opened",
"ambient.contacts.2.status": "opened",
"ambient.count": "0",
"ambient.humidity": "29.90",
"ambient.humidity.high": "90",
"ambient.humidity.high.critical": "90",
"ambient.humidity.high.warning": "65",
"ambient.humidity.low": "10",
"ambient.humidity.low.critical": "10",
"ambient.humidity.low.warning": "20",
"ambient.humidity.status": "good",
"ambient.present": "yes",
"ambient.temperature": "28.9",
"ambient.temperature.high": "43.30",
"ambient.temperature.high.critical": "43.30",
"ambient.temperature.high.warning": "37.70",
"ambient.temperature.low": "5",
"ambient.temperature.low.critical": "5",
"ambient.temperature.low.warning": "10",
"ambient.temperature.status": "good",
"device.contact": "Contact Name",
"device.count": "1",
"device.description": "ePDU MA 00U-C IN: TYPE 00A 0P OUT: 00xTYPE",
"device.location": "Device Location",
"device.macaddr": "00 00 00 FF FF FF ",
"device.mfr": "EATON",
"device.model": "ePDU MA 00U-C IN: TYPE 00A 0P OUT: 00xTYPE",
"device.part": "EMA000-00",
"device.serial": "A000A00000",
"device.type": "pdu",
"driver.debug": "0",
"driver.flag.allow_killpower": "0",
"driver.name": "snmp-ups",
"driver.parameter.pollinterval": "2",
"driver.parameter.port": "eaton-pdu",
"driver.parameter.synchronous": "auto",
"driver.state": "dumping",
"driver.version": "2.8.2.882-882-g63d90ebcb",
"driver.version.data": "eaton_epdu MIB 0.69",
"driver.version.internal": "1.31",
"input.current": "4.30",
"input.current.high.critical": "16",
"input.current.high.warning": "12.80",
"input.current.low.warning": "0",
"input.current.nominal": "16",
"input.current.status": "good",
"input.feed.color": "0",
"input.feed.desc": "Feed A",
"input.frequency": "60",
"input.frequency.status": "good",
"input.L1.current": "4.30",
"input.L1.current.high.critical": "16",
"input.L1.current.high.warning": "12.80",
"input.L1.current.low.warning": "0",
"input.L1.current.nominal": "16",
"input.L1.current.status": "good",
"input.L1.load": "26",
"input.L1.power": "529",
"input.L1.realpower": "482",
"input.L1.voltage": "122.91",
"input.L1.voltage.high.critical": "140",
"input.L1.voltage.high.warning": "130",
"input.L1.voltage.low.critical": "90",
"input.L1.voltage.low.warning": "95",
"input.L1.voltage.status": "good",
"input.load": "26",
"input.phases": "1",
"input.power": "532",
"input.realpower": "482",
"input.realpower.nominal": "1920",
"input.voltage": "122.91",
"input.voltage.high.critical": "140",
"input.voltage.high.warning": "130",
"input.voltage.low.critical": "90",
"input.voltage.low.warning": "95",
"input.voltage.status": "good",
"outlet.1.current": "0",
"outlet.1.current.high.critical": "16",
"outlet.1.current.high.warning": "12.80",
"outlet.1.current.low.warning": "0",
"outlet.1.current.status": "good",
"outlet.1.delay.shutdown": "120",
"outlet.1.delay.start": "1",
"outlet.1.desc": "Outlet A1",
"outlet.1.groupid": "1",
"outlet.1.id": "1",
"outlet.1.name": "A1",
"outlet.1.power": "0",
"outlet.1.realpower": "0",
"outlet.1.status": "on",
"outlet.1.switchable": "yes",
"outlet.1.timer.shutdown": "-1",
"outlet.1.timer.start": "-1",
"outlet.1.type": "nema520",
"outlet.10.current": "0.26",
"outlet.10.current.high.critical": "16",
"outlet.10.current.high.warning": "12.80",
"outlet.10.current.low.warning": "0",
"outlet.10.current.status": "good",
"outlet.10.delay.shutdown": "120",
"outlet.10.delay.start": "10",
"outlet.10.desc": "Outlet A10",
"outlet.10.groupid": "1",
"outlet.10.id": "10",
"outlet.10.name": "A10",
"outlet.10.power": "32",
"outlet.10.realpower": "15",
"outlet.10.status": "on",
"outlet.10.switchable": "yes",
"outlet.10.timer.shutdown": "-1",
"outlet.10.timer.start": "-1",
"outlet.10.type": "nema520",
"outlet.11.current": "0.24",
"outlet.11.current.high.critical": "16",
"outlet.11.current.high.warning": "12.80",
"outlet.11.current.low.warning": "0",
"outlet.11.current.status": "good",
"outlet.11.delay.shutdown": "120",
"outlet.11.delay.start": "11",
"outlet.11.desc": "Outlet A11",
"outlet.11.groupid": "1",
"outlet.11.id": "11",
"outlet.11.name": "A11",
"outlet.11.power": "29",
"outlet.11.realpower": "22",
"outlet.11.status": "on",
"outlet.11.switchable": "yes",
"outlet.11.timer.shutdown": "-1",
"outlet.11.timer.start": "-1",
"outlet.11.type": "nema520",
"outlet.12.current": "0",
"outlet.12.current.high.critical": "16",
"outlet.12.current.high.warning": "12.80",
"outlet.12.current.low.warning": "0",
"outlet.12.current.status": "good",
"outlet.12.delay.shutdown": "120",
"outlet.12.delay.start": "12",
"outlet.12.desc": "Outlet A12",
"outlet.12.groupid": "1",
"outlet.12.id": "12",
"outlet.12.name": "A12",
"outlet.12.power": "0",
"outlet.12.realpower": "0",
"outlet.12.status": "on",
"outlet.12.switchable": "yes",
"outlet.12.timer.shutdown": "-1",
"outlet.12.timer.start": "-1",
"outlet.12.type": "nema520",
"outlet.13.current": "0.23",
"outlet.13.current.high.critical": "16",
"outlet.13.current.high.warning": "12.80",
"outlet.13.current.low.warning": "0",
"outlet.13.current.status": "good",
"outlet.13.delay.shutdown": "0",
"outlet.13.delay.start": "0",
"outlet.13.desc": "Outlet A13",
"outlet.13.groupid": "1",
"outlet.13.id": "0",
"outlet.13.name": "A13",
"outlet.13.power": "27",
"outlet.13.realpower": "9",
"outlet.13.status": "on",
"outlet.13.switchable": "yes",
"outlet.13.timer.shutdown": "-1",
"outlet.13.timer.start": "-1",
"outlet.13.type": "nema520",
"outlet.14.current": "0.10",
"outlet.14.current.high.critical": "16",
"outlet.14.current.high.warning": "12.80",
"outlet.14.current.low.warning": "0",
"outlet.14.current.status": "good",
"outlet.14.delay.shutdown": "120",
"outlet.14.delay.start": "14",
"outlet.14.desc": "Outlet A14",
"outlet.14.groupid": "1",
"outlet.14.id": "14",
"outlet.14.name": "A14",
"outlet.14.power": "12",
"outlet.14.realpower": "7",
"outlet.14.status": "on",
"outlet.14.switchable": "yes",
"outlet.14.timer.shutdown": "-1",
"outlet.14.timer.start": "-1",
"outlet.14.type": "nema520",
"outlet.15.current": "0.03",
"outlet.15.current.high.critical": "16",
"outlet.15.current.high.warning": "12.80",
"outlet.15.current.low.warning": "0",
"outlet.15.current.status": "good",
"outlet.15.delay.shutdown": "120",
"outlet.15.delay.start": "15",
"outlet.15.desc": "Outlet A15",
"outlet.15.groupid": "1",
"outlet.15.id": "15",
"outlet.15.name": "A15",
"outlet.15.power": "3",
"outlet.15.realpower": "1",
"outlet.15.status": "on",
"outlet.15.switchable": "yes",
"outlet.15.timer.shutdown": "-1",
"outlet.15.timer.start": "-1",
"outlet.15.type": "nema520",
"outlet.16.current": "0.04",
"outlet.16.current.high.critical": "16",
"outlet.16.current.high.warning": "12.80",
"outlet.16.current.low.warning": "0",
"outlet.16.current.status": "good",
"outlet.16.delay.shutdown": "120",
"outlet.16.delay.start": "16",
"outlet.16.desc": "Outlet A16",
"outlet.16.groupid": "1",
"outlet.16.id": "16",
"outlet.16.name": "A16",
"outlet.16.power": "4",
"outlet.16.realpower": "1",
"outlet.16.status": "on",
"outlet.16.switchable": "yes",
"outlet.16.timer.shutdown": "-1",
"outlet.16.timer.start": "-1",
"outlet.16.type": "nema520",
"outlet.17.current": "0.19",
"outlet.17.current.high.critical": "16",
"outlet.17.current.high.warning": "12.80",
"outlet.17.current.low.warning": "0",
"outlet.17.current.status": "good",
"outlet.17.delay.shutdown": "0",
"outlet.17.delay.start": "0",
"outlet.17.desc": "Outlet A17",
"outlet.17.groupid": "1",
"outlet.17.id": "0",
"outlet.17.name": "A17",
"outlet.17.power": "23",
"outlet.17.realpower": "5",
"outlet.17.status": "on",
"outlet.17.switchable": "yes",
"outlet.17.timer.shutdown": "-1",
"outlet.17.timer.start": "-1",
"outlet.17.type": "nema520",
"outlet.18.current": "0.35",
"outlet.18.current.high.critical": "16",
"outlet.18.current.high.warning": "12.80",
"outlet.18.current.low.warning": "0",
"outlet.18.current.status": "good",
"outlet.18.delay.shutdown": "0",
"outlet.18.delay.start": "0",
"outlet.18.desc": "Outlet A18",
"outlet.18.groupid": "1",
"outlet.18.id": "0",
"outlet.18.name": "A18",
"outlet.18.power": "42",
"outlet.18.realpower": "34",
"outlet.18.status": "on",
"outlet.18.switchable": "yes",
"outlet.18.timer.shutdown": "-1",
"outlet.18.timer.start": "-1",
"outlet.18.type": "nema520",
"outlet.19.current": "0.12",
"outlet.19.current.high.critical": "16",
"outlet.19.current.high.warning": "12.80",
"outlet.19.current.low.warning": "0",
"outlet.19.current.status": "good",
"outlet.19.delay.shutdown": "0",
"outlet.19.delay.start": "0",
"outlet.19.desc": "Outlet A19",
"outlet.19.groupid": "1",
"outlet.19.id": "0",
"outlet.19.name": "A19",
"outlet.19.power": "15",
"outlet.19.realpower": "6",
"outlet.19.status": "on",
"outlet.19.switchable": "yes",
"outlet.19.timer.shutdown": "-1",
"outlet.19.timer.start": "-1",
"outlet.19.type": "nema520",
"outlet.2.current": "0.39",
"outlet.2.current.high.critical": "16",
"outlet.2.current.high.warning": "12.80",
"outlet.2.current.low.warning": "0",
"outlet.2.current.status": "good",
"outlet.2.delay.shutdown": "120",
"outlet.2.delay.start": "2",
"outlet.2.desc": "Outlet A2",
"outlet.2.groupid": "1",
"outlet.2.id": "2",
"outlet.2.name": "A2",
"outlet.2.power": "47",
"outlet.2.realpower": "43",
"outlet.2.status": "on",
"outlet.2.switchable": "yes",
"outlet.2.timer.shutdown": "-1",
"outlet.2.timer.start": "-1",
"outlet.2.type": "nema520",
"outlet.20.current": "0",
"outlet.20.current.high.critical": "16",
"outlet.20.current.high.warning": "12.80",
"outlet.20.current.low.warning": "0",
"outlet.20.current.status": "good",
"outlet.20.delay.shutdown": "120",
"outlet.20.delay.start": "20",
"outlet.20.desc": "Outlet A20",
"outlet.20.groupid": "1",
"outlet.20.id": "20",
"outlet.20.name": "A20",
"outlet.20.power": "0",
"outlet.20.realpower": "0",
"outlet.20.status": "on",
"outlet.20.switchable": "yes",
"outlet.20.timer.shutdown": "-1",
"outlet.20.timer.start": "-1",
"outlet.20.type": "nema520",
"outlet.21.current": "0",
"outlet.21.current.high.critical": "16",
"outlet.21.current.high.warning": "12.80",
"outlet.21.current.low.warning": "0",
"outlet.21.current.status": "good",
"outlet.21.delay.shutdown": "120",
"outlet.21.delay.start": "21",
"outlet.21.desc": "Outlet A21",
"outlet.21.groupid": "1",
"outlet.21.id": "21",
"outlet.21.name": "A21",
"outlet.21.power": "0",
"outlet.21.realpower": "0",
"outlet.21.status": "on",
"outlet.21.switchable": "yes",
"outlet.21.timer.shutdown": "-1",
"outlet.21.timer.start": "-1",
"outlet.21.type": "nema520",
"outlet.22.current": "0",
"outlet.22.current.high.critical": "16",
"outlet.22.current.high.warning": "12.80",
"outlet.22.current.low.warning": "0",
"outlet.22.current.status": "good",
"outlet.22.delay.shutdown": "0",
"outlet.22.delay.start": "0",
"outlet.22.desc": "Outlet A22",
"outlet.22.groupid": "1",
"outlet.22.id": "0",
"outlet.22.name": "A22",
"outlet.22.power": "0",
"outlet.22.realpower": "0",
"outlet.22.status": "on",
"outlet.22.switchable": "yes",
"outlet.22.timer.shutdown": "-1",
"outlet.22.timer.start": "-1",
"outlet.22.type": "nema520",
"outlet.23.current": "0.34",
"outlet.23.current.high.critical": "16",
"outlet.23.current.high.warning": "12.80",
"outlet.23.current.low.warning": "0",
"outlet.23.current.status": "good",
"outlet.23.delay.shutdown": "120",
"outlet.23.delay.start": "23",
"outlet.23.desc": "Outlet A23",
"outlet.23.groupid": "1",
"outlet.23.id": "23",
"outlet.23.name": "A23",
"outlet.23.power": "41",
"outlet.23.realpower": "39",
"outlet.23.status": "on",
"outlet.23.switchable": "yes",
"outlet.23.timer.shutdown": "-1",
"outlet.23.timer.start": "-1",
"outlet.23.type": "nema520",
"outlet.24.current": "0.19",
"outlet.24.current.high.critical": "16",
"outlet.24.current.high.warning": "12.80",
"outlet.24.current.low.warning": "0",
"outlet.24.current.status": "good",
"outlet.24.delay.shutdown": "0",
"outlet.24.delay.start": "0",
"outlet.24.desc": "Outlet A24",
"outlet.24.groupid": "1",
"outlet.24.id": "0",
"outlet.24.name": "A24",
"outlet.24.power": "23",
"outlet.24.realpower": "11",
"outlet.24.status": "on",
"outlet.24.switchable": "yes",
"outlet.24.timer.shutdown": "-1",
"outlet.24.timer.start": "-1",
"outlet.24.type": "nema520",
"outlet.3.current": "0.46",
"outlet.3.current.high.critical": "16",
"outlet.3.current.high.warning": "12.80",
"outlet.3.current.low.warning": "0",
"outlet.3.current.status": "good",
"outlet.3.delay.shutdown": "120",
"outlet.3.delay.start": "3",
"outlet.3.desc": "Outlet A3",
"outlet.3.groupid": "1",
"outlet.3.id": "3",
"outlet.3.name": "A3",
"outlet.3.power": "56",
"outlet.3.realpower": "53",
"outlet.3.status": "on",
"outlet.3.switchable": "yes",
"outlet.3.timer.shutdown": "-1",
"outlet.3.timer.start": "-1",
"outlet.3.type": "nema520",
"outlet.4.current": "0.44",
"outlet.4.current.high.critical": "16",
"outlet.4.current.high.warning": "12.80",
"outlet.4.current.low.warning": "0",
"outlet.4.current.status": "good",
"outlet.4.delay.shutdown": "120",
"outlet.4.delay.start": "4",
"outlet.4.desc": "Outlet A4",
"outlet.4.groupid": "1",
"outlet.4.id": "4",
"outlet.4.name": "A4",
"outlet.4.power": "53",
"outlet.4.realpower": "48",
"outlet.4.status": "on",
"outlet.4.switchable": "yes",
"outlet.4.timer.shutdown": "-1",
"outlet.4.timer.start": "-1",
"outlet.4.type": "nema520",
"outlet.5.current": "0.43",
"outlet.5.current.high.critical": "16",
"outlet.5.current.high.warning": "12.80",
"outlet.5.current.low.warning": "0",
"outlet.5.current.status": "good",
"outlet.5.delay.shutdown": "120",
"outlet.5.delay.start": "5",
"outlet.5.desc": "Outlet A5",
"outlet.5.groupid": "1",
"outlet.5.id": "5",
"outlet.5.name": "A5",
"outlet.5.power": "52",
"outlet.5.realpower": "48",
"outlet.5.status": "on",
"outlet.5.switchable": "yes",
"outlet.5.timer.shutdown": "-1",
"outlet.5.timer.start": "-1",
"outlet.5.type": "nema520",
"outlet.6.current": "1.07",
"outlet.6.current.high.critical": "16",
"outlet.6.current.high.warning": "12.80",
"outlet.6.current.low.warning": "0",
"outlet.6.current.status": "good",
"outlet.6.delay.shutdown": "120",
"outlet.6.delay.start": "6",
"outlet.6.desc": "Outlet A6",
"outlet.6.groupid": "1",
"outlet.6.id": "6",
"outlet.6.name": "A6",
"outlet.6.power": "131",
"outlet.6.realpower": "118",
"outlet.6.status": "on",
"outlet.6.switchable": "yes",
"outlet.6.timer.shutdown": "-1",
"outlet.6.timer.start": "-1",
"outlet.6.type": "nema520",
"outlet.7.current": "0",
"outlet.7.current.high.critical": "16",
"outlet.7.current.high.warning": "12.80",
"outlet.7.current.low.warning": "0",
"outlet.7.current.status": "good",
"outlet.7.delay.shutdown": "120",
"outlet.7.delay.start": "7",
"outlet.7.desc": "Outlet A7",
"outlet.7.groupid": "1",
"outlet.7.id": "7",
"outlet.7.name": "A7",
"outlet.7.power": "0",
"outlet.7.realpower": "0",
"outlet.7.status": "on",
"outlet.7.switchable": "yes",
"outlet.7.timer.shutdown": "-1",
"outlet.7.timer.start": "-1",
"outlet.7.type": "nema520",
"outlet.8.current": "0",
"outlet.8.current.high.critical": "16",
"outlet.8.current.high.warning": "12.80",
"outlet.8.current.low.warning": "0",
"outlet.8.current.status": "good",
"outlet.8.delay.shutdown": "120",
"outlet.8.delay.start": "8",
"outlet.8.desc": "Outlet A8",
"outlet.8.groupid": "1",
"outlet.8.id": "8",
"outlet.8.name": "A8",
"outlet.8.power": "0",
"outlet.8.realpower": "0",
"outlet.8.status": "on",
"outlet.8.switchable": "yes",
"outlet.8.timer.shutdown": "-1",
"outlet.8.timer.start": "-1",
"outlet.8.type": "nema520",
"outlet.9.current": "0",
"outlet.9.current.high.critical": "16",
"outlet.9.current.high.warning": "12.80",
"outlet.9.current.low.warning": "0",
"outlet.9.current.status": "good",
"outlet.9.delay.shutdown": "120",
"outlet.9.delay.start": "9",
"outlet.9.desc": "Outlet A9",
"outlet.9.groupid": "1",
"outlet.9.id": "9",
"outlet.9.name": "A9",
"outlet.9.power": "0",
"outlet.9.realpower": "0",
"outlet.9.status": "on",
"outlet.9.switchable": "yes",
"outlet.9.timer.shutdown": "-1",
"outlet.9.timer.start": "-1",
"outlet.9.type": "nema520",
"outlet.count": "24",
"outlet.current": "43.05",
"outlet.desc": "All outlets",
"outlet.frequency": "60",
"outlet.group.1.color": "16051527",
"outlet.group.1.count": "24",
"outlet.group.1.desc": "Section A",
"outlet.group.1.id": "1",
"outlet.group.1.input": "1",
"outlet.group.1.name": "A",
"outlet.group.1.phase": "1",
"outlet.group.1.status": "on",
"outlet.group.1.type": "outlet-section",
"outlet.group.1.voltage": "122.83",
"outlet.group.1.voltage.high.critical": "140",
"outlet.group.1.voltage.high.warning": "130",
"outlet.group.1.voltage.low.critical": "90",
"outlet.group.1.voltage.low.warning": "95",
"outlet.group.1.voltage.status": "good",
"outlet.group.count": "1",
"outlet.id": "0",
"outlet.switchable": "yes",
"outlet.voltage": "122.91",
"ups.firmware": "05.01.0002",
"ups.mfr": "EATON",
"ups.model": "ePDU MA 00U-C IN: TYPE 00A 0P OUT: 00xTYPE",
"ups.serial": "A000A00000",
"ups.status": "",
"ups.type": "pdu"
}

View File

@ -1,12 +1,19 @@
"""Test init of Nut integration.""" """Test init of Nut integration."""
from copy import deepcopy
from unittest.mock import patch from unittest.mock import patch
from aionut import NUTError, NUTLoginError from aionut import NUTError, NUTLoginError
from homeassistant.components.nut.const import DOMAIN from homeassistant.components.nut.const import DOMAIN
from homeassistant.config_entries import ConfigEntryState from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import CONF_HOST, CONF_PORT, STATE_UNAVAILABLE from homeassistant.const import (
CONF_HOST,
CONF_PASSWORD,
CONF_PORT,
CONF_USERNAME,
STATE_UNAVAILABLE,
)
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr from homeassistant.helpers import device_registry as dr
@ -147,3 +154,44 @@ async def test_device_location(hass: HomeAssistant) -> None:
assert device_entry is not None assert device_entry is not None
assert device_entry.suggested_area == mock_device_location assert device_entry.suggested_area == mock_device_location
async def test_update_options(hass: HomeAssistant) -> None:
"""Test update options triggers reload."""
mock_pynut = _get_mock_nutclient(
list_ups={"ups1": "UPS 1"}, list_vars={"ups.status": "OL"}
)
with patch(
"homeassistant.components.nut.AIONUTClient",
return_value=mock_pynut,
):
mock_config_entry = MockConfigEntry(
domain=DOMAIN,
data={
CONF_HOST: "mock",
CONF_PASSWORD: "somepassword",
CONF_PORT: "mock",
CONF_USERNAME: "someuser",
},
options={
"device_options": {
"fake_option": "fake_option_value",
},
},
)
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
assert mock_config_entry.state is ConfigEntryState.LOADED
new_options = deepcopy(dict(mock_config_entry.options))
new_options["device_options"].clear()
hass.config_entries.async_update_entry(mock_config_entry, options=new_options)
await hass.async_block_till_done()
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
assert mock_config_entry.state is ConfigEntryState.LOADED

View File

@ -5,17 +5,23 @@ from unittest.mock import patch
import pytest import pytest
from homeassistant.components.nut.const import DOMAIN from homeassistant.components.nut.const import DOMAIN
from homeassistant.components.sensor import SensorDeviceClass, SensorStateClass
from homeassistant.const import ( from homeassistant.const import (
CONF_HOST, CONF_HOST,
CONF_PORT, CONF_PORT,
CONF_RESOURCES, CONF_RESOURCES,
PERCENTAGE, PERCENTAGE,
STATE_UNKNOWN, STATE_UNKNOWN,
UnitOfElectricPotential,
) )
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 .util import _get_mock_nutclient, async_init_integration from .util import (
_get_mock_nutclient,
_test_sensor_and_attributes,
async_init_integration,
)
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
@ -32,7 +38,7 @@ from tests.common import MockConfigEntry
"blazer_usb", "blazer_usb",
], ],
) )
async def test_devices( async def test_ups_devices(
hass: HomeAssistant, entity_registry: er.EntityRegistry, model: str hass: HomeAssistant, entity_registry: er.EntityRegistry, model: str
) -> None: ) -> None:
"""Test creation of device sensors.""" """Test creation of device sensors."""
@ -67,7 +73,7 @@ async def test_devices(
), ),
], ],
) )
async def test_devices_with_unique_ids( async def test_ups_devices_with_unique_ids(
hass: HomeAssistant, entity_registry: er.EntityRegistry, model: str, unique_id: str hass: HomeAssistant, entity_registry: er.EntityRegistry, model: str, unique_id: str
) -> None: ) -> None:
"""Test creation of device sensors with unique ids.""" """Test creation of device sensors with unique ids."""
@ -92,6 +98,65 @@ async def test_devices_with_unique_ids(
) )
@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_devices_with_unique_ids(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
model: str,
unique_id_base: str,
) -> None:
"""Test creation of device sensors with unique ids."""
await _test_sensor_and_attributes(
hass,
entity_registry,
model,
unique_id=f"{unique_id_base}input.voltage",
device_id="sensor.ups1_input_voltage",
state_value="122.91",
expected_attributes={
"device_class": SensorDeviceClass.VOLTAGE,
"state_class": SensorStateClass.MEASUREMENT,
"friendly_name": "Ups1 Input voltage",
"unit_of_measurement": UnitOfElectricPotential.VOLT,
},
)
await _test_sensor_and_attributes(
hass,
entity_registry,
model,
unique_id=f"{unique_id_base}ambient.humidity.status",
device_id="sensor.ups1_ambient_humidity_status",
state_value="good",
expected_attributes={
"device_class": SensorDeviceClass.ENUM,
"friendly_name": "Ups1 Ambient humidity status",
},
)
await _test_sensor_and_attributes(
hass,
entity_registry,
model,
unique_id=f"{unique_id_base}ambient.temperature.status",
device_id="sensor.ups1_ambient_temperature_status",
state_value="good",
expected_attributes={
"device_class": SensorDeviceClass.ENUM,
"friendly_name": "Ups1 Ambient temperature status",
},
)
async def test_state_sensors(hass: HomeAssistant) -> None: async def test_state_sensors(hass: HomeAssistant) -> None:
"""Test creation of status display sensors.""" """Test creation of status display sensors."""
entry = MockConfigEntry( entry = MockConfigEntry(

View File

@ -6,6 +6,7 @@ from unittest.mock import AsyncMock, MagicMock, patch
from homeassistant.components.nut.const import DOMAIN from homeassistant.components.nut.const import DOMAIN
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from tests.common import MockConfigEntry, load_fixture from tests.common import MockConfigEntry, load_fixture
@ -79,3 +80,29 @@ async def async_init_integration(
await hass.async_block_till_done() await hass.async_block_till_done()
return entry return entry
async def _test_sensor_and_attributes(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
model: str,
unique_id: str,
device_id: str,
state_value: str,
expected_attributes: dict,
) -> None:
"""Test creation of device sensors with unique ids."""
await async_init_integration(hass, model)
entry = entity_registry.async_get(device_id)
assert entry
assert entry.unique_id == unique_id
state = hass.states.get(device_id)
assert state.state == state_value
# Only test for a subset of attributes in case
# HA changes the implementation and a new one appears
assert all(
state.attributes[key] == attr for key, attr in expected_attributes.items()
)