From 725602c51b4b59b2e97a82801bb5b5fe8bd5ec0b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 13 Apr 2020 16:52:41 +0000 Subject: [PATCH 1/8] Add a fallback unique id for nut If the device is missing a serial number we fallback to the host/port/alias as the sensors will not show up on the integrations page which was confusing. --- homeassistant/components/nut/__init__.py | 20 +++++++++++++- homeassistant/components/nut/config_flow.py | 20 ++++---------- tests/components/nut/test_sensor.py | 30 ++++++++++++--------- tests/components/nut/util.py | 15 +++++++---- 4 files changed, 52 insertions(+), 33 deletions(-) diff --git a/homeassistant/components/nut/__init__.py b/homeassistant/components/nut/__init__.py index b98522feb4e250..04969a13d47ea8 100644 --- a/homeassistant/components/nut/__init__.py +++ b/homeassistant/components/nut/__init__.py @@ -83,10 +83,18 @@ async def async_update_data(): undo_listener = entry.add_update_listener(_async_update_listener) + unique_id = _unique_id_from_status(status) + + if unique_id is None: + # If the device is missing a serial number + # we fallback to the host/port/alias + # as the sensors won't show up on the integrations page. + unique_id = format_host_port_alias(config) + hass.data[DOMAIN][entry.entry_id] = { COORDINATOR: coordinator, PYNUT_DATA: data, - PYNUT_UNIQUE_ID: _unique_id_from_status(status), + PYNUT_UNIQUE_ID: unique_id, PYNUT_MANUFACTURER: _manufacturer_from_status(status), PYNUT_MODEL: _model_from_status(status), PYNUT_FIRMWARE: _firmware_from_status(status), @@ -166,6 +174,16 @@ def find_resources_in_config_entry(config_entry): return config_entry.data[CONF_RESOURCES] +def format_host_port_alias(user_input): + """Format a host, port, and alias.""" + host = user_input[CONF_HOST] + port = user_input[CONF_PORT] + alias = user_input.get(CONF_ALIAS) + if alias: + return f"{alias}@{host}:{port}" + return f"{host}:{port}" + + async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): """Unload a config entry.""" unload_ok = all( diff --git a/homeassistant/components/nut/config_flow.py b/homeassistant/components/nut/config_flow.py index ba005f04a6a8b9..c14fab268f33da 100644 --- a/homeassistant/components/nut/config_flow.py +++ b/homeassistant/components/nut/config_flow.py @@ -16,7 +16,7 @@ from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from . import PyNUTData, find_resources_in_config_entry +from . import PyNUTData, find_resources_in_config_entry, format_host_port_alias from .const import ( DEFAULT_HOST, DEFAULT_PORT, @@ -93,16 +93,6 @@ async def validate_input(hass: core.HomeAssistant, data): return {"ups_list": data.ups_list, "available_resources": status} -def _format_host_port_alias(user_input): - """Format a host, port, and alias so it can be used for comparison or display.""" - host = user_input[CONF_HOST] - port = user_input[CONF_PORT] - alias = user_input.get(CONF_ALIAS) - if alias: - return f"{alias}@{host}:{port}" - return f"{host}:{port}" - - class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Handle a config flow for Network UPS Tools (NUT).""" @@ -125,7 +115,7 @@ async def async_step_import(self, user_input=None): _, errors = await self._async_validate_or_error(user_input) if not errors: - title = _format_host_port_alias(user_input) + title = format_host_port_alias(user_input) return self.async_create_entry(title=title, data=user_input) return self.async_show_form( @@ -181,16 +171,16 @@ async def async_step_resources(self, user_input=None): ) self.nut_config.update(user_input) - title = _format_host_port_alias(self.nut_config) + title = format_host_port_alias(self.nut_config) return self.async_create_entry(title=title, data=self.nut_config) def _host_port_alias_already_configured(self, user_input): """See if we already have a nut entry matching user input configured.""" existing_host_port_aliases = { - _format_host_port_alias(entry.data) + format_host_port_alias(entry.data) for entry in self._async_current_entries() } - return _format_host_port_alias(user_input) in existing_host_port_aliases + return format_host_port_alias(user_input) in existing_host_port_aliases async def _async_validate_or_error(self, config): errors = {} diff --git a/tests/components/nut/test_sensor.py b/tests/components/nut/test_sensor.py index 645855389484bf..56fd1b0f0bfb8d 100644 --- a/tests/components/nut/test_sensor.py +++ b/tests/components/nut/test_sensor.py @@ -8,7 +8,9 @@ async def test_pr3000rt2u(hass): """Test creation of PR3000RT2U sensors.""" - await async_init_integration(hass, "PR3000RT2U", ["battery.charge"]) + await async_init_integration( + hass, "192.168.103.12", "PR3000RT2U", ["battery.charge"] + ) registry = await hass.helpers.entity_registry.async_get_registry() entry = registry.async_get("sensor.ups1_battery_charge") assert entry @@ -33,13 +35,14 @@ async def test_pr3000rt2u(hass): async def test_cp1350c(hass): """Test creation of CP1350C sensors.""" - await async_init_integration(hass, "CP1350C", ["battery.charge"]) + await async_init_integration(hass, "192.168.103.18", "CP1350C", ["battery.charge"]) registry = await hass.helpers.entity_registry.async_get_registry() entry = registry.async_get("sensor.ups1_battery_charge") - # No unique id, no registry entry - assert not entry + assert entry + assert entry.unique_id == "ups1@192.168.103.18:mock_battery.charge" state = hass.states.get("sensor.ups1_battery_charge") + assert state.state == "100" expected_attributes = { @@ -58,11 +61,11 @@ async def test_cp1350c(hass): async def test_5e850i(hass): """Test creation of 5E850I sensors.""" - await async_init_integration(hass, "5E850I", ["battery.charge"]) + await async_init_integration(hass, "192.168.103.45", "5E850I", ["battery.charge"]) registry = await hass.helpers.entity_registry.async_get_registry() entry = registry.async_get("sensor.ups1_battery_charge") - # No unique id, no registry entry - assert not entry + assert entry + assert entry.unique_id == "ups1@192.168.103.45:mock_battery.charge" state = hass.states.get("sensor.ups1_battery_charge") assert state.state == "100" @@ -108,10 +111,11 @@ async def test_5e650i(hass): async def test_backupsses600m1(hass): """Test creation of BACKUPSES600M1 sensors.""" - await async_init_integration(hass, "BACKUPSES600M1", ["battery.charge"]) + await async_init_integration( + hass, "192.168.103.84", "BACKUPSES600M1", ["battery.charge"] + ) registry = await hass.helpers.entity_registry.async_get_registry() entry = registry.async_get("sensor.ups1_battery_charge") - # No unique id, no registry entry assert entry assert ( entry.unique_id @@ -137,11 +141,13 @@ async def test_backupsses600m1(hass): async def test_cp1500pfclcd(hass): """Test creation of CP1500PFCLCD sensors.""" - await async_init_integration(hass, "CP1500PFCLCD", ["battery.charge"]) + await async_init_integration( + hass, "192.168.103.99", "CP1500PFCLCD", ["battery.charge"] + ) registry = await hass.helpers.entity_registry.async_get_registry() entry = registry.async_get("sensor.ups1_battery_charge") - # No unique id, no registry entry - assert not entry + assert entry + assert entry.unique_id == "ups1@192.168.103.99:mock_battery.charge" state = hass.states.get("sensor.ups1_battery_charge") assert state.state == "100" diff --git a/tests/components/nut/util.py b/tests/components/nut/util.py index 788076a7c9f21a..a7cc61e893437f 100644 --- a/tests/components/nut/util.py +++ b/tests/components/nut/util.py @@ -5,7 +5,7 @@ from asynctest import MagicMock, patch from homeassistant.components.nut.const import DOMAIN -from homeassistant.const import CONF_HOST, CONF_PORT, CONF_RESOURCES +from homeassistant.const import CONF_ALIAS, CONF_HOST, CONF_PORT, CONF_RESOURCES from homeassistant.core import HomeAssistant from tests.common import MockConfigEntry, load_fixture @@ -19,12 +19,12 @@ def _get_mock_pynutclient(list_vars=None, list_ups=None): async def async_init_integration( - hass: HomeAssistant, ups_fixture: str, resources: list + hass: HomeAssistant, host: str, ups_fixture: str, resources: list ) -> MockConfigEntry: """Set up the nexia integration in Home Assistant.""" - ups_fixture = f"nut/{ups_fixture}.json" - list_vars = json.loads(load_fixture(ups_fixture)) + fixture_path = f"nut/{ups_fixture}.json" + list_vars = json.loads(load_fixture(fixture_path)) mock_pynut = _get_mock_pynutclient(list_ups={"ups1": "UPS 1"}, list_vars=list_vars) @@ -33,7 +33,12 @@ async def async_init_integration( ): entry = MockConfigEntry( domain=DOMAIN, - data={CONF_HOST: "mock", CONF_PORT: "mock", CONF_RESOURCES: resources}, + data={ + CONF_HOST: host, + CONF_PORT: "mock", + CONF_ALIAS: "ups1", + CONF_RESOURCES: resources, + }, ) entry.add_to_hass(hass) From aba93d3356eab801d970d7136223eb3760587f27 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 15 Apr 2020 15:06:25 +0000 Subject: [PATCH 2/8] Use mac address for NUT unique_id if serial is not available. --- homeassistant/components/nut/__init__.py | 43 ++++++++++++++++++++-- homeassistant/components/nut/const.py | 9 ++++- homeassistant/components/nut/manifest.json | 2 +- requirements_all.txt | 1 + requirements_test_all.txt | 1 + 5 files changed, 50 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/nut/__init__.py b/homeassistant/components/nut/__init__.py index 04969a13d47ea8..da041ee45df238 100644 --- a/homeassistant/components/nut/__init__.py +++ b/homeassistant/components/nut/__init__.py @@ -1,9 +1,11 @@ """The nut component.""" import asyncio from datetime import timedelta +import ipaddress import logging import async_timeout +import getmac from pynut2.nut2 import PyNUTClient, PyNUTError from homeassistant.config_entries import ConfigEntry @@ -19,11 +21,16 @@ from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.update_coordinator import DataUpdateCoordinator +from homeassistant.util.network import is_loopback from .const import ( COORDINATOR, DEFAULT_SCAN_INTERVAL, + DEFAULT_UPS_ALIAS, DOMAIN, + EMPTY_MAC_ADDRESS, + IPV4_LOOPBACK, + LOCALHOST, PLATFORMS, PYNUT_DATA, PYNUT_FIRMWARE, @@ -86,10 +93,9 @@ async def async_update_data(): unique_id = _unique_id_from_status(status) if unique_id is None: - # If the device is missing a serial number - # we fallback to the host/port/alias - # as the sensors won't show up on the integrations page. - unique_id = format_host_port_alias(config) + unique_id = await hass.async_add_executor_job( + _unique_id_from_nut_server, config + ) hass.data[DOMAIN][entry.entry_id] = { COORDINATOR: coordinator, @@ -115,6 +121,35 @@ async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry): await hass.config_entries.async_reload(entry.entry_id) +def _unique_id_from_nut_server(config): + """Get the mac address of NUT if its on the LAN.""" + nut_host = config[CONF_HOST] + host_is_ip = True + + if nut_host == LOCALHOST: + nut_host = IPV4_LOOPBACK + else: + try: + ipaddress.ip_address(nut_host) + except ValueError: + host_is_ip = False + + if host_is_ip and is_loopback(ipaddress.ip_address(nut_host)): + mac_addr = getmac.get_mac_address() + elif host_is_ip: + mac_addr = getmac.get_mac_address(ip=nut_host) + else: + mac_addr = getmac.get_mac_address(hostname=nut_host) + + _LOGGER.debug("mac_address of NUT at %s: %s", nut_host, mac_addr) + + if mac_addr is None or mac_addr == EMPTY_MAC_ADDRESS: + return None + + ups_alias = config.get(CONF_ALIAS, DEFAULT_UPS_ALIAS) + return f"{ups_alias}_{mac_addr}" + + def _manufacturer_from_status(status): """Find the best manufacturer value from the status.""" return ( diff --git a/homeassistant/components/nut/const.py b/homeassistant/components/nut/const.py index 13ffa318a4a533..afadaac6f34b64 100644 --- a/homeassistant/components/nut/const.py +++ b/homeassistant/components/nut/const.py @@ -20,8 +20,9 @@ UNDO_UPDATE_LISTENER = "undo_update_listener" DEFAULT_NAME = "NUT UPS" -DEFAULT_HOST = "localhost" +DEFAULT_HOST = "127.0.0.1" DEFAULT_PORT = 3493 +DEFAULT_UPS_ALIAS = "ups" KEY_STATUS = "ups.status" KEY_STATUS_DISPLAY = "ups.status.display" @@ -36,6 +37,12 @@ PYNUT_FIRMWARE = "firmware" PYNUT_NAME = "name" +EMPTY_MAC_ADDRESS = "00:00:00:00:00:00" + +LOCALHOST = "localhost" +IPV4_LOOPBACK = "127.0.0.1" + + SENSOR_TYPES = { "ups.status.display": ["Status", "", "mdi:information-outline", None], "ups.status": ["Status Data", "", "mdi:information-outline", None], diff --git a/homeassistant/components/nut/manifest.json b/homeassistant/components/nut/manifest.json index 226250b9a52f18..8ab6eb1937a81b 100644 --- a/homeassistant/components/nut/manifest.json +++ b/homeassistant/components/nut/manifest.json @@ -2,7 +2,7 @@ "domain": "nut", "name": "Network UPS Tools (NUT)", "documentation": "https://www.home-assistant.io/integrations/nut", - "requirements": ["pynut2==2.1.2"], + "requirements": ["pynut2==2.1.2", "getmac==0.8.2"], "codeowners": ["@bdraco"], "config_flow": true } diff --git a/requirements_all.txt b/requirements_all.txt index 38133b1a4760e6..b9cf6762dc06ad 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -607,6 +607,7 @@ georss_qld_bushfire_alert_client==0.3 # homeassistant.components.kef # homeassistant.components.minecraft_server # homeassistant.components.nmap_tracker +# homeassistant.components.nut getmac==0.8.2 # homeassistant.components.gios diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 6f013a52da050b..dd61e20ece6fc3 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -242,6 +242,7 @@ georss_qld_bushfire_alert_client==0.3 # homeassistant.components.kef # homeassistant.components.minecraft_server # homeassistant.components.nmap_tracker +# homeassistant.components.nut getmac==0.8.2 # homeassistant.components.gios From 34602aaf3a1eebaaaffbebafb43515eef183acdd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 15 Apr 2020 15:09:05 +0000 Subject: [PATCH 3/8] revert changes to format_host_port_alias as they are no longer needed --- homeassistant/components/nut/__init__.py | 10 ---------- homeassistant/components/nut/config_flow.py | 20 +++++++++++++++----- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/nut/__init__.py b/homeassistant/components/nut/__init__.py index da041ee45df238..7acb7f77e2402b 100644 --- a/homeassistant/components/nut/__init__.py +++ b/homeassistant/components/nut/__init__.py @@ -209,16 +209,6 @@ def find_resources_in_config_entry(config_entry): return config_entry.data[CONF_RESOURCES] -def format_host_port_alias(user_input): - """Format a host, port, and alias.""" - host = user_input[CONF_HOST] - port = user_input[CONF_PORT] - alias = user_input.get(CONF_ALIAS) - if alias: - return f"{alias}@{host}:{port}" - return f"{host}:{port}" - - async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): """Unload a config entry.""" unload_ok = all( diff --git a/homeassistant/components/nut/config_flow.py b/homeassistant/components/nut/config_flow.py index c14fab268f33da..4742eac8bfb169 100644 --- a/homeassistant/components/nut/config_flow.py +++ b/homeassistant/components/nut/config_flow.py @@ -16,7 +16,7 @@ from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from . import PyNUTData, find_resources_in_config_entry, format_host_port_alias +from . import PyNUTData, find_resources_in_config_entry from .const import ( DEFAULT_HOST, DEFAULT_PORT, @@ -93,6 +93,16 @@ async def validate_input(hass: core.HomeAssistant, data): return {"ups_list": data.ups_list, "available_resources": status} +def _format_host_port_alias(user_input): + """Format a host, port, and alias.""" + host = user_input[CONF_HOST] + port = user_input[CONF_PORT] + alias = user_input.get(CONF_ALIAS) + if alias: + return f"{alias}@{host}:{port}" + return f"{host}:{port}" + + class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Handle a config flow for Network UPS Tools (NUT).""" @@ -115,7 +125,7 @@ async def async_step_import(self, user_input=None): _, errors = await self._async_validate_or_error(user_input) if not errors: - title = format_host_port_alias(user_input) + title = _format_host_port_alias(user_input) return self.async_create_entry(title=title, data=user_input) return self.async_show_form( @@ -171,16 +181,16 @@ async def async_step_resources(self, user_input=None): ) self.nut_config.update(user_input) - title = format_host_port_alias(self.nut_config) + title = _format_host_port_alias(self.nut_config) return self.async_create_entry(title=title, data=self.nut_config) def _host_port_alias_already_configured(self, user_input): """See if we already have a nut entry matching user input configured.""" existing_host_port_aliases = { - format_host_port_alias(entry.data) + _format_host_port_alias(entry.data) for entry in self._async_current_entries() } - return format_host_port_alias(user_input) in existing_host_port_aliases + return _format_host_port_alias(user_input) in existing_host_port_aliases async def _async_validate_or_error(self, config): errors = {} From 2bb4217b44bbc81e6236fa71ff9e0707453115e3 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 15 Apr 2020 15:10:30 +0000 Subject: [PATCH 4/8] more reverts --- homeassistant/components/nut/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/nut/config_flow.py b/homeassistant/components/nut/config_flow.py index 4742eac8bfb169..ba005f04a6a8b9 100644 --- a/homeassistant/components/nut/config_flow.py +++ b/homeassistant/components/nut/config_flow.py @@ -94,7 +94,7 @@ async def validate_input(hass: core.HomeAssistant, data): def _format_host_port_alias(user_input): - """Format a host, port, and alias.""" + """Format a host, port, and alias so it can be used for comparison or display.""" host = user_input[CONF_HOST] port = user_input[CONF_PORT] alias = user_input.get(CONF_ALIAS) From 3fd5c8b25f1ec849145d9a3c92fbeee3feb31327 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 15 Apr 2020 15:16:04 +0000 Subject: [PATCH 5/8] Update tests --- tests/components/nut/test_sensor.py | 26 +++++++++++++++++--------- tests/components/nut/util.py | 6 +++--- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/tests/components/nut/test_sensor.py b/tests/components/nut/test_sensor.py index 56fd1b0f0bfb8d..2df9654345eb99 100644 --- a/tests/components/nut/test_sensor.py +++ b/tests/components/nut/test_sensor.py @@ -9,7 +9,7 @@ async def test_pr3000rt2u(hass): """Test creation of PR3000RT2U sensors.""" await async_init_integration( - hass, "192.168.103.12", "PR3000RT2U", ["battery.charge"] + hass, "192.168.103.12", "PR3000RT2U", ["battery.charge"], "54:A6:00:B3:00:00" ) registry = await hass.helpers.entity_registry.async_get_registry() entry = registry.async_get("sensor.ups1_battery_charge") @@ -35,11 +35,13 @@ async def test_pr3000rt2u(hass): async def test_cp1350c(hass): """Test creation of CP1350C sensors.""" - await async_init_integration(hass, "192.168.103.18", "CP1350C", ["battery.charge"]) + await async_init_integration( + hass, "192.168.103.18", "CP1350C", ["battery.charge"], "00:00:00:00:00:00" + ) registry = await hass.helpers.entity_registry.async_get_registry() entry = registry.async_get("sensor.ups1_battery_charge") - assert entry - assert entry.unique_id == "ups1@192.168.103.18:mock_battery.charge" + # No unique id + assert not entry state = hass.states.get("sensor.ups1_battery_charge") @@ -61,11 +63,13 @@ async def test_cp1350c(hass): async def test_5e850i(hass): """Test creation of 5E850I sensors.""" - await async_init_integration(hass, "192.168.103.45", "5E850I", ["battery.charge"]) + await async_init_integration( + hass, "192.168.103.45", "5E850I", ["battery.charge"], "54:A6:00:B3:00:00" + ) registry = await hass.helpers.entity_registry.async_get_registry() entry = registry.async_get("sensor.ups1_battery_charge") assert entry - assert entry.unique_id == "ups1@192.168.103.45:mock_battery.charge" + assert entry.unique_id == "ups1_54:A6:00:B3:00:00_battery.charge" state = hass.states.get("sensor.ups1_battery_charge") assert state.state == "100" @@ -112,7 +116,11 @@ async def test_backupsses600m1(hass): """Test creation of BACKUPSES600M1 sensors.""" await async_init_integration( - hass, "192.168.103.84", "BACKUPSES600M1", ["battery.charge"] + hass, + "192.168.103.84", + "BACKUPSES600M1", + ["battery.charge"], + "24:A6:00:B3:00:00", ) registry = await hass.helpers.entity_registry.async_get_registry() entry = registry.async_get("sensor.ups1_battery_charge") @@ -142,12 +150,12 @@ async def test_cp1500pfclcd(hass): """Test creation of CP1500PFCLCD sensors.""" await async_init_integration( - hass, "192.168.103.99", "CP1500PFCLCD", ["battery.charge"] + hass, "192.168.103.99", "CP1500PFCLCD", ["battery.charge"], "24:A6:00:B3:21:00" ) registry = await hass.helpers.entity_registry.async_get_registry() entry = registry.async_get("sensor.ups1_battery_charge") assert entry - assert entry.unique_id == "ups1@192.168.103.99:mock_battery.charge" + assert entry.unique_id == "ups1_24:A6:00:B3:21:00_battery.charge" state = hass.states.get("sensor.ups1_battery_charge") assert state.state == "100" diff --git a/tests/components/nut/util.py b/tests/components/nut/util.py index a7cc61e893437f..4b03d63bdeae79 100644 --- a/tests/components/nut/util.py +++ b/tests/components/nut/util.py @@ -19,7 +19,7 @@ def _get_mock_pynutclient(list_vars=None, list_ups=None): async def async_init_integration( - hass: HomeAssistant, host: str, ups_fixture: str, resources: list + hass: HomeAssistant, host: str, ups_fixture: str, resources: list, mac_addr: str ) -> MockConfigEntry: """Set up the nexia integration in Home Assistant.""" @@ -29,8 +29,8 @@ async def async_init_integration( mock_pynut = _get_mock_pynutclient(list_ups={"ups1": "UPS 1"}, list_vars=list_vars) with patch( - "homeassistant.components.nut.PyNUTClient", return_value=mock_pynut, - ): + "homeassistant.components.nut.PyNUTClient", return_value=mock_pynut + ), patch("getmac.get_mac_address", return_value=mac_addr): entry = MockConfigEntry( domain=DOMAIN, data={ From 6855f68cd19511d44920b85f6fb6f584e86b9190 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 16 Apr 2020 01:37:54 +0000 Subject: [PATCH 6/8] fix test --- tests/components/nut/test_sensor.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/tests/components/nut/test_sensor.py b/tests/components/nut/test_sensor.py index 2df9654345eb99..3a91ef8f5b6e54 100644 --- a/tests/components/nut/test_sensor.py +++ b/tests/components/nut/test_sensor.py @@ -90,7 +90,9 @@ async def test_5e850i(hass): async def test_5e650i(hass): """Test creation of 5E650I sensors.""" - await async_init_integration(hass, "5E650I", ["battery.charge"]) + await async_init_integration( + hass, "192.168.2.1", "5E650I", ["battery.charge"], "00:00:00:00:00:00" + ) registry = await hass.helpers.entity_registry.async_get_registry() entry = registry.async_get("sensor.ups1_battery_charge") # No unique id, no registry entry @@ -176,11 +178,13 @@ async def test_cp1500pfclcd(hass): async def test_dl650elcd(hass): """Test creation of DL650ELCD sensors.""" - await async_init_integration(hass, "DL650ELCD", ["battery.charge"]) + await async_init_integration( + hass, "192.168.2.1", "DL650ELCD", ["battery.charge"], "54:A6:00:B3:00:00" + ) registry = await hass.helpers.entity_registry.async_get_registry() entry = registry.async_get("sensor.ups1_battery_charge") - # No unique id, no registry entry - assert not entry + assert entry + assert entry.unique_id == "ups1_54:A6:00:B3:00:00_battery.charge" state = hass.states.get("sensor.ups1_battery_charge") assert state.state == "100" @@ -201,11 +205,13 @@ async def test_dl650elcd(hass): async def test_blazer_usb(hass): """Test creation of blazer_usb sensors.""" - await async_init_integration(hass, "blazer_usb", ["battery.charge"]) + await async_init_integration( + hass, "192.168.2.1", "blazer_usb", ["battery.charge"], "54:A6:00:B3:00:00" + ) registry = await hass.helpers.entity_registry.async_get_registry() entry = registry.async_get("sensor.ups1_battery_charge") - # No unique id, no registry entry - assert not entry + assert entry + assert entry.unique_id == "ups1_54:A6:00:B3:00:00_battery.charge" state = hass.states.get("sensor.ups1_battery_charge") assert state.state == "100" From 78ed6fd18918c1dbcee5c8e2d672abb7cc6f9d40 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 16 Apr 2020 21:18:02 +0000 Subject: [PATCH 7/8] Much simpler solution --- homeassistant/components/nut/__init__.py | 40 +--------------- homeassistant/components/nut/const.py | 9 +--- homeassistant/components/nut/manifest.json | 2 +- tests/components/nut/test_sensor.py | 54 ++++++++-------------- tests/components/nut/util.py | 19 +++----- 5 files changed, 28 insertions(+), 96 deletions(-) diff --git a/homeassistant/components/nut/__init__.py b/homeassistant/components/nut/__init__.py index 7acb7f77e2402b..99563ca65d437a 100644 --- a/homeassistant/components/nut/__init__.py +++ b/homeassistant/components/nut/__init__.py @@ -1,11 +1,9 @@ """The nut component.""" import asyncio from datetime import timedelta -import ipaddress import logging import async_timeout -import getmac from pynut2.nut2 import PyNUTClient, PyNUTError from homeassistant.config_entries import ConfigEntry @@ -21,16 +19,11 @@ from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.update_coordinator import DataUpdateCoordinator -from homeassistant.util.network import is_loopback from .const import ( COORDINATOR, DEFAULT_SCAN_INTERVAL, - DEFAULT_UPS_ALIAS, DOMAIN, - EMPTY_MAC_ADDRESS, - IPV4_LOOPBACK, - LOCALHOST, PLATFORMS, PYNUT_DATA, PYNUT_FIRMWARE, @@ -93,9 +86,7 @@ async def async_update_data(): unique_id = _unique_id_from_status(status) if unique_id is None: - unique_id = await hass.async_add_executor_job( - _unique_id_from_nut_server, config - ) + unique_id = entry.entry_id hass.data[DOMAIN][entry.entry_id] = { COORDINATOR: coordinator, @@ -121,35 +112,6 @@ async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry): await hass.config_entries.async_reload(entry.entry_id) -def _unique_id_from_nut_server(config): - """Get the mac address of NUT if its on the LAN.""" - nut_host = config[CONF_HOST] - host_is_ip = True - - if nut_host == LOCALHOST: - nut_host = IPV4_LOOPBACK - else: - try: - ipaddress.ip_address(nut_host) - except ValueError: - host_is_ip = False - - if host_is_ip and is_loopback(ipaddress.ip_address(nut_host)): - mac_addr = getmac.get_mac_address() - elif host_is_ip: - mac_addr = getmac.get_mac_address(ip=nut_host) - else: - mac_addr = getmac.get_mac_address(hostname=nut_host) - - _LOGGER.debug("mac_address of NUT at %s: %s", nut_host, mac_addr) - - if mac_addr is None or mac_addr == EMPTY_MAC_ADDRESS: - return None - - ups_alias = config.get(CONF_ALIAS, DEFAULT_UPS_ALIAS) - return f"{ups_alias}_{mac_addr}" - - def _manufacturer_from_status(status): """Find the best manufacturer value from the status.""" return ( diff --git a/homeassistant/components/nut/const.py b/homeassistant/components/nut/const.py index afadaac6f34b64..13ffa318a4a533 100644 --- a/homeassistant/components/nut/const.py +++ b/homeassistant/components/nut/const.py @@ -20,9 +20,8 @@ UNDO_UPDATE_LISTENER = "undo_update_listener" DEFAULT_NAME = "NUT UPS" -DEFAULT_HOST = "127.0.0.1" +DEFAULT_HOST = "localhost" DEFAULT_PORT = 3493 -DEFAULT_UPS_ALIAS = "ups" KEY_STATUS = "ups.status" KEY_STATUS_DISPLAY = "ups.status.display" @@ -37,12 +36,6 @@ PYNUT_FIRMWARE = "firmware" PYNUT_NAME = "name" -EMPTY_MAC_ADDRESS = "00:00:00:00:00:00" - -LOCALHOST = "localhost" -IPV4_LOOPBACK = "127.0.0.1" - - SENSOR_TYPES = { "ups.status.display": ["Status", "", "mdi:information-outline", None], "ups.status": ["Status Data", "", "mdi:information-outline", None], diff --git a/homeassistant/components/nut/manifest.json b/homeassistant/components/nut/manifest.json index 8ab6eb1937a81b..226250b9a52f18 100644 --- a/homeassistant/components/nut/manifest.json +++ b/homeassistant/components/nut/manifest.json @@ -2,7 +2,7 @@ "domain": "nut", "name": "Network UPS Tools (NUT)", "documentation": "https://www.home-assistant.io/integrations/nut", - "requirements": ["pynut2==2.1.2", "getmac==0.8.2"], + "requirements": ["pynut2==2.1.2"], "codeowners": ["@bdraco"], "config_flow": true } diff --git a/tests/components/nut/test_sensor.py b/tests/components/nut/test_sensor.py index 3a91ef8f5b6e54..0c6887288b96fc 100644 --- a/tests/components/nut/test_sensor.py +++ b/tests/components/nut/test_sensor.py @@ -8,9 +8,7 @@ async def test_pr3000rt2u(hass): """Test creation of PR3000RT2U sensors.""" - await async_init_integration( - hass, "192.168.103.12", "PR3000RT2U", ["battery.charge"], "54:A6:00:B3:00:00" - ) + await async_init_integration(hass, "PR3000RT2U", ["battery.charge"]) registry = await hass.helpers.entity_registry.async_get_registry() entry = registry.async_get("sensor.ups1_battery_charge") assert entry @@ -35,16 +33,14 @@ async def test_pr3000rt2u(hass): async def test_cp1350c(hass): """Test creation of CP1350C sensors.""" - await async_init_integration( - hass, "192.168.103.18", "CP1350C", ["battery.charge"], "00:00:00:00:00:00" - ) + config_entry = await async_init_integration(hass, "CP1350C", ["battery.charge"]) + registry = await hass.helpers.entity_registry.async_get_registry() entry = registry.async_get("sensor.ups1_battery_charge") - # No unique id - assert not entry + assert entry + assert entry.unique_id == f"{config_entry.entry_id}_battery.charge" state = hass.states.get("sensor.ups1_battery_charge") - assert state.state == "100" expected_attributes = { @@ -63,13 +59,11 @@ async def test_cp1350c(hass): async def test_5e850i(hass): """Test creation of 5E850I sensors.""" - await async_init_integration( - hass, "192.168.103.45", "5E850I", ["battery.charge"], "54:A6:00:B3:00:00" - ) + config_entry = await async_init_integration(hass, "5E850I", ["battery.charge"]) registry = await hass.helpers.entity_registry.async_get_registry() entry = registry.async_get("sensor.ups1_battery_charge") assert entry - assert entry.unique_id == "ups1_54:A6:00:B3:00:00_battery.charge" + assert entry.unique_id == f"{config_entry.entry_id}_battery.charge" state = hass.states.get("sensor.ups1_battery_charge") assert state.state == "100" @@ -90,13 +84,11 @@ async def test_5e850i(hass): async def test_5e650i(hass): """Test creation of 5E650I sensors.""" - await async_init_integration( - hass, "192.168.2.1", "5E650I", ["battery.charge"], "00:00:00:00:00:00" - ) + config_entry = await async_init_integration(hass, "5E650I", ["battery.charge"]) registry = await hass.helpers.entity_registry.async_get_registry() entry = registry.async_get("sensor.ups1_battery_charge") - # No unique id, no registry entry - assert not entry + assert entry + assert entry.unique_id == f"{config_entry.entry_id}_battery.charge" state = hass.states.get("sensor.ups1_battery_charge") assert state.state == "100" @@ -117,13 +109,7 @@ async def test_5e650i(hass): async def test_backupsses600m1(hass): """Test creation of BACKUPSES600M1 sensors.""" - await async_init_integration( - hass, - "192.168.103.84", - "BACKUPSES600M1", - ["battery.charge"], - "24:A6:00:B3:00:00", - ) + await async_init_integration(hass, "BACKUPSES600M1", ["battery.charge"]) registry = await hass.helpers.entity_registry.async_get_registry() entry = registry.async_get("sensor.ups1_battery_charge") assert entry @@ -151,13 +137,13 @@ async def test_backupsses600m1(hass): async def test_cp1500pfclcd(hass): """Test creation of CP1500PFCLCD sensors.""" - await async_init_integration( - hass, "192.168.103.99", "CP1500PFCLCD", ["battery.charge"], "24:A6:00:B3:21:00" + config_entry = await async_init_integration( + hass, "CP1500PFCLCD", ["battery.charge"] ) registry = await hass.helpers.entity_registry.async_get_registry() entry = registry.async_get("sensor.ups1_battery_charge") assert entry - assert entry.unique_id == "ups1_24:A6:00:B3:21:00_battery.charge" + assert entry.unique_id == f"{config_entry.entry_id}_battery.charge" state = hass.states.get("sensor.ups1_battery_charge") assert state.state == "100" @@ -178,13 +164,11 @@ async def test_cp1500pfclcd(hass): async def test_dl650elcd(hass): """Test creation of DL650ELCD sensors.""" - await async_init_integration( - hass, "192.168.2.1", "DL650ELCD", ["battery.charge"], "54:A6:00:B3:00:00" - ) + config_entry = await async_init_integration(hass, "DL650ELCD", ["battery.charge"]) registry = await hass.helpers.entity_registry.async_get_registry() entry = registry.async_get("sensor.ups1_battery_charge") assert entry - assert entry.unique_id == "ups1_54:A6:00:B3:00:00_battery.charge" + assert entry.unique_id == f"{config_entry.entry_id}_battery.charge" state = hass.states.get("sensor.ups1_battery_charge") assert state.state == "100" @@ -205,13 +189,11 @@ async def test_dl650elcd(hass): async def test_blazer_usb(hass): """Test creation of blazer_usb sensors.""" - await async_init_integration( - hass, "192.168.2.1", "blazer_usb", ["battery.charge"], "54:A6:00:B3:00:00" - ) + config_entry = await async_init_integration(hass, "blazer_usb", ["battery.charge"]) registry = await hass.helpers.entity_registry.async_get_registry() entry = registry.async_get("sensor.ups1_battery_charge") assert entry - assert entry.unique_id == "ups1_54:A6:00:B3:00:00_battery.charge" + assert entry.unique_id == f"{config_entry.entry_id}_battery.charge" state = hass.states.get("sensor.ups1_battery_charge") assert state.state == "100" diff --git a/tests/components/nut/util.py b/tests/components/nut/util.py index 4b03d63bdeae79..788076a7c9f21a 100644 --- a/tests/components/nut/util.py +++ b/tests/components/nut/util.py @@ -5,7 +5,7 @@ from asynctest import MagicMock, patch from homeassistant.components.nut.const import DOMAIN -from homeassistant.const import CONF_ALIAS, CONF_HOST, CONF_PORT, CONF_RESOURCES +from homeassistant.const import CONF_HOST, CONF_PORT, CONF_RESOURCES from homeassistant.core import HomeAssistant from tests.common import MockConfigEntry, load_fixture @@ -19,26 +19,21 @@ def _get_mock_pynutclient(list_vars=None, list_ups=None): async def async_init_integration( - hass: HomeAssistant, host: str, ups_fixture: str, resources: list, mac_addr: str + hass: HomeAssistant, ups_fixture: str, resources: list ) -> MockConfigEntry: """Set up the nexia integration in Home Assistant.""" - fixture_path = f"nut/{ups_fixture}.json" - list_vars = json.loads(load_fixture(fixture_path)) + ups_fixture = f"nut/{ups_fixture}.json" + list_vars = json.loads(load_fixture(ups_fixture)) mock_pynut = _get_mock_pynutclient(list_ups={"ups1": "UPS 1"}, list_vars=list_vars) with patch( - "homeassistant.components.nut.PyNUTClient", return_value=mock_pynut - ), patch("getmac.get_mac_address", return_value=mac_addr): + "homeassistant.components.nut.PyNUTClient", return_value=mock_pynut, + ): entry = MockConfigEntry( domain=DOMAIN, - data={ - CONF_HOST: host, - CONF_PORT: "mock", - CONF_ALIAS: "ups1", - CONF_RESOURCES: resources, - }, + data={CONF_HOST: "mock", CONF_PORT: "mock", CONF_RESOURCES: resources}, ) entry.add_to_hass(hass) From 38d0f501f0b2640b5c9794a021a98eec4395db74 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 16 Apr 2020 21:19:03 +0000 Subject: [PATCH 8/8] revert unneeded --- requirements_all.txt | 1 - requirements_test_all.txt | 1 - 2 files changed, 2 deletions(-) diff --git a/requirements_all.txt b/requirements_all.txt index b9cf6762dc06ad..38133b1a4760e6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -607,7 +607,6 @@ georss_qld_bushfire_alert_client==0.3 # homeassistant.components.kef # homeassistant.components.minecraft_server # homeassistant.components.nmap_tracker -# homeassistant.components.nut getmac==0.8.2 # homeassistant.components.gios diff --git a/requirements_test_all.txt b/requirements_test_all.txt index dd61e20ece6fc3..6f013a52da050b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -242,7 +242,6 @@ georss_qld_bushfire_alert_client==0.3 # homeassistant.components.kef # homeassistant.components.minecraft_server # homeassistant.components.nmap_tracker -# homeassistant.components.nut getmac==0.8.2 # homeassistant.components.gios