November 24, 2007
Reading for November 27th, are now posted. Enjoy!

October 2, 2007
To upload your thoughtless acts, create a new assignment page like any other lab. You'll see "Thoughtless Acts" listed as one of the assignment options.

May 24, 2008
This site has been archived and is no longer editable. Stay tuned for the next version, coming in the fall!

Lab 6

Project Members: 


Explore motion as an output (in a form of display or tactile feedback).
Use your DC motor to create vibration or rotational motion (e.g., you
have seen Ambient Media that use pinwheels, dancing wires, etc as
display or notification). You can also combine the motion with other
output (e.g., sound, lights, etc.). Be creative!


I wanted to create something simple, that I could interface with an outside programming library like Python. The simple part didn't work out, and it actually took me some time to implement my idea. What I ended up with was a livewire implementation for IRC chat. This involved running a supybot (Python based IRC bot), and a small script in python that listens to the channel logs, looking for my screenname "n8agrin". Anytime someone in the channel messages me, Arduino fires up the motor which vibrates a string. Not the most creative of interfaces, but it was frustrating getting the technologies to mash together. Now I know when someone sends me a message in my chatrooms!

Components Used

  • 2 AA batteries
  • 1 electric motor
  • 1 diode
  • 1 transistor
  • Various lengths of wire
  • 1 breadboard
  • 1 arduino board


Arduino code

* Listen to a serial port and if a 1 character comes in,
* turn on a motor for a little while.
* author: n8agrin @

int motorPin = 9; // select the pin for the Motor
int incoming = 0; // set the default value for the incoming message

void setup() {
void loop() {
incoming =; // read in from the Serial port
if (incoming == 49) { // look for the character '1', which maps to 49
analogWrite(motorPin, 120); // analogWrite can be between 0-255
delay(1000); // delay for a bit
analogWrite(motorPin, 0); // turn off the motor


Python code

from Tailer import *
import serial, re

# define some 'constants'
ARDUINO_PORT = "/dev/tty.usbserial-A4001nvG"
IRC_LOGFILE = "/opt/local/supybot/logs/ChannelLogger/freenode/#n8/#n8.log"

# setup a serial connection to talk to Arduino
arduino = serial.Serial(ARDUINO_PORT, 9600)

# create a regex object to match my username
findn8 = re.compile(r'n8agrin:')

def livewire(filename, s):
print "Message received, printing to ardunio."

# create a new Tailer object to tail the IRC bot's log file
t = Tailer(IRC_LOGFILE) # Tailer takes a list of files

# call the livewire method to inform the user of an incoming message


Python Tailer library

#!/usr/bin/env python
__doc__ = """
tail -f for multiple files written natively in Python

Polls files for appended text. Can call a callback function with the
changes if you'd like, or you can control when the pollings occur.

Based on a post by Skip Montanaro in comp.lang.python (see

# a callback function
def printer(filename, s):
if s: print "%s:\t%s" % (filename, s)
t = Tailer(file1, file2) # Tailer takes a list of files

See pydoc for the other tailing modes (modes are pollloop, poll,
multipoll) by doing 'pydoc Tailer' in this directory.

Known bugs:
It doesn't handle the case when a file is alterred in the middle very well.

Please feel free to contact me at dmcc AT bigasterisk DOT com with questions,
feature requests, patches, whatever.

# todo:
# distutil-ification

__version__ = 1.2
__author__ = 'David McClosky ('

from __future__ import generators
import os, sys, time

__all__ = ['TailedFile', 'TailInterface', 'Tailer']

class TailedFile:
"""An object representing an object being tailed and it's current state"""
def __init__(self, filename, initial=None):
"""Filename is the name of the file to watch. initial is the
size where where should start watching from. Omitting initial
will result in watching changes from the end of the file."""
self.file = open(filename, 'r')
self.filename = filename
self.size = os.path.getsize(filename)
if initial is not None:
self.offset = initial
self.offset = self.size
def __len__(self):
'Returns the size of the file'
return self.size
def __str__(self):
'Returns the filename of the file'
return self.filename
def poll(self):
"""Returns a string of the new text in the file if there is any.
If there isn't, it returns None. If the file shrinks (for whatever
reason), it will start watching from the new end of the file."""
self.size = os.path.getsize(self.filename)
if self.size > self.offset:
s =
self.offset = self.size
return s
# if file shrinks, we will adjust to the new size
if self.size < self.offset:
self.offset = self.size
return None

class TailInterface:
"""An interface for file watching."""
def __init__(self, *files, **kw):
"""Files is a list of files to watch. Keyword arguments include:
interval (for the interval at which the file should be poll()ed
for pollloop() and initial for the initial end of the file.
Setting initial to zero will make it read the entire file on
the first poll() and any subsequent additions for the poll()s
after that."""
def poll(self):
"""If any files have grown, return a tuple containing the filename and
new text. If no files have changed, we return None"""
def pollloop(self, callback):
"""Continously watches the files for changes, sleeping for
'interval' amount of seconds in between (see __init__). If there
are any changes, it will call the callback with two arguments:
the filename and new text."""

class Tailer(TailInterface):
"""An object that watches one or more files for additions. You can
either call it whenever you want with poll(), or use pollloop()
to call it regularly."""
def __init__(self, *files, **kw):
"""Given a list of files and some keyword arguments, constructs
an object which will watch the files for additions. The optional
keyword 'interval' affects the frequency at which pollloop()
will check the files. Keywords arguments will be passed along
to the TailedFiles."""
self.interval = kw.get('interval', 0.1)
self.files = [TailedFile(f, **kw) for f in files]
def poll(self):
"""If any files have grown, return a tuple containing the
filename and new text. If no files have changed, we return None.
Note that this function is a generator so that it will (try to)
give each file equal treatment in polling."""
while 1:
for f in self.files:
s = f.poll()
if s:
yield (f, s)
yield None
def multipoll(self):
"""Returns a list of changes in files. It returns a list of
(filename, newtext) tuples. If there are no changes, it will
return the empty list."""
changes = []
for f in self.files:
s = f.poll()
if s:
changes.append((f, s))
return changes
def pollloop(self, callback):
"""Continously watches the files for changes, sleeping for
'interval' amount of seconds in between (see __init__). If there
are any changes, it will call the callback with two arguments:
the filename and new text."""
while 1:
changes = self.multipoll()
for change in changes:

if __name__ == '__main__':
print "Tailing %s:" % (', '.join(sys.argv[1:]))
def printer(filename, s):
if s: print "%s:\t%s" % (filename, s)
t = Tailer(*sys.argv[1:])


Good job! Getting different

Good job! Getting different technologies to talk to each other can be a pain -- serial communication is versatile but not always the most user friendly. So, nice work getting everything working together! It sounds like you've created something that's actually useful to you, too. It reminds me of the "Dangling String" project that used a dancing string to indicate network activity.

When you have a chance, please post a photo of your project!

Powered by Drupal - Design by Artinet