Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 2 commits into from Oct 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 0 additions & 1 deletion .coveragerc
Expand Up @@ -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/*
Expand Down
2 changes: 1 addition & 1 deletion CODEOWNERS
Validating CODEOWNERS rules …
Expand Up @@ -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
Expand Down
10 changes: 2 additions & 8 deletions homeassistant/components/nut/__init__.py
Expand Up @@ -16,7 +16,6 @@
CONF_USERNAME,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed

from .const import (
Expand All @@ -28,7 +27,6 @@
PYNUT_FIRMWARE,
PYNUT_MANUFACTURER,
PYNUT_MODEL,
PYNUT_NAME,
PYNUT_UNIQUE_ID,
UNDO_UPDATE_LISTENER,
)
Expand Down Expand Up @@ -61,6 +59,7 @@ async def async_update_data():
await hass.async_add_executor_job(data.update)
if not data.status:
raise UpdateFailed("Error fetching UPS state")
return data.status

coordinator = DataUpdateCoordinator(
hass,
Expand All @@ -72,11 +71,7 @@ async def async_update_data():

# 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)

Expand All @@ -95,7 +90,6 @@ async def async_update_data():
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,
}

Expand Down
1 change: 0 additions & 1 deletion homeassistant/components/nut/const.py
Expand Up @@ -45,7 +45,6 @@
PYNUT_MANUFACTURER = "manufacturer"
PYNUT_MODEL = "model"
PYNUT_FIRMWARE = "firmware"
PYNUT_NAME = "name"

SENSOR_TYPES: Final[dict[str, SensorEntityDescription]] = {
"ups.status.display": SensorEntityDescription(
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/nut/manifest.json
Expand Up @@ -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"
Expand Down
27 changes: 8 additions & 19 deletions homeassistant/components/nut/sensor.py
Expand Up @@ -20,7 +20,6 @@
PYNUT_FIRMWARE,
PYNUT_MANUFACTURER,
PYNUT_MODEL,
PYNUT_NAME,
PYNUT_UNIQUE_ID,
SENSOR_TYPES,
STATE_TYPES,
Expand All @@ -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]
Expand All @@ -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,
Expand All @@ -76,7 +73,6 @@ def __init__(
self,
coordinator: DataUpdateCoordinator,
data: PyNUTData,
name: str,
sensor_description: SensorEntityDescription,
unique_id: str,
manufacturer: str | None,
Expand All @@ -90,20 +86,16 @@ def __init__(
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,
Expand All @@ -119,17 +111,14 @@ def device_info(self):
@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:
Expand Down
73 changes: 71 additions & 2 deletions tests/components/nut/test_sensor.py
@@ -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):
Expand Down Expand Up @@ -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."""

Expand Down