core/homeassistant/components/synologydsm/sensor.py

263 lines
9.2 KiB
Python

"""Support for Synology NAS Sensors."""
import logging
from datetime import timedelta
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (
CONF_NAME,
CONF_HOST,
CONF_USERNAME,
CONF_PASSWORD,
CONF_PORT,
CONF_SSL,
ATTR_ATTRIBUTION,
TEMP_CELSIUS,
CONF_MONITORED_CONDITIONS,
EVENT_HOMEASSISTANT_START,
CONF_DISKS,
)
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__)
ATTRIBUTION = "Data provided by Synology"
CONF_VOLUMES = "volumes"
DEFAULT_NAME = "Synology DSM"
DEFAULT_PORT = 5001
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=15)
_UTILISATION_MON_COND = {
"cpu_other_load": ["CPU Load (Other)", "%", "mdi:chip"],
"cpu_user_load": ["CPU Load (User)", "%", "mdi:chip"],
"cpu_system_load": ["CPU Load (System)", "%", "mdi:chip"],
"cpu_total_load": ["CPU Load (Total)", "%", "mdi:chip"],
"cpu_1min_load": ["CPU Load (1 min)", "%", "mdi:chip"],
"cpu_5min_load": ["CPU Load (5 min)", "%", "mdi:chip"],
"cpu_15min_load": ["CPU Load (15 min)", "%", "mdi:chip"],
"memory_real_usage": ["Memory Usage (Real)", "%", "mdi:memory"],
"memory_size": ["Memory Size", "Mb", "mdi:memory"],
"memory_cached": ["Memory Cached", "Mb", "mdi:memory"],
"memory_available_swap": ["Memory Available (Swap)", "Mb", "mdi:memory"],
"memory_available_real": ["Memory Available (Real)", "Mb", "mdi:memory"],
"memory_total_swap": ["Memory Total (Swap)", "Mb", "mdi:memory"],
"memory_total_real": ["Memory Total (Real)", "Mb", "mdi:memory"],
"network_up": ["Network Up", "Kbps", "mdi:upload"],
"network_down": ["Network Down", "Kbps", "mdi:download"],
}
_STORAGE_VOL_MON_COND = {
"volume_status": ["Status", None, "mdi:checkbox-marked-circle-outline"],
"volume_device_type": ["Type", None, "mdi:harddisk"],
"volume_size_total": ["Total Size", None, "mdi:chart-pie"],
"volume_size_used": ["Used Space", None, "mdi:chart-pie"],
"volume_percentage_used": ["Volume Used", "%", "mdi:chart-pie"],
"volume_disk_temp_avg": ["Average Disk Temp", None, "mdi:thermometer"],
"volume_disk_temp_max": ["Maximum Disk Temp", None, "mdi:thermometer"],
}
_STORAGE_DSK_MON_COND = {
"disk_name": ["Name", None, "mdi:harddisk"],
"disk_device": ["Device", None, "mdi:dots-horizontal"],
"disk_smart_status": ["Status (Smart)", None, "mdi:checkbox-marked-circle-outline"],
"disk_status": ["Status", None, "mdi:checkbox-marked-circle-outline"],
"disk_exceed_bad_sector_thr": ["Exceeded Max Bad Sectors", None, "mdi:test-tube"],
"disk_below_remain_life_thr": ["Below Min Remaining Life", None, "mdi:test-tube"],
"disk_temp": ["Temperature", None, "mdi:thermometer"],
}
_MONITORED_CONDITIONS = (
list(_UTILISATION_MON_COND.keys())
+ list(_STORAGE_VOL_MON_COND.keys())
+ list(_STORAGE_DSK_MON_COND.keys())
)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_SSL, default=True): cv.boolean,
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_MONITORED_CONDITIONS): vol.All(
cv.ensure_list, [vol.In(_MONITORED_CONDITIONS)]
),
vol.Optional(CONF_DISKS): cv.ensure_list,
vol.Optional(CONF_VOLUMES): cv.ensure_list,
}
)
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Synology NAS Sensor."""
def run_setup(event):
"""Wait until Home Assistant is fully initialized before creating.
Delay the setup until Home Assistant is fully initialized.
This allows any entities to be created already
"""
name = config.get(CONF_NAME)
host = config.get(CONF_HOST)
port = config.get(CONF_PORT)
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
use_ssl = config.get(CONF_SSL)
unit = hass.config.units.temperature_unit
monitored_conditions = config.get(CONF_MONITORED_CONDITIONS)
api = SynoApi(host, port, username, password, unit, use_ssl)
sensors = [
SynoNasUtilSensor(api, name, variable, _UTILISATION_MON_COND[variable])
for variable in monitored_conditions
if variable in _UTILISATION_MON_COND
]
# Handle all volumes
if api.storage.volumes is not None:
for volume in config.get(CONF_VOLUMES, api.storage.volumes):
sensors += [
SynoNasStorageSensor(
api, name, variable, _STORAGE_VOL_MON_COND[variable], volume
)
for variable in monitored_conditions
if variable in _STORAGE_VOL_MON_COND
]
# Handle all disks
if api.storage.disks is not None:
for disk in config.get(CONF_DISKS, api.storage.disks):
sensors += [
SynoNasStorageSensor(
api, name, variable, _STORAGE_DSK_MON_COND[variable], disk
)
for variable in monitored_conditions
if variable in _STORAGE_DSK_MON_COND
]
add_entities(sensors, True)
# Wait until start event is sent to load this component.
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, run_setup)
class SynoApi:
"""Class to interface with Synology DSM API."""
def __init__(self, host, port, username, password, temp_unit, use_ssl):
"""Initialize the API wrapper class."""
from SynologyDSM import SynologyDSM
self.temp_unit = temp_unit
try:
self._api = SynologyDSM(host, port, username, password, use_https=use_ssl)
except: # noqa: E722 pylint: disable=bare-except
_LOGGER.error("Error setting up Synology DSM")
# Will be updated when update() gets called.
self.utilisation = self._api.utilisation
self.storage = self._api.storage
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Update function for updating api information."""
self._api.update()
class SynoNasSensor(Entity):
"""Representation of a Synology NAS Sensor."""
def __init__(self, api, name, variable, variable_info, monitor_device=None):
"""Initialize the sensor."""
self.var_id = variable
self.var_name = "{} {}".format(name, variable_info[0])
self.var_units = variable_info[1]
self.var_icon = variable_info[2]
self.monitor_device = monitor_device
self._api = api
@property
def name(self):
"""Return the name of the sensor, if any."""
if self.monitor_device is not None:
return f"{self.var_name} ({self.monitor_device})"
return self.var_name
@property
def icon(self):
"""Icon to use in the frontend, if any."""
return self.var_icon
@property
def unit_of_measurement(self):
"""Return the unit the value is expressed in."""
if self.var_id in ["volume_disk_temp_avg", "volume_disk_temp_max", "disk_temp"]:
return self._api.temp_unit
return self.var_units
def update(self):
"""Get the latest data for the states."""
if self._api is not None:
self._api.update()
@property
def device_state_attributes(self):
"""Return the state attributes."""
return {ATTR_ATTRIBUTION: ATTRIBUTION}
class SynoNasUtilSensor(SynoNasSensor):
"""Representation a Synology Utilisation Sensor."""
@property
def state(self):
"""Return the state of the sensor."""
network_sensors = ["network_up", "network_down"]
memory_sensors = [
"memory_size",
"memory_cached",
"memory_available_swap",
"memory_available_real",
"memory_total_swap",
"memory_total_real",
]
if self.var_id in network_sensors or self.var_id in memory_sensors:
attr = getattr(self._api.utilisation, self.var_id)(False)
if self.var_id in network_sensors:
return round(attr / 1024.0, 1)
if self.var_id in memory_sensors:
return round(attr / 1024.0 / 1024.0, 1)
else:
return getattr(self._api.utilisation, self.var_id)
class SynoNasStorageSensor(SynoNasSensor):
"""Representation a Synology Utilisation Sensor."""
@property
def state(self):
"""Return the state of the sensor."""
temp_sensors = ["volume_disk_temp_avg", "volume_disk_temp_max", "disk_temp"]
if self.monitor_device is not None:
if self.var_id in temp_sensors:
attr = getattr(self._api.storage, self.var_id)(self.monitor_device)
if attr is None:
return None
if self._api.temp_unit == TEMP_CELSIUS:
return attr
return round(attr * 1.8 + 32.0, 1)
return getattr(self._api.storage, self.var_id)(self.monitor_device)