Introduction: Android/iOS App to Access Your OpenWrt Router Remotely

About: Blynk Co-Founder, Senior Engineer, Public Speaker, Dreamer and Doer

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 ;)

Step 1: I Assume, That You Have OpenWrt Already....

I had to install OpenWrt first... Mostly, I followed this guide (specific for this router model):
https://dzone.com/articles/hacking-into-xiaomi-mi-...

While working on this, I found this awesome video: Openwrt installation, WiFi benchmark, Girlfriend Flashing.
Wow 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.

Also, 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

Step 2: 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.

Step 3: Creating a Minimal App

Getting started with Blynk and Lua is as easy as:

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:

Connecting...
SSL handshake...
Ready.

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

Step 4: 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:

IP address       HW type     Flags       HW address            Mask     Device
192.168.10.206   0x1         0x2         78:02:f8:fb:d6:bf     *        br-lan
194.----------   0x1         0x2         4c:5e:0c:14:e0:5c     *        eth0.2
192.168.10.162   0x1         0x0         04:b1:67:2f:e3:74     *        br-lan

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

Let's try it:

root@router:~/lua-blynk# cat /proc/net/arp | grep br-lan | grep 0x2 | wc -l
1

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}

Step 5: 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.

Step 6: 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.

Step 7: 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

Step 8: 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}

Step 9: 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.

Step 10: 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

Step 11: Conclusion & Further Ideas

You can scan this QR to get the clone of my Blynk Project.
It requires some energy points (4600), as it uses lot's of widgets!

Find Full Lua code here:
https://gist.github.com/vshymanskyy/3d3388a4e17f44...

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?