[speedtest] Binding for Ookla's Speedtest - Initial contribution (#9913)

Also-by: Brian Homeyer <bhomeyer@gmail.com>
Signed-off-by: Michael Weger <weger.michael@gmx.net>
This commit is contained in:
bigbasec 2023-04-07 04:18:02 -04:00 committed by GitHub
parent 46a73befae
commit 86f829fa21
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 1552 additions and 0 deletions

View File

@ -324,6 +324,7 @@
/bundles/org.openhab.binding.sonyaudio/ @freke
/bundles/org.openhab.binding.sonyprojector/ @lolodomo
/bundles/org.openhab.binding.souliss/ @lucacalcaterra @fazioa
/bundles/org.openhab.binding.speedtest/ @MikeTheTux
/bundles/org.openhab.binding.spotify/ @Hilbrand
/bundles/org.openhab.binding.squeezebox/ @digitaldan @mhilbush
/bundles/org.openhab.binding.surepetcare/ @renescherer @HerzScheisse

View File

@ -1611,6 +1611,11 @@
<artifactId>org.openhab.binding.souliss</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.speedtest</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.spotify</artifactId>

View File

@ -0,0 +1,13 @@
This content is produced and maintained by the openHAB project.
* Project home: https://www.openhab.org
== Declared Project Licenses
This program and the accompanying materials are made available under the terms
of the Eclipse Public License 2.0 which is available at
https://www.eclipse.org/legal/epl-2.0/.
== Source Code
https://github.com/openhab/openhab-addons

View File

@ -0,0 +1,118 @@
# Speedtest Binding
The Speedtest Binding can be used to perform a network speed test for your openHAB instance.
It is based on the command line interface (CLI) version of Ookla's Speedtest (https://www.speedtest.net/apps/cli).
The Ookla CLI Speedtest application MUST be installed on your openHAB instance when using the Speedtest Binding.
## Why Ookla's Speedtest?
Fully supported and maintained by Ookla with the latest Speedtest technology for best results:
- Consistent measurement, even on high bandwidth
- Consistent measurement, very much independent from the performance of the host system
## What functionality does Ookla's Speedtest offer?
The Speedtest Binding is using the following functionality, provided by Ookla's Speedtest:
- Output of an accurate timestamp per measurement
- Output of Ping time and Jitter
- Output of Bandwidth, transferred Bytes and elapsed time for Down-/Upload
- Output of the used interface with Internal/External IP, MAC Address and the Internet Service Provider (ISP)
- Output of a result ID and a result URL
- Output of the used Server and location the Speedtest was run against
- Possibility to pre-select a server used for testing
## What interfaces does the Speedtest Binding offer?
The Speedtest Binding provides the Ookla's Speedtest functionality via the following openHAB interface:
- Execute Speedtest time based or triggered
- Provide results via openHAB Channels
- List available Ookla Speedtest servers that can be used for testing (optional)
## Ookla License and Privacy Terms
When using this binding, you automatically accept the license and privacy terms of Ookla's Speedtest.
You can find the latest version of those terms at the following webpages:
- https://www.speedtest.net/about/eula
- https://www.speedtest.net/about/terms
- https://www.speedtest.net/about/privacy
## Supported Things
Speedtest thing.
## Binding Configuration
For this binding to work, you MUST install Ookla's Speedtest command line tool (`speedtest` or `speedtest.exe`).
It will not work with other versions like `speedtest-cli` or other `speedtest` variants.
To install Ookla's version of Speedtest, head to https://www.speedtest.net/apps/cli and follow the instructions for your Operating System.
## Thing Configuration
| Parameter | Description | Default |
|-------------------|------------------------------------------------------------------------------------------------------------------------------|---------|
| `refreshInterval` | How often to test network speed, in minutes | `60` |
| `execPath` | The path of the Ookla Speedtest executable.<br/>Linux machines may leave this blank and it defaults to `/usr/bin/speedtest`. | |
| `serverID` | Optional: A specific server that shall be used for testing. You can pick the server ID from the "Thing Properties".<br/>If this is left blank the best option will be selected by Ookla. | |
The `refreshInterval` parameter can also be set to `0` which means "Do not test automatically".
This can be used if you want to use the "Trigger Test" channel in order to test via rules, or an item instead.
Ensure that the user that openHAB is running with, has the permissions to access and execute the executable.
## Properties
| Property | Description |
|---------------------|------------------------------------------------------------------------------------------------------------|
| Server List 1...10 | A List of Ookla Speedtest servers that can be used in order to specify a specific server for the Speedtest.<br/>Configure the Server ID via the `serverID` Thing Configuration Parameter. |
## Channels
| Channel | Type | Description |
|-----------------------|---------------------------|-------------------------------------------------------------------|
| `server` | `String` | The remote server that the Speedtest was run against |
| `pingJitter` | `Number:Time` | Ping Jitter - the variation in the response time |
| `pingLatency` | `Number:Time` | Ping Latency - the reaction time of your internet connection |
| `downloadBandwidth` | `Number:DataTransferRate` | Download bandwidth, e.g. in Mbit/s |
| `downloadBytes` | `Number:DataAmount` | Amount of data that was used for the last download bandwidth test |
| `downloadElapsed` | `Number:Time` | Time spent for the last download bandwidth test |
| `uploadBandwidth` | `Number:DataTransferRate` | Upload bandwidth, e.g. in Mbit/s |
| `uploadBytes` | `Number:DataAmount` | Amount of data that was used for the last upload bandwidth test |
| `uploadElapsed` | `Number:Time` | Time spent for the last upload bandwidth test |
| `isp` | `String` | Your Internet Service Provider (ISP) as calculated by Ookla |
| `interfaceInternalIp` | `String` | IP address of the internal interface that was used for the test |
| `interfaceExternalIp` | `String` | IP address of the external interface that was used for the test |
| `resultUrl` | `String` | The URL to the Speedtest results in HTML on the Ookla webserver |
| `triggerTest` | `Switch` | Trigger in order to run Speedtest manually |
## Full Example
### Thing File
```java
Thing speedtest:speedtest:myspeedtest "Ookla Speedtest" [ execPath="/usr/bin/speedtest", refreshInterval=60 ]
```
### Item File
```java
String Speedtest_Server "Server" { channel="speedtest:speedtest:myspeedtest:server" }
Number:Time Speedtest_Ping_Jitter "Ping Jitter" { channel="speedtest:speedtest:myspeedtest:pingJitter" }
Number:Time Speedtest_Ping_Latency "Ping Latency" { channel="speedtest:speedtest:myspeedtest:pingLatency" }
Number:DataTransferRate Speedtest_Download_Bandwith "Download Bandwith" { channel="speedtest:speedtest:myspeedtest:downloadBandwidth" }
Number:DataAmount Speedtest_Download_Bytes "Download Bytes" { channel="speedtest:speedtest:myspeedtest:downloadBytes" }
Number:Time Speedtest_Download_Elapsed "Download Elapsed" { channel="speedtest:speedtest:myspeedtest:downloadElapsed" }
Number:DataTransferRate Speedtest_Upload_Bandwith "Upload Bandwith" { channel="speedtest:speedtest:myspeedtest:uploadBandwidth" }
Number:DataAmount Speedtest_Upload_Bytes "Upload Bytes" { channel="speedtest:speedtest:myspeedtest:uploadBytes" }
Number:Time Speedtest_Upload_Elapsed "Upload Elapsed" { channel="speedtest:speedtest:myspeedtest:uploadElapsed" }
String Speedtest_ISP "ISP" { channel="speedtest:speedtest:myspeedtest:isp" }
String Speedtest_Interface_InternalIP "Internal IP Address" { channel="speedtest:speedtest:myspeedtest:interfaceInternalIp" }
String Speedtest_Interface_ExternalIP "External IP Address" { channel="speedtest:speedtest:myspeedtest:interfaceExternalIp" }
String Speedtest_ResultURL "Result URL" { channel="speedtest:speedtest:myspeedtest:resultUrl" }
Switch Speedtest_TriggerTest "Trigger Test" { channel="speedtest:speedtest:myspeedtest:triggerTest" }
```

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>4.0.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.speedtest</artifactId>
<name>openHAB Add-ons :: Bundles :: Speedtest Binding</name>
</project>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.speedtest-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-speedtest" description="Speedtest Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.speedtest/${project.version}</bundle>
</feature>
</features>

View File

@ -0,0 +1,71 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.speedtest.internal;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link SpeedtestBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Brian Homeyer - Initial contribution
*/
@NonNullByDefault
public class SpeedtestBindingConstants {
private static final String BINDING_ID = "speedtest";
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_SPEEDTEST = new ThingTypeUID(BINDING_ID, "speedtest");
// Config
public static final String REFRESH_INTERVAL = "refreshInterval";
public static final String EXEC_PATH = "execPath";
public static final String SPEEDTEST_VERSION = "speedTestVersion";
public static final String SERVER_ID = "serverID";
// Channels
public static final String SERVER = "server";
public static final String PING_JITTER = "pingJitter";
public static final String PING_LATENCY = "pingLatency";
public static final String DOWNLOAD_BANDWIDTH = "downloadBandwidth";
public static final String DOWNLOAD_BYTES = "downloadBytes";
public static final String DOWNLOAD_ELAPSED = "downloadElapsed";
public static final String UPLOAD_BANDWIDTH = "uploadBandwidth";
public static final String UPLOAD_BYTES = "uploadBytes";
public static final String UPLOAD_ELAPSED = "uploadElapsed";
public static final String ISP = "isp";
public static final String INTERFACE_INTERNALIP = "interfaceInternalIp";
public static final String INTERFACE_EXTERNALIP = "interfaceExternalIp";
public static final String RESULT_URL = "resultUrl";
public static final String TRIGGER_TEST = "triggerTest";
public static final String PROPERTY_SERVER_LIST1 = "Server List 1";
public static final String PROPERTY_SERVER_LIST2 = "Server List 2";
public static final String PROPERTY_SERVER_LIST3 = "Server List 3";
public static final String PROPERTY_SERVER_LIST4 = "Server List 4";
public static final String PROPERTY_SERVER_LIST5 = "Server List 5";
public static final String PROPERTY_SERVER_LIST6 = "Server List 6";
public static final String PROPERTY_SERVER_LIST7 = "Server List 7";
public static final String PROPERTY_SERVER_LIST8 = "Server List 8";
public static final String PROPERTY_SERVER_LIST9 = "Server List 9";
public static final String PROPERTY_SERVER_LIST10 = "Server List 10";
public static final Set<String> SUPPORTED_CHANNEL_IDS = Set.of(SERVER, PING_JITTER, PING_LATENCY,
DOWNLOAD_BANDWIDTH, DOWNLOAD_BYTES, DOWNLOAD_ELAPSED, UPLOAD_BANDWIDTH, UPLOAD_BYTES, UPLOAD_ELAPSED, ISP,
INTERFACE_INTERNALIP, INTERFACE_EXTERNALIP, RESULT_URL);
}

View File

@ -0,0 +1,29 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.speedtest.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link SpeedtestConfiguration} class contains fields mapping thing configuration parameters.
*
* @author Brian Homeyer - Initial contribution
*/
@NonNullByDefault
public class SpeedtestConfiguration {
public int refreshInterval = 60;
public String execPath = "";
public String serverID = "";
public String speedTestVersion = "";
}

View File

@ -0,0 +1,539 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.speedtest.internal;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.regex.PatternSyntaxException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.speedtest.internal.dto.ResultContainer;
import org.openhab.binding.speedtest.internal.dto.ResultsContainerServerList;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
/**
* The {@link SpeedtestHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Brian Homeyer - Initial contribution
*/
@NonNullByDefault
public class SpeedtestHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(SpeedtestHandler.class);
private SpeedtestConfiguration config = new SpeedtestConfiguration();
private Gson gson = new Gson();
private static Runtime rt = Runtime.getRuntime();
private long pollingInterval = 60;
private String serverID = "";
private @Nullable ScheduledFuture<?> pollingJob;
public volatile boolean isRunning = false;
public static final String[] SHELL_WINDOWS = new String[] { "cmd" };
public static final String[] SHELL_NIX = new String[] { "sh", "bash", "zsh", "csh" };
private String speedTestCommand = "";
private static volatile OS os = OS.NOT_SET;
private static final Object LOCK = new Object();
private String pingJitter = "";
private String pingLatency = "";
private String downloadBandwidth = "";
private String downloadBytes = "";
private String downloadElapsed = "";
private String uploadBandwidth = "";
private String uploadBytes = "";
private String uploadElapsed = "";
private String isp = "";
private String interfaceInternalIp = "";
private String interfaceExternalIp = "";
private String resultUrl = "";
private String server = "";
/**
* Contains information about which operating system openHAB is running on.
*/
public enum OS {
WINDOWS,
LINUX,
MAC,
SOLARIS,
UNKNOWN,
NOT_SET
}
public SpeedtestHandler(Thing thing) {
super(thing);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
logger.debug("handleCommand channel: {} command: {}", channelUID, command);
String ch = channelUID.getId();
if (command instanceof RefreshType) {
if (!server.isBlank()) {
updateChannels();
}
return;
}
if (ch.equals(SpeedtestBindingConstants.TRIGGER_TEST)) {
if (command instanceof OnOffType) {
if (command == OnOffType.ON) {
getSpeed();
updateState(channelUID, OnOffType.OFF);
}
}
}
}
@Override
public void initialize() {
config = getConfigAs(SpeedtestConfiguration.class);
pollingInterval = config.refreshInterval;
serverID = config.serverID;
if (!config.execPath.isEmpty()) {
speedTestCommand = config.execPath;
} else {
switch (getOperatingSystemType()) {
case WINDOWS:
speedTestCommand = "";
break;
case LINUX:
case MAC:
case SOLARIS:
speedTestCommand = "/usr/bin/speedtest";
break;
default:
speedTestCommand = "";
}
}
updateStatus(ThingStatus.UNKNOWN);
if (!checkConfig(speedTestCommand)) { // check the config
return;
}
if (!getSpeedTestVersion()) {
return;
}
getServerList();
updateStatus(ThingStatus.ONLINE);
isRunning = true;
onUpdate(); // Setup the scheduler
}
/**
* This is called to start the refresh job and also to reset that refresh job when a config change is done.
*/
private void onUpdate() {
logger.debug("Polling Interval Set: {}", pollingInterval);
if (pollingInterval > 0) {
ScheduledFuture<?> pollingJob = this.pollingJob;
if (pollingJob == null || pollingJob.isCancelled()) {
this.pollingJob = scheduler.scheduleWithFixedDelay(pollingRunnable, 0, pollingInterval,
TimeUnit.MINUTES);
}
}
}
@Override
public void dispose() {
logger.debug("Disposing Speedtest Handler Thing");
isRunning = false;
ScheduledFuture<?> pollingJob = this.pollingJob;
if (pollingJob != null) {
pollingJob.cancel(true);
this.pollingJob = null;
}
}
/**
* Called when this thing gets it's configuration changed.
*/
@Override
public void thingUpdated(Thing thing) {
dispose();
this.thing = thing;
initialize();
}
/**
* Polling event used to get speed data from speedtest
*/
private Runnable pollingRunnable = () -> {
try {
getSpeed();
} catch (Exception e) {
logger.warn("An exception occurred while running Speedtest: '{}'", e.getMessage());
updateStatus(ThingStatus.OFFLINE);
}
};
/**
* Gets the version information from speedtest, this is really for debug in the event they change things
*/
private boolean getSpeedTestVersion() {
String versionString = doExecuteRequest(" -V", String.class);
if ((versionString != null) && !versionString.isEmpty()) {
int newLI = versionString.indexOf(System.lineSeparator());
String versionLine = versionString.substring(0, newLI);
if (versionString.indexOf("Speedtest by Ookla") > -1) {
logger.debug("Speedtest Version: {}", versionLine);
return true;
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.configuration-error.type");
return false;
}
}
return false;
}
/**
* Get the server list from the speedtest command. Update the properties of the thing so the user
* can see the list of servers closest to them.
*/
private boolean getServerList() {
String serverListTxt = "";
ResultsContainerServerList tmpCont = doExecuteRequest(" -f json -L", ResultsContainerServerList.class);
if (tmpCont != null) {
int id = 1;
Map<String, String> properties = editProperties();
for (ResultsContainerServerList.Server server : tmpCont.servers) {
serverListTxt = "ID: " + server.id.toString() + ", " + server.host + " (" + server.location + ")";
switch (id) {
case 1:
properties.replace(SpeedtestBindingConstants.PROPERTY_SERVER_LIST1, serverListTxt);
break;
case 2:
properties.replace(SpeedtestBindingConstants.PROPERTY_SERVER_LIST2, serverListTxt);
break;
case 3:
properties.replace(SpeedtestBindingConstants.PROPERTY_SERVER_LIST3, serverListTxt);
break;
case 4:
properties.replace(SpeedtestBindingConstants.PROPERTY_SERVER_LIST4, serverListTxt);
break;
case 5:
properties.replace(SpeedtestBindingConstants.PROPERTY_SERVER_LIST5, serverListTxt);
break;
case 6:
properties.replace(SpeedtestBindingConstants.PROPERTY_SERVER_LIST6, serverListTxt);
break;
case 7:
properties.replace(SpeedtestBindingConstants.PROPERTY_SERVER_LIST7, serverListTxt);
break;
case 8:
properties.replace(SpeedtestBindingConstants.PROPERTY_SERVER_LIST8, serverListTxt);
break;
case 9:
properties.replace(SpeedtestBindingConstants.PROPERTY_SERVER_LIST9, serverListTxt);
break;
case 10:
properties.replace(SpeedtestBindingConstants.PROPERTY_SERVER_LIST10, serverListTxt);
break;
}
id++;
}
updateProperties(properties);
}
return false;
}
/**
* Get the speedtest data and convert it from JSON and send it to update the channels.
*/
private void getSpeed() {
logger.debug("Getting Speed Measurement");
String postCommand = "";
if (!serverID.isBlank()) {
postCommand = " -s " + serverID;
}
ResultContainer tmpCont = doExecuteRequest(" -f json --accept-license --accept-gdpr" + postCommand,
ResultContainer.class);
if (tmpCont != null) {
if (tmpCont.getType().equals("result")) {
pingJitter = tmpCont.getPing().getJitter();
pingLatency = tmpCont.getPing().getLatency();
downloadBandwidth = tmpCont.getDownload().getBandwidth();
downloadBytes = tmpCont.getDownload().getBytes();
downloadElapsed = tmpCont.getDownload().getElapsed();
uploadBandwidth = tmpCont.getUpload().getBandwidth();
uploadBytes = tmpCont.getUpload().getBytes();
uploadElapsed = tmpCont.getUpload().getElapsed();
isp = tmpCont.getIsp();
interfaceInternalIp = tmpCont.getInterface().getInternalIp();
interfaceExternalIp = tmpCont.getInterface().getExternalIp();
resultUrl = tmpCont.getResult().getUrl();
server = tmpCont.getServer().getName() + " (" + tmpCont.getServer().getId().toString() + ") "
+ tmpCont.getServer().getLocation();
updateChannels();
}
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.configuration-error.results");
}
}
private @Nullable <T> T doExecuteRequest(String arguments, Class<T> type) {
try {
String dataOut = executeCmd(speedTestCommand + arguments);
if (type != String.class) {
@Nullable
T obj = gson.fromJson(dataOut, type);
return obj;
} else {
@SuppressWarnings("unchecked")
T obj = (T) dataOut;
return obj;
}
} catch (Exception e) {
logger.debug("Exception: {}", e.getMessage());
}
return null;
}
/**
* Update the channels
*/
private void updateChannels() {
logger.debug("Updating channels");
State newState = new QuantityType<>(Double.parseDouble(pingJitter) / 1000.0, Units.SECOND);
logger.debug("pingJitter: {}", newState);
updateState(new ChannelUID(getThing().getUID(), SpeedtestBindingConstants.PING_JITTER), newState);
newState = new QuantityType<>(Double.parseDouble(pingLatency) / 1000.0, Units.SECOND);
logger.debug("pingLatency: {}", newState);
updateState(new ChannelUID(getThing().getUID(), SpeedtestBindingConstants.PING_LATENCY), newState);
newState = new QuantityType<>(Double.parseDouble(downloadBandwidth) / 125000.0, Units.MEGABIT_PER_SECOND);
logger.debug("downloadBandwidth: {}", newState);
updateState(new ChannelUID(getThing().getUID(), SpeedtestBindingConstants.DOWNLOAD_BANDWIDTH), newState);
newState = new QuantityType<>(Double.parseDouble(downloadBytes), Units.BYTE);
logger.debug("downloadBytes: {}", newState);
updateState(new ChannelUID(getThing().getUID(), SpeedtestBindingConstants.DOWNLOAD_BYTES), newState);
newState = new QuantityType<>(Double.parseDouble(downloadElapsed) / 1000.0, Units.SECOND);
logger.debug("downloadElapsed: {}", newState);
updateState(new ChannelUID(getThing().getUID(), SpeedtestBindingConstants.DOWNLOAD_ELAPSED), newState);
newState = new QuantityType<>(Double.parseDouble(uploadBandwidth) / 125000.0, Units.MEGABIT_PER_SECOND);
logger.debug("uploadBandwidth: {}", newState);
updateState(new ChannelUID(getThing().getUID(), SpeedtestBindingConstants.UPLOAD_BANDWIDTH), newState);
newState = new QuantityType<>(Double.parseDouble(uploadBytes), Units.BYTE);
logger.debug("uploadBytes: {}", newState);
updateState(new ChannelUID(getThing().getUID(), SpeedtestBindingConstants.UPLOAD_BYTES), newState);
newState = new QuantityType<>(Double.parseDouble(uploadElapsed) / 1000.0, Units.SECOND);
logger.debug("uploadElapsed: {}", newState);
updateState(new ChannelUID(getThing().getUID(), SpeedtestBindingConstants.UPLOAD_ELAPSED), newState);
updateState(new ChannelUID(getThing().getUID(), SpeedtestBindingConstants.INTERFACE_EXTERNALIP),
new StringType(String.valueOf(interfaceExternalIp)));
updateState(new ChannelUID(getThing().getUID(), SpeedtestBindingConstants.INTERFACE_INTERNALIP),
new StringType(String.valueOf(interfaceInternalIp)));
updateState(new ChannelUID(getThing().getUID(), SpeedtestBindingConstants.ISP),
new StringType(String.valueOf(isp)));
updateState(new ChannelUID(getThing().getUID(), SpeedtestBindingConstants.RESULT_URL),
new StringType(String.valueOf(resultUrl)));
updateState(new ChannelUID(getThing().getUID(), SpeedtestBindingConstants.SERVER),
new StringType(String.valueOf(server)));
}
/**
* Checks to make sure the executable for speedtest is valid
*/
public boolean checkConfig(String execPath) {
File file = new File(execPath);
if (!checkFileExists(file)) { // Check if entered path exists
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.configuration-error.file");
return false;
}
if (!checkFileExecutable(file)) { // Check if speedtest is executable
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.configuration-error.exec");
return false;
}
return true;
}
/**
* Executes a given command and returns back the String data of stdout.
*/
private String executeCmd(String commandLine) {
int timeOut = 60000;
String[] cmdArray;
String[] shell;
logger.debug("Passing to shell for parsing command.");
switch (getOperatingSystemType()) {
case WINDOWS:
shell = SHELL_WINDOWS;
logger.debug("OS: WINDOWS ({})", getOperatingSystemName());
cmdArray = createCmdArray(shell, "/c", commandLine);
break;
case LINUX:
case MAC:
case SOLARIS:
// assume sh is present, should all be POSIX-compliant
shell = SHELL_NIX;
logger.debug("OS: *NIX ({})", getOperatingSystemName());
cmdArray = createCmdArray(shell, "-c", commandLine);
break;
default:
logger.debug("OS: Unknown ({})", getOperatingSystemName());
return "";
}
if (cmdArray.length == 0) {
logger.debug("Empty command received, not executing");
return "";
}
logger.debug("The command to be executed will be '{}'", Arrays.asList(cmdArray));
Process proc;
try {
proc = rt.exec(cmdArray);
} catch (Exception e) {
logger.debug("An exception occurred while executing '{}': '{}'", Arrays.asList(cmdArray), e.getMessage());
return "";
}
StringBuilder outputBuilder = new StringBuilder();
try (InputStreamReader isr = new InputStreamReader(proc.getInputStream());
BufferedReader br = new BufferedReader(isr)) {
String line;
while ((line = br.readLine()) != null) {
outputBuilder.append(line).append(System.lineSeparator());
logger.debug("Exec [{}]: '{}'", "OUTPUT", line);
}
} catch (IOException e) {
logger.warn("An exception occurred while reading the stdout when executing '{}': '{}'", commandLine,
e.getMessage());
}
boolean exitVal = false;
try {
exitVal = proc.waitFor(timeOut, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
logger.debug("An exception occurred while waiting for the process ('{}') to finish: '{}'", commandLine,
e.getMessage());
}
if (!exitVal) {
logger.debug("Forcibly termininating the process ('{}') after a timeout of {} ms", commandLine, timeOut);
proc.destroyForcibly();
}
return outputBuilder.toString();
}
/**
* Transforms the command string into an array.
* Either invokes the shell and passes using the "c" option
* or (if command already starts with one of the shells) splits by space.
*
* @param shell (path), picks to first one to execute the command
* @param cOption "c"-option string
* @param commandLine to execute
* @return command array
*/
protected String[] createCmdArray(String[] shell, String cOption, String commandLine) {
boolean startsWithShell = false;
for (String sh : shell) {
if (commandLine.startsWith(sh + " ")) {
startsWithShell = true;
break;
}
}
if (!startsWithShell) {
return new String[] { shell[0], cOption, commandLine };
} else {
logger.debug("Splitting by spaces");
try {
String[] splitCmd = commandLine.split(" ");
return splitCmd;
} catch (PatternSyntaxException e) {
logger.warn("An exception occurred while splitting '{}': '{}'", commandLine, e.getMessage());
return new String[] {};
}
}
}
public static OS getOperatingSystemType() {
synchronized (LOCK) {
if (os == OS.NOT_SET) {
String operSys = System.getProperty("os.name");
if (operSys == null) {
os = OS.UNKNOWN;
} else {
operSys = operSys.toLowerCase();
if (operSys.contains("win")) {
os = OS.WINDOWS;
} else if (operSys.contains("nix") || operSys.contains("nux") || operSys.contains("aix")) {
os = OS.LINUX;
} else if (operSys.contains("mac")) {
os = OS.MAC;
} else if (operSys.contains("sunos")) {
os = OS.SOLARIS;
} else {
os = OS.UNKNOWN;
}
}
}
}
return os;
}
public static String getOperatingSystemName() {
String osname = System.getProperty("os.name");
return osname != null ? osname : "unknown";
}
public boolean checkFileExists(File file) {
return file.exists() && !file.isDirectory();
}
public boolean checkFileExecutable(File file) {
return file.canExecute();
}
}

View File

@ -0,0 +1,56 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.speedtest.internal;
import static org.openhab.binding.speedtest.internal.SpeedtestBindingConstants.*;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Component;
/**
* The {@link SpeedtestHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Brian Homeyer - Initial contribution
*/
@NonNullByDefault
@Component(configurationPid = "binding.speedtest", service = ThingHandlerFactory.class)
public class SpeedtestHandlerFactory extends BaseThingHandlerFactory {
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_SPEEDTEST);
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (THING_TYPE_SPEEDTEST.equals(thingTypeUID)) {
return new SpeedtestHandler(thing);
}
return null;
}
}

View File

@ -0,0 +1,402 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.speedtest.internal.dto;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
/**
* The {@link ResultContainer} class defines a container for Speedtest results.
*
* @author Brian Homeyer - Initial contribution
*/
public class ResultContainer {
@SerializedName("type")
@Expose
private String type;
@SerializedName("timestamp")
@Expose
private String timestamp;
@SerializedName("ping")
@Expose
private Ping ping;
@SerializedName("download")
@Expose
private Download download;
@SerializedName("upload")
@Expose
private Upload upload;
@SerializedName("packetLoss")
@Expose
private Double packetLoss;
@SerializedName("isp")
@Expose
private String isp;
@SerializedName("interface")
@Expose
private Interface networkInterface;
@SerializedName("server")
@Expose
private Server server;
@SerializedName("result")
@Expose
private Result result;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getTimestamp() {
return timestamp;
}
public void setTimestamp(String timestamp) {
this.timestamp = timestamp;
}
public Ping getPing() {
return ping;
}
public void setPing(Ping ping) {
this.ping = ping;
}
public Download getDownload() {
return download;
}
public void setDownload(Download download) {
this.download = download;
}
public Upload getUpload() {
return upload;
}
public void setUpload(Upload upload) {
this.upload = upload;
}
public Double getPacketLoss() {
return packetLoss;
}
public void setPacketLoss(Double packetLoss) {
this.packetLoss = packetLoss;
}
public String getIsp() {
return isp;
}
public void setIsp(String isp) {
this.isp = isp;
}
public Interface getInterface() {
return networkInterface;
}
public void setInterface(Interface networkInterface) {
this.networkInterface = networkInterface;
}
public Server getServer() {
return server;
}
public void setServer(Server server) {
this.server = server;
}
public Result getResult() {
return result;
}
public void setResult(Result result) {
this.result = result;
}
public class Download {
@SerializedName("bandwidth")
@Expose
private String bandwidth;
@SerializedName("bytes")
@Expose
private String bytes;
@SerializedName("elapsed")
@Expose
private String elapsed;
public String getBandwidth() {
return bandwidth;
}
public void setBandwidth(String bandwidth) {
this.bandwidth = bandwidth;
}
public String getBytes() {
return bytes;
}
public void setBytes(String bytes) {
this.bytes = bytes;
}
public String getElapsed() {
return elapsed;
}
public void setElapsed(String elapsed) {
this.elapsed = elapsed;
}
}
public class Interface {
@SerializedName("internalIp")
@Expose
private String internalIp;
@SerializedName("name")
@Expose
private String name;
@SerializedName("macAddr")
@Expose
private String macAddr;
@SerializedName("isVpn")
@Expose
private Boolean isVpn;
@SerializedName("externalIp")
@Expose
private String externalIp;
public String getInternalIp() {
return internalIp;
}
public void setInternalIp(String internalIp) {
this.internalIp = internalIp;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getMacAddr() {
return macAddr;
}
public void setMacAddr(String macAddr) {
this.macAddr = macAddr;
}
public Boolean getIsVpn() {
return isVpn;
}
public void setIsVpn(Boolean isVpn) {
this.isVpn = isVpn;
}
public String getExternalIp() {
return externalIp;
}
public void setExternalIp(String externalIp) {
this.externalIp = externalIp;
}
}
public class Ping {
@SerializedName("jitter")
@Expose
private String jitter;
@SerializedName("latency")
@Expose
private String latency;
public String getJitter() {
return jitter;
}
public void setJitter(String jitter) {
this.jitter = jitter;
}
public String getLatency() {
return latency;
}
public void setLatency(String latency) {
this.latency = latency;
}
}
public class Result {
@SerializedName("id")
@Expose
private String id;
@SerializedName("url")
@Expose
private String url;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
}
public class Server {
@SerializedName("id")
@Expose
private Integer id;
@SerializedName("name")
@Expose
private String name;
@SerializedName("location")
@Expose
private String location;
@SerializedName("country")
@Expose
private String country;
@SerializedName("host")
@Expose
private String host;
@SerializedName("port")
@Expose
private Integer port;
@SerializedName("ip")
@Expose
private String ip;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public Integer getPort() {
return port;
}
public void setPort(Integer port) {
this.port = port;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
}
public class Upload {
@SerializedName("bandwidth")
@Expose
private String bandwidth;
@SerializedName("bytes")
@Expose
private String bytes;
@SerializedName("elapsed")
@Expose
private String elapsed;
public String getBandwidth() {
return bandwidth;
}
public void setBandwidth(String bandwidth) {
this.bandwidth = bandwidth;
}
public String getBytes() {
return bytes;
}
public void setBytes(String bytes) {
this.bytes = bytes;
}
public String getElapsed() {
return elapsed;
}
public void setElapsed(String elapsed) {
this.elapsed = elapsed;
}
}
}

View File

@ -0,0 +1,59 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.speedtest.internal.dto;
import java.util.List;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
/**
* The {@link ResultsContainerServerList} class defines a container for the Speedtest server list.
*
* @author Brian Homeyer - Initial contribution
*/
public class ResultsContainerServerList {
@SerializedName("type")
@Expose
public String type;
@SerializedName("timestamp")
@Expose
public String timestamp;
@SerializedName("servers")
@Expose
public List<Server> servers = null;
public class Server {
@SerializedName("id")
@Expose
public Integer id;
@SerializedName("name")
@Expose
public String name;
@SerializedName("location")
@Expose
public String location;
@SerializedName("country")
@Expose
public String country;
@SerializedName("host")
@Expose
public String host;
@SerializedName("port")
@Expose
public Integer port;
}
}

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<addon:addon id="speedtest" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:addon="https://openhab.org/schemas/addon/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/addon/v1.0.0 https://openhab.org/schemas/addon-1.0.0.xsd">
<type>binding</type>
<name>Speedtest Binding</name>
<description>Binding for Ookla Speedtest (https://www.speedtest.net/)</description>
</addon:addon>

View File

@ -0,0 +1,64 @@
# add-on
addon.speedtest.name = Speedtest Binding
addon.speedtest.description = Binding for Ookla Speedtest (https://www.speedtest.net/)
# thing types
thing-type.speedtest.speedtest.label = Speedtest Binding
thing-type.speedtest.speedtest.description = Binding for Ookla Speedtest (https://www.speedtest.net/)
# thing types config
thing-type.config.speedtest.speedtest.execPath.label = Executable Path
thing-type.config.speedtest.speedtest.execPath.description = The path of the Ookla Speedtest executable. Linux machines may leave this blank and it defaults to `/usr/bin/speedtest`.
thing-type.config.speedtest.speedtest.group.config-info.label = Speedtest Configuration
thing-type.config.speedtest.speedtest.refreshInterval.label = Refresh Interval
thing-type.config.speedtest.speedtest.refreshInterval.description = How often to test network speed
thing-type.config.speedtest.speedtest.refreshInterval.option.15 = 15 minutes
thing-type.config.speedtest.speedtest.refreshInterval.option.30 = 30 minutes
thing-type.config.speedtest.speedtest.refreshInterval.option.60 = Every Hour
thing-type.config.speedtest.speedtest.refreshInterval.option.360 = Every 6 Hours
thing-type.config.speedtest.speedtest.refreshInterval.option.720 = Every 12 Hours
thing-type.config.speedtest.speedtest.refreshInterval.option.1440 = Once a Day
thing-type.config.speedtest.speedtest.refreshInterval.option.0 = Don't test automatically
thing-type.config.speedtest.speedtest.serverID.label = Server ID
thing-type.config.speedtest.speedtest.serverID.description = Optional: A specific server that shall be used for testing. You can pick the server ID from the "Thing Properties". If this is left blank the best option will be selected by Ookla.
# channel types
channel-type.speedtest.downloadBandwidth.label = Download Bandwidth
channel-type.speedtest.downloadBandwidth.description = Download bandwidth, e.g. in Mbit/s
channel-type.speedtest.downloadBytes.label = Downloaded Bytes
channel-type.speedtest.downloadBytes.description = Amount of data that was used for the last upload bandwidth test
channel-type.speedtest.downloadElapsed.label = Download Elapsed Time
channel-type.speedtest.downloadElapsed.description = Time spent for the last download bandwidth test
channel-type.speedtest.interfaceExternalIp.label = Interface External IP
channel-type.speedtest.interfaceExternalIp.description = IP address of the external interface that was used for the test
channel-type.speedtest.interfaceInternalIp.label = Interface Internal IP
channel-type.speedtest.interfaceInternalIp.description = IP address of the internal interface that was used for the test
channel-type.speedtest.isp.label = ISP
channel-type.speedtest.isp.description = Your Internet Service Provider (ISP) as calculated by Ookla
channel-type.speedtest.pingJitter.label = Ping Jitter
channel-type.speedtest.pingJitter.description = Ping Jitter - the variation in the response time
channel-type.speedtest.pingLatency.label = Ping Latency
channel-type.speedtest.pingLatency.description = Ping Latency - the reaction time of your internet connection
channel-type.speedtest.resultUrl.label = Result URL
channel-type.speedtest.resultUrl.description = The URL to the Speedtest results in HTML on the Ookla webserver
channel-type.speedtest.server.label = Server
channel-type.speedtest.server.description = The remote server that the Speedtest was run against
channel-type.speedtest.triggerTest.label = Trigger Test
channel-type.speedtest.triggerTest.description = Trigger in order to run Speedtest manually
channel-type.speedtest.uploadBandwidth.label = Upload Bandwidth
channel-type.speedtest.uploadBandwidth.description = Upload bandwidth, e.g. in Mbit/s
channel-type.speedtest.uploadBytes.label = Uploaded Bytes
channel-type.speedtest.uploadBytes.description = Amount of data that was used for the last upload bandwidth test
channel-type.speedtest.uploadElapsed.label = Upload Elapsed Time
channel-type.speedtest.uploadElapsed.description = Time spent for the last upload bandwidth test
# thing status
offline.configuration-error.type = Unsupported type of Speedtest recognized, Speedtest CLI tool from Ookla is REQUIRED (https://www.speedtest.net/). Please check installation/configuration.
offline.configuration-error.results = Speedtest is not returning valid results.
offline.configuration-error.file = Speedtest executable not found. Please check configuration.
offline.configuration-error.exec = Speedtest CLI tool not executable. Please check installation/configuration.

View File

@ -0,0 +1,158 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="speedtest"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="speedtest">
<label>Speedtest Binding</label>
<description>Binding for Ookla Speedtest (https://www.speedtest.net/)</description>
<channels>
<channel id="server" typeId="server"/>
<channel id="pingJitter" typeId="pingJitter"/>
<channel id="pingLatency" typeId="pingLatency"/>
<channel id="downloadBandwidth" typeId="downloadBandwidth"/>
<channel id="downloadBytes" typeId="downloadBytes"/>
<channel id="downloadElapsed" typeId="downloadElapsed"/>
<channel id="uploadBandwidth" typeId="uploadBandwidth"/>
<channel id="uploadBytes" typeId="uploadBytes"/>
<channel id="uploadElapsed" typeId="uploadElapsed"/>
<channel id="isp" typeId="isp"/>
<channel id="interfaceInternalIp" typeId="interfaceInternalIp"/>
<channel id="interfaceExternalIp" typeId="interfaceExternalIp"/>
<channel id="resultUrl" typeId="resultUrl"/>
<channel id="triggerTest" typeId="triggerTest"/>
</channels>
<properties>
<property name="Server List 1"></property>
<property name="Server List 2"></property>
<property name="Server List 3"></property>
<property name="Server List 4"></property>
<property name="Server List 5"></property>
<property name="Server List 6"></property>
<property name="Server List 7"></property>
<property name="Server List 8"></property>
<property name="Server List 9"></property>
<property name="Server List 10"></property>
</properties>
<config-description>
<parameter-group name="config-info">
<label>Speedtest Configuration</label>
</parameter-group>
<parameter name="refreshInterval" type="integer" required="true" groupName="config-info">
<label>Refresh Interval</label>
<description>How often to test network speed</description>
<options>
<option value="15">15 minutes</option>
<option value="30">30 minutes</option>
<option value="60">Every Hour</option>
<option value="360">Every 6 Hours</option>
<option value="720">Every 12 Hours</option>
<option value="1440">Once a Day</option>
<option value="0">Don't test automatically</option>
</options>
<default>60</default>
</parameter>
<parameter name="execPath" type="text" required="false" groupName="config-info">
<label>Executable Path</label>
<description>The path of the Ookla Speedtest executable. Linux machines may leave this blank and it defaults to
`/usr/bin/speedtest`.</description>
</parameter>
<parameter name="serverID" type="text" required="false" groupName="config-info">
<label>Server ID</label>
<description>Optional: A specific server that shall be used for testing. You can pick the server ID from the "Thing
Properties". If this is left blank the best option will be selected by Ookla.</description>
</parameter>
</config-description>
</thing-type>
<channel-type id="server">
<item-type>String</item-type>
<label>Server</label>
<description>The remote server that the Speedtest was run against</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="pingJitter" advanced="true">
<item-type>Number:Time</item-type>
<label>Ping Jitter</label>
<description>Ping Jitter - the variation in the response time</description>
<state readOnly="true" pattern="%.2f ms"></state>
</channel-type>
<channel-type id="pingLatency">
<item-type>Number:Time</item-type>
<label>Ping Latency</label>
<description>Ping Latency - the reaction time of your internet connection</description>
<state readOnly="true" pattern="%.2f ms"></state>
</channel-type>
<channel-type id="downloadBandwidth">
<item-type>Number:DataTransferRate</item-type>
<label>Download Bandwidth</label>
<description>Download bandwidth, e.g. in Mbit/s</description>
<state readOnly="true" pattern="%.2f Mbit/s"></state>
</channel-type>
<channel-type id="downloadBytes" advanced="true">
<item-type>Number:DataAmount</item-type>
<label>Downloaded Bytes</label>
<description>Amount of data that was used for the last upload bandwidth test</description>
<state readOnly="true" pattern="%.0f MB"></state>
</channel-type>
<channel-type id="downloadElapsed" advanced="true">
<item-type>Number:Time</item-type>
<label>Download Elapsed Time</label>
<description>Time spent for the last download bandwidth test</description>
<state readOnly="true" pattern="%.2f s"></state>
</channel-type>
<channel-type id="uploadBandwidth">
<item-type>Number:DataTransferRate</item-type>
<label>Upload Bandwidth</label>
<description>Upload bandwidth, e.g. in Mbit/s</description>
<state readOnly="true" pattern="%.2f Mbit/s"></state>
</channel-type>
<channel-type id="uploadBytes" advanced="true">
<item-type>Number:DataAmount</item-type>
<label>Uploaded Bytes</label>
<description>Amount of data that was used for the last upload bandwidth test</description>
<state readOnly="true" pattern="%.0f MB"></state>
</channel-type>
<channel-type id="uploadElapsed" advanced="true">
<item-type>Number:Time</item-type>
<label>Upload Elapsed Time</label>
<description>Time spent for the last upload bandwidth test</description>
<state readOnly="true" pattern="%.2f s"></state>
</channel-type>
<channel-type id="isp">
<item-type>String</item-type>
<label>ISP</label>
<description>Your Internet Service Provider (ISP) as calculated by Ookla</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="interfaceInternalIp" advanced="true">
<item-type>String</item-type>
<label>Interface Internal IP</label>
<description>IP address of the internal interface that was used for the test</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="interfaceExternalIp" advanced="true">
<item-type>String</item-type>
<label>Interface External IP</label>
<description>IP address of the external interface that was used for the test</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="resultUrl">
<item-type>String</item-type>
<label>Result URL</label>
<description>The URL to the Speedtest results in HTML on the Ookla webserver</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="triggerTest" advanced="true">
<item-type>Switch</item-type>
<label>Trigger Test</label>
<description>Trigger in order to run Speedtest manually</description>
</channel-type>
</thing:thing-descriptions>

View File

@ -358,6 +358,7 @@
<module>org.openhab.binding.sonyaudio</module>
<module>org.openhab.binding.sonyprojector</module>
<module>org.openhab.binding.souliss</module>
<module>org.openhab.binding.speedtest</module>
<module>org.openhab.binding.spotify</module>
<module>org.openhab.binding.squeezebox</module>
<module>org.openhab.binding.surepetcare</module>