Nut: Use coordinator data, code cleanup and add test coverage (#57643)

pull/53901/head^2
ollo69 2021-10-14 04:05:06 +02:00 committed by GitHub
parent 8c326198cf
commit 827501659c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 83 additions and 33 deletions

View File

@ -731,7 +731,6 @@ omit =
homeassistant/components/nuki/const.py
homeassistant/components/nuki/binary_sensor.py
homeassistant/components/nuki/lock.py
homeassistant/components/nut/sensor.py
homeassistant/components/nx584/alarm_control_panel.py
homeassistant/components/nzbget/coordinator.py
homeassistant/components/obihai/*

View File

@ -361,7 +361,7 @@ homeassistant/components/nsw_rural_fire_service_feed/* @exxamalte
homeassistant/components/nuki/* @pschmitt @pvizeli @pree
homeassistant/components/numato/* @clssn
homeassistant/components/number/* @home-assistant/core @Shulyaka
homeassistant/components/nut/* @bdraco
homeassistant/components/nut/* @bdraco @ollo69
homeassistant/components/nws/* @MatthewFlamm
homeassistant/components/nzbget/* @chriscla
homeassistant/components/obihai/* @dshokouhi

View File

@ -16,7 +16,6 @@ from homeassistant.const import (
CONF_USERNAME,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import (
@ -28,7 +27,6 @@ from .const import (
PYNUT_FIRMWARE,
PYNUT_MANUFACTURER,
PYNUT_MODEL,
PYNUT_NAME,
PYNUT_UNIQUE_ID,
UNDO_UPDATE_LISTENER,
)
@ -61,6 +59,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
await hass.async_add_executor_job(data.update)
if not data.status:
raise UpdateFailed("Error fetching UPS state")
return data.status
coordinator = DataUpdateCoordinator(
hass,
@ -72,11 +71,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
# Fetch initial data so we have data when entities subscribe
await coordinator.async_config_entry_first_refresh()
status = data.status
if not status:
_LOGGER.error("NUT Sensor has no data, unable to set up")
raise ConfigEntryNotReady
status = coordinator.data
_LOGGER.debug("NUT Sensors Available: %s", status)
@ -95,7 +90,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
PYNUT_MANUFACTURER: _manufacturer_from_status(status),
PYNUT_MODEL: _model_from_status(status),
PYNUT_FIRMWARE: _firmware_from_status(status),
PYNUT_NAME: data.name,
UNDO_UPDATE_LISTENER: undo_listener,
}

View File

@ -45,7 +45,6 @@ PYNUT_UNIQUE_ID = "unique_id"
PYNUT_MANUFACTURER = "manufacturer"
PYNUT_MODEL = "model"
PYNUT_FIRMWARE = "firmware"
PYNUT_NAME = "name"
SENSOR_TYPES: Final[dict[str, SensorEntityDescription]] = {
"ups.status.display": SensorEntityDescription(

View File

@ -3,7 +3,7 @@
"name": "Network UPS Tools (NUT)",
"documentation": "https://www.home-assistant.io/integrations/nut",
"requirements": ["pynut2==2.1.2"],
"codeowners": ["@bdraco"],
"codeowners": ["@bdraco", "@ollo69"],
"config_flow": true,
"zeroconf": ["_nut._tcp.local."],
"iot_class": "local_polling"

View File

@ -20,7 +20,6 @@ from .const import (
PYNUT_FIRMWARE,
PYNUT_MANUFACTURER,
PYNUT_MODEL,
PYNUT_NAME,
PYNUT_UNIQUE_ID,
SENSOR_TYPES,
STATE_TYPES,
@ -37,10 +36,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
manufacturer = pynut_data[PYNUT_MANUFACTURER]
model = pynut_data[PYNUT_MODEL]
firmware = pynut_data[PYNUT_FIRMWARE]
name = pynut_data[PYNUT_NAME]
coordinator = pynut_data[COORDINATOR]
data = pynut_data[PYNUT_DATA]
status = data.status
status = coordinator.data
enabled_resources = [
resource.lower() for resource in config_entry.data[CONF_RESOURCES]
@ -55,7 +53,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
NUTSensor(
coordinator,
data,
name.title(),
SENSOR_TYPES[sensor_type],
unique_id,
manufacturer,
@ -76,7 +73,6 @@ class NUTSensor(CoordinatorEntity, SensorEntity):
self,
coordinator: DataUpdateCoordinator,
data: PyNUTData,
name: str,
sensor_description: SensorEntityDescription,
unique_id: str,
manufacturer: str | None,
@ -90,20 +86,16 @@ class NUTSensor(CoordinatorEntity, SensorEntity):
self._manufacturer = manufacturer
self._firmware = firmware
self._model = model
self._device_name = name
self._data = data
self._device_name = data.name.title()
self._unique_id = unique_id
self._attr_entity_registry_enabled_default = enabled_default
self._attr_name = f"{name} {sensor_description.name}"
if unique_id is not None:
self._attr_unique_id = f"{unique_id}_{sensor_description.key}"
self._attr_entity_registry_enabled_default = enabled_default
self._attr_name = f"{self._device_name} {sensor_description.name}"
self._attr_unique_id = f"{unique_id}_{sensor_description.key}"
@property
def device_info(self):
"""Device info for the ups."""
if not self._unique_id:
return None
device_info = {
"identifiers": {(DOMAIN, self._unique_id)},
"name": self._device_name,
@ -119,17 +111,14 @@ class NUTSensor(CoordinatorEntity, SensorEntity):
@property
def native_value(self):
"""Return entity state from ups."""
if not self._data.status:
return None
status = self.coordinator.data
if self.entity_description.key == KEY_STATUS_DISPLAY:
return _format_display_state(self._data.status)
return self._data.status.get(self.entity_description.key)
return _format_display_state(status)
return status.get(self.entity_description.key)
def _format_display_state(status):
"""Return UPS display state."""
if status is None:
return STATE_TYPES["OFF"]
try:
return " ".join(STATE_TYPES[state] for state in status[KEY_STATUS].split())
except KeyError:

View File

@ -1,9 +1,20 @@
"""The sensor tests for the nut platform."""
from homeassistant.const import PERCENTAGE
from unittest.mock import patch
from homeassistant.components.nut.const import DOMAIN
from homeassistant.const import (
CONF_HOST,
CONF_PORT,
CONF_RESOURCES,
PERCENTAGE,
STATE_UNKNOWN,
)
from homeassistant.helpers import entity_registry as er
from .util import async_init_integration
from .util import _get_mock_pynutclient, async_init_integration
from tests.common import MockConfigEntry
async def test_pr3000rt2u(hass):
@ -204,6 +215,64 @@ async def test_blazer_usb(hass):
)
async def test_state_sensors(hass):
"""Test creation of status display sensors."""
entry = MockConfigEntry(
domain=DOMAIN,
data={
CONF_HOST: "mock",
CONF_PORT: "mock",
CONF_RESOURCES: ["ups.status", "ups.status.display"],
},
)
entry.add_to_hass(hass)
mock_pynut = _get_mock_pynutclient(
list_ups={"ups1": "UPS 1"}, list_vars={"ups.status": "OL"}
)
with patch(
"homeassistant.components.nut.PyNUTClient",
return_value=mock_pynut,
):
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
state1 = hass.states.get("sensor.ups1_status")
state2 = hass.states.get("sensor.ups1_status_data")
assert state1.state == "Online"
assert state2.state == "OL"
async def test_unknown_state_sensors(hass):
"""Test creation of unknown status display sensors."""
entry = MockConfigEntry(
domain=DOMAIN,
data={
CONF_HOST: "mock",
CONF_PORT: "mock",
CONF_RESOURCES: ["ups.status", "ups.status.display"],
},
)
entry.add_to_hass(hass)
mock_pynut = _get_mock_pynutclient(
list_ups={"ups1": "UPS 1"}, list_vars={"ups.status": "OQ"}
)
with patch(
"homeassistant.components.nut.PyNUTClient",
return_value=mock_pynut,
):
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
state1 = hass.states.get("sensor.ups1_status")
state2 = hass.states.get("sensor.ups1_status_data")
assert state1.state == STATE_UNKNOWN
assert state2.state == "OQ"
async def test_stale_options(hass):
"""Test creation of sensors with stale options to remove."""