Intro

I recently bought a new router (Xiaomi Mi Router 3G). And of course, this new, awesome piece of hardware inspired me to start working on this project ;)

The result is above all expectations!

I had to install OpenWrt first...

Mostly, I followed this guide (specific for this router model):

https://dzone.com/articles/hacking-into-xiaomi-mi-router-3g-and-openwrt-firmw

While working on this, I found this awesome video: Openwrt installation, WiFi benchmark, Girlfriend Flashing.

I laughed so hard! 😂

Attention! Installing OpenWrt can brick your router. But once completed, it unlocks full power and control. I'm not brave enough to provide any instructions here, as they may be different for every router model.

But if you already have OpenWrt on your router, you'll be able start with this tutorial in notime. BTW, some development boards come with OpenWrt out-of-the-box, like Onion Omega, VoCore, LinkIt Smart 7688 and others.This tutorial also explains some basic ideas behind creating such apps, so you could easily adapt it to work with Raspberry Pi and the likes.

For this project, I'll be mostly using pre-installed software (available on any OpenWrt-enabled router). But for some advanced functionality, I had to install additional packages. This is done in just a few clicks, so I'll include the instructions here.

Skills Required

I assume that you already know:

How to open/use SSH terminal to your OpenWrt router

How to upload/edit files on your router (use FileZilla or scp/sftp)

How to work with Linux console

Software and tools

On the smartphone side, I'm using Blynk. It provides iOS and Android apps to control any hardware. You can easily build beautiful graphic interfaces for all your projects by simply dragging and dropping widgets, right on your smartphone.

Blynk is mostly used with Arduino, Raspberry Pi, etc. But why not running it on the router itself? ;)

On the device side I'll be using Lua to script the needed functionality.

I could also use Python or Node.js, but unfortunately these options are not always available, due to lack of resources on some routers. Or C/C++, but it's not so convenient to work with (recompiling for every change, etc.)

On the other hand, Lua is pre-installed, is simple to use and learn. It's used by the default web interface, LuCI .

Creating a minimal App

Getting started with Blynk and Lua is as easy as:

Download the Blynk App ( App Store , Google Play )

App Store Google Play Create a new project and get the Auth Token

Follow the Blynk Lua installation instructions for OpenWrt .

Use SSH to access you router console. After running the default example:

lua ./examples/client.lua <your_auth_token>

We should see something like this:

Which means the secure, bi-directional connection to the app is established!

YAY!

We can now easily extend the provided example, so it does something interesting. I have created a copy of this example to edit it:

cp ./examples/client.lua ./blynkmon.lua

Adding some info: Number of Clients, WAN IP Address, Uptime

The basic idea is to get the info from the OS periodically, perform some simple computations if needed, and then send the result to Blynk for display.

In Linux/OpenWrt, we have several ways of getting the system data:

Run a command, and parse the text it outputs

Run a command, and watch the exit code it returns

Read a system file, located in /proc/ and /sys/class/ directories

Now I want to display the number of connected devices.

When I run cat /proc/net/arp on the console, it outputs the list of known devices, along with their MAC and IP addresses.

We can parse it directly in Lua, but it is often easier to use specialized utilities. On Linux, these are grep, head, tail, cut, wc, awk.

To get number of clients from arp output, I need to filter the table (remove unrelated items) and count the table rows, which results in the following command:

cat /proc/net/arp | grep br-lan | grep 0x2 | wc -l

Great. We now get the idea of how we can collect all the required info. Let's automate it.

To make our code clean and extensible, let's create some helper functions:

function exec_out(cmd) local file = io.popen(cmd) if not file then return nil end local output = file:read('*all') file:close() print("Run: "..cmd.." -> "..output) return output end function read_file(path) local file = io.open(path, "rb") if not file then return nil end local content = file:read "*a" file:close() print("Read: "..path.." -> "..content) return content end

Using these utilities, we can now implement the actual data fetching functions:

function getArpClients() return tonumber(exec_out("cat /proc/net/arp | grep br-lan | grep 0x2 | wc -l")) end function getUptime() return tonumber(exec_out("cat /proc/uptime | awk '{print $1}'")) end function getWanIP() return exec_out("ifconfig eth0.2 | grep 'inet addr:' | cut -d: -f2 | awk '{print $1}'") end

You can run parts of these shell commands, to gain deeper understanding of how it works, and to adjust it to your needs.

The easiest part is sending the data to the Blynk App. The default example already sets up the timer, which runs some code every 5 seconds, so we just reuse it:

local tmr1 = Timer:new{interval = 5000, func = function() blynk:virtualWrite(10, getArpClients()) blynk:virtualWrite(11, string.format("%.1f h", getUptime()/60/60)) blynk:virtualWrite(12, getWanIP()) end}

In the app, we add 3 label widgets, and assign them to Virtual Pins 10, 11, 12 accordingly.

While this works, it is rather inefficient, as WAN IP or number of clients do not update so frequently. Let's fix this.

For WAN IP, we move it to connected handler. It will be run every time the router establishes connection to Blynk Cloud. This should be sufficient:

blynk:on("connected", function() print("Ready.") blynk:virtualWrite(12, getWanIP()) end)

For Uptime and Clients Number, we create a separate timer with 5 min. interval:

local tmr2 = Timer:new{interval = 5*60*1000, func = function() blynk:virtualWrite(10, getArpClients()) blynk:virtualWrite(11, string.format("%.1f h", getUptime()/60/60)) end}

WiFi control: ON/OFF

Up until now, we were only getting some info from the device.

Let's try controlling it!

blynk:on("V20", function(param) if param[1] == "1" then os.execute("wifi up") else os.execute("wifi down") end end)

On the app side, I just added a Button widget (mode: Switch) and assigned it to V20.

That's it. Amazing.

System Stats chart

function getCpuLoad() return tonumber(exec_out("top -bn1 | grep 'CPU:' | head -n1 | awk '{print $2+$4}'")) end function getRamUsage() return tonumber(exec_out("free | grep Mem | awk '{print ($3-$7)/$2 * 100.0}'")) end

We also need to send the data to Blynk (let's use tmr1 again):

local tmr1 = Timer:new{interval = 5000, func = function() blynk:virtualWrite(5, getCpuLoad()) blynk:virtualWrite(6, getRamUsage()) end}

On the App Side, add SuperChart widget. Add CPU, RAM datastreams and assign to V5, V6.

Result:

HDD spinning status

My router has an external HDD drive connected as a Network Attached Storage device.

The thing is, this drive is configured to start spinning when someone accesses it, and to suspend after a timeout.

Obviously, it would be cool to know how many times it turns on throughout a day.

So I added another datastream to my System chart.

It's a little bit more tricky to get the status of the HDD drive, but I found a way!

First of all, install smartmontools from the SSH console:

opkg update opkg install smartmontools

Then, in our code, we need to run a special command and check the exit code:

function exec_ret(cmd) local exit = os.execute(cmd) print("Run: "..cmd.." -> exit:"..exit) return exit end function getHddSpinning() if exec_ret("smartctl --nocheck=standby --info /dev/sda > /dev/null") == 0 then return 1 else return 0 end end

Note: my HDD is /dev/sda

Network Activity chart

We Create another SuperChart widget (similar to previous one), Add TX and RX datastreams, and assign to V1 and V2.

Note: I want to display WAN port statc, and my WAN port is eth0.2

Helper functions:

function getWanRxBytes() return tonumber(read_file("/sys/class/net/eth0.2/statistics/rx_bytes")) end function getWanTxBytes() return tonumber(read_file("/sys/class/net/eth0.2/statistics/tx_bytes")) end

Next, add some code to the same tmr1. This is more complicated, as we only need to compute and display the difference in transmitted/received bytes:

local prevTx, prevRx local tmr1 = Timer:new{interval = 5000, func = function() local tx = getWanTxBytes() local rx = getWanRxBytes() if prevTx and prevTx ~= tx then blynk:virtualWrite(1, tx - prevTx) end if prevRx and prevRx ~= rx then blynk:virtualWrite(2, rx - prevRx) end prevTx = tx prevRx = rx blynk:virtualWrite(5, getCpuLoad()) blynk:virtualWrite(6, getRamUsage()) blynk:virtualWrite(7, getHddSpinning()) end}

Result:

Notifications

I also wanted to be notified when my Router looses power or internet connection.For this, we need Notification widget:

In widget settings, enable "offline notification":

No code needed. But we can also send custom notifications from our code.

Autorun in background

For now the script has to be manually executed, but I want to make it run in background automatically when router is powered up.

This is done by creating a service. Create a file /etc/init.d/blynkmon :

#!/bin/sh /etc/rc.common START=99 STOP= pidfile="/var/run/blynkmon.pid" start() { if [ -f $pidfile ]; then echo "blynkmon already running" exit 0 fi cd /root/lua-blynk lua blynkmon.lua your-auth-token > /dev/null & echo $! > $pidfile } stop() { if [ ! -f $pidfile ]; then echo "blynkmon not running" exit 0 fi kill -9 $(cat $pidfile) rm $pidfile }

Note: do not forget to replace your-auth-token

Then, enable blynkmon service:

service blynkmon enable

Further ideas

So far so good, but here are some ideas I'd like to add in near future.

Add Reboot command. Prevent clicking on it accidentally.

Add Terminal widget to run any linux command.

Add CPU temperature chart.

UPD: Unfortunately OpenWrt currently lacks some drivers for my router model. But it is available for many other routers.

Add notification when a particular device joins/leaves the network.

We already have arp info, now only check the MAC address.



This way, we can monitor and control 3D Printers, Robots, a regular PC/Laptop, Arduino/ESP8266/ESP32/RaspberryPi stuff, Smart Home devices, and virtually anything around.

Let me know if have any other interesting ideas.What do you think about all this?