####################  serialport.rb -- brent@mbari.org  ##################
# $Source$
#    Copyright (C) 2003 MBARI
#    MBARI Proprietary Information. All rights reserved.
# $Id$
#
#     SerialPort abstract module
#       included by Posix::SerialPort and I2C::SerialPort
#
#  This module in intended to be included by all concrete implementations
#  for RS232 serial ports.  It defines SerialPort::Configuration class
#  common to all implementations and some handy line break translation methods
#  
#  SerialPort (mixin) Operations include:
#     #crlf(String) return String with line break expanded for glass ttys
#     #cr(String) return String with \r substituted for all line breaks
#     #verbatim(String) return String
#
#  SerialPort::Configuration Operations include:
#     .default  new copy of default configuration
#     .charBits(opts)  character data bits associated with specified opts mask
#     #charBits        character data bits associated with self
#     #totalBits       total # of data bits associated with self
#     #charsPerSec     # of characters per seconds (max rate) for self
#
#     optionString(opts)  string representation of opts
#     optionString        " of opts in self
#     to_s                string representation of self
#
#
############################################################################


module SerialPort
  
  class Error < StandardError; end
  
  Configuration = Struct.new nil,
    :options,         #option flags
    :baud             #baud rate

  class Configuration

    FiveBitChar = 0x3
    SixBitChar = 0x2
    SevenBitChar = 0x1
    EightBitChar = 0x0

    NoParity = 0x0
    OddParity = 0x4
    EvenParity = 0x8

    TwoStopBits = 0x10

    CTSRTS = 0x20
    DSRDTR = 0x40
    DCD = 0x80

    Handshakes = CTSRTS|DSRDTR|DCD
    Parity = OddParity|EvenParity
    CharMask = FiveBitChar
    AllOptions = Handshakes|Parity|TwoStopBits|CharMask

    @@default = new 0, 38400  #3-wire, 8bits, 1stop, no parity, 38.4Kbaud

    def self.default
      @@default.dup
    end

    @@parityTxt = "no", "odd", "even", "(even+odd)?!"

    def self.charBits opts
      8 - (opts & 3)
    end
    def charBits
      klass.charBits options
    end

    def totalBits
      charBits + (options & TwoStopBits !=0 ? 3 : 2) +  #start+stop bits
                 (options & Parity !=0 ? 1 : 0)
    end

    def charsPerSec
      baud / Float(totalBits)
    end

    def self.optionString opts
    #convert configuration options mask to a string
      opt = []
      opt << "#{charBits(opts)} bits/char"
      opt << (@@parityTxt[(opts >> 2) & 3] + " parity")
      opt << "2 stop bits" if opts & TwoStopBits !=0
      opt << "CTSRTS" if opts & CTSRTS !=0
      opt << "DSRDTR" if opts & DSRDTR !=0
      opt << "DCD" if opts & DCD !=0
      opt.join ', '
    end

    def optionString
      klass.optionString options
    end

    def to_s
      baud.to_s << " baud with " << optionString
    end
    alias_method :asIRBtext, :to_s

    class Error < Error
      UnsupportedBaudRate = 0x1
      UnsupportedBitsChar = 0x2
      attr_reader :badConfig, :errMask
      def initialize errMask, badConfig
        @badConfig = badConfig
        @errMask = errMask 
        super klass.message(@errMask) << ' in ' << badConfig.to_s
      end
      def self.message mask
        if mask
          if mask == 0
            'Truncated Request or Invalid/missing channel'
          else
            errs = []
            errs << 'Rate' if mask & UnsupportedBaudRate !=0
            errs << 'Bits/Char' if mask & UnsupportedBitsChar !=0
            errs << 'Parity' if mask & Parity != 0
            errs << 'Stop Bits' if mask & TwoStopBits != 0
            errs << 'Handshake' if mask & Handshakes != 0
            errs << ('Unknown features 0x%02x'%mask) if 
              mask & ~AllOptions != 0
            'Unsupported '<<errs.join('&')
          end
        else
          ''
        end
      end        
    end

  end  #SerialPort::Configuration
    
  
  def crlf string
  #insert carridge returns where needed
    s=''
    string.each_byte {|c|
      case c
        when 0xD
          @col=0; s<<c
        when 0xA
          s<<0xD if @col!=0
          @col=0; s<<c
        else
          s<<c
          @col+=1
      end
    }
    s
  end
  
  @@outRS = "\n"  #(default) output record separator

  def cr string
    string.gsub @@outRS, "\r"
  end

  def verbatim string
    string
  end

  def startReading(*args)  #some types of serialports need this
  end

  def stopReading	   #some types of serialports need this
  end

end
