From c232242af0bb2a24b21f2c9bffeb98159b301f54 Mon Sep 17 00:00:00 2001 From: Justyn Shull Date: Sun, 3 Apr 2016 17:57:50 -0500 Subject: [PATCH] Add support for monitoring nzbget Calculate average download rate the same way as downloadrate Add config option for choosing which nzbget variables to monitor, and provide a default Pep8 fixes Refactoring and changes based on @balloob's comments Add nzbget.py to .coveragerc omit list. Check if there are any http errors (like bad auth) when creating the nzbgetapi class. If there are, the setup_platform() function will return false. Exceptions are logged as well. When a new sensor is created, assign the initial value from the api instead of simply using None until the first update call. --- .coveragerc | 1 + homeassistant/components/sensor/nzbget.py | 164 ++++++++++++++++++++++ 2 files changed, 165 insertions(+) create mode 100644 homeassistant/components/sensor/nzbget.py diff --git a/.coveragerc b/.coveragerc index 89c9c8a498f..820c7562276 100644 --- a/.coveragerc +++ b/.coveragerc @@ -139,6 +139,7 @@ omit = homeassistant/components/sensor/glances.py homeassistant/components/sensor/gtfs.py homeassistant/components/sensor/netatmo.py + homeassistant/components/sensor/nzbget.py homeassistant/components/sensor/loopenergy.py homeassistant/components/sensor/neurio_energy.py homeassistant/components/sensor/onewire.py diff --git a/homeassistant/components/sensor/nzbget.py b/homeassistant/components/sensor/nzbget.py new file mode 100644 index 00000000000..91ffc163621 --- /dev/null +++ b/homeassistant/components/sensor/nzbget.py @@ -0,0 +1,164 @@ +""" +Support for monitoring NZBGet nzb client. + +Uses NZBGet's JSON-RPC API to query for monitored variables. +""" +import logging +from datetime import timedelta +import requests + +from homeassistant.helpers.entity import Entity +from homeassistant.util import Throttle + +REQUIREMENTS = [] +SENSOR_TYPES = { + "ArticleCacheMB": ("Article Cache", "MB"), + "AverageDownloadRate": ("Average Speed", "MB/s"), + "DownloadRate": ("Speed", "MB/s"), + "DownloadPaused": ("Download Paused", None), + "FreeDiskSpaceMB": ("Disk Free", "MB"), + "PostPaused": ("Post Processing Paused", None), + "RemainingSizeMB": ("Queue Size", "MB"), +} +DEFAULT_TYPES = [ + "DownloadRate", + "DownloadPaused", + "RemainingSizeMB", +] + +_LOGGER = logging.getLogger(__name__) + + +# pylint: disable=unused-argument +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up nzbget sensors.""" + base_url = config.get("base_url") + name = config.get("name", "NZBGet") + username = config.get("username") + password = config.get("password") + monitored_types = config.get("monitored_variables", DEFAULT_TYPES) + + if not base_url: + _LOGGER.error("Missing base_url config for NzbGet") + return False + + url = "{}/jsonrpc".format(base_url) + + try: + nzbgetapi = NZBGetAPI(api_url=url, + username=username, + password=password) + nzbgetapi.update() + except (requests.exceptions.ConnectionError, + requests.exceptions.HTTPError) as conn_err: + _LOGGER.error("Error setting up NZBGet API: %r", conn_err) + return False + + devices = [] + for ng_type in monitored_types: + if ng_type in SENSOR_TYPES: + new_sensor = NZBGetSensor(api=nzbgetapi, + sensor_type=ng_type, + client_name=name) + devices.append(new_sensor) + else: + _LOGGER.error("Unknown nzbget sensor type: %s", ng_type) + add_devices(devices) + + +class NZBGetAPI(object): + """Simple json-rpc wrapper for nzbget's api.""" + + def __init__(self, api_url, username=None, password=None): + """Initialize NZBGet API and set headers needed later.""" + self.api_url = api_url + self.status = None + self.headers = {'content-type': 'application/json'} + if username is not None and password is not None: + self.auth = (username, password) + else: + self.auth = None + # set the intial state + self.update() + + def post(self, method, params=None): + """Send a post request, and return the response as a dict.""" + payload = {"method": method} + if params: + payload['params'] = params + try: + response = requests.post(self.api_url, + json=payload, + auth=self.auth, + headers=self.headers, + timeout=30) + response.raise_for_status() + return response.json() + except requests.exceptions.ConnectionError as conn_exc: + _LOGGER.error("Failed to update nzbget status from %s. Error: %s", + self.api_url, conn_exc) + raise + + @Throttle(timedelta(seconds=5)) + def update(self): + """Update cached response.""" + try: + self.status = self.post('status')['result'] + except requests.exceptions.ConnectionError: + # failed to update status - exception already logged in self.post + raise + + +class NZBGetSensor(Entity): + """Represents an NZBGet sensor.""" + + def __init__(self, api, sensor_type, client_name): + """Initialize a new NZBGet sensor.""" + self._name = client_name + ' ' + SENSOR_TYPES[sensor_type][0] + self.type = sensor_type + self.client_name = client_name + self.api = api + self._state = None + self._unit_of_measurement = SENSOR_TYPES[sensor_type][1] + # Set initial state + self.update() + _LOGGER.debug("created nzbget sensor %r", self) + + @property + def name(self): + """Return the name of the sensor.""" + return self._name + + @property + def state(self): + """Return the state of the sensor.""" + return self._state + + @property + def unit_of_measurement(self): + """Unit of measurement of this entity, if any.""" + return self._unit_of_measurement + + def update(self): + """Update state of sensor.""" + try: + self.api.update() + except requests.exceptions.ConnectionError: + # Error calling the api, already logged in api.update() + return + + if self.api.status is None: + _LOGGER.debug("update of %s requested, but no status is available", + self._name) + return + + value = self.api.status.get(self.type) + if value is None: + _LOGGER.warning("unable to locate value for %s", self.type) + return + + if "DownloadRate" in self.type and value > 0: + # Convert download rate from bytes/s to mb/s + self._state = value / 1024 / 1024 + else: + self._state = value