Wednesday 26 June 2019

Internet over Easy: TP-Link MR3420 as 3G Failover

The TP-Link MR-3420 takes a 3G USB Modem
(2020-01-22 Update): See here for an updated version.
The last six months I have been building and installing Internet of Things devices around the house and my nearby office. I run them from a single 8Mbps ADSL copper line from the Telekom Malaysia (TM), which connects to a TP-Link Archer D50 and which in turn acts as WAN for a TP-Link MR-3420. This provides ADSL with a failover to 3G.

TP-Link Archer D50 is a budget ADSL WiFi modem router

Now I did not really put much thought in selecting TP-Link or other models. They happened to be what was available in the Seremban stores over the years and I just happened to buy one. Or rather, a few, as I live on top of a little hill and the house tends to get struck by lightning a few times a year, usually killing the ADSL modem. I usually preset each modem with identical settings so that they can be swapped out in a few minutes.

If we are at home during a lightning storm we often disconnect the modem from the ADSL line as this seems to save it from getting destroyed in a strike. There is little to lose as the ADSL line often cuts out during an electrical storm anyway. When this happens, the MR-3420 fails over to a 3G connection using an MA260 3G USB dongle.

TP-Link MA260 3G USB dongle

The MR-3420 has an ADSL to 3G failover function:

MR-3420 ADSL to 3G failover
Now both the D50 and MR-3420 can put out WiFi hotspots but only the MR-3420 hotspot is backed up over 3G. This became really useful with the IoT devices because losing your Internet connection now meant losing access to your lights, fans and other IoT gizmos.

What's more being reliant on Google Assistant for reminders and note-taking makes you really feel the loss of an Internet connection. The failover to 3G is quite smooth and you really only notice a change in speed and responsiveness of the Internet. It costs me an extra RM29 a month for a basic 3GB data-only 3G connection.

When we went away for a week-long holiday we noticed that the MR-3420 reliably fails over, but will not fail back. Which means once it cuts over to the backup 3G line it will not cut back to the restored ADSL connection. This especially happens if the ADSL (ie WAN) connection went down but the network connection with the D50 is still up. Upgrading the firmware did not seem to help. However, disconnecting and reconnecting  the LAN cable to the D50 will result in a failback.

We used to get by 'failing back' by reconnecting the LAN cable by hand, but of late the ADSL has taken to intermittent disconnects several times a day. This pretty much leaves the MR-3420 in failover mode permanently and messing up our IoT chi.

Now I can probably buy a proper modem with 3G failover (and working failback!), but I am loath to junk electronics that still have life in them, especially having bought a few identical spare units. Ideally we get it to work by adding a little more junk ...

I have an orphaned Raspberry Pi Model B+ after having upgraded to the superlative Raspberry Pi 3 B+.

A picture of the Raspberry Pi B+ might seem redundant, but the various models can be hard to distinguish


 I also happened to have a Piface Digital 2, made redundant by those cheap ESP8266 relay boards.

PiFace Digital 2


And if I threw in a D-Link DES-1005A LAN switch, I can use traceroute to detect a restored ADSL connection to the D50 and use the Piface Digital 2 to power-cycle the LAN connection  (DES-1005A) between the D50 and the MR-3420. This should cause the MR-3420 to fail back.


D-Link DES-1005A uses a 5Vdc power supply
Now you can use any LAN switch you like, but the DES-1005A only requires 5Vdc at 60mA or so, which means the whole setup will run off the Raspberry Pi power supply (via a USB port). You can just as easily switch the mains power to the DES-1005A, but not having lethal voltages exposed (around the Piface connector) makes things a lot safer.

The D50 is necessary because the MR-3420 does not have an ADSL modem.

To prepare any Raspberry Pi, you just need to set it up with an Internet connection (in my case a D-Link DWA-123 USB WiFi dongle) and do:

# apt-get update
# apt-get upgrade

Next, using raspi-config I set it up to log into my D50 hotspot whose IP address for the purpose of this post is 'xx.xx.xx.1'. Next set up the Pi's Ethernet connection to the MR-3420, whose IP is 'yy.yy.yy.1'.  In your /etc/dhcpcd.conf add:

interface eth0
static ip_address=yy.yy.yy.2/24

interface wlan0
static ip_address=yy.yy.yy.2/24
static routers=yy.yy.yy.1
static domain_name_servers=8.8.8.8

Now set it so that access to 8.8.8.8 is routed via the TL-MR3420:

route add -host 8.8.8.8 gw yy.yy.yy.1 dev eth0
On reboot, your primary link to the Internet is now via the D50 WiFi hotspot. You are also linked to the MR-3420 via Ethernet. 

Now set up the Piface Digital 2. There is a slight hitch, but we can get it to work. Connect the DES 1005A LAN switch 5V line to Relay0:

Piface Digital 2 Pinout. Only Relay0 NC and CO are used
The 0V return line does not need to be switched.

The Piface Digital 2's program is simplicity itself;

# cat relay1.py
from time import sleep
import pifacedigitalio

DELAY = 2.0  # seconds

if __name__ == "__main__":
    pifacedigital = pifacedigitalio.PiFaceDigital()
    #  power-cycle LAN switch
    pifacedigital.leds[0].turn_on()
    sleep(DELAY)
    pifacedigital.leds[0].turn_off()

You run it using the command:

# python3 ./relay1.py

Now we will need to detect a failover condition. We try to connect to an always available server, say Google's DNS server 8.8.8.8. Now the Pi will default to using the D50 WiFi, so to force it to use Ethernet we do:
# route add -host 8.8.8.8 gw yy.yy.yy.1 dev eth0

The traceroute should now produce:
# traceroute 8.8.8.8
traceroute to 8.8.8.8 (8.8.8.8), 30 hops max, 60 byte packets
 1  yy.yy.yy.1 (yy.yy.yy.1)  0.768 ms  0.939 ms  0.612 ms
 2  xx.xx.xx.1 (xx.xx.xx.1)  0.893 ms  0.706 ms  0.630 ms
 3  115.134.254.254 (115.134.254.254)  40.218 ms  42.331 ms  43.880 ms
 4  10.55.106.61 (10.55.106.61)  53.481 ms 10.55.106.63 (10.55.106.63)  53.336 m
s  55.914 ms
 5  10.55.39.196 (10.55.39.196)  55.378 ms 10.55.39.146 (10.55.39.146)  55.988 m
s 10.55.39.196 (10.55.39.196)  57.889 ms
 6  10.55.48.64 (10.55.48.64)  61.153 ms 10.55.48.66 (10.55.48.66)  46.202 ms 10
.55.48.64 (10.55.48.64)  48.096 ms
 7  72.14.197.66 (72.14.197.66)  50.004 ms  51.596 ms  53.118 ms
 8  108.170.249.225 (108.170.249.225)  55.585 ms 108.170.249.241 (108.170.249.24
1)  46.389 ms 108.170.250.1 (108.170.250.1)  49.268 ms
 9  dns.google (8.8.8.8)  50.805 ms  52.660 ms  54.818 ms

Now we only need to look for the D50's IP address so this is sufficient:
# traceroute 8.8.8.8 | grep -w 'xx\.xx\.xx\.1'
 2  xx.xx.xx.1 (xx.xx.xx.1)  0.893 ms  0.706 ms  0.630 ms

Now if the above command produces nothing we now know we are in failover mode and we now need to check if the ADSL is restored. Here is the output of a restored D50:

# ping -c 4 -I wlan0 8.8.8.8 
PING 8.8.8.8 (8.8.8.8) from xx.xx.xx.2 wlan0: 56(84) bytes of data. 
64 bytes from 8.8.8.8: icmp_seq=1 ttl=57 time=57.1 ms 
64 bytes from 8.8.8.8: icmp_seq=2 ttl=57 time=52.6 ms 
64 bytes from 8.8.8.8: icmp_seq=3 ttl=57 time=51.4 ms 
64 bytes from 8.8.8.8: icmp_seq=4 ttl=57 time=49.1 ms 
--- 8.8.8.8 ping statistics --- 4 packets transmitted, 4 received, 0% packet loss, time 3005ms
rttmin/avg/max/mdev = 49.103/52.587/57.140/2.931 ms

Again, when there is no ADSL service there is no output. After confirming ADSL is back we just need to power cycle the DES-1005A LAN switch:

# python3 ./relay1.py

A bash script can do this:

# cat failover
#!/bin/bash
ADSL=$(traceroute 8.8.8.8 | grep -w 'xx\.xx\.xx\.1')
echo "result is "  $ADSL
if  [[ "$ADSL" == "" ]]
then
  date
  echo "Streamyx failed ..."
  ping -c 4 -I wlan0 8.8.8.8
  if [ $? == 0 ]
  then
    echo "Need to cut back!"
    python3 ./relay1.py
  fi
else
  echo "all's well"
fi

And for continuous operation:

# cat failover_loop
#!/bin/bash
echo on
while [ 1 ]
do 
  console_output=$(/home/heong/failover/failover)
  echo $console_output
  echo "Sleeping 60s"
  sleep 60
done


3G failback: from left, TL-MR3420 with MA260 3G dongle, Raspberry Pi with Piface 2 shield, DES-1005A LAN switch and Archer D50

And there you have it. MR-3420 failback. Internet over easy.

Note if you are a moderate Internet user you can get by with just the 3G line and you would not have to bother with this ADSL failover palaver. Or if you are blessed with a fiber optic connection, or any other service provider besides TM ... 

Happy Trails.

Tuesday 25 June 2019

Piface Digital 2 Problem: The Case of the Reluctant Model (Updated 2020-01-16)

Erle Stanley Gardner's 'The Case of the Reluctant Model'
Before the ESP8266, I started off using old Linux laptops, then Raspberry Pis as IoT devices. If it fits the space, power or cost budget, why not? Especially when Linux is a natural for remote operation and runs on nearly everything.

Piface Digital for the Raspberry Pi Model A


I was an early user of the Piface Digital running on the Raspberry Pi Model A. While not the cheapest systems around, you get get IoT systems up up and running very fast, e.g., as motor driver for an IoT robot platform. In addition to the relay outputs, here are also inputs with very handy LED indicators. There was even a PiFace Digital 2 which lets you upgrade to a Raspberry Pi Model B:

Piface Digital 2 for the Raspberry Pi Model B
I bought my Digital 2 and, so it was quite a shock when early in the year I used it with my Raspberry Pi Model B+ and it did not work:

# python3 /usr/share/doc/python3-pifacedigitalio/examples/blink.py
Traceback (most recent call last):
  File "/usr/share/doc/python3-pifacedigitalio/examples/blink.py", line 9, in <module>
    pifacedigital = pifacedigitalio.PiFaceDigital()
  File "/usr/lib/python3/dist-packages/pifacedigitalio/core.py", line 82, in __init__
    self.init_board()
  File "/usr/lib/python3/dist-packages/pifacedigitalio/core.py", line 107, in init_board
    h=self.hardware_addr, b=self.bus, c=self.chip_select))
pifacedigitalio.core.NoPiFaceDigitalDetectedError: No PiFace Digital board detected (hardware_addr=0, bus=0, chip_select=0).

I found the manual from the Farnell website, and all the jumpers checked OK. A quick search of the Internet produced the first clue here. It looks to be an error in the new Piface software. An upgrade to Raspbian had changed the default SPI frequency to 10MHz which is too high for the PiFace Digital 2, so it stopped working out of the box.

This is really Piface's problem - it dropped the ball maintaining the software, but for now, Mike Richards has a good writeup on the solution, which is to get the latest update directly from the Piface github.

So I upgraded and found exactly the same problem! Dropping the ball seems to be getting to be a habit for Piface, or perhaps something went awry during the upgrade.

From the original link, I simply need to specify the SPI speed in the file spi.py. In my installation it came up as /usr/lib/python3/dist-packages/pifacecommon/spi.py

At line 65:
        # create the spi transfer struct
        transfer = spi_ioc_transfer(
            tx_buf=ctypes.addressof(wbuffer),
            rx_buf=ctypes.addressof(rbuffer),
            len=ctypes.sizeof(wbuffer)
        )

Change to
        # create the spi transfer struct
        transfer = spi_ioc_transfer(
            tx_buf=ctypes.addressof(wbuffer),
            rx_buf=ctypes.addressof(rbuffer),
            len=ctypes.sizeof(wbuffer),
            speed_hz=ctypes.c_uint32(15000) # 2019-06-25
        )

And now the example program works and LED7 lit up:

# python3 /usr/share/doc/python3-pifacedigitalio/examples/blink.py

That seems to conclude the Case of the Reluctant Piface Model 2. Later when I checked, the same problem recurs if you upgraded the software to a Piface Digital 1. The same solution applies.

(Update 2020-01-16)
The piface github repository has updated their code, and the proper solution is now:
        # create the spi transfer struct
        transfer = spi_ioc_transfer(
            tx_buf=ctypes.addressof(wbuffer),
            rx_buf=ctypes.addressof(rbuffer),
            len=ctypes.sizeof(wbuffer),
            speed_hz=ctypes.c_uint32(self.speed_hz)
        )

Sadly if you have an up-to-date version of Raspbian, the ugrade instructions no longer work:
$ sudo apt-get install python{,3}-pifacedigitalio
Instead, use:
 pip3 install pifacedigitalio
Or,
 pip install pifacedigitalio

Happy trails.