Ruby Weekly is a weekly newsletter covering the latest Ruby and Rails news.

Cross-platform Ruby Serial Port Library

By Peter Cooper / December 11, 2006

Arduino

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?

Comments

  1. topfunky says:

    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.

  2. Peter Cooper says:

    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.

  3. Thomas says:

    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/.

  4. Peter Cooper says:

    I've tweaked it :) I tend to equate PICs and microcontrollers, but have cleared it up.

  5. Tony Buser says:

    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.

  6. David Roberts says:

    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...

  7. Derek Hans says:

    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

Other Posts to Enjoy

Twitter Mentions