Use entity_registry_enabled_default for Nut sensors (#56854)

This commit is contained in:
ollo69 2021-10-13 23:33:03 +02:00 committed by GitHub
parent b220ab6e91
commit b54fc0229d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 56 additions and 95 deletions

View File

@ -39,6 +39,11 @@ _LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Network UPS Tools (NUT) from a config entry.""" """Set up Network UPS Tools (NUT) from a config entry."""
# strip out the stale options CONF_RESOURCES
if CONF_RESOURCES in entry.options:
new_options = {k: v for k, v in entry.options.items() if k != CONF_RESOURCES}
hass.config_entries.async_update_entry(entry, options=new_options)
config = entry.data config = entry.data
host = config[CONF_HOST] host = config[CONF_HOST]
port = config[CONF_PORT] port = config[CONF_PORT]
@ -156,13 +161,6 @@ def _unique_id_from_status(status):
return "_".join(unique_id_group) return "_".join(unique_id_group)
def find_resources_in_config_entry(config_entry):
"""Find the configured resources in the config entry."""
if CONF_RESOURCES in config_entry.options:
return config_entry.options[CONF_RESOURCES]
return config_entry.data[CONF_RESOURCES]
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry.""" """Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

View File

@ -17,7 +17,7 @@ from homeassistant.const import (
from homeassistant.core import callback from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from . import PyNUTData, find_resources_in_config_entry from . import PyNUTData
from .const import ( from .const import (
DEFAULT_HOST, DEFAULT_HOST,
DEFAULT_PORT, DEFAULT_PORT,
@ -229,35 +229,17 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
if user_input is not None: if user_input is not None:
return self.async_create_entry(title="", data=user_input) return self.async_create_entry(title="", data=user_input)
resources = find_resources_in_config_entry(self.config_entry)
scan_interval = self.config_entry.options.get( scan_interval = self.config_entry.options.get(
CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL
) )
errors = {} base_schema = {
try: vol.Optional(CONF_SCAN_INTERVAL, default=scan_interval): vol.All(
info = await validate_input(self.hass, self.config_entry.data) vol.Coerce(int), vol.Clamp(min=10, max=300)
except CannotConnect: )
errors[CONF_BASE] = "cannot_connect" }
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected exception")
errors[CONF_BASE] = "unknown"
if errors: return self.async_show_form(step_id="init", data_schema=vol.Schema(base_schema))
return self.async_show_form(step_id="abort", errors=errors)
base_schema = _resource_schema_base(info["available_resources"], resources)
base_schema[
vol.Optional(CONF_SCAN_INTERVAL, default=scan_interval)
] = cv.positive_int
return self.async_show_form(
step_id="init", data_schema=vol.Schema(base_schema), errors=errors
)
async def async_step_abort(self, user_input=None):
"""Abort options flow."""
return self.async_create_entry(title="", data=self.config_entry.options)
class CannotConnect(exceptions.HomeAssistantError): class CannotConnect(exceptions.HomeAssistantError):

View File

@ -42,39 +42,29 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
data = pynut_data[PYNUT_DATA] data = pynut_data[PYNUT_DATA]
status = data.status status = data.status
entities = [] enabled_resources = [
resource.lower() for resource in config_entry.data[CONF_RESOURCES]
]
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)
if CONF_RESOURCES in config_entry.options: entities = [
resources = config_entry.options[CONF_RESOURCES] NUTSensor(
else: coordinator,
resources = config_entry.data[CONF_RESOURCES] data,
name.title(),
for resource in resources: SENSOR_TYPES[sensor_type],
sensor_type = resource.lower() unique_id,
manufacturer,
# Display status is a special case that falls back to the status value model,
# of the UPS instead. firmware,
if sensor_type in status or ( sensor_type in enabled_resources,
sensor_type == KEY_STATUS_DISPLAY and KEY_STATUS in status )
): for sensor_type in resources
entities.append( ]
NUTSensor(
coordinator,
data,
name.title(),
SENSOR_TYPES[sensor_type],
unique_id,
manufacturer,
model,
firmware,
)
)
else:
_LOGGER.info(
"Sensor type: %s does not appear in the NUT status "
"output, cannot add",
sensor_type,
)
async_add_entities(entities, True) async_add_entities(entities, True)
@ -92,6 +82,7 @@ class NUTSensor(CoordinatorEntity, SensorEntity):
manufacturer: str | None, manufacturer: str | None,
model: str | None, model: str | None,
firmware: str | None, firmware: str | None,
enabled_default: bool,
) -> None: ) -> None:
"""Initialize the sensor.""" """Initialize the sensor."""
super().__init__(coordinator) super().__init__(coordinator)
@ -102,6 +93,7 @@ class NUTSensor(CoordinatorEntity, SensorEntity):
self._device_name = name self._device_name = name
self._data = data self._data = data
self._unique_id = unique_id self._unique_id = unique_id
self._attr_entity_registry_enabled_default = enabled_default
self._attr_name = f"{name} {sensor_description.name}" self._attr_name = f"{name} {sensor_description.name}"
if unique_id is not None: if unique_id is not None:

View File

@ -35,16 +35,10 @@
"options": { "options": {
"step": { "step": {
"init": { "init": {
"description": "Choose Sensor Resources.",
"data": { "data": {
"resources": "Resources",
"scan_interval": "Scan Interval (seconds)" "scan_interval": "Scan Interval (seconds)"
} }
} }
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
} }
} }
} }

View File

@ -33,17 +33,11 @@
} }
}, },
"options": { "options": {
"error": {
"cannot_connect": "Failed to connect",
"unknown": "Unexpected error"
},
"step": { "step": {
"init": { "init": {
"data": { "data": {
"resources": "Resources",
"scan_interval": "Scan Interval (seconds)" "scan_interval": "Scan Interval (seconds)"
}, }
"description": "Choose Sensor Resources."
} }
} }
} }

View File

@ -294,37 +294,25 @@ async def test_options_flow(hass):
domain=DOMAIN, domain=DOMAIN,
unique_id="abcde12345", unique_id="abcde12345",
data=VALID_CONFIG, data=VALID_CONFIG,
options={CONF_RESOURCES: ["battery.charge"]},
) )
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
mock_pynut = _get_mock_pynutclient( with patch("homeassistant.components.nut.async_setup_entry", return_value=True):
list_vars={"battery.voltage": "voltage"}, list_ups=["ups1"]
)
with patch(
"homeassistant.components.nut.PyNUTClient",
return_value=mock_pynut,
), patch("homeassistant.components.nut.async_setup_entry", return_value=True):
result = await hass.config_entries.options.async_init(config_entry.entry_id) result = await hass.config_entries.options.async_init(config_entry.entry_id)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "init" assert result["step_id"] == "init"
result = await hass.config_entries.options.async_configure( result = await hass.config_entries.options.async_configure(
result["flow_id"], user_input={CONF_RESOURCES: ["battery.voltage"]} result["flow_id"], user_input={}
) )
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert config_entry.options == { assert config_entry.options == {
CONF_RESOURCES: ["battery.voltage"],
CONF_SCAN_INTERVAL: 60, CONF_SCAN_INTERVAL: 60,
} }
with patch( with patch("homeassistant.components.nut.async_setup_entry", return_value=True):
"homeassistant.components.nut.PyNUTClient",
return_value=mock_pynut,
), patch("homeassistant.components.nut.async_setup_entry", return_value=True):
result2 = await hass.config_entries.options.async_init(config_entry.entry_id) result2 = await hass.config_entries.options.async_init(config_entry.entry_id)
assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM
@ -332,11 +320,10 @@ async def test_options_flow(hass):
result2 = await hass.config_entries.options.async_configure( result2 = await hass.config_entries.options.async_configure(
result2["flow_id"], result2["flow_id"],
user_input={CONF_RESOURCES: ["battery.voltage"], CONF_SCAN_INTERVAL: 12}, user_input={CONF_SCAN_INTERVAL: 12},
) )
assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert config_entry.options == { assert config_entry.options == {
CONF_RESOURCES: ["battery.voltage"],
CONF_SCAN_INTERVAL: 12, CONF_SCAN_INTERVAL: 12,
} }

View File

@ -202,3 +202,16 @@ async def test_blazer_usb(hass):
assert all( assert all(
state.attributes[key] == expected_attributes[key] for key in expected_attributes state.attributes[key] == expected_attributes[key] for key in expected_attributes
) )
async def test_stale_options(hass):
"""Test creation of sensors with stale options to remove."""
config_entry = await async_init_integration(
hass, "blazer_usb", ["battery.charge"], True
)
registry = er.async_get(hass)
entry = registry.async_get("sensor.ups1_battery_charge")
assert entry
assert entry.unique_id == f"{config_entry.entry_id}_battery.charge"
assert config_entry.options == {}

View File

@ -18,7 +18,7 @@ def _get_mock_pynutclient(list_vars=None, list_ups=None):
async def async_init_integration( async def async_init_integration(
hass: HomeAssistant, ups_fixture: str, resources: list hass: HomeAssistant, ups_fixture: str, resources: list, add_options: bool = False
) -> MockConfigEntry: ) -> MockConfigEntry:
"""Set up the nexia integration in Home Assistant.""" """Set up the nexia integration in Home Assistant."""
@ -34,6 +34,7 @@ async def async_init_integration(
entry = MockConfigEntry( entry = MockConfigEntry(
domain=DOMAIN, domain=DOMAIN,
data={CONF_HOST: "mock", CONF_PORT: "mock", CONF_RESOURCES: resources}, data={CONF_HOST: "mock", CONF_PORT: "mock", CONF_RESOURCES: resources},
options={CONF_RESOURCES: resources} if add_options else {},
) )
entry.add_to_hass(hass) entry.add_to_hass(hass)