Cross-platform Ruby Serial Port Library
After reading this interesting post about using Ruby and a microcontroller for homebrew electronics projects, I discovered Ruby/SerialPort. It's a Ruby library that works on Windows, Linux, BSD, OS X, and other POSIX operating systems. It's reasonably old, but as demonstrated in the first link, works on OS X pretty well even now in 2006. There's some code demonstrating its use here.
Using Ruby/SerialPort and Arduino, an open-source physical computing platform (basically a microcontroller with a standardized serial interface, it seems), it's pretty easy to connect Ruby to electronics circuits. Tied up with a nice LED display or mini text display, this could become a cute tool for building desktop display devices for showing RSS, weather, etc. Perhaps using these tools you could become the next Art Lebedev?
December 11, 2006 at 4:53 pm
I was looking at gumstix the other day which can run a full Ruby interpreter on it and also has options for wifi, GPS, and other hardware interfaces.
December 11, 2006 at 5:00 pm
Found it, looks interesting! Gumstix (for others).
BTW, got your mail a few days ago, but want to dig around Rubinius before I post anything about it.. it's pretty intriguing stuff :) Want to decide if it's something I want to support, so gotta do some research first.
December 11, 2006 at 7:49 pm
Not to be too pedantic or anything, but he actually doesn't mention PICs in there, only the Arduino (which uses the Atmel AVR ATmega8 or ATmega168, depending on the particulars). I'm actually hoping to do some PIC+Ruby hacking in the near future. Of course, a microcontroller that could actually *run* Ruby would be even better, but that takes a bit more memory than these things have. The Gumstix, as mentioned, is one possibility, though NSLU2 is another option: http://www.nslu2-linux.org/.
December 11, 2006 at 7:51 pm
I've tweaked it :) I tend to equate PICs and microcontrollers, but have cleared it up.
December 11, 2006 at 9:12 pm
I use ruby-serial port to control LEGO Mindstorms NXT (via bluetooth serial port connection, until the ruby bluetooth gem gets updated to work on OSX): http://rubyforge.org/projects/ruby-nxt/
I just wish ruby-serialport was available as a gem and that it was easier to install on windows. The best I could do on windows was to compile it using cygwin.
December 12, 2006 at 11:19 am
NOT cross-platform, but a second arrow in the quiver for Windows-only is win32serial - see http://raa.ruby-lang.org/project/win32serial/
This too needs building from source - and I don't remember how I did it as I don't usually have c program development tools on my windows machines. I know I got it to work, and used it to get data out of a Mastech MAS-343, a multimeter that has an rs232 serial port.
I generically echo Tony's wish - I wish serial port support was available as a gem for mswin32... it could be either of these two, or something else...
December 18, 2006 at 1:36 am
Based on some posts on the ruby mailing list and the Microsoft docs, I threw together a library that uses the win32API for serial communication on Windows, so no compilation needed. The code still has some issues, specifically setting the serial port speed doesn't work properly, though if you start hyperterm beforehand with the correct settings, it'll initialize it properly for you.
module Serial
require 'Win32API'
require 'thread'
# Windows constants.
GENERIC_READ = 0x80000000
GENERIC_WRITE = 0x40000000
OPEN_EXISTING = 0x00000003
FILE_FLAG_OVERLAPPED = 0x40000000
NULL = 0x00000000
EV_RXCHAR = 0x0001
# Windows system error codes
ERROR_IO_PENDING = 997
class Port
def initialize(port="com1",baud=9600)
@port = port
@run = true
@read_buffer = ""
@mutex = Mutex.new
# Create objects to access Windows file system API.
@CreateFile = Win32API.new("Kernel32", "CreateFile", "PLLLLLL", "L")
@CloseHandle = Win32API.new("Kernel32","CloseHandle", "L", "N")
@ReadFile = Win32API.new('Kernel32','ReadFile','LPLPP','I')
@WriteFile = Win32API.new('Kernel32','WriteFile','LPLPP','I')
@SetCommState = Win32API.new("Kernel32","SetCommState","LP","N")
@SetCommTimeouts = Win32API.new("Kernel32","SetCommTimeouts","LP","N")
@GetLastError = Win32API.new("Kernel32","GetLastError", "V", "N")
@GetCommState = Win32API.new("Kernel32","GetCommState", "LP", "N")
@GetOverlappedResult = Win32API.new('Kernel32','GetOverlappedResult', 'LPPI', 'I')
@CreateEvent = Win32API.new("Kernel32","CreateEvent", "LLLP", "L")
@WaitCommEvent = Win32API.new("Kernel32","WaitCommEvent", "LPP", "I")
@SetCommMask = Win32API.new("Kernel32","SetCommMask", "LL", "I")
# Get a file handle
@hFile = @CreateFile.Call(port, GENERIC_READ|GENERIC_WRITE, 0,NULL,OPEN_EXISTING,FILE_FLAG_OVERLAPPED,NULL)
# Create and populate overlapped structures
hEvent_write = @CreateEvent.Call(0,0,0,0);
@overlapped_write = [0,0,0,0,hEvent_write].pack("L*")
hEvent_read = @CreateEvent.Call(0,0,0,0);
@overlapped_read = [0,0,0,0,hEvent_read].pack("L*")
#set_speed baud
set_timeouts(2,1,1,0,0)
end
def port
@port
end
def report
puts "GetLastError = #{@GetLastError.Call}"
end
#this is currently badly screwing up the connection - I don't know why
def set_speed(speed)
dcb = ""*80
@GetCommState.Call(@hFile, dcb)
dcb_unpacked = dcb.unpack("L2B4B2B6B2BB17S3C8S")
dcb_unpacked[1] = speed
dcb = dcb_unpacked.pack("L2B4B2B6B2BB17S3C8S")
@SetCommState.Call(@hFile, dcb)
end
def set_timeouts(a=0,b=0,c=0,d=0,e=0)
commTimeouts = [a,b,c,d,e].pack("L*") #hm - should actually look up what a,b,c,d,e are
@SetCommTimeouts.Call(@hFile, commTimeouts)
end
def write(data)
numwritten = ""*4
#puts "Serial write:" + data + ":"
if (@GetOverlappedResult.Call(@hFile,@overlapped_write,numwritten,1)==0)
puts "Write Error"
else
@WriteFile.Call(@hFile,data,data.size,numwritten,@overlapped_write)
end
end
def listen(proc = nil ) #empty proc - assumes that the read method will be used instead of the proc
thread = Thread.new {
count = 0
#=begin
while (@run)
inbuff = ""*128
numread = ""*4
if (@ReadFile.Call(@hFile,inbuff,inbuff.size,numread,@overlapped_read)==0)
if (@GetLastError.Call() == ERROR_IO_PENDING)
if (@GetOverlappedResult.Call(@hFile,@overlapped_read,numread,1)==0)
puts "GetOverlappedResult failed"
break
end
end
else
puts "Readfile failed"
#break
end
#puts ":" + inbuff.sub(/*\z/,"") + ":"
#puts "numread:" + numread.to_s
numchar = numread.unpack("L")[0]
#puts "numchar:" + numchar.to_s
if (numchar > 0)
#puts inbuff[0..numchar-1]
#inbuff.sub!(/*\z/,"") #getting rid of the trailing 's
#puts "Serial read:" + inbuff + ":"
if proc == nil
@read_buffer