1 """Support for Iperf3 network measurement tool."""
3 from __future__
import annotations
5 from datetime
import timedelta
9 import voluptuous
as vol
12 DOMAIN
as SENSOR_DOMAIN,
14 SensorEntityDescription,
20 CONF_MONITORED_CONDITIONS,
34 DATA_UPDATED = f
"{DOMAIN}_data_updated"
36 _LOGGER = logging.getLogger(__name__)
38 CONF_DURATION =
"duration"
39 CONF_PARALLEL =
"parallel"
40 CONF_MANUAL =
"manual"
45 DEFAULT_PROTOCOL =
"tcp"
48 ATTR_DOWNLOAD =
"download"
49 ATTR_UPLOAD =
"upload"
50 ATTR_VERSION =
"Version"
53 SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
56 name=ATTR_DOWNLOAD.capitalize(),
58 state_class=SensorStateClass.MEASUREMENT,
59 device_class=SensorDeviceClass.DATA_RATE,
60 native_unit_of_measurement=UnitOfDataRate.MEGABITS_PER_SECOND,
64 name=ATTR_UPLOAD.capitalize(),
66 state_class=SensorStateClass.MEASUREMENT,
67 device_class=SensorDeviceClass.DATA_RATE,
68 native_unit_of_measurement=UnitOfDataRate.MEGABITS_PER_SECOND,
71 SENSOR_KEYS: list[str] = [desc.key
for desc
in SENSOR_TYPES]
73 PROTOCOLS = [
"tcp",
"udp"]
75 HOST_CONFIG_SCHEMA = vol.Schema(
77 vol.Required(CONF_HOST): cv.string,
78 vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
79 vol.Optional(CONF_DURATION, default=DEFAULT_DURATION): vol.Range(5, 10),
80 vol.Optional(CONF_PARALLEL, default=DEFAULT_PARALLEL): vol.Range(1, 20),
81 vol.Optional(CONF_PROTOCOL, default=DEFAULT_PROTOCOL): vol.In(PROTOCOLS),
85 CONFIG_SCHEMA = vol.Schema(
89 vol.Required(CONF_HOSTS): vol.All(cv.ensure_list, [HOST_CONFIG_SCHEMA]),
90 vol.Optional(CONF_MONITORED_CONDITIONS, default=SENSOR_KEYS): vol.All(
91 cv.ensure_list, [vol.In(SENSOR_KEYS)]
93 vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_INTERVAL): vol.All(
94 cv.time_period, cv.positive_timedelta
96 vol.Optional(CONF_MANUAL, default=
False): cv.boolean,
100 extra=vol.ALLOW_EXTRA,
103 SERVICE_SCHEMA = vol.Schema({vol.Optional(ATTR_HOST, default=
None): cv.string})
106 async
def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
107 """Set up the iperf3 component."""
108 hass.data[DOMAIN] = {}
110 conf = config[DOMAIN]
111 for host
in conf[CONF_HOSTS]:
112 data = hass.data[DOMAIN][host[CONF_HOST]] =
Iperf3Data(hass, host)
114 if not conf[CONF_MANUAL]:
117 def update(call: ServiceCall) ->
None:
118 """Service call to manually update the data."""
119 called_host = call.data[ATTR_HOST]
120 if called_host
in hass.data[DOMAIN]:
121 hass.data[DOMAIN][called_host].
update()
123 for iperf3_host
in hass.data[DOMAIN].values():
126 hass.services.async_register(DOMAIN,
"speedtest", update, schema=SERVICE_SCHEMA)
128 hass.async_create_task(
133 {CONF_MONITORED_CONDITIONS: conf[CONF_MONITORED_CONDITIONS]},
142 """Get the latest data from iperf3."""
145 """Initialize the data object."""
148 self.
datadata = {ATTR_DOWNLOAD:
None, ATTR_UPLOAD:
None, ATTR_VERSION:
None}
151 """Create a new iperf3 client to use for measurement."""
152 client = iperf3.Client()
153 client.duration = self.
_host_host[CONF_DURATION]
154 client.server_hostname = self.
_host_host[CONF_HOST]
155 client.port = self.
_host_host[CONF_PORT]
156 client.num_streams = self.
_host_host[CONF_PARALLEL]
157 client.protocol = self.
_host_host[CONF_PROTOCOL]
158 client.verbose =
False
163 """Return the protocol used for this connection."""
164 return self.
_host_host[CONF_PROTOCOL]
168 """Return the host connected to."""
169 return self.
_host_host[CONF_HOST]
173 """Return the port on the host connected to."""
174 return self.
_host_host[CONF_PORT]
177 """Get the latest data from iperf3."""
180 result = self.
_run_test_run_test(ATTR_DOWNLOAD)
181 self.
datadata[ATTR_DOWNLOAD] = self.
datadata[ATTR_UPLOAD] = getattr(
184 self.
datadata[ATTR_VERSION] = getattr(result,
"version",
None)
186 result = self.
_run_test_run_test(ATTR_DOWNLOAD)
187 self.
datadata[ATTR_DOWNLOAD] = getattr(result,
"received_Mbps",
None)
188 self.
datadata[ATTR_VERSION] = getattr(result,
"version",
None)
189 self.
datadata[ATTR_UPLOAD] = getattr(
190 self.
_run_test_run_test(ATTR_UPLOAD),
"sent_Mbps",
None
196 """Run and return the iperf3 data."""
198 client.reverse = test_type == ATTR_DOWNLOAD
200 result = client.run()
201 except (AttributeError, OSError, ValueError)
as error:
202 _LOGGER.error(
"Iperf3 error: %s", error)
205 if result
is not None and hasattr(result,
"error")
and result.error
is not None:
206 _LOGGER.error(
"Iperf3 error: %s", result.error)
def __init__(self, hass, host)
def _run_test(self, test_type)
def update(self, now=None)
bool async_setup(HomeAssistant hass, ConfigType config)
IssData update(pyiss.ISS iss)
None async_load_platform(core.HomeAssistant hass, Platform|str component, str platform, DiscoveryInfoType|None discovered, ConfigType hass_config)
None dispatcher_send(HomeAssistant hass, str signal, *Any args)
CALLBACK_TYPE async_track_time_interval(HomeAssistant hass, Callable[[datetime], Coroutine[Any, Any, None]|None] action, timedelta interval, *str|None name=None, bool|None cancel_on_shutdown=None)