Add the official speedtest cli

A bit improved way is to develop a wrapper around the speedtest-cli so that we get output in flat json so that we can read all interesting attributes in single command_line sensor update:

1 Like

Looks pretty good. Do you know how I can make it work in HA docker?

Not sure as I’m using HA Core installation. But here is a post I’ve found on how to access HA command line in docker.
Alternatively you could run the speedtest-cli in the host or another vm etc and run the command_line sensor from there e.g. via ssh e.g. like that:
'sshpass -p "yourpass" ssh -o StrictHostKeyChecking=no user_id@ip_address "python3.8 speedtest_script.py"' - with proper names, paths, python version etc.

Hi @adorobis
i use your script but i have an error with parsing json result of your python script :slight_smile:

Unable to parse output as JSON: Running Speedtest Stdout: {“type”:“result”,“timestamp”:“2022-02-20T09:20:02Z”,“ping”:{“jitter”:0.157,“latency”:2.5649999999999999},“download”:{“bandwidth”:104044380,“bytes”:534511716,“elapsed”:5112},“upload”:{“bandwidth”:73438235,“bytes”:841584580,“elapsed”:12215},“packetLoss”:0,“isp”:“Orange”,“interface”:{“internalIp”:“2a01:cb19:7b9:de00:cf9b:8af5:6490:fdfc”,“name”:“enp0s3”,“macAddr”:“08:00:27:34:06:C2”,“isVpn”:false,“externalIp”:“2a01:cb19:7b9:de00:cf9b:8af5:6490:fdfc”},“server”:{“id”:29542,“host”:“bordeaux3.speedtest.orange.fr”,“port”:8080,“name”:“ORANGE FRANCE”,“location”:“Bordeaux”,“country”:“France”,“ip”:“2a01:cb19:2004:4000::3”},“result”:{“id”:“95f65d94-aa71-4c87-9b91-af11674bf8b0”,“url”:“Speedtest by Ookla - The Global Broadband Speed Test”:true}}

Not sure what’s happening as the above json looks formatted already, instead of "" there are “” characters. Try to run speedtest cli from command line and see if that is what you get. The command that the python script executes is:

speedtest --format=json --precision=4 --server-id=7175 --accept-license --accept-gdpr

and result in my case is:

{"type":"result","timestamp":"2022-02-20T10:09:08Z","ping":{"jitter":1.284,"latency":2.161},"download":{"bandwidth":68744699,"bytes":331946224,"elapsed":4811},"upload":{"bandwidth":14055341,"bytes":214469729,"elapsed":14616},"packetLoss":0,"isp":"Orange Swiatlowod","interface":{"internalIp":"10.144.1.20","name":"em0","macAddr":"68:05:CA:C6:D2:06","isVpn":false,"externalIp":"83.29.138.82"},"server":{"id":7175,"name":"Orange Polska S.A.","location":"Krakow","country":"Poland","host":"kra-o1.speedtest.orange.pl","port":8080,"ip":"80.50.113.170"},"result":{"id":"4602136a-a83d-4a3c-bbf3-0eb8f3521062","url":"https://www.speedtest.net/result/c/4602136a-a83d-4a3c-bbf3-0eb8f3521062"}}

You can then check if that returned result is a proper json (e.g. http://json.parser.online.fr/)

# speedtest-cli ookla bin

sensor:

  - platform: command_line
    name: "Ookla Data"
    command: "/config/apps/speedtest -f json --accept-license --accept-gdpr"
    scan_interval: 1833
    command_timeout: 30
    value_template: >-
        {% set ping = value_json.ping.latency|string %}
        {% set dn = value_json.download.bandwidth|string %}
        {% set up = value_json.upload.bandwidth|string %}
        {{ ping + "," + dn + "," + up }}

template:

  - sensor:

    - name: "Ookla Ping"
      state: >-
        {% set x = states('sensor.ookla_data').split(',')[0] | float(0) %}
        {% if x > 0 %}
          {{ x|round(2) }}
        {% else %}
          {{ states('sensor.ookla_ping') }}
        {% endif %}
      icon: mdi:gauge
      unit_of_measurement: ms
      state_class: measurement

    - name: "Ookla Download"
      state: >-
        {% set x = states('sensor.ookla_data').split(',')[1] | float(0) %}
        {% if x > 0 %}
          {{ (x / 1024 / 1024 * 8)|round(2) }}
        {% else %}
          {{ states('sensor.ookla_download') }}
        {% endif %}
      icon: mdi:gauge
      unit_of_measurement: Mbps
      state_class: measurement

    - name: "Ookla Upload"
      state: >-
        {% set x = states('sensor.ookla_data').split(',')[2] | float(0) %}
        {% if x > 0 %}
          {{ (x / 1024 / 1024 * 8)|round(2) }}
        {% else %}
          {{ states('sensor.ookla_upload') }}
        {% endif %}
      icon: mdi:gauge
      unit_of_measurement: Mbps
      state_class: measurement

#eof

4 Likes

Thank you, I also found the built in integration to be unreliable after I switched to gigabit fiber.
Here is my solution:

sensors.yaml:

# SpeedTest.net CLI
# https://www.speedtest.net/apps/cli
# Template sensors are in configuration.yaml
# Download and test, use binary matching platform, e.g. for x86_64
# wget -qO- https://install.speedtest.net/app/cli/ookla-speedtest-1.2.0-linux-x86_64.tgz | tar xvz
# ./speedtest --accept-license --accept-gdpr
# Copy binary to desired HA config folder
# mkdir -p /config/3rdparty/speedtest
# cp ./speedtest /config/3rdparty/speedtest/
- platform: command_line
  name: "SpeedTest CLI Data"
  unique_id: speedtest_cli_data
  # Use the path as configured on your system
  command: "/config/3rdparty/speedtest/speedtest --format=json --accept-license --accept-gdpr"
  # Every 4 hours, 60 * 60 * 4 = 14400
  scan_interval: 14400
  command_timeout: 60
  # Summarize results to stay below string limit and convert to JSON
  value_template: >-
    {{ 
      { 
        "ping": value_json.ping.latency, 
        "download": value_json.download.bandwidth, 
        "upload": value_json.upload.bandwidth 
      }
      | to_json 
    }}

configuration.yaml:

# SpeedTest.net CLI
# https://www.speedtest.net/apps/cli
# Command_line sensor is in sensors.yaml
template:
  - sensor:
    - name: 'SpeedTest CLI Ping'
      unique_id: speedtest_cli_ping
      icon: mdi:speedometer
      # TIME_MILLISECONDS: Final = "ms"
      unit_of_measurement: ms
      state_class: measurement
      state: "{{ (states('sensor.speedtest_cli_data') | from_json).ping | round(2) }}"
    - name: 'SpeedTest CLI Download'
      unique_id: speedtest_cli_download
      icon: mdi:speedometer
      # DATA_RATE_MEGABITS_PER_SECOND: Final = "Mbit/s"
      unit_of_measurement: Mbit/s
      state_class: measurement
      state: "{{ ((states('sensor.speedtest_cli_data') | from_json).download * 8 / 1000 / 1000) | round(2) }}"
    - name: 'SpeedTest CLI Upload'
      unique_id: speedtest_cli_upload
      icon: mdi:speedometer
      unit_of_measurement: Mbit/s
      state_class: measurement
      state: "{{ ((states('sensor.speedtest_cli_data') | from_json).upload * 8 / 1000 / 1000) | round(2) }}"
12 Likes

Hi, can you let me know what you have placed in your /config/3rdparty/speedtest/ directory ?

Thanks

It is where I extracted the speedtest CLI binary.
https://install.speedtest.net/app/cli/ookla-speedtest-1.1.1-linux-x86_64.tgz

Ah that’s it all working now, many thanks

The solution I was looking for! Thank you so much!!

Thanks for your yamls. Just set it up on my instance and finally get “reasonable” speeds on a gigabit connection. They should consider making this the official addon.

I might be missing a step, and haven’t got this working. I downloaded the xxx.tgz file on a Windows PC and used an extract tool to open to a xxx.tar file then uncompressed this and copied the three speedtest.* files to \config\3rdparty\speedtest - but I get a file execution error on restart.

I’m only intermediate on Windows/lunix differences…should I have uncompressed the xxx.tgz file using a linux tool under Terminal etc? If so, what exact steps should I take?

Thanks!

You may then need to set the execute bit on the binaries, sudo chmod +x [filename]
I’m away from computer, can double check, else I’ll update weekend with linux shell commands.

Thanks for that. I’m on Rpi 4 so executed the command chmod +x speedtest using Terminal, which changed the file attributes to include executable, but this made no difference to the errors. The HASS Core error log shows the following:

TemplateError('JSONDecodeError: unexpected character: line 1 column 2 (char 1)') while processing template 'Template("{{ (states('sensor.speedtest_cli_data') | from_json).ping | round(2) }}")' for attribute '_attr_native_value' in entity 'sensor.speedtest_cli_ping'
12:13:21 PM – (ERROR) helpers/template_entity.py - message first occurred at 12:13:21 PM and shows up 3 times

Error while processing template: Template("{{ (states('sensor.speedtest_cli_data') | from_json).ping | round(2) }}")
12:13:21 PM – (ERROR) helpers/template.py - message first occurred at 12:13:21 PM and shows up 3 times

Command failed (with return code 2): /config/3rdparty/speedtest/speedtest --format=json --accept-license --accept-gdpr
12:13:10 PM – (ERROR) command_line

After a bit of research I thought the unexpected character could be double quotes in the command line, so I replaced these with a single quotes but it made no difference. If I try and execute the command under terminal I get the error speedtest: command not found, but I have the three files shown in the right directory.

Another issue this seems to create (even after a reboot, after removal of all files and config) is the HASSOS File Editor no longer displays any files under \config directory. The files are still visible on a network Windows PC, and can be accessed using File Editor “history” button if used recently.

I’m traveling on vacation, will verify in two days if you can wait if not figuring it out.
Test running just the speedtest commandline to verify it is at least running.

Thanks again, I’ll keep playing and see if anyone else has some hints. Typing speedtest at the commandline (in \config\3rdparty\speedtest directory) also fails with a command not found error. Three files are there: speedtest, speedtest.5, and speedtest.md.

As a check, if you downloaded the speedtest binary for x86_64, that is for an Intel platform and I don’t think that will work on an RPi 4. You’ll need one of the ARM binaries (not sure which one).

UPDATE: Looking at this link, you’ll probably need the aarch64 binary

Ahhh, thankyou…the x86_64 extension in the filename is definitely a hint isn’t it! I’ve downloaded the aarch64 binary from Speedtest CLI: Internet speed test for the command line and extracted it, then set it as executable.

Maybe a really dumb question, but what’s the syntaxt to test / execute the commands within the Terminal app at the CLI? I get a “speedtest: not found” error or “speedtest: command not found” error. I worked it out; needed to rename the file adding a .bin extension then execute using ./speedtest.bin.

Thanks to everyone for their assistance, I have it working on my RPi4 now. Summary of steps I used (for others on same platform, assume you have Terminal installed and Samba for a connected Windows 10/11 PC, with a drive mapped for \config):

On PC:

  1. Download compressed .tgz image from Speedtest CLI: Internet speed test for the command line, which for the RPi4 is under “Linux download / aarch64”.
  2. Open image (content is a .tar file).
  3. Connect to your HASSOS RPi, and under \config create folders \3rdparty\speedtest.
  4. Extract .tar file contents (3 files, speedtest.*) to the new directory \config\3rdparty\speedtest .

On HA, use Terminal:

  1. I had to make speedtest executable using chmod +x speedtest .
  2. Execute speedtest (to accept licences, typing “YES”) using ./speedtest .

Using HA File Editor, add all YAML config as shown in @ptr727’s post above Add the official speedtest cli - #27 by ptr727.

Lastly, restart HASSOS then add the new entities (I use dashboard option to locate “Unused entities”) to a card.

3 Likes