# Speedtest CLI by Ookla - Internet Up-/Downlink Measurement Integration

**URL:** <https://community.openhab.org/t/speedtest-cli-by-ookla-internet-up-downlink-measurement-integration/94447>\
**Category:** Tutorials & Examples\
**Tags:** exec, openhab2, speedtest\
**Created:** [March 12, 2020, 1:02pm UTC](https://community.openhab.org/t/speedtest-cli-by-ookla-internet-up-downlink-measurement-integration/94447 "2020-03-12T13:02:02Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![Bredmich](https://community.openhab.org/user_avatar/community.openhab.org/bredmich/32/30457_2.png) [@Bredmich](https://community.openhab.org/u/Bredmich)\
**Post date:** [March 12, 2020, 1:02pm UTC](https://community.openhab.org/t/speedtest-cli-by-ookla-internet-up-downlink-measurement-integration/94447/1 "2020-03-12T13:02:02Z")

</div>

Based on the [solution](https://community.openhab.org/t/speedtest-cli-internet-up-downlink-measurement-integration/7611) from ThomDietrich i made a migration to the new official Speedtest CLI by Ookla.

## Update:

**I´m not using this implementation anymore before @bhomeyer made a [Speedtest binding](https://community.openhab.org/t/oh3-new-binding-speedtest/114242) that is using the Speedtest CLI by Ookla.**

## Why use the Speedtest CLI by Ookla instead of the speedtest-cli by Sivel?

- The speedtest-cli by Sivel uses very old technology from Ookla that is outdated
- The maximum speed of the speedtest-cli is limited
- The speedtest-cli is heavily based on the performance of the host system
- The Speedtest CLI by Ookla exports clean JSON to make it machine readable

## What functionality does the Speedtest CLI by Ookla offers?

- Fully supported by Ookla with the latest speedtest technology for best results
- Output of a timestamp instead of the own timestamp by openHAB
- Output of Jitter and Ping
- Output of Bandwidth, transferred bytes and elapsed time for Down-/Upload
- Output of the used interface with Internal/External IP, MAC Address and interface name
- Output of the used Server and location
- Output of the result id and result URL

## What functionality does the integration offers?

- Execute a speedtest time based or directly
- Show summarized results of the speedtest
- Export the data to a database and visualize with Grafana
- Start the speedtest from another oH instance or from a rule

 ![IMG_7480.PNG](https://community-openhab-org.s3.dualstack.eu-central-1.amazonaws.com/original/3X/d/b/dbb5aa4ea36f742daaab5d33dad69e534692a18c.jpeg)

# Requirements

- Uninstall the speedtest-cli if already installed
  - This only applies when switching form the old speedtest-cli

- ~~Install the [Exec Binding](http://docs.openhab.org/addons/bindings/exec/readme.html)~~ - we don´t need this anymore because `executeCommandLine` works without the Exec binding
  - ~~Exec Binding 2.5.2: You need to add the full path to your [exec.whiteliste]~~([Security limitations in 2.5.2 exec binding](https://community.openhab.org/t/security-limitations-in-2-5-2-exec-binding/93191)) file!

- Install the [JSONPatch Transformation](https://www.openhab.org/addons/transformations/jsonpath/)
- Install the Speedtest CLI by Ookla
- Test the speedtest before you continue
- Build the rules and test

# Linux

## Uninstalling old packages

### Uninstall the speedtest-cli

#### Only applies if you had it installed before, it´s not preinstalled on Debian or raspbian

I had some problems to completely uninstall the speedtest-cli and there´s no real guide on how to uninstall.

```yaml
# If you installed the speedtest-cli with apt-get
sudo apt-get remove --auto-remove speedtest-cli
# If you installed the speedtest-cli with pip
sudo pip uninstall speedtest-cli

# Check for speedtest and speedtest-cli in /usr/local/bin/ and remove them
sudo rm /usr/local/bin/speedtest
sudo rm /usr/local/bin/speedtest-cli

# Check for speedtest or speedtest-cli parts in /usr/local/lib/python2.7/dist-packages and delete them
# Depending on the python version installed

# Just to be sure
apt-get update
reboot

```

### Uninstalling the old `bintray` based package if you used that before

```yaml
## If migrating from prior bintray install instructions please first...
sudo rm /etc/apt/sources.list.d/speedtest.list
sudo apt-get update
sudo apt-get remove speedtest
## Other non-official binaries will conflict with Speedtest CLI
# Example how to remove using apt-get
sudo apt-get remove speedtest-cli

```

### Install the speedtest package

Based on the [official install](https://www.speedtest.net/apps/cli) options from Ookla.

**Install on Debian Buster**

```yaml
curl -s https://install.speedtest.net/app/cli/install.deb.sh | sudo bash
sudo apt-get install speedtest

```

**Install on raspbian (Buster)**  
You won´t be able to use the above `install.deb.sh` because it´s not meant for raspbian  
So we need to add the `ookla_speedtest-cli.list` [manually](https://packagecloud.io/ookla/speedtest-cli/install#manual-deb) and modify the source info

```yaml
## Check if you´re running a Buster based raspbian
cat /etc/issue
## If you get "Raspbian GNU/Linux 10" you´re running a Buster based raspbian

## Start with a refresh of the package cache
sudo apt-get update
## Install the debian keyring so official repositories will be verified
sudo apt-get install debian-archive-keyring
## Ensure the required tools are installed
sudo apt-get install curl gnupg apt-transport-https
## Now we install the GPG key that is used to sign repository metadata
curl -L https://packagecloud.io/ookla/speedtest-cli/gpgkey | sudo apt-key add -
## Create the repository file and add the following lines
sudo nano /etc/apt/sources.list.d/ookla_speedtest-cli.list

```

`ookla_speedtest-cli.list`

```auto
deb https://packagecloud.io/ookla/speedtest-cli/debian/ buster main
deb-src https://packagecloud.io/ookla/speedtest-cli/debian/ buster main

```

```yaml
## Refresh the package cache again
sudo apt-get update
## Install the speedtest package
sudo apt-get install speedtest
## Start the speedtest as the openhab user and accept the license
speedtest -u openhab speedtest
## Start the speedtest again and make sure you get the output as JSON
speedtest -u openhab speedtest -f json

```

```auto
openhabian@openHAB:~ $ speedtest -f json
{"type":"result","timestamp":"2021-08-08T10:06:01Z","ping":{"jitter":0.032000000000000001,"latency":11.861000000000001},"download":{"bandwidth":39311633,"bytes":285433920,"elapsed":7305},"upload":{"bandwidth":6421818,"bytes":23171040,"elapsed":3600},"isp":"<ISP>","interface":{"internalIp":"<Your-internal-IP>","name":"eth0","macAddr":"<MAC-address>","isVpn":false,"externalIp":"<Your-external-IP>"},"server":{"id":<server-Id>,"name":"Spacken.net","location":"Hagen","country":"Germany","host":"speedtest.spacken.net","port":8080,"ip":"5.9.151.176"},"result":{"id":"<result-Id>","url":"<result-URL>"}}

```

Thanks @StephenGray for the modified raspbian instructions

# Windows

### Download and install the speedtest.exe

Thanks to @KidSquid for the Windows details!

Please download the latest Speedtest CLI from the Ookla [website](https://www.speedtest.net/apps/cli):

 ![image](https://community-openhab-org.s3.dualstack.eu-central-1.amazonaws.com/original/3X/9/c/9c7653a4b0a2769432ae8d7eb1db6b9b44f40725.png)

Choose or create a new folder where your Speedtest CLI runs and place the downloaded `speedtest.exe` in this folder.  
Example: `D:/Tools/Speedtest/`

Please run the speedtest from your commandline and use the user that runs openHAB before continuing to the next steps!  
You may need to accept the GDPR and license when running `speedtest` for the first time.  
This should be saved after the first execution.

### Test the Speedtest CLI

Just type `speedtest` and watch the speedtest working.  
_Note: If you´re not using the openhab user, please use `sudo -u openhab speedtest`_  
You may need to accept the GDPR and license when running `speedtest` for the first time.  
This should be saved after the first execution.

```auto
[13:02:28] openhab@oH-Speedtest:/$ speedtest

   Speedtest by Ookla

     Server: InoHost - Düsseldorf (id = 21467)
        ISP: Unitymedia
    Latency: 9.34 ms (2.18 ms jitter)
   Download: 578.42 Mbps (data used: 621.6 MB)                               
     Upload: 52.74 Mbps (data used: 31.5 MB)                               
Packet Loss: 0.0%
 Result URL: https://www.speedtest.net/result/c/###

```

And start another test with the argument `-f json` or `-f json-pretty` to check if everything works fine.  
The result should look like the following example and not start with

```auto
{"type":"log","timestamp":"2020-03-12T16:02:56Z","message":"Error: [-2] Protocol error: Did not receive HELLO","level":"error"}

```

as this would stop the rule from working.  
Maybe you need to define a server with `-s <ID>`, to get a list of nearly server just use `-L`  
I had to test some servers to find one that gives me reliable results.

```auto
[13:04:56] openhab@oH-Speedtest:/$ speedtest -f json-pretty
{
    "type": "result",
    "timestamp": "2020-03-12T12:05:14Z",
    "ping": {
        "jitter": 0.91000000000000003,
        "latency": 8.4429999999999996
    },
    "download": {
        "bandwidth": 114371030,
        "bytes": 1202757605,
        "elapsed": 10815
    },
    "upload": {
        "bandwidth": 6385055,
        "bytes": 25651164,
        "elapsed": 4026
    },
    "packetLoss": 0,
    "isp": "Unitymedia",
    "interface": {
        "internalIp": "###",
        "name": "###",
        "macAddr": "###",
        "isVpn": false,
        "externalIp": "###"
    },
    "server": {
        "id": 17392,
        "name": "myLoc managed IT AG",
        "location": "Dusseldorf",
        "country": "Germany",
        "host": "ookla.myloc.de",
        "port": 8080,
        "ip": "2001:4ba1:e001:103::1"
    },
    "result": {
        "id": "###",
        "url": "###"
    }
}

```

## General information

As you can see my speedtest changed the server between the two tests and that results in two different bandwiths for my download.  
Another thing you can see is the different output format for the bandwith.  
While the human-readable output shows the bandwidth as Mbps the JSON output just shows the bandwidth in bits. This output can´t be changed and we need to divide the result by 125000 to get the bandwidth in Mbps.

# openHAB files

### Icons

 ![Bildschirmfoto 2020-03-12 um 13.19.38](https://community-openhab-org.s3.dualstack.eu-central-1.amazonaws.com/original/3X/0/6/06266738d732ab368a6f76e6fa537b13ce93e237.png)  
I made an icon set that fits better into my openHAB sitemaps and you´re free to use it: [Download](https://www.dropbox.com/s/mdrf43of05c43ak/Speedtest%20Icons.zip?dl=0)  
Extract all icons to the icons folder `$OPENHAB_CONF/icons/classic/`

### Items (speedtest.items)

```csv
Group gSpeedtest <"speedtest">
Group gSpeedChart
String SpeedtestCharts

String SpeedtestSummary "Speedtest [%s]" <"speedtest_summary"> (gSpeedtest, gPersist)
Number SpeedtestResultPing "Ping [%.3f ms]" <"speedtest_ping"> (gSpeedtest, gSpeedChart)
Number SpeedtestResultDown "Download [%.2f Mbit/s]" <"speedtest_download"> (gSpeedtest, gSpeedChart)
Number SpeedtestResultUp "Upload [%.2f Mbit/s]" <"speedtest_upload"> (gSpeedtest, gSpeedChart)
String SpeedtestRunning "Speedtest running ... [%s]" <"speedtest_run"> (gSpeedtest)
Switch SpeedtestRerun "Manuell starten" <"speedtest_reload"> (gSpeedtest)
DateTime SpeedtestResultDate "Last Run [%1$td.%1$tm.%1$tY, %1$tH:%1$tM]" <"speedtest_date"> (gSpeedtest, gPersist)
String SpeedtestResultError "Error Message [%s]" <"speedtest_error"> (gSpeedtest, gPersist)

String SpeedtestResultImage "Bild"

```

### Sitemap (part of my default.sitemap)

```php
			Text item=SpeedtestSummary icon="speedtest"
			{
				Frame label="Ergebnisse"
				{
					Text item=SpeedtestResultDown
					Text item=SpeedtestResultUp
					Text item=SpeedtestResultPing
				}
				Frame label="Steuerung"
				{
					Text item=SpeedtestResultDate
					Text item=SpeedtestRunning label="Speedtest [%s]" visibility=[SpeedtestRunning != "-"]
					Text item=SpeedtestResultError visibility=[SpeedtestRunning == "Fehler"]
					Switch item=SpeedtestRerun mappings=[ON="Start"]
				}
				Frame label="Statistik"
				{
					Image label="Letzte Messung" item=SpeedtestResultImage
                                        Webview url="/static/chart.html" height=12 icon="speedtest_summary"
				}
			}

```

I´m using [InfluxDB+Grafana](https://community.openhab.org/t/influxdb-grafana-persistence-and-graphing/13761) to visualize the results and have them stored to have some proofs for my ISP when the speed is far under the agreed bandwidth.

 ![Bildschirmfoto 2020-03-12 um 13.45.16](https://community-openhab-org.s3.dualstack.eu-central-1.amazonaws.com/original/3X/e/0/e0ab9ed705ef809123b3e431f3a671c9061160f8.png)

### Linux rule file (speedtest.rules)

```php
val String ruleId = "Speedtest"
val Number calc = 125000 // Converting from bits to Mbits

rule "Speedtest init"

when

    System started

then

    createTimer(now.plusSeconds(195))
    [|
        if(SpeedtestRerun.state == NULL)
        {
            SpeedtestRerun.postUpdate(OFF)
        }

        if(SpeedtestRunning.state == NULL)
        {
            SpeedtestRunning.postUpdate("-")
        }

        if(SpeedtestSummary.state == NULL || SpeedtestSummary.state == "")
        {
            SpeedtestSummary.postUpdate("⁉ (unbekannt)")
        }
    ]

end

rule "Speedtest"

when

    Time cron "0 0/15 * * * ?" or
    Item SpeedtestRerun changed from OFF to ON or
    Item SpeedtestRerun received command ON

then

    //logInfo(ruleId, "--> speedtest executed...")
    SpeedtestRunning.postUpdate("Messung läuft...")

    // execute the script, you may have to change the path depending on your system
    // Please use -f json and not -f json-pretty
    // openHAB 3:
    var speedtestCliOutput = executeCommandLine(Duration.ofSeconds(120), "speedtest", "-f", "json")
    // openHAB 2.5:
    //val speedtestExecute = "speedtest -f json"
    //var speedtestCliOutput = executeCommandLine(speedtestExecute, 120*1000)
    
    // for debugging:
    // var String speedtestCliOutput = "Ping: 43.32 ms\nDownload: 21.64 Mbit/s\nUpload: 4.27 Mbit/s"
    //logInfo(ruleId, "--> speedtest output:\n" + speedtestCliOutput + "\n\n")
    SpeedtestRunning.postUpdate("Datenauswertung...")

    // starts off with a fairly simple error check, should be enough to catch all problems I can think of
    if (speedtestCliOutput.startsWith("{\"type\":\"result\",") && speedtestCliOutput.endsWith("}}"))
    {
        var ping = Float::parseFloat(transform("JSONPATH", "$.ping.latency", speedtestCliOutput))
        SpeedtestResultPing.postUpdate(ping)

        var float down = Float::parseFloat(transform("JSONPATH", "$.download.bandwidth", speedtestCliOutput))
        down = (down / calc)
        SpeedtestResultDown.postUpdate(down)

        var float up = Float::parseFloat(transform("JSONPATH", "$.upload.bandwidth", speedtestCliOutput))
        up = (up / calc)
        SpeedtestResultUp.postUpdate(up)

        var String url = transform("JSONPATH", "$.result.url", speedtestCliOutput)
        val img = url + ".png"
        SpeedtestResultImage.postUpdate(img)

        SpeedtestSummary.postUpdate(String::format("ᐁ %.1f Mbit/s ᐃ %.1f Mbit/s (%.0f ms)", down, up, ping))

        SpeedtestRunning.postUpdate("-")

        // update timestamp for last execution
        val DateTimeType ResultDate = DateTimeType.valueOf(transform("JSONPATH", "$.timestamp", speedtestCliOutput))
        SpeedtestResultDate.postUpdate(ResultDate)
    }
    else
    {
        SpeedtestResultPing.postUpdate(0)
        SpeedtestResultDown.postUpdate(0)
        SpeedtestResultUp.postUpdate(0)
        SpeedtestSummary.postUpdate("(unbekannt)")
        SpeedtestRunning.postUpdate("Fehler")

        logError(ruleId, "--> speedtest failed. Output:\n" + speedtestCliOutput + "\n\n")
    }

    SpeedtestRerun.postUpdate(OFF)

end

```

The `executeCommandLine` action has a different syntax in oH 3, see [Actions](https://www.openhab.org/docs/configuration/actions.html#exec-actions).  
I added the oH 3 line and only commented out the oH 2.5 line.

### Windows rule file (speedtest.rules)

Please check the path on line 47 and change it to match your system.

```auto
val String ruleId = "Speedtest"
val Number calc = 125000 // Converting from bits to Mbits

rule "Speedtest init"

when

    System started

then

    createTimer(now.plusSeconds(195))
    [|
        if(SpeedtestRerun.state == NULL)
        {
            SpeedtestRerun.postUpdate(OFF)
        }

        if(SpeedtestRunning.state == NULL)
        {
            SpeedtestRunning.postUpdate("-")
        }

        if(SpeedtestSummary.state == NULL || SpeedtestSummary.state == "")
        {
            SpeedtestSummary.postUpdate("⁉ (unbekannt)")
        }
    ]

end

rule "Speedtest"

when

    Time cron "0 0/15 * * * ?" or
    Item SpeedtestRerun changed from OFF to ON or
    Item SpeedtestRerun received command ON

then

    //logInfo(ruleId, "--> speedtest executed...")
    SpeedtestRunning.postUpdate("Measurement in Progress...")

    // execute the script, you may have to change the path depending on your system
    // Please use -f json and not -f json-pretty
    val speedtestExecute = "D:/Tools/Speedtest/speedtest.exe -f json"
    var speedtestCliOutput = executeCommandLine(speedtestExecute, 120*1000)

    // for debugging:
    // var String speedtestCliOutput = "Ping: 43.32 ms\nDownload: 21.64 Mbit/s\nUpload: 4.27 Mbit/s"
    //logInfo(ruleId, "--> speedtest output:\n" + speedtestCliOutput + "\n\n")
    SpeedtestRunning.postUpdate("Data Analysis...")

    // starts off with a fairly simple error check, should be enough to catch all problems I can think of
    if (speedtestCliOutput.startsWith("{\"type\":\"result\",") && speedtestCliOutput.endsWith("}}"))
    {
        var ping = Float::parseFloat(transform("JSONPATH", "$.ping.latency", speedtestCliOutput))
        SpeedtestResultPing.postUpdate(ping)

        var float down = Float::parseFloat(transform("JSONPATH", "$.download.bandwidth", speedtestCliOutput))
        down = (down / calc)
        SpeedtestResultDown.postUpdate(down)

        var float up = Float::parseFloat(transform("JSONPATH", "$.upload.bandwidth", speedtestCliOutput))
        up = (up / calc)
        SpeedtestResultUp.postUpdate(up)

        var String url = transform("JSONPATH", "$.result.url", speedtestCliOutput)
        val img = url + ".png"
        SpeedtestResultImage.postUpdate(img)

        SpeedtestSummary.postUpdate(String::format("ᐁ %.1f Mbit/s ᐃ %.1f Mbit/s (%.0f ms)", down, up, ping))

        SpeedtestRunning.postUpdate("-")

        // update timestamp for last execution
        val DateTimeType ResultDate = DateTimeType.valueOf(transform("JSONPATH", "$.timestamp", speedtestCliOutput))
        SpeedtestResultDate.postUpdate(ResultDate)
    }
    else
    {
        SpeedtestResultPing.postUpdate(0)
        SpeedtestResultDown.postUpdate(0)
        SpeedtestResultUp.postUpdate(0)
        SpeedtestSummary.postUpdate("(unknown)")
        SpeedtestRunning.postUpdate("Error")

        logError(ruleId, "--> speedtest failed. Output:\n" + speedtestCliOutput + "\n\n")
    }

    SpeedtestRerun.postUpdate(OFF)

end

```

### Using a different openHAB instance for the speedtest

As i´m using two different openHAB instances and build my rule to transfer the values to my main instance.  
The main instance is running on a raspberry pi 4 and isn´t able to measure the full speed of my bandwidth, that´s why i created a virtual machine with raspbian running with an own Gigabit port.

My current rule:

> **Summary**
>
> ```php
> val String ruleId = "Speedtest"
> val Number calc = 125000 // Converting from bits to Mbits
> 
> // Speedtest init placeholder
> 
> rule "Speedtest"
> 
> when
> 
> //Time cron "0 0 5,13 * * ?" or
> Time cron "0 0/15 * * * ?" or
> Item SpeedtestRerun changed from OFF to ON or
> Item SpeedtestRerun received command ON
> 
> then
> 
> //logInfo(ruleId, "--> speedtest executed...")
> SpeedtestRunning.postUpdate("Messung läuft...")
> sendHttpPutRequest("http://192.168.2.10:8080/rest/items/SpeedtestRunning/state", "text/plain", "Messung läuft...")
> 
> // execute the script, you may have to change the path depending on your system
> // oH 2.5:
> //val speedtestExecute = "speedtest -f json -s 28624"
> //var speedtestCliOutput = executeCommandLine(speedtestExecute, 120*1000)
> // openHAB 3:
> var speedtestCliOutput = executeCommandLine(Duration.ofSeconds(120), "speedtest", "-f", "json")
> 
> // for debugging:
> // var String speedtestCliOutput = "Ping: 43.32 ms\nDownload: 21.64 Mbit/s\nUpload: 4.27 Mbit/s"
> //logInfo(ruleId, "--> speedtest output:\n" + speedtestCliOutput + "\n\n")
> 
> SpeedtestRunning.postUpdate("Datenauswertung...")
> sendHttpPutRequest("http://192.168.2.10:8080/rest/items/SpeedtestRunning/state", "text/plain", "Datenauswertung...")
> 
> // starts off with a fairly simple error check, should be enough to catch all problems I can think of
> if (speedtestCliOutput.startsWith("{\"type\":\"result\",") && speedtestCliOutput.endsWith("}}"))
> {
> var ping = Float::parseFloat(transform("JSONPATH", "$.ping.latency", speedtestCliOutput))
> //logInfo(ruleId, "Ping " + ping)
> SpeedtestResultPing.postUpdate(ping)
> sendHttpPutRequest("http://192.168.2.10:8080/rest/items/SpeedtestResultPing/state", "text/plain", ping.toString)
> 
> var float down = Float::parseFloat(transform("JSONPATH", "$.download.bandwidth", speedtestCliOutput))
> down = (down / calc)
> //logInfo(ruleId, "Download " + down)
> SpeedtestResultDown.postUpdate(down)
> sendHttpPutRequest("http://192.168.2.10:8080/rest/items/SpeedtestResultDown/state", "text/plain", String::format("%s",down))
> 
> var float up = Float::parseFloat(transform("JSONPATH", "$.upload.bandwidth", speedtestCliOutput))
> up = (up / calc)
> //logInfo(ruleId, "Upload " + up)
> SpeedtestResultUp.postUpdate(up)
> sendHttpPutRequest("http://192.168.2.10:8080/rest/items/SpeedtestResultUp/state", "text/plain", String::format("%s",up))
> 
> var String url = transform("JSONPATH", "$.result.url", speedtestCliOutput)
> val img = url + ".png"
> sendHttpPutRequest("http://192.168.2.10:8080/rest/items/SpeedtestResultImage/state", "text/plain", String::format("%s",img))
> 
> SpeedtestSummary.postUpdate(String::format("ᐁ %.1f Mbit/s ᐃ %.1f Mbit/s (%.0f ms)", down, up, ping))
> sendHttpPutRequest("http://192.168.2.10:8080/rest/items/SpeedtestSummary/state", "text/plain", String::format("ᐁ %.1f Mbit/s ᐃ %.1f Mbit/s (%.0f ms)", down, up, ping))
> 
> SpeedtestRunning.postUpdate("-")
> sendHttpPutRequest("http://192.168.2.10:8080/rest/items/SpeedtestRunning/state", "text/plain", "-")
> 
> // update timestamp for last execution
> val DateTimeType ResultDate = DateTimeType.valueOf(transform("JSONPATH", "$.timestamp", speedtestCliOutput))
> SpeedtestResultDate.postUpdate(ResultDate)
> sendHttpPutRequest("http://192.168.2.10:8080/rest/items/SpeedtestResultDate/state", "text/plain", ResultDate.toString)
> 
> //logInfo(ruleId, "--> speedtest finished.")
> }
> else
> {
> SpeedtestResultPing.postUpdate(0)
> sendHttpPutRequest("http://192.168.2.10:8080/rest/items/SpeedtestResultPing/state", "text/plain", 0)
> SpeedtestResultDown.postUpdate(0)
> sendHttpPutRequest("http://192.168.2.10:8080/rest/items/SpeedtestResultDown/state", "text/plain", 0)
> SpeedtestResultUp.postUpdate(0)
> sendHttpPutRequest("http://192.168.2.10:8080/rest/items/SpeedtestResultUp/state", "text/plain", 0)
> SpeedtestSummary.postUpdate("(unbekannt)")
> sendHttpPutRequest("http://192.168.2.10:8080/rest/items/SpeedtestSummary/state", "text/plain", "Error")
> SpeedtestRunning.postUpdate("Fehler")
> sendHttpPutRequest("http://192.168.2.10:8080/rest/items/SpeedtestRunning/state", "text/plain", "Fehler")
> 
> sendHttpPutRequest("http://192.168.2.10:8080/rest/items/SpeedtestResultError/state", "text/plain", speedtestCliOutput.toString)
> logError(ruleId, "--> speedtest failed. Output:\n" + speedtestCliOutput + "\n\n")
> }
> 
> SpeedtestRerun.postUpdate(OFF)
> 
> end
> 
> ```

## Threshold checking

I´ve build another rule to check for user defined thresholds and send Telegram messages when the bandwidth falls under a warning or critical threshold.

The rule has three values to control the rule:  
`warnMsg` This message will be send when the `downWarn` or `upWarn` threshold matched  
`critMsg` This message will be send when the `downCrit` or `upWarn` threshold matched  
`logMsg` This will build a `logInfo` with the threshold checks

#### Items

```auto
Number SpeedtestResultDownStatus "Download Status"
Number SpeedtestResultUpStatus "Upload Status"

```

#### Sitemap

```php
Text item=SpeedtestResultDown valuecolor=[SpeedtestResultDownStatus==3="red",SpeedtestResultDownStatus==2="orange",SpeedtestResultDownStatus==1="green"]
Text item=SpeedtestResultUp valuecolor=[SpeedtestResultUpStatus==3="red",SpeedtestResultUpStatus==2="orange",SpeedtestResultUpStatus==1="green"]

```

#### Rule (attach it to the speedtest.rules from above)

```php
val telegramAction = getActions("telegram","telegram:telegramBot:bot")
val long bot1 = <BotID>

rule "Speedtest Threshold"

when

    Item SpeedtestRunning changed to "-"

then

    // Options to control the warn, crit and log message
    var warnMsg = 0
    var critMsg = 1
    var logMsg = 0

    // Build the strings for logging and messaging
    val StringBuilder speedThreshold = new StringBuilder
    val StringBuilder speedMessage = new StringBuilder
    // Messsage will be build like: xMsg1 + Download/Upload + xMsg2 + +Download/Upload speed + speedValue
    // Attention! Download reduced to 100 Mbps
    val critMsg1 = "Attention!\n"
    val critMsg2 = "critical: "
    val warnMsg1 = "Warning!\n"
    val warnMsg2 = "reduced to "
    val speedValue = " Mbps\n"

    // Import results
    var down = SpeedtestResultDown.state
    var up = SpeedtestResultUp.state

    // Check results for threshold
    // Please insert your thresholds, i used the thresholds of my ISP
    var downOk = (down >= 851)
    var downWarn = (down >= 501 && down <= 850)
    var downCrit = (down <= 500)
    var upOk = (up >= 36)
    var upWarn = (up >= 16 && up <= 35)
    var upCrit = (up <= 15)
    // Reduce the values to full Mbits
    down = down.toString.split("\\.").get(0)
    up = up.toString.split("\\.").get(0)

    // Logging to check if the thresholds are working
    if(logMsg == 1)
    {
        speedThreshold.append("Results:\n")
        speedThreshold.append("Download OK: " + downOk + "\n")
        speedThreshold.append("Download Warning: " + downWarn + "\n")
        speedThreshold.append("Download Critical: " + downCrit + "\n")
        speedThreshold.append("Upload OK: " + upOk + "\n")
        speedThreshold.append("Upload Warning: " + upWarn + "\n")
        speedThreshold.append("Upload Critical: " + upCrit + "\n")

        logInfo(ruleId, speedThreshold.toString)
    }

    // Check for critital and warning threshold and build a message
    if(downCrit || upCrit)
    {
        speedMessage.append(critMsg1)
    }
    else if(downWarn || upWarn)
    {
        speedMessage.append(warnMsg1)
    }
    if(downCrit)
    {
        speedMessage.append("Download " + critMsg2 + down + speedValue)
    }
    if(upCrit)
    {
        speedMessage.append("Upload " + critMsg2 + up + speedValue)
    }
    if(downWarn)
    {
        speedMessage.append("Download " + warnMsg2 + down + speedValue)
    }
    if(upWarn)
    {
        speedMessage.append("Upload " + warnMsg2 + up + speedValue)
    }
    if((downCrit || upCrit) && critMsg == 1)
    {
        telegramAction.sendTelegram(bot1, speedMessage.toString)
    }
    else if((downWarn || upWarn) && warnMsg == 1)
    {
        telegramAction.sendTelegram(bot1, speedMessage.toString)
    }

    // Update the status items for colored sitemap values
    if(downOk)
    {
        SpeedtestResultDownStatus.postUpdate(1)
    }
    if(downWarn)
    {
        SpeedtestResultDownStatus.postUpdate(2)
    }
    if(downCrit)
    {
        SpeedtestResultDownStatus.postUpdate(3)
    }
    if(upOk)
    {
        SpeedtestResultUpStatus.postUpdate(1)
    }
    if(upWarn)
    {
        SpeedtestResultUpStatus.postUpdate(2)
    }
    if(upCrit)
    {
        SpeedtestResultUpStatus.postUpdate(3)
    }

end

```

## My Environment

- Raspberry Pi4 4GB
- Raspbian GNU/Linux 10 (buster)
- openHAB 3.1
- ~~Exec Binding 2.5.1~~
- openjdk 11.0.12 2021-07-20 LTS
- OpenJDK Runtime Environment Zulu11.50+19-CA (build 11.0.12+7-LTS)
- OpenJDK Client VM Zulu11.50+19-CA (build 11.0.12+7-LTS, mixed mode)

## Conclusion

Why did i changed from the speedtest-cli by Sivel to the official Speedtest CLI by Ookla?  
Because the speedtest-cli is using old technology that isn´t able to measure my available bandwith and because the tests weren´t consistent.

Using speedtest-cli

 ![Bildschirmfoto 2020-03-12 um 13.55.21](https://community-openhab-org.s3.dualstack.eu-central-1.amazonaws.com/original/3X/3/6/368b12119763fb60bafdebae1916d5c29d4e057f.png)

Using Speedtest CLI by Ookla

 ![Bildschirmfoto 2020-03-12 um 13.56.05](https://community-openhab-org.s3.dualstack.eu-central-1.amazonaws.com/original/3X/a/2/a2608c8d31558e7aaaefdc78b43a4edb864047aa.png)

### Next steps

- I´m open for suggestions
- ~~Windows users to test how it works~~ thanks KidSquid!
- ~~Use the timestamp from the json output~~

kind regards  
Michael

---

<div class="post-metadata">

**Author:** ![sergej](https://community.openhab.org/letter_avatar_proxy/v4/letter/s/bcef8e/32.png) [@sergej](https://community.openhab.org/u/sergej)\
**Post date:** [March 12, 2020, 4:17pm UTC](https://community.openhab.org/t/speedtest-cli-by-ookla-internet-up-downlink-measurement-integration/94447/2 "2020-03-12T16:17:18Z")

</div>

Hi Michael,  
thank you for detailed guide.  
I had to modify speedtestExecute to accept license and gdpr

> val speedtestExecute = “speedtest --accept-license --accept-gdpr -f json”

but I still get 2 json objects as result:

```json
[17:02:55] openhabian@openhab:~$ speedtest --accept-license --accept-gdpr -f json
{"type":"log","timestamp":"2020-03-12T16:02:56Z","message":"Error: [-2] Protocol error: Did not receive HELLO","level":"error"}
{"type":"result","timestamp":"2020-03-12T16:03:17Z","ping":{"jitter":5.0119999999999996,"latency":14.154999999999999},"download":{"bandwidth":27342982,"bytes":302438216,"elapsed":11310},"upload":{"bandwidth":2503195,"bytes":10272648,"elapsed":4100},"packetLoss":0,"isp":"Unitymedia","interface":{"internalIp":"192.168.178.###","name":"eth0","macAddr":"####","isVpn":false,"externalIp":"####"},"server":{"id":25863,"name":"NOREST-TELECOM","location":"Hatten","country":"France","host":"speedtest.norest-telecom.fr","port":8080,"ip":"185.239.199.150"},"result":{"id":"####","url":"https://www.speedtest.net/result/c/######"}}

```

do you have a hint to get rid of this protocol error or parse 2 json objects?

Version: speedtest 1.0.0.2  
Running openhab 2.5.0 release build

Thank you  
/Sergej

---

<div class="post-metadata">

**Author:** ![Bredmich](https://community.openhab.org/user_avatar/community.openhab.org/bredmich/32/30457_2.png) [@Bredmich](https://community.openhab.org/u/Bredmich)\
**Post date:** [March 12, 2020, 4:45pm UTC](https://community.openhab.org/t/speedtest-cli-by-ookla-internet-up-downlink-measurement-integration/94447/3 "2020-03-12T16:45:47Z")

</div>

Hi Sergej

did you started the speedtest before? When i did, i only had to accept the license + gdpr once and that decision was saved.

The `{"type":"log",` only appears if something went wrong, in this case `"message":"Error: [-2] Protocol error: Did not receive HELLO",`  
I´ve to admit that i don´t know what this means.

Does this error always appears?

---

<div class="post-metadata">

**Author:** ![jantum](https://community.openhab.org/letter_avatar_proxy/v4/letter/j/a3d4f5/32.png) [@jantum](https://community.openhab.org/u/jantum)\
**Post date:** [March 12, 2020, 4:53pm UTC](https://community.openhab.org/t/speedtest-cli-by-ookla-internet-up-downlink-measurement-integration/94447/4 "2020-03-12T16:53:19Z")

</div>

> [@sergej](#):
>
> val speedtestExecute = “speedtest --accept-license --accept-gdpr -f json

I added the -f json before the --accept Syntax. I did get no errors as you.

Jan

---

<div class="post-metadata">

**Author:** ![sergej](https://community.openhab.org/letter_avatar_proxy/v4/letter/s/bcef8e/32.png) [@sergej](https://community.openhab.org/u/sergej)\
**Post date:** [March 13, 2020, 2:21pm UTC](https://community.openhab.org/t/speedtest-cli-by-ookla-internet-up-downlink-measurement-integration/94447/5 "2020-03-13T14:21:18Z")

</div>

> did you started the speedtest before? When i did, i only had to accept the license + gdpr once and that decision was saved.

starting speedtest from ssh session is different as start it from openhab rules I guess.

> Does this error always appears?

Yes, also after changing to `speedtest -f json --accept-license --accept-gdpr`  
/Sergej

---

<div class="post-metadata">

**Author:** ![Bredmich](https://community.openhab.org/user_avatar/community.openhab.org/bredmich/32/30457_2.png) [@Bredmich](https://community.openhab.org/u/Bredmich)\
**Post date:** [March 13, 2020, 2:28pm UTC](https://community.openhab.org/t/speedtest-cli-by-ookla-internet-up-downlink-measurement-integration/94447/6 "2020-03-13T14:28:11Z")

</div>

> [@sergej](#):
>
> starting speedtest from ssh session is different as start it from openhab rules I guess.

Not for me.  
What platform does your openhab instance run on?

---

<div class="post-metadata">

**Author:** ![sergej](https://community.openhab.org/letter_avatar_proxy/v4/letter/s/bcef8e/32.png) [@sergej](https://community.openhab.org/u/sergej)\
**Post date:** [March 13, 2020, 2:29pm UTC](https://community.openhab.org/t/speedtest-cli-by-ookla-internet-up-downlink-measurement-integration/94447/7 "2020-03-13T14:29:57Z")

</div>

> [@Bredmich](#):
>
> What platform does your openhab instance run on?

rpi4 openhabian  
I suppose I need to open some ports…

---

<div class="post-metadata">

**Author:** ![Bredmich](https://community.openhab.org/user_avatar/community.openhab.org/bredmich/32/30457_2.png) [@Bredmich](https://community.openhab.org/u/Bredmich)\
**Post date:** [March 13, 2020, 2:34pm UTC](https://community.openhab.org/t/speedtest-cli-by-ookla-internet-up-downlink-measurement-integration/94447/8 "2020-03-13T14:34:04Z")

</div>

I´m also running an rpi4 with openhabian.  
Strange, as i said. I only had to accept the license and gdpr once.

---

<div class="post-metadata">

**Author:** ![sergej](https://community.openhab.org/letter_avatar_proxy/v4/letter/s/bcef8e/32.png) [@sergej](https://community.openhab.org/u/sergej)\
**Post date:** [March 13, 2020, 2:45pm UTC](https://community.openhab.org/t/speedtest-cli-by-ookla-internet-up-downlink-measurement-integration/94447/9 "2020-03-13T14:45:20Z")

</div>

Ok guys, I understand now what is the issue.  
If you start speedtest without selecting a server it grab any server from server list (type `speedtest -L` to get the list).  
In my situation it took a server that is not compatible with the requests send by speedtest app.  
[15:36:55] openhabian@openhab:~$ speedtest -L  
Closest servers:

```auto
ID Name Location Country
==============================================================================
 14156 STARFACE GmbH Karlsruhe Germany
 18613 TelemaxX Telekommunikation GmbH Karlsruhe Germany
  5829 Bratschnitzel.de Karlsruhe Germany
 27612 Rabe.Systems Karlsruhe Germany
 10709 bc-networks Ludwigsburg Germany
 17811 bc-networks Remseck Germany
 25863 NOREST-TELECOM Hatten France
 16633 Stadtwerke Schorndorf GmbH Schorndorf Germany
 10291 TWL-KOM Ludwigshafen Germany
 28818 Pfalzkom GMBH Ludwigshafen Germany

```

So I selected special server from `speedtest -L` and now perform:  
`speedtest --accept-license --accept-gdpr -s 18613 -f json` and get correct result without any log messages in between.

As Michael mentioned add `--accept-license --accept-gdpr` is needed only once for first time.

btw. the position of parameters like `-f json` is not important in linux app see getopt() function.

/Sergej

---

<div class="post-metadata">

**Author:** ![Bredmich](https://community.openhab.org/user_avatar/community.openhab.org/bredmich/32/30457_2.png) [@Bredmich](https://community.openhab.org/u/Bredmich)\
**Post date:** [March 13, 2020, 2:49pm UTC](https://community.openhab.org/t/speedtest-cli-by-ookla-internet-up-downlink-measurement-integration/94447/10 "2020-03-13T14:49:30Z")

</div>

Thanks for clarifying!  
I directly choosed a server as only this server offers me full speed almost every time.

---

<div class="post-metadata">

**Author:** ![sergej](https://community.openhab.org/letter_avatar_proxy/v4/letter/s/bcef8e/32.png) [@sergej](https://community.openhab.org/u/sergej)\
**Post date:** [March 13, 2020, 3:24pm UTC](https://community.openhab.org/t/speedtest-cli-by-ookla-internet-up-downlink-measurement-integration/94447/11 "2020-03-13T15:24:48Z")

</div>

Hi Michael,  
Would be awesome if you attach chart.html or link to “how to” for beginners 😉  
Again, many thanks for guide and sharing your work.

If you use rrd4j  
instead of Webview use:  
`Chart item=gSpeedtest service="rrd4j" period=h refresh=15000`

 ![image](https://community-openhab-org.s3.dualstack.eu-central-1.amazonaws.com/original/3X/9/3/9377a9e3acc2813541c81dd8943290dc6fb1434a.png)  
Note: only `Number` items are displayed in Chart if you add items to persistency group

/Sergej

---

<div class="post-metadata">

**Author:** ![Bredmich](https://community.openhab.org/user_avatar/community.openhab.org/bredmich/32/30457_2.png) [@Bredmich](https://community.openhab.org/u/Bredmich)\
**Post date:** [March 13, 2020, 3:32pm UTC](https://community.openhab.org/t/speedtest-cli-by-ookla-internet-up-downlink-measurement-integration/94447/12 "2020-03-13T15:32:30Z")

</div>

It´s all based on the [InfluxDB+Grafana](https://community.openhab.org/t/influxdb-grafana-persistence-and-graphing/13761) guide i already linked 🙂

---

<div class="post-metadata">

**Author:** ![KidSquid](https://community.openhab.org/user_avatar/community.openhab.org/kidsquid/32/3985_2.png) [@KidSquid](https://community.openhab.org/u/KidSquid)\
**Post date:** [March 13, 2020, 4:26pm UTC](https://community.openhab.org/t/speedtest-cli-by-ookla-internet-up-downlink-measurement-integration/94447/13 "2020-03-13T16:26:13Z")

</div>

> [@Bredmich](#):
>
> ## Windows
> 
> _I need some input from other users for this part_

Just a quick note to let you know I have this up and running on Windows…it was fairly easy…

I downloaded the appropriate version of the CLI for Windows

 ![image](https://community-openhab-org.s3.dualstack.eu-central-1.amazonaws.com/original/3X/9/c/9c7653a4b0a2769432ae8d7eb1db6b9b44f40725.png)

As of now I have removed the charting as I wanted to get the basic functionality working.

My Items:

```csv
Group gSpeedtest <"speedtest">

String SpeedtestSummary "Speedtest [%s]" <"speedtest_summary"> (gSpeedtest)
Number SpeedtestResultPing "Ping [%.3f ms]" <"speedtest_ping"> (gSpeedtest)
Number SpeedtestResultDown "Download [%.2f Mbit/s]" <"speedtest_download"> (gSpeedtest)
Number SpeedtestResultUp "Upload [%.2f Mbit/s]" <"speedtest_upload"> (gSpeedtest)
String SpeedtestRunning "Speedtest Running ... [%s]" <"speedtest_run"> (gSpeedtest)
Switch SpeedtestRerun "Start Manually" <"speedtest_reload"> (gSpeedtest)
DateTime SpeedtestResultDate "Last Run [%1$td.%1$tm.%1$tY, %1$tH:%1$tM Uhr]" <"speedtest_date"> (gSpeedtest)
String SpeedtestResultError "Error Message [%s]" <"speedtest_error"> (gSpeedtest)

String SpeedtestResultImage "Image"

```

My Rules:

```php
val String ruleId = "Speedtest"
val Number calc = 125000 // Converting from bits to Mbits

rule "Speedtest init"

when

    System started

then

    createTimer(now.plusSeconds(195))
    [|
        if(SpeedtestRerun.state == NULL)
        {
            SpeedtestRerun.postUpdate(OFF)
        }

        if(SpeedtestRunning.state == NULL)
        {
            SpeedtestRunning.postUpdate("-")
        }

        if(SpeedtestSummary.state == NULL || SpeedtestSummary.state == "")
        {
            SpeedtestSummary.postUpdate("⁉ (unbekannt)")
        }
    ]

end

rule "Speedtest"

when

    Time cron "0 0/15 * * * ?" or
    Item SpeedtestRerun changed from OFF to ON or
    Item SpeedtestRerun received command ON

then

    //logInfo(ruleId, "--> speedtest executed...")
    SpeedtestRunning.postUpdate("Measurement in Progress...")

    // execute the script, you may have to change the path depending on your system
    // Please use -f json and not -f json-pretty
    val speedtestExecute = "D:/Tools/Speedtest/speedtest.exe -f json"
    var speedtestCliOutput = executeCommandLine(speedtestExecute, 120*1000)

    // for debugging:
    // var String speedtestCliOutput = "Ping: 43.32 ms\nDownload: 21.64 Mbit/s\nUpload: 4.27 Mbit/s"
    //logInfo(ruleId, "--> speedtest output:\n" + speedtestCliOutput + "\n\n")
    SpeedtestRunning.postUpdate("Data Analysis...")

    // starts off with a fairly simple error check, should be enough to catch all problems I can think of
    if (speedtestCliOutput.startsWith("{\"type\":\"result\",") && speedtestCliOutput.endsWith("}}"))
    {
        var ping = Float::parseFloat(transform("JSONPATH", "$.ping.latency", speedtestCliOutput))
        SpeedtestResultPing.postUpdate(ping)

        var float down = Float::parseFloat(transform("JSONPATH", "$.download.bandwidth", speedtestCliOutput))
        down = (down / calc)
        SpeedtestResultDown.postUpdate(down)

        var float up = Float::parseFloat(transform("JSONPATH", "$.upload.bandwidth", speedtestCliOutput))
        up = (up / calc)
        SpeedtestResultUp.postUpdate(up)

        var String url = transform("JSONPATH", "$.result.url", speedtestCliOutput)
        val img = url + ".png"
        SpeedtestResultImage.postUpdate(img)

        SpeedtestSummary.postUpdate(String::format("ᐁ %.1f Mbit/s ᐃ %.1f Mbit/s (%.0f ms)", down, up, ping))

        SpeedtestRunning.postUpdate("-")

        // update timestamp for last execution
        val String ResultDate = "" + new DateTimeType()
        SpeedtestResultDate.postUpdate(ResultDate)
    }
    else
    {
        SpeedtestResultPing.postUpdate(0)
        SpeedtestResultDown.postUpdate(0)
        SpeedtestResultUp.postUpdate(0)
        SpeedtestSummary.postUpdate("(unknown)")
        SpeedtestRunning.postUpdate("Error")

        logError(ruleId, "--> speedtest failed. Output:\n" + speedtestCliOutput + "\n\n")
    }

    SpeedtestRerun.postUpdate(OFF)

end

```

On line 47 note how the path to the .exe is created…you also must add the entire path to the executable in the exec.whitelist file if you are running on 2.5.2

My rendered sitemap

 ![image](https://community-openhab-org.s3.dualstack.eu-central-1.amazonaws.com/original/3X/4/0/40844cd0cd411b85661e926bb58fc96951ebefb6.png)

Only things left to do are:

1, Change time and date display ✔  
2. Figure out why my Gigabit services is only showing 246Mb on the download. ✔  
3. Make HabPanel widget ✔

Squid 🦑

---

<div class="post-metadata">

**Author:** ![Bredmich](https://community.openhab.org/user_avatar/community.openhab.org/bredmich/32/30457_2.png) [@Bredmich](https://community.openhab.org/u/Bredmich)\
**Post date:** [March 13, 2020, 4:42pm UTC](https://community.openhab.org/t/speedtest-cli-by-ookla-internet-up-downlink-measurement-integration/94447/14 "2020-03-13T16:42:08Z")

</div>

> [@KidSquid](#):
>
> 1. Figure out why my Gigabit services is only showing 246Mb on the download.

Try other servers and add `-s <ID>` to your rule when you found a reliable server.

For my region only one server (marked with \>) is able to provide reliable bandwidth for Gigabit services.

```auto
[17:40:42] openhab@oH-Speedtest:~$ speedtest -L
Closest servers:

    ID Name Location Country
==============================================================================
   15431 Spacken.net Hagen Germany
   17392 myLoc managed IT AG Dusseldorf Germany
   21467 InoHost Düsseldorf Germany
 > 28624 Händle & Korte GmbH Dusseldorf Germany
   28716 ColocationIX 10G Dusseldorf Germany
   30906 Deutsche Telekom Technik GmbH Dusseldorf Germany
    6601 NetCologne Cologne Germany
    6670 hotspot.koeln Cologne Germany
   22274 Gemeindewerke Nümbrecht GmbH Nümbrecht Germany
   24887 Studenten Net Twente Enschede Netherlands

```

> [@KidSquid](#):
>
> you also must add the entire path to the executable in the exec.whitelist file if you are running on 2.5.2

~~Could you please tell me where this directory and file should be? I can´t find it in my openhabian setup.  
The [docs](https://www.openhab.org/addons/bindings/exec/) are not a great help as there´s no full path listed.~~  
Nevermind… i need to create it myself under `/etc/openhab2/misc`

---

<div class="post-metadata">

**Author:** ![KidSquid](https://community.openhab.org/user_avatar/community.openhab.org/kidsquid/32/3985_2.png) [@KidSquid](https://community.openhab.org/u/KidSquid)\
**Post date:** [March 13, 2020, 4:59pm UTC](https://community.openhab.org/t/speedtest-cli-by-ookla-internet-up-downlink-measurement-integration/94447/15 "2020-03-13T16:59:58Z")

</div>

@Bredmich

Question for you…I’m wanting to use the SpeedtestSummary data in a HabPanel Widget

Do to my current layout the data renders as follows:

 ![image](https://community-openhab-org.s3.dualstack.eu-central-1.amazonaws.com/original/3X/e/3/e3389342aefd3e67793d0e8bf82a0cb857b8e940.png)

How can I modify the String below to place line breaks between each of the three values so up, down, and ping are all on separate lines?

```csv
SpeedtestSummary.postUpdate(String::format("ᐁ %.1f Mbit/s ᐃ %.1f Mbit/s (%.0f ms)", down, up, ping))

```

---

<div class="post-metadata">

**Author:** ![Bredmich](https://community.openhab.org/user_avatar/community.openhab.org/bredmich/32/30457_2.png) [@Bredmich](https://community.openhab.org/u/Bredmich)\
**Post date:** [March 13, 2020, 5:07pm UTC](https://community.openhab.org/t/speedtest-cli-by-ookla-internet-up-downlink-measurement-integration/94447/16 "2020-03-13T17:07:58Z")

</div>

You could try `\n` but i´m not sure if HABpanel will do it correctly.

`SpeedtestSummary.postUpdate(String::format("ᐁ %.1f Mbit/s\nᐃ %.1f Mbit/s\n(%.0f ms)", down, up, ping))`

---

<div class="post-metadata">

**Author:** ![Kimzer](https://community.openhab.org/user_avatar/community.openhab.org/kimzer/32/36725_2.png) [@Kimzer](https://community.openhab.org/u/Kimzer)\
**Post date:** [March 13, 2020, 5:07pm UTC](https://community.openhab.org/t/speedtest-cli-by-ookla-internet-up-downlink-measurement-integration/94447/17 "2020-03-13T17:07:58Z")

</div>

```
E: The repository 'https://ookla.bintray.com/debian eoan Release' does not have a Release file.
N: Updating from such a repository can't be done securely, and is therefore disabled by default.
N: See apt-secure(8) manpage for repository creation and user configuration details.

```

I assume i am doing something simple, wrong. Running openhab on ubuntu 19.10

---

<div class="post-metadata">

**Author:** ![Bredmich](https://community.openhab.org/user_avatar/community.openhab.org/bredmich/32/30457_2.png) [@Bredmich](https://community.openhab.org/u/Bredmich)\
**Post date:** [March 13, 2020, 5:18pm UTC](https://community.openhab.org/t/speedtest-cli-by-ookla-internet-up-downlink-measurement-integration/94447/18 "2020-03-13T17:18:20Z")

</div>

I think you need to `--allow-unauthenticated` to your `sudo apt-get update`  
As i´m not using Ubuntu i´m not sure if that´s the correct way.

---

<div class="post-metadata">

**Author:** ![KidSquid](https://community.openhab.org/user_avatar/community.openhab.org/kidsquid/32/3985_2.png) [@KidSquid](https://community.openhab.org/u/KidSquid)\
**Post date:** [March 13, 2020, 9:30pm UTC](https://community.openhab.org/t/speedtest-cli-by-ookla-internet-up-downlink-measurement-integration/94447/19 "2020-03-13T21:30:29Z")

</div>

As an FYI -

The `/n` would not work in HabPanel as you mentioned might be the case…

What I ended up doing was using the individual elements and making a widget from them.

Came out very nice and fits in with the overall design of my status board.

 ![image](https://community-openhab-org.s3.dualstack.eu-central-1.amazonaws.com/original/3X/9/b/9bf9786cb4983b30fc7a7ad5270b2ce5b76ca159.png)

Thanks again for this great addition to OH!

Squid

---

<div class="post-metadata">

**Author:** ![Bredmich](https://community.openhab.org/user_avatar/community.openhab.org/bredmich/32/30457_2.png) [@Bredmich](https://community.openhab.org/u/Bredmich)\
**Post date:** [March 13, 2020, 9:33pm UTC](https://community.openhab.org/t/speedtest-cli-by-ookla-internet-up-downlink-measurement-integration/94447/20 "2020-03-13T21:33:31Z")

</div>

> [@KidSquid](#):
>
> The `/n` would not work in HabPanel as you mentioned might be the case…

Just to be sure, you used `\n` and not `/n`?

[Next page](https://community.openhab.org/t/speedtest-cli-by-ookla-internet-up-downlink-measurement-integration/94447.md?page=2)
