Add Nextcloud Integration (#30871)

* some sensors working in homeassistant

* bring up to date

* add codeowner

* update requirements

* overhaul data imports from api & sensor discovery

* remove print statement

* delete requirements_test_all

* add requrements_test_all.txt

* Update homeassistant/components/nextcloud/sensor.py

Co-Authored-By: springstan <46536646+springstan@users.noreply.github.com>

* Update homeassistant/components/nextcloud/sensor.py

Co-Authored-By: springstan <46536646+springstan@users.noreply.github.com>

* describe recursive function

* clarify that dict is returned

* remove requirements from requirements_test_all

* improve and simplify sensor naming

* add basic tests

* restore pre-commit config

* update requirements_test_all

* remove codespell requirement

* update pre-commit-config

* add-back codespell

* rename class variables as suggested by @springstan

* add dev branch to no-commit-to-branch git hook

Because my fork had the same 'dev' branch i wasn't able to push. Going forward I should probably name my branches differently.

* move config logic to __init__.py

* restore .pre-commit-config.yaml

* remove tests

* remove nextcloud test requirement

* remove debugging code

* implement binary sensors

* restore .pre-commit-config.yaml

* bump dependency version

* bump requirements files

* bump nextcloud reqirement to latest

* update possible exceptions, use fstrings

* add list of sensors & fix inconsistency in get_data_points

* use domain for config

* fix guard clause

* repair pre-commit-config

* Remove period from logging

* include url in unique_id

* update requirements_all.txt

Co-authored-by: springstan <46536646+springstan@users.noreply.github.com>
This commit is contained in:
MeIchthys
2020-03-24 06:11:35 -04:00
committed by GitHub
parent b50281a917
commit 3c59791b2e
7 changed files with 268 additions and 0 deletions

View File

@ -468,6 +468,7 @@ omit =
homeassistant/components/netgear_lte/*
homeassistant/components/netio/switch.py
homeassistant/components/neurio_energy/sensor.py
homeassistant/components/nextcloud/*
homeassistant/components/nfandroidtv/notify.py
homeassistant/components/niko_home_control/light.py
homeassistant/components/nilu/air_quality.py

View File

@ -247,6 +247,7 @@ homeassistant/components/netatmo/* @cgtobi
homeassistant/components/netdata/* @fabaff
homeassistant/components/nexia/* @ryannazaretian @bdraco
homeassistant/components/nextbus/* @vividboarder
homeassistant/components/nextcloud/* @meichthys
homeassistant/components/nilu/* @hfurubotten
homeassistant/components/nissan_leaf/* @filcole
homeassistant/components/nmbs/* @thibmaek

View File

@ -0,0 +1,147 @@
"""The Nextcloud integration."""
from datetime import timedelta
import logging
from nextcloudmonitor import NextcloudMonitor, NextcloudMonitorError
import voluptuous as vol
from homeassistant.const import (
CONF_PASSWORD,
CONF_SCAN_INTERVAL,
CONF_URL,
CONF_USERNAME,
)
from homeassistant.helpers import config_validation as cv, discovery
from homeassistant.helpers.event import track_time_interval
_LOGGER = logging.getLogger(__name__)
DOMAIN = "nextcloud"
NEXTCLOUD_COMPONENTS = ("sensor", "binary_sensor")
SCAN_INTERVAL = timedelta(seconds=60)
# Validate user configuration
CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.Schema(
{
vol.Required(CONF_URL): cv.url,
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL): cv.time_period,
}
)
},
extra=vol.ALLOW_EXTRA,
)
BINARY_SENSORS = (
"nextcloud_system_enable_avatars",
"nextcloud_system_enable_previews",
"nextcloud_system_filelocking.enabled",
"nextcloud_system_debug",
)
SENSORS = (
"nextcloud_system_version",
"nextcloud_system_theme",
"nextcloud_system_memcache.local",
"nextcloud_system_memcache.distributed",
"nextcloud_system_memcache.locking",
"nextcloud_system_freespace",
"nextcloud_system_cpuload",
"nextcloud_system_mem_total",
"nextcloud_system_mem_free",
"nextcloud_system_swap_total",
"nextcloud_system_swap_free",
"nextcloud_system_apps_num_installed",
"nextcloud_system_apps_num_updates_available",
"nextcloud_system_apps_app_updates_calendar",
"nextcloud_system_apps_app_updates_contacts",
"nextcloud_system_apps_app_updates_tasks",
"nextcloud_system_apps_app_updates_twofactor_totp",
"nextcloud_storage_num_users",
"nextcloud_storage_num_files",
"nextcloud_storage_num_storages",
"nextcloud_storage_num_storages_local",
"nextcloud_storage_num_storage_home",
"nextcloud_storage_num_storages_other",
"nextcloud_shares_num_shares",
"nextcloud_shares_num_shares_user",
"nextcloud_shares_num_shares_groups",
"nextcloud_shares_num_shares_link",
"nextcloud_shares_num_shares_mail",
"nextcloud_shares_num_shares_room",
"nextcloud_shares_num_shares_link_no_password",
"nextcloud_shares_num_fed_shares_sent",
"nextcloud_shares_num_fed_shares_received",
"nextcloud_shares_permissions_3_1",
"nextcloud_server_webserver",
"nextcloud_server_php_version",
"nextcloud_server_php_memory_limit",
"nextcloud_server_php_max_execution_time",
"nextcloud_server_php_upload_max_filesize",
"nextcloud_database_type",
"nextcloud_database_version",
"nextcloud_database_version",
"nextcloud_activeusers_last5minutes",
"nextcloud_activeusers_last1hour",
"nextcloud_activeusers_last24hours",
)
def setup(hass, config):
"""Set up the Nextcloud integration."""
# Fetch Nextcloud Monitor api data
conf = config[DOMAIN]
try:
ncm = NextcloudMonitor(conf[CONF_URL], conf[CONF_USERNAME], conf[CONF_PASSWORD])
except NextcloudMonitorError:
_LOGGER.error("Nextcloud setup failed - Check configuration")
hass.data[DOMAIN] = get_data_points(ncm.data)
hass.data[DOMAIN]["instance"] = conf[CONF_URL]
def nextcloud_update(event_time):
"""Update data from nextcloud api."""
try:
ncm.update()
except NextcloudMonitorError:
_LOGGER.error("Nextcloud update failed")
return False
hass.data[DOMAIN] = get_data_points(ncm.data)
# Update sensors on time interval
track_time_interval(hass, nextcloud_update, conf[CONF_SCAN_INTERVAL])
for component in NEXTCLOUD_COMPONENTS:
discovery.load_platform(hass, component, DOMAIN, {}, config)
return True
# Use recursion to create list of sensors & values based on nextcloud api data
def get_data_points(api_data, key_path="", leaf=False):
"""Use Recursion to discover data-points and values.
Get dictionary of data-points by recursing through dict returned by api until
the dictionary value does not contain another dictionary and use the
resulting path of dictionary keys and resulting value as the name/value
for the data-point.
returns: dictionary of data-point/values
"""
result = {}
for key, value in api_data.items():
if isinstance(value, dict):
if leaf:
key_path = f"{key}_"
if not leaf:
key_path += f"{key}_"
leaf = True
result.update(get_data_points(value, key_path, leaf))
else:
result[f"{DOMAIN}_{key_path}{key}"] = value
leaf = False
return result

View File

@ -0,0 +1,52 @@
"""Summary binary data from Nextcoud."""
import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
from . import BINARY_SENSORS, DOMAIN
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Nextcloud sensors."""
if discovery_info is None:
return
binary_sensors = []
for name in hass.data[DOMAIN]:
if name in BINARY_SENSORS:
binary_sensors.append(NextcloudBinarySensor(name))
add_entities(binary_sensors, True)
class NextcloudBinarySensor(BinarySensorDevice):
"""Represents a Nextcloud binary sensor."""
def __init__(self, item):
"""Initialize the Nextcloud binary sensor."""
self._name = item
self._is_on = None
@property
def icon(self):
"""Return the icon for this binary sensor."""
return "mdi:cloud"
@property
def name(self):
"""Return the name for this binary sensor."""
return self._name
@property
def is_on(self):
"""Return true if the binary sensor is on."""
return self._is_on == "yes"
@property
def unique_id(self):
"""Return the unique ID for this binary sensor."""
return f"{self.hass.data[DOMAIN]['instance']}#{self._name}"
def update(self):
"""Update the binary sensor."""
self._is_on = self.hass.data[DOMAIN][self._name]

View File

@ -0,0 +1,12 @@
{
"domain": "nextcloud",
"name": "Nextcloud",
"documentation": "https://www.home-assistant.io/integrations/nextcloud",
"requirements": [
"nextcloudmonitor==1.1.0"
],
"dependencies": [],
"codeowners": [
"@meichthys"
]
}

View File

@ -0,0 +1,52 @@
"""Summary data from Nextcoud."""
import logging
from homeassistant.helpers.entity import Entity
from . import DOMAIN, SENSORS
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Nextcloud sensors."""
if discovery_info is None:
return
sensors = []
for name in hass.data[DOMAIN]:
if name in SENSORS:
sensors.append(NextcloudSensor(name))
add_entities(sensors, True)
class NextcloudSensor(Entity):
"""Represents a Nextcloud sensor."""
def __init__(self, item):
"""Initialize the Nextcloud sensor."""
self._name = item
self._state = None
@property
def icon(self):
"""Return the icon for this sensor."""
return "mdi:cloud"
@property
def name(self):
"""Return the name for this sensor."""
return self._name
@property
def state(self):
"""Return the state for this sensor."""
return self._state
@property
def unique_id(self):
"""Return the unique ID for this sensor."""
return f"{self.hass.data[DOMAIN]['instance']}#{self._name}"
def update(self):
"""Update the sensor."""
self._state = self.hass.data[DOMAIN][self._name]

View File

@ -924,6 +924,9 @@ neurio==0.3.1
# homeassistant.components.nexia
nexia==0.7.1
# homeassistant.components.nextcloud
nextcloudmonitor==1.1.0
# homeassistant.components.niko_home_control
niko-home-control==0.2.1