mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 12:47:08 +00:00
Add Synology DSM scan interval option flow (#35183)
* Add Synology DSM scan interval option flow * Add options tests * Review: use entry.update_listeners * Use cv.positive_int * Try to fix "ValueError: Config entry has already been setup!" * Fix ValueError
This commit is contained in:
parent
f1ecac92df
commit
81651b0b25
@ -14,6 +14,7 @@ from homeassistant.const import (
|
||||
CONF_MAC,
|
||||
CONF_PASSWORD,
|
||||
CONF_PORT,
|
||||
CONF_SCAN_INTERVAL,
|
||||
CONF_SSL,
|
||||
CONF_USERNAME,
|
||||
)
|
||||
@ -22,7 +23,14 @@ from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
from homeassistant.helpers.event import async_track_time_interval
|
||||
from homeassistant.helpers.typing import HomeAssistantType
|
||||
|
||||
from .const import CONF_VOLUMES, DEFAULT_SSL, DOMAIN
|
||||
from .const import (
|
||||
CONF_VOLUMES,
|
||||
DEFAULT_SCAN_INTERVAL,
|
||||
DEFAULT_SSL,
|
||||
DOMAIN,
|
||||
SYNO_API,
|
||||
UNDO_UPDATE_LISTENER,
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema(
|
||||
{
|
||||
@ -41,8 +49,6 @@ CONFIG_SCHEMA = vol.Schema(
|
||||
extra=vol.ALLOW_EXTRA,
|
||||
)
|
||||
|
||||
SCAN_INTERVAL = timedelta(minutes=15)
|
||||
|
||||
|
||||
async def async_setup(hass, config):
|
||||
"""Set up Synology DSM sensors from legacy config file."""
|
||||
@ -63,20 +69,17 @@ async def async_setup(hass, config):
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry):
|
||||
"""Set up Synology DSM sensors."""
|
||||
host = entry.data[CONF_HOST]
|
||||
port = entry.data[CONF_PORT]
|
||||
username = entry.data[CONF_USERNAME]
|
||||
password = entry.data[CONF_PASSWORD]
|
||||
unit = hass.config.units.temperature_unit
|
||||
use_ssl = entry.data[CONF_SSL]
|
||||
device_token = entry.data.get("device_token")
|
||||
|
||||
api = SynoApi(hass, host, port, username, password, unit, use_ssl, device_token)
|
||||
api = SynoApi(hass, entry)
|
||||
|
||||
await api.async_setup()
|
||||
|
||||
undo_listener = entry.add_update_listener(_async_update_listener)
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
hass.data[DOMAIN][entry.unique_id] = api
|
||||
hass.data[DOMAIN][entry.unique_id] = {
|
||||
SYNO_API: api,
|
||||
UNDO_UPDATE_LISTENER: undo_listener,
|
||||
}
|
||||
|
||||
# For SSDP compat
|
||||
if not entry.data.get(CONF_MAC):
|
||||
@ -94,34 +97,31 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry):
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry):
|
||||
"""Unload Synology DSM sensors."""
|
||||
api = hass.data[DOMAIN][entry.unique_id]
|
||||
await api.async_unload()
|
||||
return await hass.config_entries.async_forward_entry_unload(entry, "sensor")
|
||||
unload_ok = await hass.config_entries.async_forward_entry_unload(entry, "sensor")
|
||||
|
||||
if unload_ok:
|
||||
entry_data = hass.data[DOMAIN][entry.unique_id]
|
||||
entry_data[UNDO_UPDATE_LISTENER]()
|
||||
await entry_data[SYNO_API].async_unload()
|
||||
hass.data[DOMAIN].pop(entry.unique_id)
|
||||
|
||||
return unload_ok
|
||||
|
||||
|
||||
async def _async_update_listener(hass: HomeAssistantType, entry: ConfigEntry):
|
||||
"""Handle options update."""
|
||||
await hass.config_entries.async_reload(entry.entry_id)
|
||||
|
||||
|
||||
class SynoApi:
|
||||
"""Class to interface with Synology DSM API."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistantType,
|
||||
host: str,
|
||||
port: int,
|
||||
username: str,
|
||||
password: str,
|
||||
temp_unit: str,
|
||||
use_ssl: bool,
|
||||
device_token: str,
|
||||
):
|
||||
def __init__(self, hass: HomeAssistantType, entry: ConfigEntry):
|
||||
"""Initialize the API wrapper class."""
|
||||
self._hass = hass
|
||||
self._host = host
|
||||
self._port = port
|
||||
self._username = username
|
||||
self._password = password
|
||||
self._use_ssl = use_ssl
|
||||
self._device_token = device_token
|
||||
self.temp_unit = temp_unit
|
||||
self._entry = entry
|
||||
|
||||
self.temp_unit = hass.config.units.temperature_unit
|
||||
|
||||
self.dsm: SynologyDSM = None
|
||||
self.information: SynoDSMInformation = None
|
||||
@ -138,19 +138,25 @@ class SynoApi:
|
||||
async def async_setup(self):
|
||||
"""Start interacting with the NAS."""
|
||||
self.dsm = SynologyDSM(
|
||||
self._host,
|
||||
self._port,
|
||||
self._username,
|
||||
self._password,
|
||||
self._use_ssl,
|
||||
device_token=self._device_token,
|
||||
self._entry.data[CONF_HOST],
|
||||
self._entry.data[CONF_PORT],
|
||||
self._entry.data[CONF_USERNAME],
|
||||
self._entry.data[CONF_PASSWORD],
|
||||
self._entry.data[CONF_SSL],
|
||||
device_token=self._entry.data.get("device_token"),
|
||||
)
|
||||
|
||||
await self._hass.async_add_executor_job(self._fetch_device_configuration)
|
||||
await self.update()
|
||||
|
||||
self._unsub_dispatcher = async_track_time_interval(
|
||||
self._hass, self.update, SCAN_INTERVAL
|
||||
self._hass,
|
||||
self.update,
|
||||
timedelta(
|
||||
minutes=self._entry.options.get(
|
||||
CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
def _fetch_device_configuration(self):
|
||||
|
@ -21,11 +21,20 @@ from homeassistant.const import (
|
||||
CONF_NAME,
|
||||
CONF_PASSWORD,
|
||||
CONF_PORT,
|
||||
CONF_SCAN_INTERVAL,
|
||||
CONF_SSL,
|
||||
CONF_USERNAME,
|
||||
)
|
||||
from homeassistant.core import callback
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
from .const import CONF_VOLUMES, DEFAULT_PORT, DEFAULT_PORT_SSL, DEFAULT_SSL
|
||||
from .const import (
|
||||
CONF_VOLUMES,
|
||||
DEFAULT_PORT,
|
||||
DEFAULT_PORT_SSL,
|
||||
DEFAULT_SCAN_INTERVAL,
|
||||
DEFAULT_SSL,
|
||||
)
|
||||
from .const import DOMAIN # pylint: disable=unused-import
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@ -61,6 +70,12 @@ class SynologyDSMFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
VERSION = 1
|
||||
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL
|
||||
|
||||
@staticmethod
|
||||
@callback
|
||||
def async_get_options_flow(config_entry):
|
||||
"""Get the options flow for this handler."""
|
||||
return SynologyDSMOptionsFlowHandler(config_entry)
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the synology_dsm config flow."""
|
||||
self.saved_user_input = {}
|
||||
@ -212,6 +227,31 @@ class SynologyDSMFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
return mac in existing_macs
|
||||
|
||||
|
||||
class SynologyDSMOptionsFlowHandler(config_entries.OptionsFlow):
|
||||
"""Handle a option flow."""
|
||||
|
||||
def __init__(self, config_entry: config_entries.ConfigEntry):
|
||||
"""Initialize options flow."""
|
||||
self.config_entry = config_entry
|
||||
|
||||
async def async_step_init(self, user_input=None):
|
||||
"""Handle options flow."""
|
||||
if user_input is not None:
|
||||
return self.async_create_entry(title="", data=user_input)
|
||||
|
||||
data_schema = vol.Schema(
|
||||
{
|
||||
vol.Optional(
|
||||
CONF_SCAN_INTERVAL,
|
||||
default=self.config_entry.options.get(
|
||||
CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL
|
||||
),
|
||||
): cv.positive_int
|
||||
}
|
||||
)
|
||||
return self.async_show_form(step_id="init", data_schema=data_schema)
|
||||
|
||||
|
||||
def _login_and_fetch_syno_info(api, otp_code):
|
||||
"""Login to the NAS and fetch basic data."""
|
||||
# These do i/o
|
||||
|
@ -9,10 +9,17 @@ from homeassistant.const import (
|
||||
DOMAIN = "synology_dsm"
|
||||
BASE_NAME = "Synology"
|
||||
|
||||
# Entry keys
|
||||
SYNO_API = "syno_api"
|
||||
UNDO_UPDATE_LISTENER = "undo_update_listener"
|
||||
|
||||
# Configuration
|
||||
CONF_VOLUMES = "volumes"
|
||||
DEFAULT_SSL = True
|
||||
DEFAULT_PORT = 5000
|
||||
DEFAULT_PORT_SSL = 5001
|
||||
# Options
|
||||
DEFAULT_SCAN_INTERVAL = 15 # min
|
||||
|
||||
UTILISATION_SENSORS = {
|
||||
"cpu_other_load": ["CPU Load (Other)", UNIT_PERCENTAGE, "mdi:chip"],
|
||||
|
@ -22,6 +22,7 @@ from .const import (
|
||||
DOMAIN,
|
||||
STORAGE_DISK_SENSORS,
|
||||
STORAGE_VOL_SENSORS,
|
||||
SYNO_API,
|
||||
TEMP_SENSORS_KEYS,
|
||||
UTILISATION_SENSORS,
|
||||
)
|
||||
@ -34,7 +35,7 @@ async def async_setup_entry(
|
||||
) -> None:
|
||||
"""Set up the Synology NAS Sensor."""
|
||||
|
||||
api = hass.data[DOMAIN][entry.unique_id]
|
||||
api = hass.data[DOMAIN][entry.unique_id][SYNO_API]
|
||||
|
||||
sensors = [
|
||||
SynoNasUtilSensor(api, sensor_type, UTILISATION_SENSORS[sensor_type])
|
||||
|
@ -37,5 +37,14 @@
|
||||
"unknown": "Unknown error: please check logs to get more details"
|
||||
},
|
||||
"abort": { "already_configured": "Host already configured" }
|
||||
},
|
||||
"options": {
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"scan_interval": "Minutes between scans"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,6 @@
|
||||
},
|
||||
"link": {
|
||||
"data": {
|
||||
"api_version": "DSM version",
|
||||
"password": "Password",
|
||||
"port": "Port (Optional)",
|
||||
"ssl": "Use SSL/TLS to connect to your NAS",
|
||||
@ -31,7 +30,6 @@
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
"api_version": "DSM version",
|
||||
"host": "Host",
|
||||
"password": "Password",
|
||||
"port": "Port (Optional)",
|
||||
@ -41,5 +39,14 @@
|
||||
"title": "Synology DSM"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"scan_interval": "Minutes between scans"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -4,8 +4,8 @@ import pytest
|
||||
from tests.async_mock import patch
|
||||
|
||||
|
||||
@pytest.fixture(name="dsm_bypass_setup", autouse=True)
|
||||
def dsm_bypass_setup_fixture():
|
||||
@pytest.fixture(name="bypass_setup", autouse=True)
|
||||
def bypass_setup_fixture():
|
||||
"""Mock component setup."""
|
||||
with patch(
|
||||
"homeassistant.components.synology_dsm.async_setup_entry", return_value=True
|
||||
|
@ -17,6 +17,7 @@ from homeassistant.components.synology_dsm.const import (
|
||||
CONF_VOLUMES,
|
||||
DEFAULT_PORT,
|
||||
DEFAULT_PORT_SSL,
|
||||
DEFAULT_SCAN_INTERVAL,
|
||||
DEFAULT_SSL,
|
||||
DOMAIN,
|
||||
)
|
||||
@ -27,6 +28,7 @@ from homeassistant.const import (
|
||||
CONF_MAC,
|
||||
CONF_PASSWORD,
|
||||
CONF_PORT,
|
||||
CONF_SCAN_INTERVAL,
|
||||
CONF_SSL,
|
||||
CONF_USERNAME,
|
||||
)
|
||||
@ -393,3 +395,40 @@ async def test_form_ssdp(hass: HomeAssistantType, service: MagicMock):
|
||||
assert result["data"].get("device_token") is None
|
||||
assert result["data"].get(CONF_DISKS) is None
|
||||
assert result["data"].get(CONF_VOLUMES) is None
|
||||
|
||||
|
||||
async def test_options_flow(hass: HomeAssistantType, service: MagicMock):
|
||||
"""Test config flow options."""
|
||||
config_entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data={
|
||||
CONF_HOST: HOST,
|
||||
CONF_USERNAME: USERNAME,
|
||||
CONF_PASSWORD: PASSWORD,
|
||||
CONF_MAC: MACS,
|
||||
},
|
||||
unique_id=SERIAL,
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
assert config_entry.options == {}
|
||||
|
||||
result = await hass.config_entries.options.async_init(config_entry.entry_id)
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["step_id"] == "init"
|
||||
|
||||
# Scan interval
|
||||
# Default
|
||||
result = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"], user_input={},
|
||||
)
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||
assert config_entry.options[CONF_SCAN_INTERVAL] == DEFAULT_SCAN_INTERVAL
|
||||
|
||||
# Manual
|
||||
result = await hass.config_entries.options.async_init(config_entry.entry_id)
|
||||
result = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"], user_input={CONF_SCAN_INTERVAL: 2},
|
||||
)
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||
assert config_entry.options[CONF_SCAN_INTERVAL] == 2
|
||||
|
Loading…
x
Reference in New Issue
Block a user