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

Then try to do what is in the last line of your code :slight_smile:

It will be this file probably:

https://install.speedtest.net/app/cli/ookla-speedtest-1.1.1-linux-x86_64.tgz

Hi,
thanks for this.

I have an error and i don’t see the problem :frowning:

2022-01-18 10:45:29.629 [ERROR] [.openhab.core.model.script.Speedtest] - --> speedtest failed. Output:
{"type":"result","timestamp":"2022-01-18T09:45:29Z","ping":{"jitter":2.948,"latency":13.353999999999999},"download":{"bandwidth":67932065,"bytes":907303768,"elapsed":15005},"upload":{"bandwidth":5883783,"bytes":37594424,"elapsed":6413},"packetLoss":0,"isp":"Vodafone Germany Cable","interface":{"internalIp":"192.168.230.10","name":"eth0","macAddr":"9E:EA:51:22:9A:90","isVpn":false,"externalIp":"95.91.30.83"},"server":{"id":38032,"host":"speedtest.drahtlos-dsl.de","port":8080,"name":"Drahtlos-DSL GmbH Mittelsachsen","location":"Leipzig","country":"Germany","ip":"94.100.70.2"},"result":{"id":"1d3bfc6f-3a83-4df7-a966-47004dc21c3f","url":"https://www.speedtest.net/result/c/1d3bfc6f-3a83-4df7-a966-47004dc21c3f","persisted":true}}

Anyone see the problem?
Greets

I found the place with the error, but do not understand the problem :frowning:
I changed the rules and now it works.

    if (speedtestCliOutput.startsWith("{\"type\":\"result\",")) //&& speedtestCliOutput.endsWith("}}"))```

Hi
thx. I am not an expert. I run openhabian on a RasPi4. I have no idea where to put these files then and what to do. If I got it right, you suggest there “armhf” config. OK. I have downloaded the files to my Mac. I can put it to my Rapsi…where? And what next?

You can put it in folder of your choice (e.g. home folder)
Then decompress it and copy speedtest executable file to /usr/bin/

Thx a lot @kristofejro - I can start speedtest now from the command line. Now I need to get it into OH3…which I realise is a challenge as there is no binding, and I dont know how to create a THING without a binding…but I am searching;)

I think a search is not required … Just look into the first post in this thread.
You have the speedtest linux client executable that needs to be called from within a rule and will assign values to your items. Items definition are also in the first post.

There´s a working binding available.

Just create the items. Thing is not required here. Just as it is in the first post.

@Alexollon just keep in mind that this binding is not officially supported (not included in official bundle) and its installation have to be done manually and not via GUI.

Just as the Speedtest CLI for this solution…
It´s just downloading the .jar and placing it into the addons folder.
After that he can create and manage the thing and items in the GUI while he needs to create items and a rule for my solution.
There are way more things that could go wrong with my solution compared to the binding.

Thx Wolfgang. I found it, included now the “speedtest.things” file. What is strange: I cannot see this new thing in the UI. Is this normal?

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"

as well as the rule “rSpeedtest.rules” (and I know it is not properly edited yet, but I try to get it step by step running):

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/2 * * * ?" or //was set to 15min
    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.178.111:8080/rest/items/SpeedtestRunning/state", "text/plain", "Messung läuft...")
    logInfo(ruleId, "--> speedtest was executed...")

    // 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.178.111: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.178.111: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://1192.168.178.111: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.178.111: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.178.111: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.178.111: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.178.111: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.178.111:8080/rest/items/SpeedtestResultDate/state", "text/plain", ResultDate.toString)

        //logInfo(ruleId, "--> speedtest finished.")
    }
    else
    {
        SpeedtestResultPing.postUpdate(0)
        sendHttpPutRequest("http://192.168.178.111:8080/rest/items/SpeedtestResultPing/state", "text/plain", 0)
        SpeedtestResultDown.postUpdate(0)
        sendHttpPutRequest("http://192.168.178.111:8080/rest/items/SpeedtestResultDown/state", "text/plain", 0)
        SpeedtestResultUp.postUpdate(0)
        sendHttpPutRequest("http://192.168.178.111:8080/rest/items/SpeedtestResultUp/state", "text/plain", 0)
        SpeedtestSummary.postUpdate("(unbekannt)")
        sendHttpPutRequest("http://192.168.178.111:8080/rest/items/SpeedtestSummary/state", "text/plain", "Error")
        SpeedtestRunning.postUpdate("Fehler")
        sendHttpPutRequest("http://192.168.178.111:8080/rest/items/SpeedtestRunning/state", "text/plain", "Fehler")

        sendHttpPutRequest("http://192.168.178.111:8080/rest/items/SpeedtestResultError/state", "text/plain", speedtestCliOutput.toString)
        logError(ruleId, "--> speedtest failed. Output:\n" + speedtestCliOutput + "\n\n")
    }

    SpeedtestRerun.postUpdate(OFF)

end

And this results in the following error message in the log:

2022-01-22 15:39:38.278 [INFO ] [el.core.internal.ModelRepositoryImpl] - Loading model 'rSpeedtest.rules'
2022-01-22 15:40:00.232 [INFO ] [.openhab.core.model.script.Speedtest] - --> speedtest executed...
2022-01-22 15:40:00.234 [ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID 'rSpeedtest-1' failed: The name 'SpeedtestRunning' cannot be resolved to an item or type; line 18, column 9, length 16 in rSpeedtest

And I dont know what is wrong…sorry for being such a noob. I read the documentation how to add things and it looks very different which is confusing me…so there seems to be something wrong with the thing, but I dont know what…

You should rename that file into speedtest.items and place it inside the items folder of openHAB.
My solution doesn´t need a thing because it´s not using a binding.

I would really suggest to look into the speedtest binding because it can be configured using the GUI.

I´m pretty sure you´ll run into even more problems because my solution is outdated.

Thx Michael, I installed the binding as you suggested. It works. Now I will try to get the rules working…and develop them into the following direction:

I take the monitoring routine with the Telegram message when a certain download speed threshold is reached, ie speed to low. But then I also want - until the download speed goes up again - do more measurements in shorter period. As the data volume is rather large by a single test, performing tests every minute is generating too much data volume (in my case I calculated some 300GB a month if I do a test every 10mins). So I would use 15-30min as standard setting and only in case of problems go down to shorter test cycles. And furthermore such intensive testing should be trigger able by sending a Telegram message:)

Michael, how do I trigger the speedtest with the binding? the send command does not work.

sendcommand (Speedtest_TestTrigger, ON)

Also the following rule trigger does not work, any idea what is wrong?

rule "speedtesttelegramalarm"
when    
        //Time cron "0 0/15 * * * ?" or
        Item Speedtest_TestTrigger changed from ON to OFF or
        Item Speedtest changed
then

First off all the Trigger needs to be OFF and being switched to ON
I´m doing this with an expiration of 2s.
So everytime i switch ON the SpeedtestTrigger it´ll be switched back OFF 2 seconds later.
For me it´s configured like this:
Switch spdTrigger {channel="speedtest:speedtest:binding:trigger_test", expire="2s,command=OFF"}

Another thing is your syntax, it´s outdated for openHAB 3.
The new syntax is Speedtest_TestTrigger.sendCommand(ON)

Thx Michael! The send command works now.

Where is do I need to put this configuration of the TestTrigger? In the config page of Speedtest I can’t see such parameter. Meantime I used a Timer to cope with this…

Furthermore, what is a proper trigger expression that triggers a rule always when the Speedtest is finished (either by another Cron rule I setup or an manual trigger)? I have tried the following and neither worked properly. Only the first with the “bandwidth” was triggered, but so often that it launched every few seconds the rule. The other 2 versions did not fire at all. I need a rule that is only triggered when a Speedtest was finished.

rule "speedtesttelegramalarm"
when    
        //Item Speedtest_DownloadBandwidth changed
        Thing Speedtest received update 
        //Item Speedtest received command
then

Hi,

in case someone wants to use it in ECMA-Javascript Version 2021 here are the 2 rules

initialization

configuration: {}
triggers:
  - id: "1"
    configuration:
      startlevel: 100
    type: core.SystemStartlevelTrigger
conditions: []
actions:
  - inputs: {}
    id: "2"
    configuration:
      type: application/javascript;version=ECMAScript-2021
      script: >-
        var SpeedtestRerun = items.getItem("SpeedtestRerun");

        var SpeedtestRunning = items.getItem("SpeedtestRunning");

        var SpeedtestSummary = items.getItem("SpeedtestSummary");


        var SpeedtestScriptExecution = Java.type("org.openhab.core.model.script.actions.ScriptExecution");

        var jetzt = time.ZonedDateTime.now();


        SpeedtestTimer = (SpeedtestTimer===undefined) ? null : SpeedtestTimer;


        SpeedtestTimer = SpeedtestScriptExecution.createTimer(jetzt.plusSeconds(195), function(){
              console.log("Speedtest wird initialisiert");
              if (SpeedtestRerun.state == NULL) SpeedtestRerun.postUpdate("OFF");
              if (SpeedtestRunning.state == NULL) SpeedtestRunning.postUpdate("-");
              if (SpeedtestSummary.state == NULL || SpeedtestSummary.state == "");
                SpeedtestSummary.postUpdate("⁉ (unbekannt)");
        });
    type: script.ScriptAction

Execution:

configuration: {}
triggers:
  - id: "1"
    configuration:
      cronExpression: 0 0 * * * ? *
    type: timer.GenericCronTrigger
  - id: "2"
    configuration:
      itemName: SpeedtestRerun
      command: ON
    type: core.ItemCommandTrigger
conditions: []
actions:
  - inputs: {}
    id: "3"
    configuration:
      type: application/javascript;version=ECMAScript-2021
      script: >-
        var SpeedtestRunning = items.getItem("SpeedtestRunning");

        var SpeedtestResultDate = items.getItem("SpeedtestResultDate");

        var SpeedtestResultPing = items.getItem("SpeedtestResultPing");

        var SpeedtestResultDown = items.getItem("SpeedtestResultDown");

        var SpeedtestResultUp = items.getItem("SpeedtestResultUp");

        var SpeedtestRerun = items.getItem("SpeedtestRerun");

        var SpeedtestSummary = items.getItem("SpeedtestSummary");

        var jetzt = time.ZonedDateTime.now();


        var Exec = Java.type('org.openhab.core.model.script.actions.Exec');

        var Duration = Java.type('java.time.Duration');

        var Transformation = Java.type("org.openhab.core.transform.actions.Transformation");

        var StringFormat = Java.type("java.lang.String");


        // console.log("speedtest executed...");

        SpeedtestRunning.postUpdate("Messung läuft...");


        // update timestamp for last execution

        // SpeedtestResult = jetzt;


        // execute the script, you may have to change the path depending on your system


        var speedtestCliOutput = Exec.executeCommandLine(Duration.ofSeconds(120), "/usr/bin/speedtest", "--progress=no", "-f", "json", "--accept-license", "-s", "12390");


        // for debugging:

        // var String speedtestCliOutput = "Ping: 43.32 ms\nDownload: 21.64 Mbit/s\nUpload: 4.27 Mbit/s"

        // console.log("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("}}"))*/) {
          SpeedtestResultPing.postUpdate(parseFloat(Transformation.transform("JSONPATH", "$.ping.latency", speedtestCliOutput)));
          SpeedtestResultDown.postUpdate(parseFloat(Transformation.transform("JSONPATH", "$.download.bandwidth", speedtestCliOutput))/125000);
          SpeedtestResultUp.postUpdate(parseFloat(Transformation.transform("JSONPATH", "$.upload.bandwidth", speedtestCliOutput))/125000);


          /* var url = transform("JSONPATH", "$.result.url", speedtestCliOutput);
          var img = url + ".png";
          SpeedtestResultImage.postUpdate(img);*/

          SpeedtestSummary.postUpdate(StringFormat.format("ᐁ %.1f Mbit/s  ᐃ %.1f Mbit/s (%.0f ms)", parseFloat(SpeedtestResultDown.state), parseFloat(SpeedtestResultUp.state), parseFloat(SpeedtestResultPing.state)));

          SpeedtestRunning.postUpdate("-");

          // update timestamp for last execution
          SpeedtestResultDate.postUpdate((Transformation.transform("JSONPATH", "$.timestamp", speedtestCliOutput)));
          
          // console.log("speedtest finished.");
        }

        else {
          SpeedtestResultPing.postUpdate(0);
          SpeedtestResultDown.postUpdate(0);
          SpeedtestResultUp.postUpdate(0);
          SpeedtestSummary.postUpdate("(unbekannt)");
          SpeedtestRunning.postUpdate("Fehler bei der Ausführung");
          // console.log("speedtest failed. Output:\n" + speedtestCliOutput + "\n\n");
        }

        SpeedtestRerun.postUpdate("OFF");
    type: script.ScriptAction

The issue with “endsWith” from @Maik_Kurzhals I did not resolve. Any feedback is welcome :slight_smile:

BR, Thomas

I also could not find the solution to the “endsWith” error issue.
When I removed it there is no more error in parsing the JSON result.

EDIT:
found a solution, added \n:
speedtestCliOutput.endsWith("}}\n")

Speedtest Binding from @bhomeyer found it’s way into the Marketplace in the meanwhile:

1 Like