Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,7 @@ omit =
homeassistant/components/nuki/lock.py
homeassistant/components/nut/sensor.py
homeassistant/components/nx584/alarm_control_panel.py
homeassistant/components/nzbget/__init__.py
homeassistant/components/nzbget/sensor.py
homeassistant/components/obihai/*
homeassistant/components/octoprint/*
Expand Down
1 change: 1 addition & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ homeassistant/components/nsw_fuel_station/* @nickw444
homeassistant/components/nsw_rural_fire_service_feed/* @exxamalte
homeassistant/components/nuki/* @pschmitt
homeassistant/components/nws/* @MatthewFlamm
homeassistant/components/nzbget/* @chriscla
homeassistant/components/obihai/* @dshokouhi
homeassistant/components/ohmconnect/* @robbiet480
homeassistant/components/onboarding/* @home-assistant/core
Expand Down
105 changes: 105 additions & 0 deletions homeassistant/components/nzbget/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,106 @@
"""The nzbget component."""
from datetime import timedelta
import logging

import pynzbgetapi
import requests
import voluptuous as vol

from homeassistant.const import (
CONF_HOST,
CONF_NAME,
CONF_PASSWORD,
CONF_PORT,
CONF_SCAN_INTERVAL,
CONF_SSL,
CONF_USERNAME,
)
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.dispatcher import dispatcher_send
from homeassistant.helpers.event import track_time_interval

_LOGGER = logging.getLogger(__name__)

DOMAIN = "nzbget"
DATA_NZBGET = "data_nzbget"
DATA_UPDATED = "nzbget_data_updated"

DEFAULT_NAME = "NZBGet"
DEFAULT_PORT = 6789

DEFAULT_SCAN_INTERVAL = timedelta(seconds=5)

CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.Schema(
{
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_PASSWORD): cv.string,
vol.Optional(CONF_USERNAME): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(
CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL
): cv.time_period,
vol.Optional(CONF_SSL, default=False): cv.boolean,
}
)
},
extra=vol.ALLOW_EXTRA,
)


def setup(hass, config):
"""Set up the NZBGet sensors."""
host = config[DOMAIN][CONF_HOST]
port = config[DOMAIN][CONF_PORT]
ssl = "s" if config[DOMAIN][CONF_SSL] else ""
name = config[DOMAIN][CONF_NAME]
username = config[DOMAIN].get(CONF_USERNAME)
password = config[DOMAIN].get(CONF_PASSWORD)
scan_interval = config[DOMAIN][CONF_SCAN_INTERVAL]

try:
nzbget_api = pynzbgetapi.NZBGetAPI(host, username, password, ssl, ssl, port)
nzbget_api.version()
except pynzbgetapi.NZBGetAPIException as conn_err:
_LOGGER.error("Error setting up NZBGet API: %s", conn_err)
return False

_LOGGER.debug("Successfully validated NZBGet API connection")

nzbget_data = hass.data[DATA_NZBGET] = NZBGetData(hass, nzbget_api)
nzbget_data.update()

def refresh(event_time):
"""Get the latest data from NZBGet."""
nzbget_data.update()

track_time_interval(hass, refresh, scan_interval)

sensorconfig = {"client_name": name}

hass.helpers.discovery.load_platform("sensor", DOMAIN, sensorconfig, config)

return True


class NZBGetData:
"""Get the latest data and update the states."""

def __init__(self, hass, api):
"""Initialize the NZBGet RPC API."""
self.hass = hass
self.status = None
self.available = True
self._api = api

def update(self):
"""Get the latest data from NZBGet instance."""
try:
self.status = self._api.status()
self.available = True
dispatcher_send(self.hass, DATA_UPDATED)
except requests.exceptions.ConnectionError:
self.available = False
Comment thread
MartinHjelmare marked this conversation as resolved.
Outdated
_LOGGER.error("Unable to refresh NZBGet data")
4 changes: 2 additions & 2 deletions homeassistant/components/nzbget/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"domain": "nzbget",
"name": "Nzbget",
"documentation": "https://www.home-assistant.io/components/nzbget",
"requirements": [],
"requirements": ["pynzbgetapi==0.2.0"],
"dependencies": [],
"codeowners": []
"codeowners": ["@chriscla"]
}
157 changes: 39 additions & 118 deletions homeassistant/components/nzbget/sensor.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,15 @@
"""Support for monitoring NZBGet NZB client."""
from datetime import timedelta
"""Monitor the NZBGet API."""
import logging

from aiohttp.hdrs import CONTENT_TYPE
import requests
import voluptuous as vol

from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (
CONF_SSL,
CONF_HOST,
CONF_NAME,
CONF_PORT,
CONF_PASSWORD,
CONF_USERNAME,
CONTENT_TYPE_JSON,
CONF_MONITORED_VARIABLES,
)
import homeassistant.helpers.config_validation as cv
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle

from . import DATA_NZBGET, DATA_UPDATED

_LOGGER = logging.getLogger(__name__)

DEFAULT_NAME = "NZBGet"
DEFAULT_PORT = 6789

MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=5)

SENSOR_TYPES = {
"article_cache": ["ArticleCacheMB", "Article Cache", "MB"],
Expand All @@ -40,66 +23,39 @@
"uptime": ["UpTimeSec", "Uptime", "min"],
}

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_MONITORED_VARIABLES, default=["download_rate"]): vol.All(
cv.ensure_list, [vol.In(SENSOR_TYPES)]
),
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PASSWORD): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_SSL, default=False): cv.boolean,
vol.Optional(CONF_USERNAME): cv.string,
}
)


def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the NZBGet sensors."""
host = config.get(CONF_HOST)
port = config.get(CONF_PORT)
ssl = "s" if config.get(CONF_SSL) else ""
name = config.get(CONF_NAME)
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
monitored_types = config.get(CONF_MONITORED_VARIABLES)

url = f"http{ssl}://{host}:{port}/jsonrpc"

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: %s", conn_err)
return False
"""Create NZBGet sensors."""

if discovery_info is None:
return

nzbget_data = hass.data[DATA_NZBGET]
name = discovery_info["client_name"]

devices = []
for ng_type in monitored_types:
for sensor_type, sensor_config in SENSOR_TYPES.items():
new_sensor = NZBGetSensor(
api=nzbgetapi, sensor_type=SENSOR_TYPES.get(ng_type), client_name=name
nzbget_data, sensor_type, name, sensor_config[0], sensor_config[1]
)
devices.append(new_sensor)

add_entities(devices)
add_entities(devices, True)


class NZBGetSensor(Entity):
"""Representation of a NZBGet sensor."""

def __init__(self, api, sensor_type, client_name):
def __init__(
self, nzbget_data, sensor_type, client_name, sensor_name, unit_of_measurement
):
"""Initialize a new NZBGet sensor."""
self._name = "{} {}".format(client_name, sensor_type[1])
self.type = sensor_type[0]
self._name = f"{client_name} {sensor_type}"
self.type = sensor_name
self.client_name = client_name
self.api = api
self.nzbget_data = nzbget_data
self._state = None
self._unit_of_measurement = sensor_type[2]
self.update()
_LOGGER.debug("Created NZBGet sensor: %s", self.type)
self._unit_of_measurement = unit_of_measurement

@property
def name(self):
Expand All @@ -116,21 +72,31 @@ def unit_of_measurement(self):
"""Return the unit of measurement of this entity, if any."""
return self._unit_of_measurement

@property
def available(self):
"""Return whether the sensor is available."""
return self.nzbget_data.available

async def async_added_to_hass(self):
"""Handle entity which will be added."""
async_dispatcher_connect(
self.hass, DATA_UPDATED, self._schedule_immediate_update
)

@callback
def _schedule_immediate_update(self):
self.async_schedule_update_ha_state(True)

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:
if self.nzbget_data.status is None:
_LOGGER.debug(
"Update of %s requested, but no status is available", self._name
)
return

value = self.api.status.get(self.type)
value = self.nzbget_data.status.get(self.type)
if value is None:
_LOGGER.warning("Unable to locate value for %s", self.type)
return
Expand All @@ -143,48 +109,3 @@ def update(self):
self._state = round(value / 60, 2)
else:
self._state = value


class NZBGetAPI:
"""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: CONTENT_TYPE_JSON}

if username is not None and password is not None:
self.auth = (username, password)
else:
self.auth = None
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=5,
)
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(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Update cached response."""
self.status = self.post("status")["result"]
3 changes: 3 additions & 0 deletions requirements_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1347,6 +1347,9 @@ pynws==0.7.4
# homeassistant.components.nx584
pynx584==0.4

# homeassistant.components.nzbget
pynzbgetapi==0.2.0

# homeassistant.components.obihai
pyobihai==1.0.2

Expand Down