wl520gu

I’ve been wanting to make my Arduino network accessible for awhile, now, and the idea of hacking a wireless-G router and loading some custom firmware like DDWRT was appealing. I happened upon a deal on an Asus wl520gu on craigslist ($20), so now the fun begins…

The Hardware

The wl520gu was acceptable for this hack, because it has solder points for a serial connection on its mainboard.  Step one, then, was making this serial connection available to connect to an Arduino.  The serial connections are 4 points in the empty space of the PCB.  I connected the bottom three (from the bottom: gnd, tx, rx).  The top connection is 3.3V, and since I wanted to run the Arduino off of the router power supply, I need 5V, which I pulled off the barrel connector at the bottom of the board.

pcb / bottom

After making all of the connections, I routed the wires out an opening in the bottom of the case and put it all back together.

casebottom

… and connected 5V / GND from the wl520 to Vcc / GND on the Arduino and TX / RX from the wl520 to RX / TX on the Arduino.

w/arduino

The “Software”

Decided against DDWRT, since I can’t determine from the website whether or not I could make changes to the source if I needed to. That being the case, I found OpenWRT and am impressed. I followed the instructions for building my own image, but so far that was unnecessary as I haven’t changed a thing, so I won’t bother with details.

Next I used the ASUS “Firmware Restoration” utility to load openwrt-brcm-2.4-squashfs.trx to the router. This was a bit difficult because I had a wireless connection as well as the wired connection to the router active on my laptop and the wl520gu somehow managed to assign itself an IP on the 192.168.24.0 network used on the wireless, instead of the 192.168.1.0 network configured on the wired nic. I figured this out by loading up wireshark; after making this determination, changed the IP of the wired interface on my laptop also to a 192.168.24.x address and used “route” to add a route to the wl520gu on the wired nic’s IP (192.168.24.10).

route add 192.168.24.49 mask 255.255.255.255 192.168.24.10

OpenWRT configuration

After loading the custom firmware, I connected my laptop to one of the LAN ports on the wl520gu and got an 192.168.1.x IP. The wl520gu initializes with wireless off and a static IP of 192.168.1.1 and I want to configure the wl520 as a wireless client. To do this, I followed these steps:

From PC:

telnet 192.168.1.1

at which point I see:

BusyBox v1.14.4 (2009-11-25 22:41:41 EST) built-in shell (ash)
Enter 'help' for a list of built-in commands.

 _______                     ________        __
|       |.-----.-----.-----.|  |  |  |.----.|  |_
|   -   ||  _  |  -__|     ||  |  |  ||   _||   _|
|_______||   __|_____|__|__||________||__|  |____|
         |__| W I R E L E S S   F R E E D O M
KAMIKAZE (bleeding edge, r18540) ------------------
 * 10 oz Vodka       Shake well with ice and strain
 * 10 oz Triple sec  mixture into 10 shot glasses.
 * 10 oz lime juice  Salute!
---------------------------------------------------
root@OpenWrt:~#

Now from the telnet window:

root@OpenWrt:~# vi /etc/config/wireless

config wifi-device  wl0
       option type     broadcom
       option channel  6
       # REMOVE THIS LINE TO ENABLE WIFI:
       # option disabled 1

config wifi-iface
       option device   wl0
       option network  lan
       option mode     sta
       option ssid     hundred
       option encryption none

Mode sta specifies wireless client, hundred is the ssid of my wireless network, and notice the commenting out of “option disabled 1″. Now to drop the static IP:

root@OpenWrt:~# vi /etc/config/network

note changes to “lan” config:

config 'interface' 'lan'
       option 'type' 'bridge'
       option 'ifname' 'eth0.0'
       option 'proto' 'dhcp'
       #option 'ipaddr' '192.168.1.1'
       #option 'netmask' '255.255.255.0'
       #option 'dns'
       #option 'gateway'

Disable telnet and enable ssh by setting a root passwd:

root@OpenWrt:~# passwd

I also installed X-Wrt, but their wiki is fairly informative and its not necessary for the rest of what’s here, so I’m going to skip that step. After a reboot, I can access the wl520gu from ssh wirelessly; just type “reboot” at the prompt and then check out your dhcp leases to figure out what IP your new wireless client got.

Talking to the Arduino

Since I wanted to communicate with the Arduino using the wl520gu’s serial connection, I started by using ser2net. Actually, first I had to also disable the serial terminal!

root@OpenWrt:~# vi /etc/inittab

#tts/0::askfirst:/bin/ash --login
#ttyS0::askfirst:/bin/ash --login

The version of openwrt I installed has serial at /dev/tts/0, but I commented out both tts/0 and ttyS0 for good measure. Now onto installing ser2net.

root@OpenWrt:~# opkg update
root@OpenWrt:~# opkg install ser2net
root@OpenWrt:~# vi /etc/ser2net.conf

I tried communication with the Arduino at 115200 at first, but there were odd errors, so I reverted to 38400 which now works rather well.  I added this line to the conf file:

3008:telnet:0:/dev/tts/0:38400 NONE 1STOPBIT 8DATABITS LOCAL -RTSCTS

After this setup, I can telnet to the wl520gu and communicate with my Arduino!

telnet 192.168.24.101 3008

Arduino SW

This is nothing fancy and requires quite a bit of improvement, but for now this is what it is. I have a thermistor connected to my Arduino, so I’m going to setup serial commands to read the temperature. Also decided to have a “start” command, because at boot the wl520gu dumps info to the serial port and a response from the Arduino caused boot to fail.

#include <LiquidCrystal.h>
#include <string.h>
// LiquidCrystal display with:
// rs on pin 12
// rw on pin 11
// enable on pin 10
// d4, d5, d6, d7 on pins 5, 4, 3, 2
LiquidCrystal lcd(12, 11, 10, 9, 8, 3, 2);
int analogPin = 0;
int val;
void setup()
{
  // Print a message to the LCD.
  //lcd.print("hello, world!");
  delay(500);
  Serial.begin(38400);
}
void loop()
{
#define MAX_CMD_LENGTH 20
  char text[17];
  double ri = 0.0986176265;
  double temp;
  static char cmds[MAX_CMD_LENGTH];
  static int cmdIdx = 0;
  static int celsius, fahrenheit;
  static char ready=0;
  int count = 0;

  long R;

  // only update temp read every 256ms (thereabouts)
  if ((count % 256) == 0)
  {
     val = analogRead(analogPin);    // read the input pin
     lcd.clear();
     lcd.setCursor(0,0);
     sprintf(text, "ADC read= %d", val);
     lcd.print(text);
     lcd.setCursor(0,1);

     R = ((long)val * 10000)/(1023-val);
     temp = 34350 / (log(R / ri));

     celsius = (int)temp - 2730;
     fahrenheit = (celsius*9)/5 + 320;

     sprintf(text, "     Temp=% 3d.%dF", fahrenheit/10, fahrenheit%10);
     lcd.print(text);
  }
  if (Serial.available())
  {
     int incomingByte = 0;
     while (incomingByte != -1)
     {
       // read the incoming byte:
       incomingByte = Serial.read();
       if (incomingByte == -1)
         continue;
       if (cmdIdx+1 >= MAX_CMD_LENGTH)
       {
         cmdIdx = 0;
       }
       switch (incomingByte)
       {
         case 13:
          cmdIdx = 0;
          break;
         case '?':
          cmds[cmdIdx]=0;
          cmdIdx = 0;
          if (strcasecmp(cmds, "QWERTYUIOP")==0)
          {
            ready =1;
            Serial.println("ready");
          }

          if (ready)
          {
            if (strcasecmp(cmds, "TEMPF")==0)
            {
              sprintf(text, "TEMPF:%d.%dF", fahrenheit/10, fahrenheit%10);
              Serial.println(text);
            }
            if (strcasecmp(cmds, "TEMPC")==0)
            {
              sprintf(text, "TEMPC:%d.%dC", celsius/10, celsius%10);
              Serial.println(text);
            }
            if (strcasecmp(cmds, "ADC")==0)
            {
              sprintf(text, "ADC:%d", val);
              Serial.println(text);
            }
          }
         break;
         default:
          cmds[cmdIdx++]=(incomingByte&0xFF);
       }
       //Serial.print("Incoming: ");
       //Serial.println(incomingByte, DEC);
     }
  }
  ++count;
  delay(1);
}

So after boot the sequence will be:

 wl520->arduino: ?qwertyuiop?
 arduino->wl520: ready
 wl520->arduino: ?tempf?
 arduino->wl520: TEMPF:xx.xF
 wl520->arduino: ?tempc?
 arduino->wl520: TEMPC:xx.xC
 ...

Obviously the protocol needs some work, but this was step 1.

WL520GU Scripts

Now to get the temp values directly from a script on the wl520. I usually would use Perl for this, and as a matter of fact, this is pretty much my first Bourne shell script. The hope is to get a rudimentary lock to prevent simultaneous access via a cron job and a cgi script:

root@OpenWrt:~# vi /root/currtemp.sh

while [ "$(ls -A /root/currtemp.lck 2> NUL)" ] ; do
  sleep 1s
done
mkdir /root/currtemp.lck 2> NUL
touch /root/currtemp.lck/$$.lck

(echo ?tempf? > /dev/tts/0) && temp=$(grep -m 1 TEMPF /dev/tts/0 | cut -d : -f 2)
temp2=$(date | cut -d " " -f 4)
echo $temp2 = $temp  

rm -r /root/currtemp.lck

I wanted to use cron to read the temperature regularly and store it to a log file, but cron was disabled, so the following was necessary:

root@OpenWrt:~# /etc/init.d/cron enable

And here’s my crontab:

root@OpenWrt:~# crontab -e

#.---------------- minute (0 - 59)
#|  .------------- hour (0 - 23)
#|  |  .---------- day of month (1 - 31)
#|  |  |  .------- month (1 - 12) OR jan,feb,mar,apr ...
#|  |  |  |  .---- day of week (0 - 6) (Sunday=0 or 7)  OR sun,mon,tue,wed,thu,f
#|  |  |  |  |
#*  *  *  *  *  command to be executed

0,20,40 * * * * /root/gettemp.sh
0 0 * * * echo -n "" > /var/log/temp.log
0,5,10,15,20,25,30,35,40,45,50,55 * * * * rdate time-a.nist.gov

Ahh .. and since it’s in the crontab, should mention that the wl520 loses time quickly .. I’m guessing this is because it is supposed to run at 240MHz, and yet I see reports that the open source firmwares are only able to run the clock at 200MHz. I installed ntpd via opkg, but didn’t see that it was working, so I force a time update using rdate from the crontab.

And here’s /root/gettemp.sh:

/root/currtemp.sh >> /var/log/temp.log

Not sure if httpd was configured before I installed x-wrt; I’m guessing not, so that may be a pre-requisite, but here’s my shell script for providing some access to my temperature log:

root@OpenWrt:~# vi /www/cgi-bin/temp.sh

#!/bin/sh
echo -en "Content-Type: text/html\r\n\r\n";
cat <<EOF
<html>
<head>
<title>Temperature Page; made possible by an Arduino</title>
<!-- meta http-equiv="refresh" content="5"; URL=/cgi-bin/temp.sh" -->
</head>
EOF
echo \<body\>
echo \<h1\>Temperature Log\</h1\>
echo -en "<h2>Current</h2>";
echo \<hr\>
one="-"
two="-"
echo "<table border=1>"
for i in `/root/currtemp.sh` ; do
  three="$two"
  two="$one";
  one="$i"
  if [ $two = "=" ] ; then
    echo -en "<tr><td>\r\n"
    echo -en "  $three   </td><td>\r\n";
    echo -en "  $one  "
    echo -en "</td></tr>\r\n"
  fi
done
echo "</table>";
echo -en "<h2>History</h2>";
echo \<hr\>
one="-"
two="-"
echo "<table border=1>"
for i in `cat /var/log/temp.log` ; do
  three="$two";
  two="$one";
  one="$i";
  if [ $two = "=" ] ; then
    echo -en "<tr><td>\r\n"
    echo -en "  $three   </td><td>\r\n"
    echo -en "  $one  "
    echo -en " </td></tr>\r\n"
  fi

done
echo "</table>"
echo \<hr\>
echo \</body\>\</html\>

.. and to make the log accessible to the public:

root@OpenWrt:~# vi /etc/httpd.conf

/cgi-bin/webif/:root:$p$root
/cgi-bin/webif/:admin:$p$root

/cgi-bin/::

.asp:text/html
.svg:image/svg+xml
.png:image/png
.gif:image/gif
.jpg:image/jpg
.js:application/x-javascript

Example Use:

From ssh:

terminal

From browser:

browser

References:

Obviously I didn’t figure all of this out on my own.  Here are some references to material I used along the way!

http://www.gumbolabs.org/2009/10/08/hacking-the-asus-wl-520gu-w-openwrt/

http://www.ooblick.com/text/sh/