OH3 and Speedtest Ookla

Hi,
i want to share my current solution for the ookla docker container integration (based on Speedtest CLI by Ookla - Internet Up-/Downlink Measurement Integration):

  • I’m using the ookla docker image tamasboros/ookla-speedtest that is
  • writing the json output to /opt/openhab/userdata/tmp/ookla.json, this file is then
  • parsed by the speedtest rule (instead of executing the ookla speedtest and fetching the json like in the original rule). To do so, i added a try-catch block around, reading the file and convert the pretty json to a one-liner with 2 regex. (-> see MODIFICATION START / END)
  • cronjob is running the ookla speedtest container every minute.

speedtest.rules:

// MODIFICATION START
import java.nio.file.Files
import java.nio.file.Paths
// MODIFICATION END

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
    //val speedtestExecute = "speedtest --accept-gdpr --accept-license -f json"
	var String speedtestCliOutput;
	// MODIFICATION START
	try {
        speedtestCliOutput = new String(Files.readAllBytes(Paths.get("/openhab/userdata/tmp/ookla.json")))
		speedtestCliOutput = speedtestCliOutput.replaceAll("\\s+", "").replaceAll("\\r\\n+","")
		// MODIFICATION END

		// for debugging:
		// var String speedtestCliOutput = "Ping: 43.32 ms\nDownload: 21.64 Mbit/s\nUpload: 4.27 Mbit/s"
		logInfo(ruleId, "--> speedtest output after readfile:\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)
		
	// MODIFICATION START	
	} catch (IOException e){
		SpeedtestResultPing.postUpdate(0)
		SpeedtestResultDown.postUpdate(0)
		SpeedtestResultUp.postUpdate(0)
		SpeedtestSummary.postUpdate("(unbekannt)")
		SpeedtestRunning.postUpdate("File Error")

		logError(ruleId, "--> speedtest failed. Output:\nookla output reading failed!\n\n")
		
	}
        // MODIFICATION END
end

/etc/cron.d/docker
* * * * * docker /home/docker/docker_ookla.sh

/home/docker/docker_ookla.sh

docker run --rm --name ookla tamasboros/ookla-speedtest > /opt/openhab/userdata/tmp/ookla.json

I’m quite sure this is not the most beautiful solution, but due to lack of time my only one working :wink:

1 Like