2017-05-19 14:39:13 +00:00
|
|
|
"""
|
|
|
|
Support for Synology NAS Sensors.
|
|
|
|
|
|
|
|
For more details about this platform, please refer to the documentation at
|
|
|
|
https://home-assistant.io/components/sensor.synologydsm/
|
|
|
|
"""
|
|
|
|
import logging
|
|
|
|
from datetime import timedelta
|
|
|
|
|
2017-08-06 08:07:05 +00:00
|
|
|
import voluptuous as vol
|
|
|
|
|
|
|
|
import homeassistant.helpers.config_validation as cv
|
2017-05-19 14:39:13 +00:00
|
|
|
from homeassistant.components.sensor import PLATFORM_SCHEMA
|
|
|
|
from homeassistant.const import (
|
2017-08-06 08:07:05 +00:00
|
|
|
CONF_HOST, CONF_USERNAME, CONF_PASSWORD, CONF_PORT, TEMP_CELSIUS,
|
2017-10-31 12:31:12 +00:00
|
|
|
CONF_MONITORED_CONDITIONS, EVENT_HOMEASSISTANT_START, CONF_DISKS)
|
2017-08-06 08:07:05 +00:00
|
|
|
from homeassistant.helpers.entity import Entity
|
2017-05-19 14:39:13 +00:00
|
|
|
from homeassistant.util import Throttle
|
|
|
|
|
|
|
|
REQUIREMENTS = ['python-synology==0.1.0']
|
|
|
|
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
CONF_VOLUMES = 'volumes'
|
|
|
|
DEFAULT_NAME = 'Synology DSM'
|
|
|
|
DEFAULT_PORT = 5000
|
|
|
|
|
|
|
|
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.Required(CONF_HOST): cv.string,
|
|
|
|
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
|
|
|
|
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)]),
|
2018-02-17 09:29:14 +00:00
|
|
|
vol.Optional(CONF_DISKS): cv.ensure_list,
|
|
|
|
vol.Optional(CONF_VOLUMES): cv.ensure_list,
|
2017-05-19 14:39:13 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
|
2017-08-06 08:07:05 +00:00
|
|
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
2017-05-19 14:39:13 +00:00
|
|
|
"""Set up the Synology NAS Sensor."""
|
|
|
|
def run_setup(event):
|
2017-08-06 08:07:05 +00:00
|
|
|
"""Wait until Home Assistant is fully initialized before creating.
|
2017-05-19 14:39:13 +00:00
|
|
|
|
|
|
|
Delay the setup until Home Assistant is fully initialized.
|
|
|
|
This allows any entities to be created already
|
|
|
|
"""
|
2017-08-06 08:07:05 +00:00
|
|
|
host = config.get(CONF_HOST)
|
|
|
|
port = config.get(CONF_PORT)
|
|
|
|
username = config.get(CONF_USERNAME)
|
|
|
|
password = config.get(CONF_PASSWORD)
|
|
|
|
unit = hass.config.units.temperature_unit
|
|
|
|
monitored_conditions = config.get(CONF_MONITORED_CONDITIONS)
|
|
|
|
|
|
|
|
api = SynoApi(host, port, username, password, unit)
|
|
|
|
|
|
|
|
sensors = [SynoNasUtilSensor(
|
|
|
|
api, variable, _UTILISATION_MON_COND[variable])
|
|
|
|
for variable in monitored_conditions
|
2017-05-19 14:39:13 +00:00
|
|
|
if variable in _UTILISATION_MON_COND]
|
|
|
|
|
2017-08-06 08:07:05 +00:00
|
|
|
# Handle all volumes
|
2018-02-17 09:29:14 +00:00
|
|
|
for volume in config.get(CONF_VOLUMES, api.storage.volumes):
|
2017-08-06 08:07:05 +00:00
|
|
|
sensors += [SynoNasStorageSensor(
|
|
|
|
api, variable, _STORAGE_VOL_MON_COND[variable], volume)
|
|
|
|
for variable in monitored_conditions
|
2017-05-19 14:39:13 +00:00
|
|
|
if variable in _STORAGE_VOL_MON_COND]
|
|
|
|
|
2017-08-06 08:07:05 +00:00
|
|
|
# Handle all disks
|
2018-02-17 09:29:14 +00:00
|
|
|
for disk in config.get(CONF_DISKS, api.storage.disks):
|
2017-08-06 08:07:05 +00:00
|
|
|
sensors += [SynoNasStorageSensor(
|
|
|
|
api, variable, _STORAGE_DSK_MON_COND[variable], disk)
|
|
|
|
for variable in monitored_conditions
|
2017-05-19 14:39:13 +00:00
|
|
|
if variable in _STORAGE_DSK_MON_COND]
|
|
|
|
|
2017-08-06 08:07:05 +00:00
|
|
|
add_devices(sensors, True)
|
2017-05-19 14:39:13 +00:00
|
|
|
|
|
|
|
# Wait until start event is sent to load this component.
|
|
|
|
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, run_setup)
|
|
|
|
|
|
|
|
|
2017-08-06 08:07:05 +00:00
|
|
|
class SynoApi(object):
|
|
|
|
"""Class to interface with Synology DSM API."""
|
2017-05-19 14:39:13 +00:00
|
|
|
|
|
|
|
def __init__(self, host, port, username, password, temp_unit):
|
|
|
|
"""Initialize the API wrapper class."""
|
|
|
|
from SynologyDSM import SynologyDSM
|
|
|
|
self.temp_unit = temp_unit
|
|
|
|
|
|
|
|
try:
|
2017-08-06 08:07:05 +00:00
|
|
|
self._api = SynologyDSM(host, port, username, password)
|
2018-01-29 08:23:53 +00:00
|
|
|
except: # noqa: E722 # pylint: disable=bare-except
|
2017-05-19 14:39:13 +00:00
|
|
|
_LOGGER.error("Error setting up Synology DSM")
|
|
|
|
|
2017-08-06 08:07:05 +00:00
|
|
|
# Will be updated when update() gets called.
|
2017-05-19 14:39:13 +00:00
|
|
|
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):
|
2017-08-06 08:07:05 +00:00
|
|
|
"""Representation of a Synology NAS Sensor."""
|
2017-05-19 14:39:13 +00:00
|
|
|
|
2017-08-06 08:07:05 +00:00
|
|
|
def __init__(self, api, variable, variable_info, monitor_device=None):
|
2017-05-19 14:39:13 +00:00
|
|
|
"""Initialize the sensor."""
|
|
|
|
self.var_id = variable
|
2017-08-06 08:07:05 +00:00
|
|
|
self.var_name = variable_info[0]
|
|
|
|
self.var_units = variable_info[1]
|
|
|
|
self.var_icon = variable_info[2]
|
2017-05-19 14:39:13 +00:00
|
|
|
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 "{} ({})".format(self.var_name, self.monitor_device)
|
2017-07-06 06:30:01 +00:00
|
|
|
return self.var_name
|
2017-05-19 14:39:13 +00:00
|
|
|
|
|
|
|
@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
|
2017-07-06 06:30:01 +00:00
|
|
|
return self.var_units
|
2017-05-19 14:39:13 +00:00
|
|
|
|
|
|
|
def update(self):
|
|
|
|
"""Get the latest data for the states."""
|
|
|
|
if self._api is not None:
|
|
|
|
self._api.update()
|
|
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
elif 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:
|
2017-08-06 08:07:05 +00:00
|
|
|
attr = getattr(
|
|
|
|
self._api.storage, self.var_id)(self.monitor_device)
|
2017-05-19 14:39:13 +00:00
|
|
|
|
|
|
|
if self._api.temp_unit == TEMP_CELSIUS:
|
|
|
|
return attr
|
2017-07-06 06:30:01 +00:00
|
|
|
|
|
|
|
return round(attr * 1.8 + 32.0, 1)
|
|
|
|
|
2017-08-06 08:07:05 +00:00
|
|
|
return getattr(self._api.storage, self.var_id)(self.monitor_device)
|