I recently bought a new router (Xiaomi Mi Router 3G). And of course, this awesome piece of hardware inspired me to start working on this project ;)
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 to start with this tutorial in no time. 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 like.
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 RequiredI 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
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 run it on the router? ;)
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 a 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 and is simple to use and learn. It's used by the default web interface, LuCI
.
Getting started with Blynk and Lua is as easy as:
- Download the Blynk App (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 your router console. After running the default example:
lua ./examples/client.lua <your_auth_token>
We should see something like this:
This means a 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, UptimeThe 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 command, and parse the text it outputs
- Run 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 the 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 a deeper understanding of how it works, and 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 the number of clients does 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 a connection to the 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/OFFUp 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 chartfunction 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 them to V5, V6.
Result:
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
We Create another SuperChart widget (similar to the previous one), Add TX and RX datastreams, and assign them to V1 and V2.
Note: I want to display WAN port stats, 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:
I also wanted to be notified when my Router loses power or internet connection. For this, we need a Notification widget:
In widget settings, enable "offline notification":
No code is needed. But we can also send custom notifications from our code.
Autorun in backgroundFor now, the script has to be manually executed, but I want to make it run in the background automatically when the 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
cd /root/blynk-library-lua/
lua blynkmon.lua your-auth-token > /dev/null &
echo $! > $pidfile
else
echo "blynkmon already running"
fi
}
stop() {
if [ -f $pidfile ]; then
kill -9 $(cat $pidfile)
rm $pidfile
else
echo "blynkmon not running"
fi
}
Note: do not forget to replace your-auth-token and blynk-library-lua path.
Then, enable blynkmon
service:
chmod a+x /etc/init.d/blynkmon
service blynkmon enable
service blynkmon start
Further ideasSo 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, regular PC/Laptops, 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?
Comments