Jill and I decided it might be fun, and maybe even informative, to repurpose magical artifacts for technological purposes as a final assignment in our Tangible User Interfaces course. I had heard about Eva Hornecker’s work (PDF) in which her students used a divining rod as a sort of “physical sketch” for a tour guide device, and thought that it might be cool to make a divining rod that would help you find strong Wi-Fi signal. So Jill found a nice branch, I wrote a little Python, we both went to town with a Dremel and some rubber bands, et voila: the Wi-Fi Divining Rod. A DC motor in the tip of the rod vibrates more intensely the stronger the signal from the strongest local Wi-Fi network.
It was a lot of fun to make, and even more fun to see people’s reactions. The simple act of exploring the unseen radio landscape of a space might be familiar to anyone who’s walked around watching bars on their cell, but somehow doing so with a big branch made it feel so much more inquisitive, and maybe even a little magical.
Read on for design sketches, code, and the other ideas. But if you don’t, make sure you check out all the other cool projects from this class!
Designs
Materials
- 1 large, Y-shaped branch
- 1 breadboard
- 1 DC motor
- 1 wine cork
- 1 Arduino microcontroller (we used a Arduino NG Rev. C, ATmega168)
- 1 diode (1N4004)
- 1 transistor (TIP120)
- 2 AA batteries and cartridge
- 1 Apple laptop
- jumper wires
Electronic Construction
The circuit was the simple one we used in our lab on DC motors:

We kept the Arduino board rubber banded to the breadboard for simplicity, with a USB cable running to the laptop, and long jumpers running to the DC motor in the branch.
Physical Construction
We found a solid branch thick enough to house the DC motor and used a saw to remove a segment from the top of the end of the single branch of the rod. We used the sanding bit on a Dremel to hollow out a region for the DC motor, leaving a platform for it to sit on. We stuck a disk cut from a wine cork on the end of the motor so it was off-center, causing the motor to vibrate when it spun. We wrapped the motor in thin bubble wrap and glued it into the compartment, re-attaching the segment we sawed off with rubber bands. The wires ran out of a hole we drilled through the branch.
Here’s a pic of the Dremeling process (before we realized the sanding bit was a better bet). I might post some pics of the finished internals soon.
Arduino Code
/*
* Divining Rod Arduino
* Modified from one pot fades one motor by Dave Nguyen
* which was a modified version of AnalogInput
* by DojoDave <http://www.0j0.org>
* http://www.arduino.cc/en/Tutorial/AnalogInput
*/
// serial input vars
char serInString[100];
char cmd;
int motorPin = 9; // select the pin for the Motor
int val;
void setup() {
Serial.begin(9600);
analogWrite(motorPin, 0);
}
void loop() {
readSerialString(serInString, 100);
cmd = serInString[0];
if (cmd == ’s’) { // if we should spin
val = atoi(serInString+1);
analogWrite(motorPin, val);
Serial.print(“Spin: “);
Serial.println(val);
}
resetSerialString(serInString, 100);
delay(200); // Python does weird things if the lines come in too fast
}
//read a string from the serial and store it in an array
//you must supply the array variable
void readSerialString (char *strArray, int maxLength) {
int i = 0;
if(!Serial.available()) {
return;
}
while (Serial.available() && i < maxLength) {
strArray[i] = Serial.read();
i++;
}
}
void resetSerialString (char *strArray, int length) {
for (int i = 0; i < length; i++) {
strArray[i] = ‘\0′;
}
}
Python Code
wifi.py
This is a trivial little module that just snarfs XML on Airport output from a script that ships with OS X. Found out about the script by looking through the AirPort Radar Dashboard widget. You’ll need plistlib, but I think that ships with both MacPython and the MacPorts Python dist. If anyone knows how to do this on another OS, leave a comment!
“”“Module to detect 802.11 WiFi networks on Mac OS X using an AirPort Card.”“”
import plistlib
from os import popen
def scan(cmd=None):
“”“Scan for wifi networks, return a list of network dictionaries.
scan() returns output from a script included in OS X. Each network dict
contains the following keys:
-
@type cmd: str
@param cmd: Shell command to execute to get wifi ntwk info in XML.
@rtype: list
@return: List of network dictionaries.
““”
if cmd == None:
cmd = ‘/System/Library/PrivateFrameworks/Apple80211.framework/Resources/airport -s -x’
try:
ntwks = plistlib.readPlist(popen(cmd))
except:
print “Failed to find networks. The command ‘%s’ may not exist.” % cmd
else:
return ntwks
def strengths(cmd=None, max_rssi=100):
“”“Return networks with only the SSIDs and the signal strenghts.
Each network dict only contains a ‘name’ (SSID) and a ’strength’, where
’strength’ = max_rssi - RSSI*-1. The list of networks is sorted by
strength. max_rssi defaults to 100.
@type cmd: str
@param cmd: Shell command to execute to get wifi ntwk info in XML.
@type max_rssi: int
@param max_rssi: Estimated max abs(RSSI) for your card (it varies).
@rtype: list
@return: List of abbreviated network dictionaries.
““”
ntwks = scan(cmd)
strengths = [{‘name’: ntwk[‘SSID_STR’],
’strength’: (ntwk[‘RSSI’] + max_rssi),
‘encrypted’: ‘WEP’ in ntwk.keys() or ‘WPA_IE’ in ntwk.keys()}
for ntwk in ntwks]
strengths.sort(key=lambda a: a[’strength’], reverse=True)
return strengths
if __name__ == ‘__main__’:
while True:
sths = strengths()
for ntwk in sths:
print “%(name)-30s : %(strength)5s%%” % ntwk
print
diviningrod.py
Again, real simple. The “normalize” method probably isn’t perfect, but it worked for this project.
“””
diviningrod.py
by Ken-ichi Ueda, 2007
““”
import wifi
from optparse import OptionParser
from serial import Serial
ARDUINO_DEV = ‘/dev/tty.usbserial-A4001nLM’ # you need to set this the tty for your own board
def normalize(x, xmin, xmax, nmin=0, nmax=255):
“”“Normalize x from an xmin <-> xmax range to nmin <-> nmax”“”
n = nmax*(x - xmin) / xmax + nmin
if n > nmax: n = nmax
if n < nmin: n = nmin
return n
def main():
parser = OptionParser()
parser.add_option(‘-o’, ‘–open-only’, action=’store_true’,
dest=’open_only’, default=False,
help=“Only scan for open, unencrypted networks”)
(options, args) = parser.parse_args()
ser = Serial(ARDUINO_DEV, 9600)
try:
while 1:
ntwks = wifi.strengths()
if options.open_only:
ntwks = [n for n in ntwks if not n[‘encrypted’]]
currvelo = ntwks[0][’strength’]
print ‘Network: %s’ % ntwks[0][‘name’]
print ‘Strength: %d’ % ntwks[0][’strength’]
normvelo = normalize(currvelo, 0, 70, 0, 150)
print ‘Normalized Velocity: %d’ % normvelo
ser.write(’s%d’ % normvelo)
# ser.write(’s%d’ % currvelo)
print
except (KeyboardInterrupt, SystemExit):
print ‘Spinning motor down to 0…’
ser.write(’s%d’ % 0)
print
print
print ‘Laters.’
ser.close()
if __name__ == ‘__main__’:
main()
Other Ideas
Book Review Pendulum Scry
Diviners traditionally used pendulums to answer “yes” or “no” questions, like, “Will this baby be a girl?” We thought it would be cool to put a bar code reader in the bob that would read the bar code off books and transmit it up wires in the string to a device that made wireless queries to Amazon or another service. Amazon would return a rating, resulting in the pendulum swinging toward and away from your body to indicate “yes” and across your body to indicate “no,” in response to the question, “Should I read this book?”
Surf Scrying Pool
I always liked the fact that in Brief Lives, one of Neil Gaiman’s excellent Sandman graphic novels, one of the characters had a “scrying pool” that sloshed around more violently when his perusers were near. Traditionally, water scrying involved discerning patterns in the ripples and reflections of water. We thought it might be useful if the degree of agitation in a pool of water indicated the sea conditions at your favorite surfing spot.
And here’s a really similar project by the artist Shelly Farnham!
Thanks!
Many thanks to everyone who helped us out, and special thanks to Kimiko for teaching such a unique class!








