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

Yes, typo on my part above

Maybe someone has a good idea for the following question.
I would like to have a rule that is able to send different messages for thresholds.
My ISP has three levels of bandwidth: Maximum, Usually and Minimum
So i´ve got six states, Upload (Min,Normal,Max) and Download (Min,Normal,Max), to measure.
I already made the checks but i´m not sure what would be a good way to write a rule for this.

Edit: Ok so here´s what i did. I´m pretty sure there´s a “better” or much simplier way to do this and i´m happy if someone has a optimized version.

Code in the first post

Ok clearly, not being a programmer I’ve done something wrong, I thought I copied and pasted the code ok, and the command line speedtest creates no issues but I get a Java error


2020-03-17 17:21:11.343 [vent.ItemStateChangedEvent] - SpeedtestRunning changed from Data Analysis... to Measurement in progress...
2020-03-17 17:21:11.358 [vent.ItemStateChangedEvent] - SpeedtestResultDate changed from 2020-03-17T17:20:12.257-0400 to 2020-03-17T17:21:11.340-0400
2020-03-17 17:21:20.575 [vent.ItemStateChangedEvent] - SpeedtestRunning changed from Measurement in progress... to Data Analysis...
2020-03-17 17:21:20.577 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Speedtest': For input string: "{"type":"result","timestamp":"2020-03-17T21:21:20Z","ping":{"jitter":0.070000000000000007,"latency":4.0490000000000004},"download":{"bandwidth":10542780,"bytes":38014560,"elapsed":3610},"upload":{"bandwidth":11753696,"bytes":42415200,"elapsed":3610},"packetLoss":0,"isp":"Bell Canada","interface":{"internalIp":"192.168.1.34","name":"eth0","macAddr":"B8:27:EB:1E:8B:85","isVpn":false,"externalIp":"216.209.224.196"},"server":{"id":12159,"name":"LARG*net","location":"London, ON","country":"Canada","host":"speedtest.largnet.ca","port":8080,"ip":"198.20.48.134"},"result":{"id":"841f35c7-9e7a-4989-8a4d-d5f280f6c97b","url":"https://www.speedtest.net/result/c/841f35c7-9e7a-4989-8a4d-d5f280f6c97b"}}"
2020-03-17 17:23:27.252 [ERROR] [org.quartz.core.JobRunShell         ] - Job DEFAULT.Timer 11 2020-03-17T17:23:27.248-04:00: Proxy for org.eclipse.xtext.xbase.lib.Procedures$Procedure0: [ | {
  org.eclipse.xtext.xbase.impl.XIfExpressionImpl@11d0ff7 (conditionalExpression: false)
  org.eclipse.xtext.xbase.impl.XIfExpressionImpl@1f5aac8 (conditionalExpression: false)
  org.eclipse.xtext.xbase.impl.XIfExpressionImpl@1905992 (conditionalExpression: false)
} ] threw an unhandled Exception: 

java.lang.NullPointerException: null
	at org.eclipse.smarthome.model.script.engine.ScriptError.<init>(ScriptError.java:65) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.invokeFeature(ScriptInterpreter.java:140) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:991) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:954) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:235) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:215) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:857) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:231) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:215) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.evaluateArgumentExpressions(XbaseInterpreter.java:1205) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._invokeFeature(XbaseInterpreter.java:1135) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.invokeFeature(XbaseInterpreter.java:1081) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.invokeFeature(ScriptInterpreter.java:151) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:991) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:237) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:215) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:469) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:255) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:215) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:458) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:239) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:215) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.evaluate(XbaseInterpreter.java:201) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.ClosureInvocationHandler.doInvoke(ClosureInvocationHandler.java:46) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.AbstractClosureInvocationHandler.invoke(AbstractClosureInvocationHandler.java:29) ~[?:?]
	at com.sun.proxy.$Proxy177.apply(Unknown Source) ~[?:?]
	at org.eclipse.smarthome.model.script.internal.actions.TimerExecutionJob.execute(TimerExecutionJob.java:48) ~[?:?]
	at org.quartz.core.JobRunShell.run(JobRunShell.java:202) [bundleFile:?]
	at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573) [bundleFile:?]
2020-03-17 17:23:27.309 [ERROR] [org.quartz.core.ErrorLogger         ] - Job (DEFAULT.Timer 11 2020-03-17T17:23:27.248-04:00: Proxy for org.eclipse.xtext.xbase.lib.Procedures$Procedure0: [ | {
  org.eclipse.xtext.xbase.impl.XIfExpressionImpl@11d0ff7 (conditionalExpression: false)
  org.eclipse.xtext.xbase.impl.XIfExpressionImpl@1f5aac8 (conditionalExpression: false)
  org.eclipse.xtext.xbase.impl.XIfExpressionImpl@1905992 (conditionalExpression: false)
} ] threw an exception.
org.quartz.SchedulerException: Job threw an unhandled exception.
	at org.quartz.core.JobRunShell.run(JobRunShell.java:213) [bundleFile:?]
	at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573) [bundleFile:?]

Caused by: java.lang.NullPointerException
	at org.eclipse.smarthome.model.script.engine.ScriptError.<init>(ScriptError.java:65) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.invokeFeature(ScriptInterpreter.java:140) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:991) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:954) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:235) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:215) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:857) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:231) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:215) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.evaluateArgumentExpressions(XbaseInterpreter.java:1205) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._invokeFeature(XbaseInterpreter.java:1135) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.invokeFeature(XbaseInterpreter.java:1081) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.invokeFeature(ScriptInterpreter.java:151) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:991) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:237) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:215) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:469) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:255) ~[?:?]

	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:215) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:458) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:239) ~[?:?]

	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:215) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.evaluate(XbaseInterpreter.java:201) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.ClosureInvocationHandler.doInvoke(ClosureInvocationHandler.java:46) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.AbstractClosureInvocationHandler.invoke(AbstractClosureInvocationHandler.java:29) ~[?:?]

	at com.sun.proxy.$Proxy177.apply(Unknown Source) ~[?:?]

	at org.eclipse.smarthome.model.script.internal.actions.TimerExecutionJob.execute(TimerExecutionJob.java:48) ~[?:?]

	at org.quartz.core.JobRunShell.run(JobRunShell.java:202) ~[?:?]

	... 1 more 
'''

Please put all your code between ```php and ```
Currently i don´t see a speedtest error.

Sorry can never remember how to get the text in those scrollable windows

Code:

// Global Variables
//
val String filename = "speedtest.rules"
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("⁉ (unknown)")
    ]
end
//
rule "Speedtest"
when
    Time cron "0 0 7/4 1/1 * ?" or //every 4hrs from 7am
    Item SpeedtestRerun changed from OFF to ON or
    Item SpeedtestRerun received command ON
then
    logInfo(filename, "--> speedtest executed...")
    SpeedtestRunning.postUpdate("Measurement in progress...")

    // update timestamp for last execution
    SpeedtestResultDate.postUpdate(new DateTimeType())

    // execute the script, you may have to change the path depending on your system
    // var String speedtestCliOutput = executeCommandLine("/usr/local/bin/speedtest-cli@@--simple", 120*1000)
    // val speedtestExecute = "speedtest -f json -s 1061"
    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(filename, "--> speedtest output:\n" + speedtestCliOutput + "\n\n")

    SpeedtestRunning.postUpdate("Data Analysis...")

 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("Failure")

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

As I said I cut and paste it

what file is this copied from? If it’s the rule…it;s missing a large part of it from the top.

1 Like

The error’s straight from my log openhabIP:9001
The code from the speedtest.rules

The code you copied is only part of the rules file…you are missing quite a bit of it from the top. You need to make sure you got it all when you copied and pasted.

Squid

1 Like

Not missing just didn’t copy it. (edited) I put logInfo’s in to see where the code was stopping, code falls through until the "first var ping = "

I think your cron part is not correct.
Please try it without the cron part and if that‘s working check the cron part.

If you are going to ask for help…you’ve got to give us the full picture…

1 Like

Cron is ok, literally I used my old speedtest-cli script and it always worked

Anyway this is manually running I’m testing and you can see it runs from the original log output


2020-03-17 17:21:11.343 [vent.ItemStateChangedEvent] - SpeedtestRunning changed from Data Analysis... to Measurement in progress...
2020-03-17 17:21:11.358 [vent.ItemStateChangedEvent] - SpeedtestResultDate changed from 2020-03-17T17:20:12.257-0400 to 2020-03-17T17:21:11.340-0400
2020-03-17 17:21:20.575 [vent.ItemStateChangedEvent] - SpeedtestRunning changed from Measurement in progress... to Data Analysis...
2020-03-17 17:21:20.577 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Speedtest': For input string: "{"type":"result","timestamp":"2020-03-17T21:21:20Z","ping":{"jitter":0.070000000000000007,"latency":4.0490000000000004},"download":{"bandwidth":10542780,"bytes":38014560,"elapsed":3610},"upload":{"bandwidth":11753696,"bytes":42415200,"elapsed":3610},"packetLoss":0,"isp":"Bell Canada","interface":{"internalIp":"192.168.1.34","name":"eth0","macAddr":"B8:27:EB:1E:8B:85","isVpn":false,"externalIp":"216.209.224.196"},"server":{"id":12159,"name":"LARG*net","location":"London, ON","country":"Canada","host":"speedtest.largnet.ca","port":8080,"ip":"198.20.48.134"},"result":{"id":"841f35c7-9e7a-4989-8a4d-d5f280f6c97b","url":"https://www.speedtest.net/result/c/841f35c7-9e7a-4989-8a4d-d5f280f6c97b"}}"

Ok, because i only see this kind of errors when something with timers or cron isn‘t correct.

I‘m not sure what openHAB wants to tell you with this line:

I just opened this in Visual Studio Code again and now I get this error

"Type mismatch: cannot convert from BigDecimal to float",

It seems to be complaining about the calculations

down = (down / calc) & up = (up / calc)

Should calc be defined as a Float?

Ok, there is something fundamentally different with my system, I deleted my speedtest.rules and only used your code and same result.

I’ll need to delve deeper…

I played around with the values a lot before posting my solution and this was the only working combination.

Edit: I changed the timestamp update to use the timestamp from the json instead of an now function.

Make sure you installed the JSONPath transformation.
If th eJSONPath transform is not installed openhab throws this error, because the rule engine can’t handle the input string.
Edit: @Bredmich you should add the requirement of having the JSONPath transform installed.

1 Like

Thanks for the hint!
I didn´t know that the JSONPath Transformation wasn´t part of the base installation of oH.

Edit: Added JSONPath Transformation to the requirements

Regarding --accept-license --accept-gdpr

I think some people may have problems because OH runs it as ‘openhab’ user.
Solution would be sudo -u openhab speedtest and then accept agreements.

Then there should be no issue with it. I hope it may help someone.

3 Likes

Thanks for the info!
I added a note to use the openhab user for the initial speedtest.