IRC and Arduino

A while back, I wrote a small python script to act as a proxy between IRC and an Arduino. This video illustrates some of the details.

Here is a zip file with everything to download.

The basic idea was this, the python IRC bot sits in the chat room and waits until someone addresses it’s nick like so:

     {NICK}: {command} {arg1} {arg2} ... {etc}

The bot parses the response into a command and a series of arguments. Originally, all this was hardcoded, but through python’s powers of introspection, I realized I could make this way more dynamic. The end result is a framework of sorts. The user can edit the Commands.py file adding functions and settings to his/her specification. The example in the video would be written like this:

#
# module for configuration and commands
#
from Arduinos import Arduino

# Program Settings
DEBUG = True

# IRC Settings
NICK = "arduino"
SERVER = "irc.paraphysics.net"
CHANNEL = "#arduinoroom"
PORT = 6667

# Arduino Settings
USB_PATH = '/dev/tty.usbserial-A7006Qe8'
BAUD = 9600

# define methods
def lightLed(arduino, args):
	arduino.send('~') # header
	arduino.send(args[0])
	arduino.send('~') # terminating
	return arduino.read(4) # read 4 bytes from arduino

def readPot(arduino):
	arduino.send('}}') # header
	return arduino.read(4) # read 4 bytes from arduino

Obviously, you can change the constants to what you need and the daemon picks up on these when it is started. The nice thing about this ‘framework’ is the second half of the script. When you define a method, the IRC bot automagically “understands” it. This is because every time it receives a message directed to it, it calls

    reload(Commands)

and reads the function names. So you don’t have to restart the server while developing your functions [An idea stolen from Rails]! Then it parses the message coming in and tries to call the first word as a method and parses the rest of the message into a list. There is no need to worry about the details, what you do need to know to use it is that there are two types of functions, ones with and ones without arguments. The above example illustrates both. Let’s say we define 2 functions:

# define methods
def function1(arduino, args):
    print "function1"
    print args

def function2(arduino):
    print "function 2"
    print "no args"

Now, we go into the IRC chat room and issue a command like this:

   arduino: function2

The output would be this:

   function 2
   no args

Then lets say we send this:

   arduino: function1 hello world!

The bot would parse it and you would see this output:

   function1
   ['hello', 'world!']

so args becomes a list by splitting the rest of the statement between whitespaces. Your function always needs to have the ‘arduino’ argument. This argument is a custom class I created which looks like this:

   import serial

class Arduino():

	def __init__(self, path='/dev/tty.usbserial', baud=9600):
		self.ser = serial.Serial(path, baud)	

	def send(self, data):
		self.ser.write(data)

	def read(self, bytes):
		while (1):
			if (self.ser.inWaiting() > bytes-1):
				return self.ser.read(bytes)

	def flush():
		self.ser.flushInput()

It is pretty simple, send() sends a string. read() waits for the defined number of bytes to come in and returns the results. To better understand how the example Commands.py script works, take a look at the arduino sketch:

#define LED 13

void setup() {
  pinMode(LED, OUTPUT);
  Serial.begin(9600);
}

void loop() {
  if (nextByte() == 126) { // header byte ('~' character) led command
    char args[] = {0,0,0,0,0,0,0,0,0,0};
    char charIn = 0;
    byte i = 0;
    while (charIn != 126) {  // wait for header byte again
       charIn = nextByte();
       args[i] = charIn;
       i += 1;
    }
    if ((args[0] == 'o') && (args[1] == 'n')) {
      digitalWrite(LED, HIGH);
      Serial.print("on  ");
    }
    else if ((args[0] == 'o') && (args[1] == 'f')) {
      digitalWrite(LED, LOW);
      Serial.print("off ");
    }
    delay(10);
    Serial.flush();
  }
  else if (nextByte() == 125) { // header byte pot command
    int val = analogRead(0);
    if (val < 10) {
      Serial.print(val);
      Serial.print("   ");
    }
    else if (val < 100) {
      Serial.print(val);
      Serial.print("  ");
    }
    else if (val < 1000) {
      Serial.print(val);
      Serial.print(" ");
    }
    else {
      Serial.print(val);
    }
  }
  delay(10);
  //if(Serial.available() > 0) {
    Serial.flush();
  //}
}

byte nextByte() {
    while(1) {
      if(Serial.available() > 0) {
          byte b =  Serial.read();
	  return b;
       }
    }
}

Yeah, not the cleanest code but hopefully you get the idea. I am not releasing a whole lot of detail on how to use this because I figure If you are using it, then you already know enough about python and Arduino to get by. I have created this to allow for support for firmata but have yet to implement it and probably never will until/if people beg. To run, first get pyserial and irclib.py. Upload the sketch to your arduino and run:

   python Main.py

8 comments ↓

#1 Dennis Finegan on 05.06.09 at 7:33 pm

Nice job. I recently purchased a bunch of ATmega hardware on ebay from Sure Electronics (try them, great stuff, good prices, fast shipment) and will have to get going on getting them hooked-up. I started using a TRS-80 back in the stone age. All the neat hardware and the internet wasn’t available back then.
Keep up the good work.

#2 benjamin on 05.06.09 at 8:35 pm

Thanks for the tip! They have some cool stuff. I definitely want some of these : http://tinyurl.com/d9fjng

#3 echoskope on 09.05.09 at 8:48 pm

Wow this is exactly what I have been looking for! Except I need an IRC bot to sit in a chan and wait for commands to have the arduino move 2 servos for a pan/tilt webcam that i built! Thanks for such good info, will deff be coming back checking for more stuff!

#4 benjamin on 09.30.09 at 8:29 am

@echoskope, let me know if you need help getting your project going.

#5 Alexander on 01.31.10 at 10:23 am

This is very useful, I am currently trying to read from the Arduino but I struggling with the limited documentation of the pyserial module. I pretty new to Python so this is quite a struggle. Thanks for posting, this will help a lot! Cheers

#6 benjamin on 01.31.10 at 2:05 pm

@Alexander

Yeah the pyserial module is a little under-documented, but remember that you can open up a python terminal and do something like :

>>> import serial
>>> dir(serial.Serial)

dir will show you all available members and functions.

Then you can get documentation on a specific function like this:

>>> serial.Serial.readline.__doc__
“read a line which is terminated with end-of-line (eol) character\n (‘\n’ by default) or until timeout”

python is self documenting to an extent. Check out the builtin functions: http://docs.python.org/library/functions.html

I would say you can get by with just knowing how to use read() and write() well. Look here :

http://blog.datasingularity.com/?p=49

One more thing, if you are new to python i would suggest reading ‘Dive into Python’, it is free and great:

http://diveintopython.org/

#7 René Weiß Abythe on 02.01.10 at 5:55 pm

This is awesome.

#8 azkrak on 02.18.12 at 4:47 am

Two years after the last post but i have to say it too: “This is f***ing awesome” ;)

Leave a Comment