Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduced Iperf3 client sensor #14213

Merged
merged 11 commits into from
May 24, 2018
1 change: 1 addition & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,7 @@ omit =
homeassistant/components/sensor/imap_email_content.py
homeassistant/components/sensor/imap.py
homeassistant/components/sensor/influxdb.py
homeassistant/components/sensor/iperf3.py
homeassistant/components/sensor/irish_rail_transport.py
homeassistant/components/sensor/kwb.py
homeassistant/components/sensor/lacrosse.py
Expand Down
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ LABEL maintainer="Paulus Schoutsen <Paulus@PaulusSchoutsen.nl>"
#ENV INSTALL_LIBCEC no
#ENV INSTALL_PHANTOMJS no
#ENV INSTALL_SSOCR no
#ENV INSTALL_IPERF3 no

VOLUME /config

Expand Down
178 changes: 178 additions & 0 deletions homeassistant/components/sensor/iperf3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
"""
Support for Iperf3 network measurement tool.

For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.iperf3/
"""
import logging
from datetime import timedelta

import voluptuous as vol

from homeassistant.components.sensor import DOMAIN, PLATFORM_SCHEMA
from homeassistant.const import (
ATTR_ATTRIBUTION, ATTR_ENTITY_ID, CONF_MONITORED_CONDITIONS,
CONF_HOST, CONF_PORT)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity

REQUIREMENTS = ['iperf3==0.1.10']

_LOGGER = logging.getLogger(__name__)

ATTR_PROTOCOL = 'Protocol'
ATTR_REMOTE_HOST = 'Remote Server'
ATTR_REMOTE_PORT = 'Remote Port'
ATTR_VERSION = 'Version'

CONF_ATTRIBUTION = 'Data retrieved using Iperf3'
CONF_DURATION = 'duration'

DEFAULT_DURATION = 10
DEFAULT_PORT = 5201

IPERF3_DATA = 'iperf3'

SCAN_INTERVAL = timedelta(minutes=30)

SERVICE_NAME = 'iperf3_update'

ICON = 'mdi:speedometer'

SENSOR_TYPES = {
'download': ['Download', 'Mbit/s'],
'upload': ['Upload', 'Mbit/s'],
}

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_MONITORED_CONDITIONS):
vol.All(cv.ensure_list, [vol.In(list(SENSOR_TYPES))]),
vol.Required(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_DURATION, default=DEFAULT_DURATION): vol.Range(5, 10),
})


SERVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.string,
})


def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Iperf3 sensor."""
if hass.data.get(IPERF3_DATA) is None:
hass.data[IPERF3_DATA] = {}
hass.data[IPERF3_DATA]['sensors'] = []

dev = []
for sensor in config[CONF_MONITORED_CONDITIONS]:
dev.append(
Iperf3Sensor(config[CONF_HOST],
config[CONF_PORT],
config[CONF_DURATION],
sensor))

hass.data[IPERF3_DATA]['sensors'].extend(dev)
add_devices(dev)

def _service_handler(service):
"""Update service for manual updates."""
entity_id = service.data.get('entity_id')
all_iperf3_sensors = hass.data[IPERF3_DATA]['sensors']

for sensor in all_iperf3_sensors:
if entity_id is not None:
if sensor.entity_id == entity_id:
sensor.update()
sensor.schedule_update_ha_state()
break
else:
sensor.update()
sensor.schedule_update_ha_state()

for sensor in dev:
hass.services.register(DOMAIN, SERVICE_NAME, _service_handler,
schema=SERVICE_SCHEMA)


class Iperf3Sensor(Entity):
"""A Iperf3 sensor implementation."""

def __init__(self, server, port, duration, sensor_type):
"""Initialize the sensor."""
self._attrs = {
ATTR_ATTRIBUTION: CONF_ATTRIBUTION,
}
self._name = \
"{} {}".format(SENSOR_TYPES[sensor_type][0], server)
self._state = None
self._sensor_type = sensor_type
self._unit_of_measurement = SENSOR_TYPES[sensor_type][1]
self._port = port
self._server = server
self._duration = duration
self.result = None

@property
def name(self):
"""Return the name of the sensor."""
return self._name

@property
def state(self):
"""Return the state of the device."""
return self._state

@property
def unit_of_measurement(self):
"""Return the unit of measurement of this entity, if any."""
return self._unit_of_measurement

@property
def device_state_attributes(self):
"""Return the state attributes."""
if self.result is not None:
self._attrs[ATTR_ATTRIBUTION] = CONF_ATTRIBUTION
self._attrs[ATTR_PROTOCOL] = self.result.protocol
self._attrs[ATTR_REMOTE_HOST] = self.result.remote_host
self._attrs[ATTR_REMOTE_PORT] = self.result.remote_port
self._attrs[ATTR_VERSION] = self.result.version
return self._attrs

def update(self):
"""Get the latest data and update the states."""
import iperf3
client = iperf3.Client()
client.duration = self._duration
client.server_hostname = self._server
client.port = self._port
client.verbose = False

# when testing download bandwith, reverse must be True
if self._sensor_type == 'download':
client.reverse = True

try:
self.result = client.run()
except (OSError, AttributeError) as error:
self.result = None
_LOGGER.error("Iperf3 sensor error: %s", error)
return

if self.result is not None and \
hasattr(self.result, 'error') and \
self.result.error is not None:
_LOGGER.error("Iperf3 sensor error: %s", self.result.error)
self.result = None
return

if self._sensor_type == 'download':
self._state = round(self.result.received_Mbps, 2)

elif self._sensor_type == 'upload':
self._state = round(self.result.sent_Mbps, 2)

@property
def icon(self):
"""Return icon."""
return ICON
3 changes: 3 additions & 0 deletions requirements_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,9 @@ insteonlocal==0.53
# homeassistant.components.insteon_plm
insteonplm==0.9.2

# homeassistant.components.sensor.iperf3
iperf3==0.1.10

# homeassistant.components.verisure
jsonpath==0.75

Expand Down
1 change: 1 addition & 0 deletions virtualization/Docker/Dockerfile.dev
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ LABEL maintainer="Paulus Schoutsen <Paulus@PaulusSchoutsen.nl>"
#ENV INSTALL_PHANTOMJS no
#ENV INSTALL_COAP no
#ENV INSTALL_SSOCR no
#ENV INSTALL_IPERF3 no

VOLUME /config

Expand Down
11 changes: 11 additions & 0 deletions virtualization/Docker/scripts/iperf3
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/bash
# Sets up iperf3.

# Stop on errors
set -e

PACKAGES=(
iperf3
)

apt-get install -y --no-install-recommends ${PACKAGES[@]}
5 changes: 5 additions & 0 deletions virtualization/Docker/setup_docker_prereqs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ INSTALL_FFMPEG="${INSTALL_FFMPEG:-yes}"
INSTALL_LIBCEC="${INSTALL_LIBCEC:-yes}"
INSTALL_PHANTOMJS="${INSTALL_PHANTOMJS:-yes}"
INSTALL_SSOCR="${INSTALL_SSOCR:-yes}"
INSTALL_IPERF3="${INSTALL_IPERF3:-yes}"

# Required debian packages for running hass or components
PACKAGES=(
Expand Down Expand Up @@ -64,6 +65,10 @@ if [ "$INSTALL_SSOCR" == "yes" ]; then
virtualization/Docker/scripts/ssocr
fi

if [ "$INSTALL_IPERF3" == "yes" ]; then
virtualization/Docker/scripts/iperf3
fi

# Remove packages
apt-get remove -y --purge ${PACKAGES_DEV[@]}
apt-get -y --purge autoremove
Expand Down