From 83b791489bcf920e0f2edc62eef53f44af00167e Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Mon, 19 Jun 2017 05:32:39 +0100 Subject: [PATCH] Upnp properties (#8067) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * make port mapping optional * dependencies + improvements * Added bytes and packets sensors from IGD * flake8 check * new sensor with upnp counters * checks * whitespaces in blank line * requirements update * added sensor.upnp to .coveragerc * downgrade miniupnpc Latest version of miniupnpc is 2.0, but pypi only has 1.9 Fortunately it is enough * revert to non async miniupnpc will do network calls, so this component can’t be moved to coroutine * hof hof forgot to remove import ot asyncio --- .coveragerc | 1 + homeassistant/components/sensor/upnp.py | 75 +++++++++++++++++++++++++ homeassistant/components/upnp.py | 31 +++++++++- requirements_all.txt | 3 + 4 files changed, 108 insertions(+), 2 deletions(-) create mode 100644 homeassistant/components/sensor/upnp.py diff --git a/.coveragerc b/.coveragerc index fa25c8ef912..24277e01912 100644 --- a/.coveragerc +++ b/.coveragerc @@ -471,6 +471,7 @@ omit = homeassistant/components/sensor/transmission.py homeassistant/components/sensor/twitch.py homeassistant/components/sensor/uber.py + homeassistant/components/sensor/upnp.py homeassistant/components/sensor/ups.py homeassistant/components/sensor/usps.py homeassistant/components/sensor/vasttrafik.py diff --git a/homeassistant/components/sensor/upnp.py b/homeassistant/components/sensor/upnp.py new file mode 100644 index 00000000000..e5acae67916 --- /dev/null +++ b/homeassistant/components/sensor/upnp.py @@ -0,0 +1,75 @@ +""" +Support for UPnP Sensors (IGD). + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.upnp/ +""" +import logging + +from homeassistant.components.upnp import DATA_UPNP, UNITS +from homeassistant.helpers.entity import Entity + +_LOGGER = logging.getLogger(__name__) + +# sensor_type: [friendly_name, convert_unit, icon] +SENSOR_TYPES = { + 'byte_received': ['received bytes', True, 'mdi:server-network'], + 'byte_sent': ['sent bytes', True, 'mdi:server-network'], + 'packets_in': ['packets received', False, 'mdi:server-network'], + 'packets_out': ['packets sent', False, 'mdi:server-network'], +} + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up the IGD sensors.""" + upnp = hass.data[DATA_UPNP] + unit = discovery_info['unit'] + add_devices([ + IGDSensor(upnp, t, unit if SENSOR_TYPES[t][1] else None) + for t in SENSOR_TYPES], True) + + +class IGDSensor(Entity): + """Representation of a UPnP IGD sensor.""" + + def __init__(self, upnp, sensor_type, unit=""): + """Initialize the IGD sensor.""" + self._upnp = upnp + self.type = sensor_type + self.unit = unit + self.unit_factor = UNITS[unit] if unit is not None else 1 + self._name = 'IGD {}'.format(SENSOR_TYPES[sensor_type][0]) + self._state = None + + @property + def name(self): + """Return the name of the sensor.""" + return self._name + + @property + def state(self): + """Return the state of the device.""" + if self._state is None: + return None + return format(self._state / self.unit_factor, '.1f') + + @property + def icon(self): + """Icon to use in the frontend, if any.""" + return SENSOR_TYPES[self.type][2] + + @property + def unit_of_measurement(self): + """Return the unit of measurement of this entity, if any.""" + return self.unit + + def update(self): + """Get the latest information from the IGD.""" + if self.type == "byte_received": + self._state = self._upnp.totalbytereceived() + elif self.type == "byte_sent": + self._state = self._upnp.totalbytesent() + elif self.type == "packets_in": + self._state = self._upnp.totalpacketreceived() + elif self.type == "packets_out": + self._state = self._upnp.totalpacketsent() diff --git a/homeassistant/components/upnp.py b/homeassistant/components/upnp.py index e6ad66d0b51..a058fdae85e 100644 --- a/homeassistant/components/upnp.py +++ b/homeassistant/components/upnp.py @@ -1,5 +1,5 @@ """ -This module will attempt to open a port in your router for Home Assistant. +Will open a port in your router for Home Assistant and provide statistics. For more details about this component, please refer to the documentation at https://home-assistant.io/components/upnp/ @@ -10,14 +10,33 @@ from urllib.parse import urlsplit import voluptuous as vol from homeassistant.const import (EVENT_HOMEASSISTANT_STOP) +from homeassistant.helpers import config_validation as cv +from homeassistant.helpers import discovery + +REQUIREMENTS = ['miniupnpc==1.9'] _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['api'] DOMAIN = 'upnp' +DATA_UPNP = 'UPNP' + +CONF_ENABLE_PORT_MAPPING = 'port_mapping' +CONF_UNITS = 'unit' + +UNITS = { + "Bytes": 1, + "KBytes": 1024, + "MBytes": 1024**2, + "GBytes": 1024**3, +} + CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({}), + DOMAIN: vol.Schema({ + vol.Optional(CONF_ENABLE_PORT_MAPPING, default=True): cv.boolean, + vol.Optional(CONF_UNITS, default="MBytes"): vol.In(UNITS), + }), }, extra=vol.ALLOW_EXTRA) @@ -27,6 +46,7 @@ def setup(hass, config): import miniupnpc upnp = miniupnpc.UPnP() + hass.data[DATA_UPNP] = upnp upnp.discoverdelay = 200 upnp.discover() @@ -36,6 +56,13 @@ def setup(hass, config): _LOGGER.exception("Error when attempting to discover an UPnP IGD") return False + unit = config[DOMAIN].get(CONF_UNITS) + discovery.load_platform(hass, 'sensor', DOMAIN, {'unit': unit}, config) + + port_mapping = config[DOMAIN].get(CONF_ENABLE_PORT_MAPPING) + if not port_mapping: + return True + base_url = urlsplit(hass.config.api.base_url) host = base_url.hostname external_port = internal_port = base_url.port diff --git a/requirements_all.txt b/requirements_all.txt index 4150850afc6..cd4039461f3 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -378,6 +378,9 @@ mficlient==0.3.0 # homeassistant.components.sensor.miflora miflora==0.1.16 +# homeassistant.components.upnp +miniupnpc==1.9 + # homeassistant.components.tts mutagen==1.37.0