Improve typing for synology_dsm (#49656)
This commit is contained in:
parent
717f4e69d5
commit
042822e35e
10 changed files with 241 additions and 132 deletions
|
@ -40,6 +40,7 @@ homeassistant.components.slack.*
|
||||||
homeassistant.components.sonos.media_player
|
homeassistant.components.sonos.media_player
|
||||||
homeassistant.components.sun.*
|
homeassistant.components.sun.*
|
||||||
homeassistant.components.switch.*
|
homeassistant.components.switch.*
|
||||||
|
homeassistant.components.synology_dsm.*
|
||||||
homeassistant.components.systemmonitor.*
|
homeassistant.components.systemmonitor.*
|
||||||
homeassistant.components.tts.*
|
homeassistant.components.tts.*
|
||||||
homeassistant.components.vacuum.*
|
homeassistant.components.vacuum.*
|
||||||
|
|
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import logging
|
import logging
|
||||||
from typing import Any
|
from typing import Any, Callable
|
||||||
|
|
||||||
import async_timeout
|
import async_timeout
|
||||||
from synology_dsm import SynologyDSM
|
from synology_dsm import SynologyDSM
|
||||||
|
@ -15,6 +15,7 @@ from synology_dsm.api.dsm.information import SynoDSMInformation
|
||||||
from synology_dsm.api.dsm.network import SynoDSMNetwork
|
from synology_dsm.api.dsm.network import SynoDSMNetwork
|
||||||
from synology_dsm.api.storage.storage import SynoStorage
|
from synology_dsm.api.storage.storage import SynoStorage
|
||||||
from synology_dsm.api.surveillance_station import SynoSurveillanceStation
|
from synology_dsm.api.surveillance_station import SynoSurveillanceStation
|
||||||
|
from synology_dsm.api.surveillance_station.camera import SynoCamera
|
||||||
from synology_dsm.exceptions import (
|
from synology_dsm.exceptions import (
|
||||||
SynologyDSMAPIErrorException,
|
SynologyDSMAPIErrorException,
|
||||||
SynologyDSMLoginFailedException,
|
SynologyDSMLoginFailedException,
|
||||||
|
@ -38,9 +39,14 @@ from homeassistant.const import (
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant, ServiceCall, callback
|
from homeassistant.core import HomeAssistant, ServiceCall, callback
|
||||||
from homeassistant.exceptions import ConfigEntryNotReady
|
from homeassistant.exceptions import ConfigEntryNotReady
|
||||||
from homeassistant.helpers import entity_registry
|
from homeassistant.helpers import device_registry, entity_registry
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
from homeassistant.helpers.device_registry import (
|
||||||
|
DeviceEntry,
|
||||||
|
async_get_registry as get_dev_reg,
|
||||||
|
)
|
||||||
from homeassistant.helpers.entity import DeviceInfo
|
from homeassistant.helpers.entity import DeviceInfo
|
||||||
|
from homeassistant.helpers.typing import ConfigType
|
||||||
from homeassistant.helpers.update_coordinator import (
|
from homeassistant.helpers.update_coordinator import (
|
||||||
CoordinatorEntity,
|
CoordinatorEntity,
|
||||||
DataUpdateCoordinator,
|
DataUpdateCoordinator,
|
||||||
|
@ -74,6 +80,7 @@ from .const import (
|
||||||
SYSTEM_LOADED,
|
SYSTEM_LOADED,
|
||||||
UNDO_UPDATE_LISTENER,
|
UNDO_UPDATE_LISTENER,
|
||||||
UTILISATION_SENSORS,
|
UTILISATION_SENSORS,
|
||||||
|
EntityInfo,
|
||||||
)
|
)
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema(
|
CONFIG_SCHEMA = vol.Schema(
|
||||||
|
@ -103,7 +110,7 @@ ATTRIBUTION = "Data provided by Synology"
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup(hass, config):
|
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||||
"""Set up Synology DSM sensors from legacy config file."""
|
"""Set up Synology DSM sensors from legacy config file."""
|
||||||
|
|
||||||
conf = config.get(DOMAIN)
|
conf = config.get(DOMAIN)
|
||||||
|
@ -122,12 +129,16 @@ async def async_setup(hass, config):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
async def async_setup_entry( # noqa: C901
|
||||||
|
hass: HomeAssistant, entry: ConfigEntry
|
||||||
|
) -> bool:
|
||||||
"""Set up Synology DSM sensors."""
|
"""Set up Synology DSM sensors."""
|
||||||
|
|
||||||
# Migrate old unique_id
|
# Migrate old unique_id
|
||||||
@callback
|
@callback
|
||||||
def _async_migrator(entity_entry: entity_registry.RegistryEntry):
|
def _async_migrator(
|
||||||
|
entity_entry: entity_registry.RegistryEntry,
|
||||||
|
) -> dict[str, str] | None:
|
||||||
"""Migrate away from ID using label."""
|
"""Migrate away from ID using label."""
|
||||||
# Reject if new unique_id
|
# Reject if new unique_id
|
||||||
if "SYNO." in entity_entry.unique_id:
|
if "SYNO." in entity_entry.unique_id:
|
||||||
|
@ -152,7 +163,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||||
):
|
):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
entity_type = None
|
entity_type: str | None = None
|
||||||
for entity_key, entity_attrs in entries.items():
|
for entity_key, entity_attrs in entries.items():
|
||||||
if (
|
if (
|
||||||
device_id
|
device_id
|
||||||
|
@ -170,6 +181,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||||
if entity_attrs[ENTITY_NAME] == label:
|
if entity_attrs[ENTITY_NAME] == label:
|
||||||
entity_type = entity_key
|
entity_type = entity_key
|
||||||
|
|
||||||
|
if entity_type is None:
|
||||||
|
return None
|
||||||
|
|
||||||
new_unique_id = "_".join([serial, entity_type])
|
new_unique_id = "_".join([serial, entity_type])
|
||||||
if device_id:
|
if device_id:
|
||||||
new_unique_id += f"_{device_id}"
|
new_unique_id += f"_{device_id}"
|
||||||
|
@ -183,6 +197,22 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||||
|
|
||||||
await entity_registry.async_migrate_entries(hass, entry.entry_id, _async_migrator)
|
await entity_registry.async_migrate_entries(hass, entry.entry_id, _async_migrator)
|
||||||
|
|
||||||
|
# migrate device indetifiers
|
||||||
|
dev_reg = await get_dev_reg(hass)
|
||||||
|
devices: list[DeviceEntry] = device_registry.async_entries_for_config_entry(
|
||||||
|
dev_reg, entry.entry_id
|
||||||
|
)
|
||||||
|
for device in devices:
|
||||||
|
old_identifier = list(next(iter(device.identifiers)))
|
||||||
|
if len(old_identifier) > 2:
|
||||||
|
new_identifier: set[tuple[str, ...]] = {
|
||||||
|
(old_identifier.pop(0), "_".join([str(x) for x in old_identifier]))
|
||||||
|
}
|
||||||
|
_LOGGER.debug(
|
||||||
|
"migrate identifier '%s' to '%s'", device.identifiers, new_identifier
|
||||||
|
)
|
||||||
|
dev_reg.async_update_device(device.id, new_identifiers=new_identifier)
|
||||||
|
|
||||||
# Migrate existing entry configuration
|
# Migrate existing entry configuration
|
||||||
if entry.data.get(CONF_VERIFY_SSL) is None:
|
if entry.data.get(CONF_VERIFY_SSL) is None:
|
||||||
hass.config_entries.async_update_entry(
|
hass.config_entries.async_update_entry(
|
||||||
|
@ -216,7 +246,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||||
entry, data={**entry.data, CONF_MAC: network.macs}
|
entry, data={**entry.data, CONF_MAC: network.macs}
|
||||||
)
|
)
|
||||||
|
|
||||||
async def async_coordinator_update_data_cameras():
|
async def async_coordinator_update_data_cameras() -> dict[
|
||||||
|
str, dict[str, SynoCamera]
|
||||||
|
] | None:
|
||||||
"""Fetch all camera data from api."""
|
"""Fetch all camera data from api."""
|
||||||
if not hass.data[DOMAIN][entry.unique_id][SYSTEM_LOADED]:
|
if not hass.data[DOMAIN][entry.unique_id][SYSTEM_LOADED]:
|
||||||
raise UpdateFailed("System not fully loaded")
|
raise UpdateFailed("System not fully loaded")
|
||||||
|
@ -238,7 +270,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async def async_coordinator_update_data_central():
|
async def async_coordinator_update_data_central() -> None:
|
||||||
"""Fetch all device and sensor data from api."""
|
"""Fetch all device and sensor data from api."""
|
||||||
try:
|
try:
|
||||||
await api.async_update()
|
await api.async_update()
|
||||||
|
@ -246,7 +278,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||||
raise UpdateFailed(f"Error communicating with API: {err}") from err
|
raise UpdateFailed(f"Error communicating with API: {err}") from err
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def async_coordinator_update_data_switches():
|
async def async_coordinator_update_data_switches() -> dict[
|
||||||
|
str, dict[str, Any]
|
||||||
|
] | None:
|
||||||
"""Fetch all switch data from api."""
|
"""Fetch all switch data from api."""
|
||||||
if not hass.data[DOMAIN][entry.unique_id][SYSTEM_LOADED]:
|
if not hass.data[DOMAIN][entry.unique_id][SYSTEM_LOADED]:
|
||||||
raise UpdateFailed("System not fully loaded")
|
raise UpdateFailed("System not fully loaded")
|
||||||
|
@ -294,7 +328,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Unload Synology DSM sensors."""
|
"""Unload Synology DSM sensors."""
|
||||||
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||||
if unload_ok:
|
if unload_ok:
|
||||||
|
@ -306,15 +340,15 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||||
return unload_ok
|
return unload_ok
|
||||||
|
|
||||||
|
|
||||||
async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry):
|
async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||||
"""Handle options update."""
|
"""Handle options update."""
|
||||||
await hass.config_entries.async_reload(entry.entry_id)
|
await hass.config_entries.async_reload(entry.entry_id)
|
||||||
|
|
||||||
|
|
||||||
async def _async_setup_services(hass: HomeAssistant):
|
async def _async_setup_services(hass: HomeAssistant) -> None:
|
||||||
"""Service handler setup."""
|
"""Service handler setup."""
|
||||||
|
|
||||||
async def service_handler(call: ServiceCall):
|
async def service_handler(call: ServiceCall) -> None:
|
||||||
"""Handle service call."""
|
"""Handle service call."""
|
||||||
serial = call.data.get(CONF_SERIAL)
|
serial = call.data.get(CONF_SERIAL)
|
||||||
dsm_devices = hass.data[DOMAIN]
|
dsm_devices = hass.data[DOMAIN]
|
||||||
|
@ -350,7 +384,7 @@ async def _async_setup_services(hass: HomeAssistant):
|
||||||
class SynoApi:
|
class SynoApi:
|
||||||
"""Class to interface with Synology DSM API."""
|
"""Class to interface with Synology DSM API."""
|
||||||
|
|
||||||
def __init__(self, hass: HomeAssistant, entry: ConfigEntry):
|
def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||||
"""Initialize the API wrapper class."""
|
"""Initialize the API wrapper class."""
|
||||||
self._hass = hass
|
self._hass = hass
|
||||||
self._entry = entry
|
self._entry = entry
|
||||||
|
@ -367,7 +401,7 @@ class SynoApi:
|
||||||
self.utilisation: SynoCoreUtilization = None
|
self.utilisation: SynoCoreUtilization = None
|
||||||
|
|
||||||
# Should we fetch them
|
# Should we fetch them
|
||||||
self._fetching_entities = {}
|
self._fetching_entities: dict[str, set[str]] = {}
|
||||||
self._with_information = True
|
self._with_information = True
|
||||||
self._with_security = True
|
self._with_security = True
|
||||||
self._with_storage = True
|
self._with_storage = True
|
||||||
|
@ -376,7 +410,7 @@ class SynoApi:
|
||||||
self._with_upgrade = True
|
self._with_upgrade = True
|
||||||
self._with_utilisation = True
|
self._with_utilisation = True
|
||||||
|
|
||||||
async def async_setup(self):
|
async def async_setup(self) -> None:
|
||||||
"""Start interacting with the NAS."""
|
"""Start interacting with the NAS."""
|
||||||
self.dsm = SynologyDSM(
|
self.dsm = SynologyDSM(
|
||||||
self._entry.data[CONF_HOST],
|
self._entry.data[CONF_HOST],
|
||||||
|
@ -406,7 +440,7 @@ class SynoApi:
|
||||||
await self.async_update()
|
await self.async_update()
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def subscribe(self, api_key, unique_id):
|
def subscribe(self, api_key: str, unique_id: str) -> Callable[[], None]:
|
||||||
"""Subscribe an entity to API fetches."""
|
"""Subscribe an entity to API fetches."""
|
||||||
_LOGGER.debug("Subscribe new entity: %s", unique_id)
|
_LOGGER.debug("Subscribe new entity: %s", unique_id)
|
||||||
if api_key not in self._fetching_entities:
|
if api_key not in self._fetching_entities:
|
||||||
|
@ -424,7 +458,7 @@ class SynoApi:
|
||||||
return unsubscribe
|
return unsubscribe
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _async_setup_api_requests(self):
|
def _async_setup_api_requests(self) -> None:
|
||||||
"""Determine if we should fetch each API, if one entity needs it."""
|
"""Determine if we should fetch each API, if one entity needs it."""
|
||||||
# Entities not added yet, fetch all
|
# Entities not added yet, fetch all
|
||||||
if not self._fetching_entities:
|
if not self._fetching_entities:
|
||||||
|
@ -488,7 +522,7 @@ class SynoApi:
|
||||||
self.dsm.reset(self.utilisation)
|
self.dsm.reset(self.utilisation)
|
||||||
self.utilisation = None
|
self.utilisation = None
|
||||||
|
|
||||||
def _fetch_device_configuration(self):
|
def _fetch_device_configuration(self) -> None:
|
||||||
"""Fetch initial device config."""
|
"""Fetch initial device config."""
|
||||||
self.information = self.dsm.information
|
self.information = self.dsm.information
|
||||||
self.network = self.dsm.network
|
self.network = self.dsm.network
|
||||||
|
@ -523,7 +557,7 @@ class SynoApi:
|
||||||
)
|
)
|
||||||
self.surveillance_station = self.dsm.surveillance_station
|
self.surveillance_station = self.dsm.surveillance_station
|
||||||
|
|
||||||
async def async_reboot(self):
|
async def async_reboot(self) -> None:
|
||||||
"""Reboot NAS."""
|
"""Reboot NAS."""
|
||||||
try:
|
try:
|
||||||
await self._hass.async_add_executor_job(self.system.reboot)
|
await self._hass.async_add_executor_job(self.system.reboot)
|
||||||
|
@ -534,7 +568,7 @@ class SynoApi:
|
||||||
)
|
)
|
||||||
_LOGGER.debug("Exception:%s", err)
|
_LOGGER.debug("Exception:%s", err)
|
||||||
|
|
||||||
async def async_shutdown(self):
|
async def async_shutdown(self) -> None:
|
||||||
"""Shutdown NAS."""
|
"""Shutdown NAS."""
|
||||||
try:
|
try:
|
||||||
await self._hass.async_add_executor_job(self.system.shutdown)
|
await self._hass.async_add_executor_job(self.system.shutdown)
|
||||||
|
@ -545,7 +579,7 @@ class SynoApi:
|
||||||
)
|
)
|
||||||
_LOGGER.debug("Exception:%s", err)
|
_LOGGER.debug("Exception:%s", err)
|
||||||
|
|
||||||
async def async_unload(self):
|
async def async_unload(self) -> None:
|
||||||
"""Stop interacting with the NAS and prepare for removal from hass."""
|
"""Stop interacting with the NAS and prepare for removal from hass."""
|
||||||
try:
|
try:
|
||||||
await self._hass.async_add_executor_job(self.dsm.logout)
|
await self._hass.async_add_executor_job(self.dsm.logout)
|
||||||
|
@ -554,7 +588,7 @@ class SynoApi:
|
||||||
"Logout from '%s' not possible:%s", self._entry.unique_id, err
|
"Logout from '%s' not possible:%s", self._entry.unique_id, err
|
||||||
)
|
)
|
||||||
|
|
||||||
async def async_update(self, now=None):
|
async def async_update(self, now: timedelta | None = None) -> None:
|
||||||
"""Update function for updating API information."""
|
"""Update function for updating API information."""
|
||||||
_LOGGER.debug("Start data update for '%s'", self._entry.unique_id)
|
_LOGGER.debug("Start data update for '%s'", self._entry.unique_id)
|
||||||
self._async_setup_api_requests()
|
self._async_setup_api_requests()
|
||||||
|
@ -582,9 +616,9 @@ class SynologyDSMBaseEntity(CoordinatorEntity):
|
||||||
self,
|
self,
|
||||||
api: SynoApi,
|
api: SynoApi,
|
||||||
entity_type: str,
|
entity_type: str,
|
||||||
entity_info: dict[str, str],
|
entity_info: EntityInfo,
|
||||||
coordinator: DataUpdateCoordinator,
|
coordinator: DataUpdateCoordinator[dict[str, dict[str, Any]]],
|
||||||
):
|
) -> None:
|
||||||
"""Initialize the Synology DSM entity."""
|
"""Initialize the Synology DSM entity."""
|
||||||
super().__init__(coordinator)
|
super().__init__(coordinator)
|
||||||
|
|
||||||
|
@ -609,12 +643,12 @@ class SynologyDSMBaseEntity(CoordinatorEntity):
|
||||||
return self._name
|
return self._name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def icon(self) -> str:
|
def icon(self) -> str | None:
|
||||||
"""Return the icon."""
|
"""Return the icon."""
|
||||||
return self._icon
|
return self._icon
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_class(self) -> str:
|
def device_class(self) -> str | None:
|
||||||
"""Return the class of this device."""
|
"""Return the class of this device."""
|
||||||
return self._class
|
return self._class
|
||||||
|
|
||||||
|
@ -639,7 +673,7 @@ class SynologyDSMBaseEntity(CoordinatorEntity):
|
||||||
"""Return if the entity should be enabled when first added to the entity registry."""
|
"""Return if the entity should be enabled when first added to the entity registry."""
|
||||||
return self._enable_default
|
return self._enable_default
|
||||||
|
|
||||||
async def async_added_to_hass(self):
|
async def async_added_to_hass(self) -> None:
|
||||||
"""Register entity for updates from API."""
|
"""Register entity for updates from API."""
|
||||||
self.async_on_remove(self._api.subscribe(self._api_key, self.unique_id))
|
self.async_on_remove(self._api.subscribe(self._api_key, self.unique_id))
|
||||||
await super().async_added_to_hass()
|
await super().async_added_to_hass()
|
||||||
|
@ -652,10 +686,10 @@ class SynologyDSMDeviceEntity(SynologyDSMBaseEntity):
|
||||||
self,
|
self,
|
||||||
api: SynoApi,
|
api: SynoApi,
|
||||||
entity_type: str,
|
entity_type: str,
|
||||||
entity_info: dict[str, str],
|
entity_info: EntityInfo,
|
||||||
coordinator: DataUpdateCoordinator,
|
coordinator: DataUpdateCoordinator[dict[str, dict[str, Any]]],
|
||||||
device_id: str = None,
|
device_id: str | None = None,
|
||||||
):
|
) -> None:
|
||||||
"""Initialize the Synology DSM disk or volume entity."""
|
"""Initialize the Synology DSM disk or volume entity."""
|
||||||
super().__init__(api, entity_type, entity_info, coordinator)
|
super().__init__(api, entity_type, entity_info, coordinator)
|
||||||
self._device_id = device_id
|
self._device_id = device_id
|
||||||
|
@ -691,16 +725,18 @@ class SynologyDSMDeviceEntity(SynologyDSMBaseEntity):
|
||||||
@property
|
@property
|
||||||
def available(self) -> bool:
|
def available(self) -> bool:
|
||||||
"""Return True if entity is available."""
|
"""Return True if entity is available."""
|
||||||
return bool(self._api.storage)
|
return self._api.storage # type: ignore [no-any-return]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_info(self) -> DeviceInfo:
|
def device_info(self) -> DeviceInfo:
|
||||||
"""Return the device information."""
|
"""Return the device information."""
|
||||||
return {
|
return {
|
||||||
"identifiers": {(DOMAIN, self._api.information.serial, self._device_id)},
|
"identifiers": {
|
||||||
|
(DOMAIN, f"{self._api.information.serial}_{self._device_id}")
|
||||||
|
},
|
||||||
"name": f"Synology NAS ({self._device_name} - {self._device_type})",
|
"name": f"Synology NAS ({self._device_name} - {self._device_type})",
|
||||||
"manufacturer": self._device_manufacturer,
|
"manufacturer": self._device_manufacturer, # type: ignore[typeddict-item]
|
||||||
"model": self._device_model,
|
"model": self._device_model, # type: ignore[typeddict-item]
|
||||||
"sw_version": self._device_firmware,
|
"sw_version": self._device_firmware, # type: ignore[typeddict-item]
|
||||||
"via_device": (DOMAIN, self._api.information.serial),
|
"via_device": (DOMAIN, self._api.information.serial),
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,9 @@ from homeassistant.components.binary_sensor import BinarySensorEntity
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_DISKS
|
from homeassistant.const import CONF_DISKS
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from . import SynologyDSMBaseEntity, SynologyDSMDeviceEntity
|
from . import SynoApi, SynologyDSMBaseEntity, SynologyDSMDeviceEntity
|
||||||
from .const import (
|
from .const import (
|
||||||
COORDINATOR_CENTRAL,
|
COORDINATOR_CENTRAL,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
|
@ -18,15 +19,19 @@ from .const import (
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities
|
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up the Synology NAS binary sensor."""
|
"""Set up the Synology NAS binary sensor."""
|
||||||
|
|
||||||
data = hass.data[DOMAIN][entry.unique_id]
|
data = hass.data[DOMAIN][entry.unique_id]
|
||||||
api = data[SYNO_API]
|
api: SynoApi = data[SYNO_API]
|
||||||
coordinator = data[COORDINATOR_CENTRAL]
|
coordinator = data[COORDINATOR_CENTRAL]
|
||||||
|
|
||||||
entities = [
|
entities: list[
|
||||||
|
SynoDSMSecurityBinarySensor
|
||||||
|
| SynoDSMUpgradeBinarySensor
|
||||||
|
| SynoDSMStorageBinarySensor
|
||||||
|
] = [
|
||||||
SynoDSMSecurityBinarySensor(
|
SynoDSMSecurityBinarySensor(
|
||||||
api, sensor_type, SECURITY_BINARY_SENSORS[sensor_type], coordinator
|
api, sensor_type, SECURITY_BINARY_SENSORS[sensor_type], coordinator
|
||||||
)
|
)
|
||||||
|
@ -63,7 +68,7 @@ class SynoDSMSecurityBinarySensor(SynologyDSMBaseEntity, BinarySensorEntity):
|
||||||
@property
|
@property
|
||||||
def is_on(self) -> bool:
|
def is_on(self) -> bool:
|
||||||
"""Return the state."""
|
"""Return the state."""
|
||||||
return getattr(self._api.security, self.entity_type) != "safe"
|
return getattr(self._api.security, self.entity_type) != "safe" # type: ignore[no-any-return]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def available(self) -> bool:
|
def available(self) -> bool:
|
||||||
|
@ -73,7 +78,7 @@ class SynoDSMSecurityBinarySensor(SynologyDSMBaseEntity, BinarySensorEntity):
|
||||||
@property
|
@property
|
||||||
def extra_state_attributes(self) -> dict[str, str]:
|
def extra_state_attributes(self) -> dict[str, str]:
|
||||||
"""Return security checks details."""
|
"""Return security checks details."""
|
||||||
return self._api.security.status_by_check
|
return self._api.security.status_by_check # type: ignore[no-any-return]
|
||||||
|
|
||||||
|
|
||||||
class SynoDSMStorageBinarySensor(SynologyDSMDeviceEntity, BinarySensorEntity):
|
class SynoDSMStorageBinarySensor(SynologyDSMDeviceEntity, BinarySensorEntity):
|
||||||
|
@ -82,7 +87,7 @@ class SynoDSMStorageBinarySensor(SynologyDSMDeviceEntity, BinarySensorEntity):
|
||||||
@property
|
@property
|
||||||
def is_on(self) -> bool:
|
def is_on(self) -> bool:
|
||||||
"""Return the state."""
|
"""Return the state."""
|
||||||
return getattr(self._api.storage, self.entity_type)(self._device_id)
|
return bool(getattr(self._api.storage, self.entity_type)(self._device_id))
|
||||||
|
|
||||||
|
|
||||||
class SynoDSMUpgradeBinarySensor(SynologyDSMBaseEntity, BinarySensorEntity):
|
class SynoDSMUpgradeBinarySensor(SynologyDSMBaseEntity, BinarySensorEntity):
|
||||||
|
@ -91,7 +96,7 @@ class SynoDSMUpgradeBinarySensor(SynologyDSMBaseEntity, BinarySensorEntity):
|
||||||
@property
|
@property
|
||||||
def is_on(self) -> bool:
|
def is_on(self) -> bool:
|
||||||
"""Return the state."""
|
"""Return the state."""
|
||||||
return getattr(self._api.upgrade, self.entity_type)
|
return bool(getattr(self._api.upgrade, self.entity_type))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def available(self) -> bool:
|
def available(self) -> bool:
|
||||||
|
|
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from synology_dsm.api.surveillance_station import SynoSurveillanceStation
|
from synology_dsm.api.surveillance_station import SynoCamera, SynoSurveillanceStation
|
||||||
from synology_dsm.exceptions import (
|
from synology_dsm.exceptions import (
|
||||||
SynologyDSMAPIErrorException,
|
SynologyDSMAPIErrorException,
|
||||||
SynologyDSMRequestException,
|
SynologyDSMRequestException,
|
||||||
|
@ -13,6 +13,7 @@ from homeassistant.components.camera import SUPPORT_STREAM, Camera
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity import DeviceInfo
|
from homeassistant.helpers.entity import DeviceInfo
|
||||||
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||||
|
|
||||||
from . import SynoApi, SynologyDSMBaseEntity
|
from . import SynoApi, SynologyDSMBaseEntity
|
||||||
|
@ -31,19 +32,21 @@ _LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities
|
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up the Synology NAS cameras."""
|
"""Set up the Synology NAS cameras."""
|
||||||
|
|
||||||
data = hass.data[DOMAIN][entry.unique_id]
|
data = hass.data[DOMAIN][entry.unique_id]
|
||||||
api = data[SYNO_API]
|
api: SynoApi = data[SYNO_API]
|
||||||
|
|
||||||
if SynoSurveillanceStation.CAMERA_API_KEY not in api.dsm.apis:
|
if SynoSurveillanceStation.CAMERA_API_KEY not in api.dsm.apis:
|
||||||
return
|
return
|
||||||
|
|
||||||
# initial data fetch
|
# initial data fetch
|
||||||
coordinator = data[COORDINATOR_CAMERAS]
|
coordinator: DataUpdateCoordinator[dict[str, dict[str, SynoCamera]]] = data[
|
||||||
await coordinator.async_refresh()
|
COORDINATOR_CAMERAS
|
||||||
|
]
|
||||||
|
await coordinator.async_config_entry_first_refresh()
|
||||||
|
|
||||||
async_add_entities(
|
async_add_entities(
|
||||||
SynoDSMCamera(api, coordinator, camera_id)
|
SynoDSMCamera(api, coordinator, camera_id)
|
||||||
|
@ -54,9 +57,14 @@ async def async_setup_entry(
|
||||||
class SynoDSMCamera(SynologyDSMBaseEntity, Camera):
|
class SynoDSMCamera(SynologyDSMBaseEntity, Camera):
|
||||||
"""Representation a Synology camera."""
|
"""Representation a Synology camera."""
|
||||||
|
|
||||||
|
coordinator: DataUpdateCoordinator[dict[str, dict[str, SynoCamera]]]
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, api: SynoApi, coordinator: DataUpdateCoordinator, camera_id: int
|
self,
|
||||||
):
|
api: SynoApi,
|
||||||
|
coordinator: DataUpdateCoordinator[dict[str, dict[str, SynoCamera]]],
|
||||||
|
camera_id: str,
|
||||||
|
) -> None:
|
||||||
"""Initialize a Synology camera."""
|
"""Initialize a Synology camera."""
|
||||||
super().__init__(
|
super().__init__(
|
||||||
api,
|
api,
|
||||||
|
@ -70,13 +78,11 @@ class SynoDSMCamera(SynologyDSMBaseEntity, Camera):
|
||||||
},
|
},
|
||||||
coordinator,
|
coordinator,
|
||||||
)
|
)
|
||||||
Camera.__init__(self)
|
Camera.__init__(self) # type: ignore[no-untyped-call]
|
||||||
|
|
||||||
self._camera_id = camera_id
|
self._camera_id = camera_id
|
||||||
self._api = api
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def camera_data(self):
|
def camera_data(self) -> SynoCamera:
|
||||||
"""Camera data."""
|
"""Camera data."""
|
||||||
return self.coordinator.data["cameras"][self._camera_id]
|
return self.coordinator.data["cameras"][self._camera_id]
|
||||||
|
|
||||||
|
@ -87,16 +93,14 @@ class SynoDSMCamera(SynologyDSMBaseEntity, Camera):
|
||||||
"identifiers": {
|
"identifiers": {
|
||||||
(
|
(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
self._api.information.serial,
|
f"{self._api.information.serial}_{self.camera_data.id}",
|
||||||
self.camera_data.id,
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
"name": self.camera_data.name,
|
"name": self.camera_data.name,
|
||||||
"model": self.camera_data.model,
|
"model": self.camera_data.model,
|
||||||
"via_device": (
|
"via_device": (
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
self._api.information.serial,
|
f"{self._api.information.serial}_{SynoSurveillanceStation.INFO_API_KEY}",
|
||||||
SynoSurveillanceStation.INFO_API_KEY,
|
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,16 +115,16 @@ class SynoDSMCamera(SynologyDSMBaseEntity, Camera):
|
||||||
return SUPPORT_STREAM
|
return SUPPORT_STREAM
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_recording(self):
|
def is_recording(self) -> bool:
|
||||||
"""Return true if the device is recording."""
|
"""Return true if the device is recording."""
|
||||||
return self.camera_data.is_recording
|
return self.camera_data.is_recording # type: ignore[no-any-return]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def motion_detection_enabled(self):
|
def motion_detection_enabled(self) -> bool:
|
||||||
"""Return the camera motion detection status."""
|
"""Return the camera motion detection status."""
|
||||||
return self.camera_data.is_motion_detection_enabled
|
return self.camera_data.is_motion_detection_enabled # type: ignore[no-any-return]
|
||||||
|
|
||||||
def camera_image(self) -> bytes:
|
def camera_image(self) -> bytes | None:
|
||||||
"""Return bytes of camera image."""
|
"""Return bytes of camera image."""
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"SynoDSMCamera.camera_image(%s)",
|
"SynoDSMCamera.camera_image(%s)",
|
||||||
|
@ -129,7 +133,7 @@ class SynoDSMCamera(SynologyDSMBaseEntity, Camera):
|
||||||
if not self.available:
|
if not self.available:
|
||||||
return None
|
return None
|
||||||
try:
|
try:
|
||||||
return self._api.surveillance_station.get_camera_image(self._camera_id)
|
return self._api.surveillance_station.get_camera_image(self._camera_id) # type: ignore[no-any-return]
|
||||||
except (
|
except (
|
||||||
SynologyDSMAPIErrorException,
|
SynologyDSMAPIErrorException,
|
||||||
SynologyDSMRequestException,
|
SynologyDSMRequestException,
|
||||||
|
@ -142,7 +146,7 @@ class SynoDSMCamera(SynologyDSMBaseEntity, Camera):
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def stream_source(self) -> str:
|
async def stream_source(self) -> str | None:
|
||||||
"""Return the source of the stream."""
|
"""Return the source of the stream."""
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"SynoDSMCamera.stream_source(%s)",
|
"SynoDSMCamera.stream_source(%s)",
|
||||||
|
@ -150,9 +154,9 @@ class SynoDSMCamera(SynologyDSMBaseEntity, Camera):
|
||||||
)
|
)
|
||||||
if not self.available:
|
if not self.available:
|
||||||
return None
|
return None
|
||||||
return self.camera_data.live_view.rtsp
|
return self.camera_data.live_view.rtsp # type: ignore[no-any-return]
|
||||||
|
|
||||||
def enable_motion_detection(self):
|
def enable_motion_detection(self) -> None:
|
||||||
"""Enable motion detection in the camera."""
|
"""Enable motion detection in the camera."""
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"SynoDSMCamera.enable_motion_detection(%s)",
|
"SynoDSMCamera.enable_motion_detection(%s)",
|
||||||
|
@ -160,7 +164,7 @@ class SynoDSMCamera(SynologyDSMBaseEntity, Camera):
|
||||||
)
|
)
|
||||||
self._api.surveillance_station.enable_motion_detection(self._camera_id)
|
self._api.surveillance_station.enable_motion_detection(self._camera_id)
|
||||||
|
|
||||||
def disable_motion_detection(self):
|
def disable_motion_detection(self) -> None:
|
||||||
"""Disable motion detection in camera."""
|
"""Disable motion detection in camera."""
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"SynoDSMCamera.disable_motion_detection(%s)",
|
"SynoDSMCamera.disable_motion_detection(%s)",
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
"""Config flow to configure the Synology DSM integration."""
|
"""Config flow to configure the Synology DSM integration."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Any
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
from synology_dsm import SynologyDSM
|
from synology_dsm import SynologyDSM
|
||||||
|
@ -12,8 +15,14 @@ from synology_dsm.exceptions import (
|
||||||
)
|
)
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import config_entries, exceptions
|
from homeassistant import exceptions
|
||||||
from homeassistant.components import ssdp
|
from homeassistant.components import ssdp
|
||||||
|
from homeassistant.config_entries import (
|
||||||
|
CONN_CLASS_CLOUD_POLL,
|
||||||
|
ConfigEntry,
|
||||||
|
ConfigFlow,
|
||||||
|
OptionsFlow,
|
||||||
|
)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_DISKS,
|
CONF_DISKS,
|
||||||
CONF_HOST,
|
CONF_HOST,
|
||||||
|
@ -28,7 +37,9 @@ from homeassistant.const import (
|
||||||
CONF_VERIFY_SSL,
|
CONF_VERIFY_SSL,
|
||||||
)
|
)
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
|
from homeassistant.data_entry_flow import FlowResult
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
from homeassistant.helpers.typing import DiscoveryInfoType
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
CONF_DEVICE_TOKEN,
|
CONF_DEVICE_TOKEN,
|
||||||
|
@ -47,11 +58,11 @@ _LOGGER = logging.getLogger(__name__)
|
||||||
CONF_OTP_CODE = "otp_code"
|
CONF_OTP_CODE = "otp_code"
|
||||||
|
|
||||||
|
|
||||||
def _discovery_schema_with_defaults(discovery_info):
|
def _discovery_schema_with_defaults(discovery_info: DiscoveryInfoType) -> vol.Schema:
|
||||||
return vol.Schema(_ordered_shared_schema(discovery_info))
|
return vol.Schema(_ordered_shared_schema(discovery_info))
|
||||||
|
|
||||||
|
|
||||||
def _user_schema_with_defaults(user_input):
|
def _user_schema_with_defaults(user_input: dict[str, Any]) -> vol.Schema:
|
||||||
user_schema = {
|
user_schema = {
|
||||||
vol.Required(CONF_HOST, default=user_input.get(CONF_HOST, "")): str,
|
vol.Required(CONF_HOST, default=user_input.get(CONF_HOST, "")): str,
|
||||||
}
|
}
|
||||||
|
@ -60,7 +71,9 @@ def _user_schema_with_defaults(user_input):
|
||||||
return vol.Schema(user_schema)
|
return vol.Schema(user_schema)
|
||||||
|
|
||||||
|
|
||||||
def _ordered_shared_schema(schema_input):
|
def _ordered_shared_schema(
|
||||||
|
schema_input: dict[str, Any]
|
||||||
|
) -> dict[vol.Required | vol.Optional, Any]:
|
||||||
return {
|
return {
|
||||||
vol.Required(CONF_USERNAME, default=schema_input.get(CONF_USERNAME, "")): str,
|
vol.Required(CONF_USERNAME, default=schema_input.get(CONF_USERNAME, "")): str,
|
||||||
vol.Required(CONF_PASSWORD, default=schema_input.get(CONF_PASSWORD, "")): str,
|
vol.Required(CONF_PASSWORD, default=schema_input.get(CONF_PASSWORD, "")): str,
|
||||||
|
@ -75,23 +88,30 @@ def _ordered_shared_schema(schema_input):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class SynologyDSMFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
class SynologyDSMFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||||
"""Handle a config flow."""
|
"""Handle a config flow."""
|
||||||
|
|
||||||
VERSION = 1
|
VERSION = 1
|
||||||
|
CONNECTION_CLASS = CONN_CLASS_CLOUD_POLL
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@callback
|
@callback
|
||||||
def async_get_options_flow(config_entry):
|
def async_get_options_flow(
|
||||||
|
config_entry: ConfigEntry,
|
||||||
|
) -> SynologyDSMOptionsFlowHandler:
|
||||||
"""Get the options flow for this handler."""
|
"""Get the options flow for this handler."""
|
||||||
return SynologyDSMOptionsFlowHandler(config_entry)
|
return SynologyDSMOptionsFlowHandler(config_entry)
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
"""Initialize the synology_dsm config flow."""
|
"""Initialize the synology_dsm config flow."""
|
||||||
self.saved_user_input = {}
|
self.saved_user_input: dict[str, Any] = {}
|
||||||
self.discovered_conf = {}
|
self.discovered_conf: dict[str, Any] = {}
|
||||||
|
|
||||||
async def _show_setup_form(self, user_input=None, errors=None):
|
async def _show_setup_form(
|
||||||
|
self,
|
||||||
|
user_input: dict[str, Any] | None = None,
|
||||||
|
errors: dict[str, str] | None = None,
|
||||||
|
) -> FlowResult:
|
||||||
"""Show the setup form to the user."""
|
"""Show the setup form to the user."""
|
||||||
if not user_input:
|
if not user_input:
|
||||||
user_input = {}
|
user_input = {}
|
||||||
|
@ -111,7 +131,9 @@ class SynologyDSMFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
description_placeholders=self.discovered_conf or {},
|
description_placeholders=self.discovered_conf or {},
|
||||||
)
|
)
|
||||||
|
|
||||||
async def async_step_user(self, user_input=None):
|
async def async_step_user(
|
||||||
|
self, user_input: dict[str, Any] | None = None
|
||||||
|
) -> FlowResult:
|
||||||
"""Handle a flow initiated by the user."""
|
"""Handle a flow initiated by the user."""
|
||||||
errors = {}
|
errors = {}
|
||||||
|
|
||||||
|
@ -188,7 +210,7 @@ class SynologyDSMFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
|
|
||||||
return self.async_create_entry(title=host, data=config_data)
|
return self.async_create_entry(title=host, data=config_data)
|
||||||
|
|
||||||
async def async_step_ssdp(self, discovery_info):
|
async def async_step_ssdp(self, discovery_info: DiscoveryInfoType) -> FlowResult:
|
||||||
"""Handle a discovered synology_dsm."""
|
"""Handle a discovered synology_dsm."""
|
||||||
parsed_url = urlparse(discovery_info[ssdp.ATTR_SSDP_LOCATION])
|
parsed_url = urlparse(discovery_info[ssdp.ATTR_SSDP_LOCATION])
|
||||||
friendly_name = (
|
friendly_name = (
|
||||||
|
@ -211,15 +233,19 @@ class SynologyDSMFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
self.context["title_placeholders"] = self.discovered_conf
|
self.context["title_placeholders"] = self.discovered_conf
|
||||||
return await self.async_step_user()
|
return await self.async_step_user()
|
||||||
|
|
||||||
async def async_step_import(self, user_input=None):
|
async def async_step_import(
|
||||||
|
self, user_input: dict[str, Any] | None = None
|
||||||
|
) -> FlowResult:
|
||||||
"""Import a config entry."""
|
"""Import a config entry."""
|
||||||
return await self.async_step_user(user_input)
|
return await self.async_step_user(user_input)
|
||||||
|
|
||||||
async def async_step_link(self, user_input):
|
async def async_step_link(self, user_input: dict[str, Any]) -> FlowResult:
|
||||||
"""Link a config entry from discovery."""
|
"""Link a config entry from discovery."""
|
||||||
return await self.async_step_user(user_input)
|
return await self.async_step_user(user_input)
|
||||||
|
|
||||||
async def async_step_2sa(self, user_input, errors=None):
|
async def async_step_2sa(
|
||||||
|
self, user_input: dict[str, Any], errors: dict[str, str] | None = None
|
||||||
|
) -> FlowResult:
|
||||||
"""Enter 2SA code to anthenticate."""
|
"""Enter 2SA code to anthenticate."""
|
||||||
if not self.saved_user_input:
|
if not self.saved_user_input:
|
||||||
self.saved_user_input = user_input
|
self.saved_user_input = user_input
|
||||||
|
@ -236,7 +262,7 @@ class SynologyDSMFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
|
|
||||||
return await self.async_step_user(user_input)
|
return await self.async_step_user(user_input)
|
||||||
|
|
||||||
def _mac_already_configured(self, mac):
|
def _mac_already_configured(self, mac: str) -> bool:
|
||||||
"""See if we already have configured a NAS with this MAC address."""
|
"""See if we already have configured a NAS with this MAC address."""
|
||||||
existing_macs = [
|
existing_macs = [
|
||||||
mac.replace("-", "")
|
mac.replace("-", "")
|
||||||
|
@ -246,14 +272,16 @@ class SynologyDSMFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
return mac in existing_macs
|
return mac in existing_macs
|
||||||
|
|
||||||
|
|
||||||
class SynologyDSMOptionsFlowHandler(config_entries.OptionsFlow):
|
class SynologyDSMOptionsFlowHandler(OptionsFlow):
|
||||||
"""Handle a option flow."""
|
"""Handle a option flow."""
|
||||||
|
|
||||||
def __init__(self, config_entry: config_entries.ConfigEntry):
|
def __init__(self, config_entry: ConfigEntry):
|
||||||
"""Initialize options flow."""
|
"""Initialize options flow."""
|
||||||
self.config_entry = config_entry
|
self.config_entry = config_entry
|
||||||
|
|
||||||
async def async_step_init(self, user_input=None):
|
async def async_step_init(
|
||||||
|
self, user_input: dict[str, Any] | None = None
|
||||||
|
) -> FlowResult:
|
||||||
"""Handle options flow."""
|
"""Handle options flow."""
|
||||||
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)
|
||||||
|
@ -277,7 +305,7 @@ class SynologyDSMOptionsFlowHandler(config_entries.OptionsFlow):
|
||||||
return self.async_show_form(step_id="init", data_schema=data_schema)
|
return self.async_show_form(step_id="init", data_schema=data_schema)
|
||||||
|
|
||||||
|
|
||||||
def _login_and_fetch_syno_info(api, otp_code):
|
def _login_and_fetch_syno_info(api: SynologyDSM, otp_code: str) -> str:
|
||||||
"""Login to the NAS and fetch basic data."""
|
"""Login to the NAS and fetch basic data."""
|
||||||
# These do i/o
|
# These do i/o
|
||||||
api.login(otp_code)
|
api.login(otp_code)
|
||||||
|
@ -293,7 +321,7 @@ def _login_and_fetch_syno_info(api, otp_code):
|
||||||
):
|
):
|
||||||
raise InvalidData
|
raise InvalidData
|
||||||
|
|
||||||
return api.information.serial
|
return api.information.serial # type: ignore[no-any-return]
|
||||||
|
|
||||||
|
|
||||||
class InvalidData(exceptions.HomeAssistantError):
|
class InvalidData(exceptions.HomeAssistantError):
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
"""Constants for Synology DSM."""
|
"""Constants for Synology DSM."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Final, TypedDict
|
||||||
|
|
||||||
from synology_dsm.api.core.security import SynoCoreSecurity
|
from synology_dsm.api.core.security import SynoCoreSecurity
|
||||||
from synology_dsm.api.core.upgrade import SynoCoreUpgrade
|
from synology_dsm.api.core.upgrade import SynoCoreUpgrade
|
||||||
|
@ -17,6 +20,17 @@ from homeassistant.const import (
|
||||||
PERCENTAGE,
|
PERCENTAGE,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class EntityInfo(TypedDict):
|
||||||
|
"""TypedDict for EntityInfo."""
|
||||||
|
|
||||||
|
name: str
|
||||||
|
unit: str | None
|
||||||
|
icon: str | None
|
||||||
|
device_class: str | None
|
||||||
|
enable: bool
|
||||||
|
|
||||||
|
|
||||||
DOMAIN = "synology_dsm"
|
DOMAIN = "synology_dsm"
|
||||||
PLATFORMS = ["binary_sensor", "camera", "sensor", "switch"]
|
PLATFORMS = ["binary_sensor", "camera", "sensor", "switch"]
|
||||||
COORDINATOR_CAMERAS = "coordinator_cameras"
|
COORDINATOR_CAMERAS = "coordinator_cameras"
|
||||||
|
@ -43,11 +57,11 @@ DEFAULT_TIMEOUT = 10 # sec
|
||||||
|
|
||||||
ENTITY_UNIT_LOAD = "load"
|
ENTITY_UNIT_LOAD = "load"
|
||||||
|
|
||||||
ENTITY_NAME = "name"
|
ENTITY_NAME: Final = "name"
|
||||||
ENTITY_UNIT = "unit"
|
ENTITY_UNIT: Final = "unit"
|
||||||
ENTITY_ICON = "icon"
|
ENTITY_ICON: Final = "icon"
|
||||||
ENTITY_CLASS = "device_class"
|
ENTITY_CLASS: Final = "device_class"
|
||||||
ENTITY_ENABLE = "enable"
|
ENTITY_ENABLE: Final = "enable"
|
||||||
|
|
||||||
# Services
|
# Services
|
||||||
SERVICE_REBOOT = "reboot"
|
SERVICE_REBOOT = "reboot"
|
||||||
|
@ -60,7 +74,7 @@ SERVICES = [
|
||||||
# Entity keys should start with the API_KEY to fetch
|
# Entity keys should start with the API_KEY to fetch
|
||||||
|
|
||||||
# Binary sensors
|
# Binary sensors
|
||||||
UPGRADE_BINARY_SENSORS = {
|
UPGRADE_BINARY_SENSORS: dict[str, EntityInfo] = {
|
||||||
f"{SynoCoreUpgrade.API_KEY}:update_available": {
|
f"{SynoCoreUpgrade.API_KEY}:update_available": {
|
||||||
ENTITY_NAME: "Update available",
|
ENTITY_NAME: "Update available",
|
||||||
ENTITY_UNIT: None,
|
ENTITY_UNIT: None,
|
||||||
|
@ -70,7 +84,7 @@ UPGRADE_BINARY_SENSORS = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
SECURITY_BINARY_SENSORS = {
|
SECURITY_BINARY_SENSORS: dict[str, EntityInfo] = {
|
||||||
f"{SynoCoreSecurity.API_KEY}:status": {
|
f"{SynoCoreSecurity.API_KEY}:status": {
|
||||||
ENTITY_NAME: "Security status",
|
ENTITY_NAME: "Security status",
|
||||||
ENTITY_UNIT: None,
|
ENTITY_UNIT: None,
|
||||||
|
@ -80,7 +94,7 @@ SECURITY_BINARY_SENSORS = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
STORAGE_DISK_BINARY_SENSORS = {
|
STORAGE_DISK_BINARY_SENSORS: dict[str, EntityInfo] = {
|
||||||
f"{SynoStorage.API_KEY}:disk_exceed_bad_sector_thr": {
|
f"{SynoStorage.API_KEY}:disk_exceed_bad_sector_thr": {
|
||||||
ENTITY_NAME: "Exceeded Max Bad Sectors",
|
ENTITY_NAME: "Exceeded Max Bad Sectors",
|
||||||
ENTITY_UNIT: None,
|
ENTITY_UNIT: None,
|
||||||
|
@ -98,7 +112,7 @@ STORAGE_DISK_BINARY_SENSORS = {
|
||||||
}
|
}
|
||||||
|
|
||||||
# Sensors
|
# Sensors
|
||||||
UTILISATION_SENSORS = {
|
UTILISATION_SENSORS: dict[str, EntityInfo] = {
|
||||||
f"{SynoCoreUtilization.API_KEY}:cpu_other_load": {
|
f"{SynoCoreUtilization.API_KEY}:cpu_other_load": {
|
||||||
ENTITY_NAME: "CPU Utilization (Other)",
|
ENTITY_NAME: "CPU Utilization (Other)",
|
||||||
ENTITY_UNIT: PERCENTAGE,
|
ENTITY_UNIT: PERCENTAGE,
|
||||||
|
@ -212,7 +226,7 @@ UTILISATION_SENSORS = {
|
||||||
ENTITY_ENABLE: True,
|
ENTITY_ENABLE: True,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
STORAGE_VOL_SENSORS = {
|
STORAGE_VOL_SENSORS: dict[str, EntityInfo] = {
|
||||||
f"{SynoStorage.API_KEY}:volume_status": {
|
f"{SynoStorage.API_KEY}:volume_status": {
|
||||||
ENTITY_NAME: "Status",
|
ENTITY_NAME: "Status",
|
||||||
ENTITY_UNIT: None,
|
ENTITY_UNIT: None,
|
||||||
|
@ -256,7 +270,7 @@ STORAGE_VOL_SENSORS = {
|
||||||
ENTITY_ENABLE: False,
|
ENTITY_ENABLE: False,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
STORAGE_DISK_SENSORS = {
|
STORAGE_DISK_SENSORS: dict[str, EntityInfo] = {
|
||||||
f"{SynoStorage.API_KEY}:disk_smart_status": {
|
f"{SynoStorage.API_KEY}:disk_smart_status": {
|
||||||
ENTITY_NAME: "Status (Smart)",
|
ENTITY_NAME: "Status (Smart)",
|
||||||
ENTITY_UNIT: None,
|
ENTITY_UNIT: None,
|
||||||
|
@ -280,7 +294,7 @@ STORAGE_DISK_SENSORS = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
INFORMATION_SENSORS = {
|
INFORMATION_SENSORS: dict[str, EntityInfo] = {
|
||||||
f"{SynoDSMInformation.API_KEY}:temperature": {
|
f"{SynoDSMInformation.API_KEY}:temperature": {
|
||||||
ENTITY_NAME: "temperature",
|
ENTITY_NAME: "temperature",
|
||||||
ENTITY_UNIT: None,
|
ENTITY_UNIT: None,
|
||||||
|
@ -298,7 +312,7 @@ INFORMATION_SENSORS = {
|
||||||
}
|
}
|
||||||
|
|
||||||
# Switch
|
# Switch
|
||||||
SURVEILLANCE_SWITCH = {
|
SURVEILLANCE_SWITCH: dict[str, EntityInfo] = {
|
||||||
f"{SynoSurveillanceStation.HOME_MODE_API_KEY}:home_mode": {
|
f"{SynoSurveillanceStation.HOME_MODE_API_KEY}:home_mode": {
|
||||||
ENTITY_NAME: "home mode",
|
ENTITY_NAME: "home mode",
|
||||||
ENTITY_UNIT: None,
|
ENTITY_UNIT: None,
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from homeassistant.components.sensor import SensorEntity
|
from homeassistant.components.sensor import SensorEntity
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
@ -14,6 +15,7 @@ from homeassistant.const import (
|
||||||
TEMP_CELSIUS,
|
TEMP_CELSIUS,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.temperature import display_temp
|
from homeassistant.helpers.temperature import display_temp
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||||
from homeassistant.util.dt import utcnow
|
from homeassistant.util.dt import utcnow
|
||||||
|
@ -30,19 +32,20 @@ from .const import (
|
||||||
SYNO_API,
|
SYNO_API,
|
||||||
TEMP_SENSORS_KEYS,
|
TEMP_SENSORS_KEYS,
|
||||||
UTILISATION_SENSORS,
|
UTILISATION_SENSORS,
|
||||||
|
EntityInfo,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities
|
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up the Synology NAS Sensor."""
|
"""Set up the Synology NAS Sensor."""
|
||||||
|
|
||||||
data = hass.data[DOMAIN][entry.unique_id]
|
data = hass.data[DOMAIN][entry.unique_id]
|
||||||
api = data[SYNO_API]
|
api: SynoApi = data[SYNO_API]
|
||||||
coordinator = data[COORDINATOR_CENTRAL]
|
coordinator = data[COORDINATOR_CENTRAL]
|
||||||
|
|
||||||
entities = [
|
entities: list[SynoDSMUtilSensor | SynoDSMStorageSensor | SynoDSMInfoSensor] = [
|
||||||
SynoDSMUtilSensor(
|
SynoDSMUtilSensor(
|
||||||
api, sensor_type, UTILISATION_SENSORS[sensor_type], coordinator
|
api, sensor_type, UTILISATION_SENSORS[sensor_type], coordinator
|
||||||
)
|
)
|
||||||
|
@ -91,7 +94,7 @@ class SynoDSMSensor(SynologyDSMBaseEntity):
|
||||||
"""Mixin for sensor specific attributes."""
|
"""Mixin for sensor specific attributes."""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unit_of_measurement(self) -> str:
|
def unit_of_measurement(self) -> str | None:
|
||||||
"""Return the unit the value is expressed in."""
|
"""Return the unit the value is expressed in."""
|
||||||
if self.entity_type in TEMP_SENSORS_KEYS:
|
if self.entity_type in TEMP_SENSORS_KEYS:
|
||||||
return self.hass.config.units.temperature_unit
|
return self.hass.config.units.temperature_unit
|
||||||
|
@ -102,7 +105,7 @@ class SynoDSMUtilSensor(SynoDSMSensor, SensorEntity):
|
||||||
"""Representation a Synology Utilisation sensor."""
|
"""Representation a Synology Utilisation sensor."""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self) -> Any | None:
|
||||||
"""Return the state."""
|
"""Return the state."""
|
||||||
attr = getattr(self._api.utilisation, self.entity_type)
|
attr = getattr(self._api.utilisation, self.entity_type)
|
||||||
if callable(attr):
|
if callable(attr):
|
||||||
|
@ -134,7 +137,7 @@ class SynoDSMStorageSensor(SynologyDSMDeviceEntity, SynoDSMSensor, SensorEntity)
|
||||||
"""Representation a Synology Storage sensor."""
|
"""Representation a Synology Storage sensor."""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self) -> Any | None:
|
||||||
"""Return the state."""
|
"""Return the state."""
|
||||||
attr = getattr(self._api.storage, self.entity_type)(self._device_id)
|
attr = getattr(self._api.storage, self.entity_type)(self._device_id)
|
||||||
if attr is None:
|
if attr is None:
|
||||||
|
@ -158,16 +161,16 @@ class SynoDSMInfoSensor(SynoDSMSensor, SensorEntity):
|
||||||
self,
|
self,
|
||||||
api: SynoApi,
|
api: SynoApi,
|
||||||
entity_type: str,
|
entity_type: str,
|
||||||
entity_info: dict[str, str],
|
entity_info: EntityInfo,
|
||||||
coordinator: DataUpdateCoordinator,
|
coordinator: DataUpdateCoordinator[dict[str, dict[str, Any]]],
|
||||||
):
|
) -> None:
|
||||||
"""Initialize the Synology SynoDSMInfoSensor entity."""
|
"""Initialize the Synology SynoDSMInfoSensor entity."""
|
||||||
super().__init__(api, entity_type, entity_info, coordinator)
|
super().__init__(api, entity_type, entity_info, coordinator)
|
||||||
self._previous_uptime = None
|
self._previous_uptime: str | None = None
|
||||||
self._last_boot = None
|
self._last_boot: str | None = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self) -> Any | None:
|
||||||
"""Return the state."""
|
"""Return the state."""
|
||||||
attr = getattr(self._api.information, self.entity_type)
|
attr = getattr(self._api.information, self.entity_type)
|
||||||
if attr is None:
|
if attr is None:
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from synology_dsm.api.surveillance_station import SynoSurveillanceStation
|
from synology_dsm.api.surveillance_station import SynoSurveillanceStation
|
||||||
|
|
||||||
|
@ -9,21 +10,28 @@ from homeassistant.components.switch import ToggleEntity
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity import DeviceInfo
|
from homeassistant.helpers.entity import DeviceInfo
|
||||||
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||||
|
|
||||||
from . import SynoApi, SynologyDSMBaseEntity
|
from . import SynoApi, SynologyDSMBaseEntity
|
||||||
from .const import COORDINATOR_SWITCHES, DOMAIN, SURVEILLANCE_SWITCH, SYNO_API
|
from .const import (
|
||||||
|
COORDINATOR_SWITCHES,
|
||||||
|
DOMAIN,
|
||||||
|
SURVEILLANCE_SWITCH,
|
||||||
|
SYNO_API,
|
||||||
|
EntityInfo,
|
||||||
|
)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities
|
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up the Synology NAS switch."""
|
"""Set up the Synology NAS switch."""
|
||||||
|
|
||||||
data = hass.data[DOMAIN][entry.unique_id]
|
data = hass.data[DOMAIN][entry.unique_id]
|
||||||
api = data[SYNO_API]
|
api: SynoApi = data[SYNO_API]
|
||||||
|
|
||||||
entities = []
|
entities = []
|
||||||
|
|
||||||
|
@ -32,7 +40,7 @@ async def async_setup_entry(
|
||||||
version = info["data"]["CMSMinVersion"]
|
version = info["data"]["CMSMinVersion"]
|
||||||
|
|
||||||
# initial data fetch
|
# initial data fetch
|
||||||
coordinator = data[COORDINATOR_SWITCHES]
|
coordinator: DataUpdateCoordinator = data[COORDINATOR_SWITCHES]
|
||||||
await coordinator.async_refresh()
|
await coordinator.async_refresh()
|
||||||
entities += [
|
entities += [
|
||||||
SynoDSMSurveillanceHomeModeToggle(
|
SynoDSMSurveillanceHomeModeToggle(
|
||||||
|
@ -47,14 +55,16 @@ async def async_setup_entry(
|
||||||
class SynoDSMSurveillanceHomeModeToggle(SynologyDSMBaseEntity, ToggleEntity):
|
class SynoDSMSurveillanceHomeModeToggle(SynologyDSMBaseEntity, ToggleEntity):
|
||||||
"""Representation a Synology Surveillance Station Home Mode toggle."""
|
"""Representation a Synology Surveillance Station Home Mode toggle."""
|
||||||
|
|
||||||
|
coordinator: DataUpdateCoordinator[dict[str, dict[str, bool]]]
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
api: SynoApi,
|
api: SynoApi,
|
||||||
entity_type: str,
|
entity_type: str,
|
||||||
entity_info: dict[str, str],
|
entity_info: EntityInfo,
|
||||||
version: str,
|
version: str,
|
||||||
coordinator: DataUpdateCoordinator,
|
coordinator: DataUpdateCoordinator[dict[str, dict[str, bool]]],
|
||||||
):
|
) -> None:
|
||||||
"""Initialize a Synology Surveillance Station Home Mode."""
|
"""Initialize a Synology Surveillance Station Home Mode."""
|
||||||
super().__init__(
|
super().__init__(
|
||||||
api,
|
api,
|
||||||
|
@ -69,7 +79,7 @@ class SynoDSMSurveillanceHomeModeToggle(SynologyDSMBaseEntity, ToggleEntity):
|
||||||
"""Return the state."""
|
"""Return the state."""
|
||||||
return self.coordinator.data["switches"][self.entity_type]
|
return self.coordinator.data["switches"][self.entity_type]
|
||||||
|
|
||||||
async def async_turn_on(self, **kwargs) -> None:
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||||
"""Turn on Home mode."""
|
"""Turn on Home mode."""
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"SynoDSMSurveillanceHomeModeToggle.turn_on(%s)",
|
"SynoDSMSurveillanceHomeModeToggle.turn_on(%s)",
|
||||||
|
@ -80,7 +90,7 @@ class SynoDSMSurveillanceHomeModeToggle(SynologyDSMBaseEntity, ToggleEntity):
|
||||||
)
|
)
|
||||||
await self.coordinator.async_request_refresh()
|
await self.coordinator.async_request_refresh()
|
||||||
|
|
||||||
async def async_turn_off(self, **kwargs) -> None:
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||||
"""Turn off Home mode."""
|
"""Turn off Home mode."""
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"SynoDSMSurveillanceHomeModeToggle.turn_off(%s)",
|
"SynoDSMSurveillanceHomeModeToggle.turn_off(%s)",
|
||||||
|
@ -103,8 +113,7 @@ class SynoDSMSurveillanceHomeModeToggle(SynologyDSMBaseEntity, ToggleEntity):
|
||||||
"identifiers": {
|
"identifiers": {
|
||||||
(
|
(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
self._api.information.serial,
|
f"{self._api.information.serial}_{SynoSurveillanceStation.INFO_API_KEY}",
|
||||||
SynoSurveillanceStation.INFO_API_KEY,
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
"name": "Surveillance Station",
|
"name": "Surveillance Station",
|
||||||
|
|
16
mypy.ini
16
mypy.ini
|
@ -529,6 +529,19 @@ warn_return_any = true
|
||||||
warn_unreachable = true
|
warn_unreachable = true
|
||||||
warn_unused_ignores = true
|
warn_unused_ignores = true
|
||||||
|
|
||||||
|
[mypy-homeassistant.components.synology_dsm.*]
|
||||||
|
check_untyped_defs = true
|
||||||
|
disallow_incomplete_defs = true
|
||||||
|
disallow_subclassing_any = true
|
||||||
|
disallow_untyped_calls = true
|
||||||
|
disallow_untyped_decorators = true
|
||||||
|
disallow_untyped_defs = true
|
||||||
|
no_implicit_optional = true
|
||||||
|
strict_equality = true
|
||||||
|
warn_return_any = true
|
||||||
|
warn_unreachable = true
|
||||||
|
warn_unused_ignores = true
|
||||||
|
|
||||||
[mypy-homeassistant.components.systemmonitor.*]
|
[mypy-homeassistant.components.systemmonitor.*]
|
||||||
check_untyped_defs = true
|
check_untyped_defs = true
|
||||||
disallow_incomplete_defs = true
|
disallow_incomplete_defs = true
|
||||||
|
@ -1232,9 +1245,6 @@ ignore_errors = true
|
||||||
[mypy-homeassistant.components.switcher_kis.*]
|
[mypy-homeassistant.components.switcher_kis.*]
|
||||||
ignore_errors = true
|
ignore_errors = true
|
||||||
|
|
||||||
[mypy-homeassistant.components.synology_dsm.*]
|
|
||||||
ignore_errors = true
|
|
||||||
|
|
||||||
[mypy-homeassistant.components.synology_srm.*]
|
[mypy-homeassistant.components.synology_srm.*]
|
||||||
ignore_errors = true
|
ignore_errors = true
|
||||||
|
|
||||||
|
|
|
@ -205,7 +205,6 @@ IGNORED_MODULES: Final[list[str]] = [
|
||||||
"homeassistant.components.surepetcare.*",
|
"homeassistant.components.surepetcare.*",
|
||||||
"homeassistant.components.switchbot.*",
|
"homeassistant.components.switchbot.*",
|
||||||
"homeassistant.components.switcher_kis.*",
|
"homeassistant.components.switcher_kis.*",
|
||||||
"homeassistant.components.synology_dsm.*",
|
|
||||||
"homeassistant.components.synology_srm.*",
|
"homeassistant.components.synology_srm.*",
|
||||||
"homeassistant.components.system_health.*",
|
"homeassistant.components.system_health.*",
|
||||||
"homeassistant.components.system_log.*",
|
"homeassistant.components.system_log.*",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue