OpenWRT Metrics & Automations with Home Assistant

January 1, 2024

20 views

Out of the box OpenWRT does not have much to offer for monitoring. You’re left in the blind on CPU and memory usage. As well as, network statistics. We can remediate that by installing a few packages, but the interface isn’t very pretty.

Home Assistant offers a great way to display these metrics and as an additional benefit we can run automation based on whether or not individual devices are connected to the router. For example, when my phone connects to the WiFi, turn on the lights in the house. This article is adapted, modernized and simplified from libe.net. It also assumes you know how to work a terminal, vi(m), and use SSH. There’s some improvements based on my own experience of setting this up for my home.

Home Assistant dashboard showcasing OpenWRT statistics.

What we’ll do

  1. Install OpenWRT Packages: First, we need to install some packages to have OpenWRT start recording and sending metrics to MQTT.

  2. Install MQTT Broker: Next, we’ll set up a message broker, because we need somewhere for these messages to go and act as a dispatcher for anyone else that may want them.

  3. Make a dashboard: Use the newly created entities in Home Assistant to create some useful dashboards.

OpenWRT

First things first. We need to install the packages that allow us to collect the metrics we’re looking for. We’ll be installing the following packages: luci-app-statistics, collectd-mod-mqtt, collectd-mod-rrdtool, collectd-mod-uptime, collectd-mod-conntrack, and collectd-mod-thermal.

Let’s SSH into the router.

ssh [email protected]

Once logged in, lets update our packages and install everything we need.

opkg update
opkg install luci-app-statistics collectd-mod-mqtt collectd-mod-rrdtool collectd-mod-uptime collectd-mod-conntrack collectd-mod-thermal

After installing head on back over to the LuCI and update our paths to the correct location for collectd. Your configuration should look like the below. When it does, save and apply.

Home Assistant dashboard showcasing OpenWRT statistics.

Click on the other tabs and make sure the things you want to report on are enabled. I have the following enabled for their respective sections:

General Plugins Network Plugins Output Plugins
Processor Conntrack RRDTool
System Load Interfaces Network
Memory Wireless
Thermal
Uptime

Once that’s done head back to your terminal and create our mqtt configuration at, /etc/collectd/conf.d/mqtt.conf:

cd /etc/collectd
mkdir conf.d
cd conf.d
vi mqtt.conf

Your mqtt.conf should look like the following:

LoadPlugin mqtt
<Plugin "mqtt">
  <Publish "OpenWRT">
    Host "192.168.1.xxx" # Your MQTT broker IP address
    Port 1883
    User "openwrt"
    Password "YourSuperSecretPassword"
    ClientId "OpenWRT"
    Prefix "collectd"
    Retain true
  </Publish>
</Plugin>

Reboot your router, and let’s get started setting up the MQTT broker.

Setup an MQTT Broker

The easiest way to set up an MQTT broker is using the official image that Eclipse Foundation provides on Docker hub.

First, we must supply the container a local configuration file without it the container will simply exit. So, somewhere on your container host’s file system, lets create a mosquitto.conf. It should look like the below.

allow_anonymous false
listener 1883
listener 9001
protocol websockets
persistence true
password_file /mosquitto/config/pwfile
persistence_file mosquitto.db
persistence_location /mosquitto/data/

Now, we can start our container.

docker run -p 1883:1883 -p 9001:9001 -v mosquitto.conf:/mosquitto/config/mosquitto.conf eclipse-mosquitto

Note: if the mosquitto configuration (mosquitto.conf) was modified to use non-default ports, the docker run command will need to be updated to expose the ports that have been configured.

Validate that you have a running container, and now let’s get a terminal into it.

docker exec -it eclipse-mosquitto

and run the following commands to set up the username and password for the broker.

mosquitto_passwd -c /mosquitto/config/pwfile openwrt # or whatever username you chose earlier
chmod 0700 /mosquitto/config/pwfile
chown root /mosquitto/config/pwfile

Once and the correct permissions have been set on pwfile, head on over to Home Assistant to validate that we’re receiving messages from the router and that the broker is passing them to Home Assistant.

Sweet Dashboards for Router Bandwidth and Performance Data

Once back in Home Assistant head on over to Settings > Devices & services. Then add the MQTT integration. Next, click on the MQTT card, and click “Configure”.

Home Assistant Settings for MQTT
Home Assistant Settings for MQTT

On the next screen lets validate that we’re receiving messages by pressing “Listen to” and setting the topic to collectd/OpenWrt/#:

MQTT Messages

Now that we’ve validated that we’re receiving messages, the next step is to create our entities in Home Assistant. To do so open up your HomeAssistant configuration.yaml. Below is my configuration that you can use as a sample, please note you will need to modify this for your specific router. Different models will output different metrics, have different interface names, etc.

# OpenWRT collectd
mqtt:
  sensor:
    - name: OpenWRT RAM buffered
      state_topic: collectd/OpenWrt/memory/memory-buffered
      unit_of_measurement: MB
      value_template: "{{ value.split(':')[1].split('\x00')[0] | float / 1000000 }}"
      unique_id: ram_buffered
    - name: OpenWRT RAM free
      state_topic: collectd/OpenWrt/memory/memory-free
      unit_of_measurement: MB
      value_template: "{{ value.split(':')[1].split('\x00')[0] | float / 1000000 }}"
      unique_id: ram_free
    - name: OpenWRT RAM cached
      state_topic: collectd/OpenWrt/memory/memory-cached
      unit_of_measurement: MB
      value_template: "{{ value.split(':')[1].split('\x00')[0] | float / 1000000 }}"
      unique_id: ram_cached
    - name: OpenWRT RAM used
      state_topic: collectd/OpenWrt/memory/memory-used
      unit_of_measurement: MB
      value_template: "{{ value.split(':')[1].split('\x00')[0] | float / 1000000 }}"
      unique_id: ram_used

    # load
    - name: OpenWRT L1
      unit_of_measurement: load
      state_topic: collectd/OpenWrt/load/load
      value_template: "{{ value.split(':')[1] | float }}"
      unique_id: L1
    - name: OpenWRT L5
      unit_of_measurement: load
      state_topic: collectd/OpenWrt/load/load
      value_template: "{{ value.split(':')[2] | float }}"
      unique_id: L5
    - name: OpenWRT L15
      unit_of_measurement: load
      state_topic: collectd/OpenWrt/load/load
      value_template: "{{ value.split(':')[3].split('\x00')[0] | float }}"
      unique_id: L15

    # wan interface
    - name: OpenWRT wan errors
      state_topic: collectd/OpenWrt/interface-eth1/if_errors
      unit_of_measurement: packets
      value_template: "{{ value.split(':')[1] | int + value.split(':')[2].split('\x00')[0] | int }}"
      unique_id: br-wan-errors
    - name: OpenWRT wan dropped
      state_topic: collectd/OpenWrt/interface-eth1/if_dropped
      unit_of_measurement: packets
      value_template: "{{ value.split(':')[1] | int + value.split(':')[2].split('\x00')[0] | int }}"
      unique_id: br-wan-dropped
    - name: OpenWRT wan TX Mbits
      state_topic: collectd/OpenWrt/interface-eth1/if_octets
      unit_of_measurement: Mbits
      value_template: "{{ value.split(':')[2].split('\x00')[0] | float * 8 / 1048576 }}"
      unique_id: br-wan-tx-transfer
    - name: OpenWRT wan RX Mbits
      state_topic: collectd/OpenWrt/interface-eth1/if_octets
      unit_of_measurement: Mbits
      value_template: "{{ value.split(':')[1] | float * 8 / 1048576 }}"
      unique_id: br-wan-rx-transfer
    - name: OpenWRT wan packets
      state_topic: collectd/OpenWrt/interface-eth1/if_packets
      unit_of_measurement: packets/s
      value_template: "{{ value.split(':')[1] | int + value.split(':')[2].split('\x00')[0] | int }}"
      unique_id: br-wan-packets

    # br-lan interface
    - name: OpenWRT br-lan errors
      state_topic: collectd/OpenWrt/interface-br-lan/if_errors
      unit_of_measurement: packets
      value_template: "{{ value.split(':')[1] | int + value.split(':')[2].split('\x00')[0] | int }}"
      unique_id: br-lan-errors
    - name: OpenWRT br-lan dropped
      state_topic: collectd/OpenWrt/interface-br-lan/if_dropped
      unit_of_measurement: packets
      value_template: "{{ value.split(':')[1] | int + value.split(':')[2].split('\x00')[0] | int }}"
      unique_id: br-lan-dropped
    - name: OpenWRT br-lan TX Mbits
      state_topic: collectd/OpenWrt/interface-br-lan/if_octets
      unit_of_measurement: Mbits
      value_template: "{{ value.split(':')[1] | float * 8 / 1048576 }}"
      unique_id: br-lan-tx-transfer
    - name: OpenWRT br-lan RX Mbits
      state_topic: collectd/OpenWrt/interface-br-lan/if_octets
      unit_of_measurement: Mbits
      value_template: "{{ value.split(':')[2].split('\x00')[0] | float * 8 / 1048576 }}"
      unique_id: br-lan-rx-transfer
    - name: OpenWRT br-lan packets
      state_topic: collectd/OpenWrt/interface-br-lan/if_packets
      unit_of_measurement: packets/s
      value_template: "{{ value.split(':')[1] | int + value.split(':')[2].split('\x00')[0] | int }}"

    # connections
    - name: OpenWRT connections
      state_topic: collectd/OpenWrt/conntrack/conntrack
      unit_of_measurement: connections
      value_template: "{{ value.split(':')[1].split('\x00')[0] | int }}"
      unique_id: connections

    # wifi main
    - name: OpenWRT 5G clients
      state_topic: collectd/OpenWrt/iwinfo-wlan0/stations
      unit_of_measurement: clients
      value_template: "{{ value.split(':')[1].split('\x00')[0] | int }}"
      unique_id: ap_clients
    - name: OpenWRT 5G TX Mbits
      state_topic: collectd/OpenWrt/interface-wlan0/if_octets
      unit_of_measurement: Mbits
      value_template: "{{ value.split(':')[1] | float * 8 / 1048576 }}"
      unique_id: iwinfo-wlan0-tx-transfer_1
    - name: OpenWRT 5G RX Mbits
      state_topic: collectd/OpenWrt/interface-wlan0/if_octets
      unit_of_measurement: Mbits
      value_template: "{{ value.split(':')[2].split('\x00')[0] | float * 8 / 1048576 }}"
      unique_id: iwinfo-wlan0-rx-transfer_1
    - name: OpenWRT 2.4G clients
      state_topic: collectd/OpenWrt/iwinfo-wlan1/stations
      unit_of_measurement: clients
      value_template: "{{ value.split(':')[1].split('\x00')[0] | int }}"
      unique_id: ap_clients_2
    - name: OpenWRT 2.4G TX Mbits
      state_topic: collectd/OpenWrt/interface-wlan1/if_octets
      unit_of_measurement: Mbits
      value_template: "{{ value.split(':')[1] | float * 8 / 1048576 }}"
      unique_id: iwinfo-wlan1-tx-transfer_2
    - name: OpenWRT 2.4G RX Mbits
      state_topic: collectd/OpenWrt/interface-wlan1/if_octets
      unit_of_measurement: Mbits
      value_template: "{{ value.split(':')[2].split('\x00')[0] | float * 8 / 1048576 }}"
      unique_id: iwinfo-wlan1-rx-transfer_2

    # temperature
    - name: OpenWRT temp
      state_topic: collectd/OpenWrt/thermal-thermal_zone0/temperature
      unit_of_measurement: °C
      value_template: "{{ value.split(':')[1].split('\x00')[0] | int }}"
      unique_id: temp

Now you should have entities that you can use to create widgets with. I ended up using mini-graph-card to build out the widgets for my network dashboard.

Here’s an example of a card using mini-graph-card:

type: custom:mini-graph-card
name: Router Load
entities:
  - sensor.openwrt_l1
  - sensor.openwrt_l5
  - sensor.openwrt_l15
show:
  icon: false