Class: OpenC3::Win32SerialDriver

Inherits:
Object
  • Object
show all
Defined in:
lib/openc3/io/win32_serial_driver.rb

Overview

Serial driver for use on Windows serial ports

Instance Method Summary collapse

Constructor Details

#initialize(port_name = 'COM1', baud_rate = 9600, parity = :NONE, stop_bits = 1, write_timeout = 10.0, read_timeout = nil, read_polling_period = 0.01, read_max_length = 1000, flow_control = :NONE, data_bits = 8) ⇒ Win32SerialDriver

Returns a new instance of Win32SerialDriver.

Parameters:

  • port_name (String) (defaults to: 'COM1')

    Name of the serial port

  • baud_rate (Integer) (defaults to: 9600)

    Serial port baud rate

  • parity (Symbol) (defaults to: :NONE)

    Must be one of :EVEN, :ODD or :NONE

  • stop_bits (Integer) (defaults to: 1)

    Number of stop bits

  • write_timeout (Float) (defaults to: 10.0)

    Seconds to wait before aborting writes

  • read_timeout (Float|nil) (defaults to: nil)

    Seconds to wait before aborting reads. Pass nil to block until the read is complete.

  • flow_control (Symbol) (defaults to: :NONE)

    Currently supported :NONE and :RTSCTS (default :NONE)

  • data_bits (Integer) (defaults to: 8)

    Number of data bits (default 8)

Raises:

  • (ArgumentError)


30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/openc3/io/win32_serial_driver.rb', line 30

def initialize(port_name = 'COM1',
               baud_rate = 9600,
               parity = :NONE,
               stop_bits = 1,
               write_timeout = 10.0,
               read_timeout = nil,
               read_polling_period = 0.01,
               read_max_length = 1000,
               flow_control = :NONE,
               data_bits = 8)

  # Verify Parameters
  port_name = '\\\\.\\' + port_name if /^COM[0-9]{2,3}$/.match?(port_name)

  raise(ArgumentError, "Invalid baud rate: #{baud_rate}") unless baud_rate.between?(Win32::BAUD_RATES[0], Win32::BAUD_RATES[-1])
  raise(ArgumentError, "Invalid data bits: #{data_bits}") unless [5, 6, 7, 8].include?(data_bits)
  raise(ArgumentError, "Invalid parity: #{parity}") if parity and !SerialDriver::VALID_PARITY.include?(parity)

  case parity
  when SerialDriver::ODD
    parity = Win32::ODDPARITY
  when SerialDriver::EVEN
    parity = Win32::EVENPARITY
  when SerialDriver::NONE
    parity = Win32::NOPARITY
  end

  raise(ArgumentError, "Invalid stop bits: #{stop_bits}") unless [1, 2].include?(stop_bits)

  if stop_bits == 1
    stop_bits = Win32::ONESTOPBIT
  else
    stop_bits = Win32::TWOSTOPBITS
  end

  @write_timeout = write_timeout
  @read_timeout = read_timeout
  @read_polling_period = read_polling_period
  @read_max_length = read_max_length

  # Open the Comm Port
  @handle = Win32.create_file(port_name,
                              Win32::GENERIC_READ | Win32::GENERIC_WRITE,
                              0,
                              Win32::NULL,
                              Win32::OPEN_EXISTING,
                              Win32::FILE_ATTRIBUTE_NORMAL)

  @mutex = Mutex.new

  # Configure the Comm Port - See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa363214(v=vs.85).aspx
  dcb = Win32.get_comm_state(@handle)
  dcb.write('BaudRate', baud_rate)
  dcb.write('ByteSize', data_bits)
  dcb.write('Parity',   parity)
  dcb.write('StopBits', stop_bits)
  if flow_control == :RTSCTS
    # Monitor CTS
    dcb.write('fOutxCtsFlow', 1)

    # 0x00 - RTS_CONTROL_DISABLE - Disables the RTS line when the device is opened and leaves it disabled.
    # 0x01 - RTS_CONTROL_ENABLE - Enables the RTS line when the device is opened and leaves it on.
    # 0x02 - RTS_CONTROL_HANDSHAKE - Enables RTS handshaking. The driver raises the RTS line when the "type-ahead" (input) buffer is less than one-half full and lowers the RTS line when the buffer is more than three-quarters full. If handshaking is enabled, it is an error for the application to adjust the line by using the EscapeCommFunction function.
    # 0x03 - RTS_CONTROL_TOGGLE - Specifies that the RTS line will be high if bytes are available for transmission. After all buffered bytes have been sent, the RTS line will be low.
    dcb.write('fRtsControl', 0x03)
  end
  Win32.set_comm_state(@handle, dcb)

  # Configure Timeouts, the WinAPI structure is COMMTIMEOUTS:
  #   DWORD ReadIntervalTimeout;
  #   DWORD ReadTotalTimeoutMultiplier;
  #   DWORD ReadTotalTimeoutConstant;
  #   DWORD WriteTotalTimeoutMultiplier;
  #   DWORD WriteTotalTimeoutConstant;
  # 0xFFFFFFFF, 0, 0 specifies that the read operation is to return immediately
  # with the bytes that have already been received, even if no bytes have been received.
  # The WriteTotalTimeoutMultiplier is multiplied by the number of bytes to be written
  # and the WriteTotalTimeoutConstant is added to that total (both are in milliseconds).
  bits_per_symbol = data_bits + 1 # 1 start bit
  case stop_bits
  when Win32::ONESTOPBIT
    bits_per_symbol += 1
  when Win32::TWOSTOPBITS
    bits_per_symbol += 2
  end
  case parity
  when Win32::ODDPARITY, Win32::EVENPARITY
    bits_per_symbol += 1
  end
  delay = (1000.0 / (baud_rate / bits_per_symbol.to_f)).ceil
  Win32.set_comm_timeouts(@handle, 0xFFFFFFFF, 0, 0, delay, 1000)
end

Instance Method Details

#closeObject

Disconnects the driver from the comm port



124
125
126
127
128
129
130
131
132
# File 'lib/openc3/io/win32_serial_driver.rb', line 124

def close
  if @handle
    # Close the Comm Port
    Win32.close_handle(@handle)
    @mutex.synchronize do
      @handle = nil
    end
  end
end

#closed?Boolean

Returns Whether the serial port has been closed.

Returns:

  • (Boolean)

    Whether the serial port has been closed



135
136
137
138
139
140
141
# File 'lib/openc3/io/win32_serial_driver.rb', line 135

def closed?
  if @handle
    false
  else
    true
  end
end

#readString

Returns Binary data read from the serial port.

Returns:

  • (String)

    Binary data read from the serial port



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/openc3/io/win32_serial_driver.rb', line 159

def read
  data = ''
  sleep_time = 0.0

  loop do
    loop do
      buffer = nil
      @mutex.synchronize do
        break unless @handle

        buffer = Win32.read_file(@handle, @read_max_length - data.length)
      end
      break unless buffer

      data << buffer
      break if buffer.length <= 0 or data.length >= @read_max_length or !@handle
    end
    break if data.length > 0 or !@handle
    if @read_timeout and sleep_time >= @read_timeout
      raise Timeout::Error, "Read Timeout"
    end

    sleep(@read_polling_period)
    sleep_time += @read_polling_period
  end

  data
end

#read_nonblockString

Returns Binary data read from the serial port.

Returns:

  • (String)

    Binary data read from the serial port



189
190
191
192
193
194
195
196
197
# File 'lib/openc3/io/win32_serial_driver.rb', line 189

def read_nonblock
  data = ''
  loop do
    buffer = Win32.read_file(@handle, @read_max_length - data.length)
    data << buffer
    break if buffer.length <= 0 or data.length >= @read_max_length
  end
  data
end

#write(data) ⇒ Object

Parameters:

  • data (String)

    Binary data to write to the serial port



144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/openc3/io/win32_serial_driver.rb', line 144

def write(data)
  # Write the data
  time = Time.now.sys
  bytes_to_write = data.length
  while bytes_to_write > 0
    bytes_written = Win32.write_file(@handle, data, data.length)
    raise "Error writing to comm port" if bytes_written <= 0

    bytes_to_write -= bytes_written
    data = data[bytes_written..-1]
    raise Timeout::Error, "Write Timeout" if @write_timeout and (Time.now.sys - time > @write_timeout) and bytes_to_write > 0
  end
end