Class: Firmata::Board

Inherits:
Object
  • Object
show all
Includes:
EventSpitter
Defined in:
lib/firmata/board.rb

Defined Under Namespace

Classes: Pin

Constant Summary collapse

INPUT =

Public: Fixnum byte for pin mode input.

0x00
OUTPUT =

Public: Fixnum byte for pin mode output.

0x01
ANALOG =

Public: Fixnum byte for pin mode analog.

0x02
PWM =

Public: Fixnum byte for pin mode pulse width modulation.

0x03
SERVO =

Public: Fixnum byte for pin mode servo.

0x04
LOW =
0
HIGH =
1
REPORT_VERSION =

Internal: Fixnum byte command for protocol version

0xF9
SYSTEM_RESET =

Internal: Fixnum byte command for system reset

0xFF
DIGITAL_MESSAGE =

Internal: Fixnum byte command for digital I/O message

0x90
DIGITAL_MESSAGE_RANGE =

Pubilc: Fixnum byte for range for digital pins for digital 2 byte data format

0x90..0x9F
ANALOG_MESSAGE =

Internal: Fixnum byte command for an analog I/O message

0xE0
ANALOG_MESSAGE_RANGE =

Internal: Fixnum byte range for analog pins for analog 14-bit data format

0xE0..0xEF
REPORT_ANALOG =

Internal: Fixnum byte command to report analog pin

0xC0
REPORT_DIGITAL =

Internal: Fixnum byte command to report digital port

0xD0
PIN_MODE =

Internal: Fixnum byte command to set pin mode (I/O)

0xF4
START_SYSEX =

Internal: Fixnum byte command for start of Sysex message

0xF0
END_SYSEX =

Internal: Fixnum byte command for end of Sysex message

0xF7
CAPABILITY_QUERY =

Internal: Fixnum byte sysex command for capabilities query

0x6B
CAPABILITY_RESPONSE =

Internal: Fixnum byte sysex command for capabilities response

0x6C
PIN_STATE_QUERY =

Internal: Fixnum byte sysex command for pin state query

0x6D
PIN_STATE_RESPONSE =

Internal: Fixnum byte sysex command for pin state response

0x6E
ANALOG_MAPPING_QUERY =

Internal: Fixnum byte sysex command for analog mapping query

0x69
ANALOG_MAPPING_RESPONSE =

Internal: Fixnum byte sysex command for analog mapping response

0x6A
FIRMWARE_QUERY =

Internal: Fixnum byte sysex command for firmware query and response

0x79

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(port) ⇒ Board

Public: Initialize a Board

port - a String port or an Object that responds to read and write.



76
77
78
79
80
81
82
83
84
# File 'lib/firmata/board.rb', line 76

def initialize(port)
  @serial_port = port.is_a?(String) ? SerialPort.new(port, 57600, 8, 1, SerialPort::NONE) : port
  @serial_port.read_timeout = 2
  @major_version = 0
  @minor_version = 0
  @pins = []
  @analog_pins = []
  @connected = false
end

Instance Attribute Details

#analog_pinsObject (readonly)

Public: Returns the Array of analog pins on Arduino.



69
70
71
# File 'lib/firmata/board.rb', line 69

def analog_pins
  @analog_pins
end

#firmware_nameObject (readonly)

Public: Returns the String firmware name of Arduion.



71
72
73
# File 'lib/firmata/board.rb', line 71

def firmware_name
  @firmware_name
end

#pinsObject (readonly)

Public: Returns the Array of pins on Arduino.



67
68
69
# File 'lib/firmata/board.rb', line 67

def pins
  @pins
end

#serial_portObject (readonly)

Public: Returns the SerialPort port the Arduino is attached to.



65
66
67
# File 'lib/firmata/board.rb', line 65

def serial_port
  @serial_port
end

Instance Method Details

#analog_write(pin, value) ⇒ Object Also known as: servo_write

Public: Write an analog messege.

pin - The Integer pin to write to. value - The Integer value to write to the pin between 0-255.

Returns nothing.



314
315
316
317
# File 'lib/firmata/board.rb', line 314

def analog_write(pin, value)
  @pins[pin].value = value
  write(ANALOG_MESSAGE | pin, value & 0x7F, (value >> 7) & 0x7F)
end

#connectObject

Public: Make connection to Arduino.

Returns Firmata::Board board.



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/firmata/board.rb', line 96

def connect
  unless @connected
    once('report_version', ->() do
      once('firmware_query', ->() do
        once('capability_query', ->() do
          once('analog_mapping_query', ->() do

            2.times { |i| toggle_pin_reporting(i) }

            @connected = true
            emit('ready')
          end)
          query_analog_mapping
       end)
        query_capabilities
      end)
    end)

     until connected?
      read_and_process
      delay(0.5)
    end
  end

  self
end

#connected?Boolean

Pubilc: Check if a connection to Arduino has been made.

Returns Boolean connected state.

Returns:

  • (Boolean)


89
90
91
# File 'lib/firmata/board.rb', line 89

def connected?
  @connected
end

#delay(seconds) ⇒ Object

Public: Ask the Arduino to sleep for a number of seconds.

seconds - The Integer seconds to sleep for.

Returns nothing.



332
333
334
# File 'lib/firmata/board.rb', line 332

def delay(seconds)
  sleep(seconds)
end

#digital_write(pin, value) ⇒ Object

Public: Write a value to a digital pin.

pin - The Integer pin to write to. value - The value to write (HIGH or LOW).

Returns nothing.



295
296
297
298
299
300
301
302
303
304
305
306
# File 'lib/firmata/board.rb', line 295

def digital_write(pin, value)
  port = (pin / 8).floor
  port_value = 0

  @pins[pin].value = value

  8.times do |i|
    port_value |= (1 << i) unless @pins[8 * port + i].value.zero?
  end

  write(DIGITAL_MESSAGE | port, port_value & 0x7F, (port_value >> 7) & 0x7F)
end

#process(data) ⇒ Object

Internal: Process a series of bytes.

data: The String data to process.

Returns nothing.



149
150
151
152
153
154
155
156
157
158
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
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
# File 'lib/firmata/board.rb', line 149

def process(data)
  bytes = StringIO.new(String(data)).bytes
  bytes.each do |byte|
    case byte
    when REPORT_VERSION
      @major_version = bytes.next
      @minor_version = bytes.next

      emit('report_version')

    when ANALOG_MESSAGE_RANGE
      least_significant_byte = bytes.next
      most_significant_byte = bytes.next

      value = least_significant_byte | (most_significant_byte << 7)
      pin = byte & 0x0F

      if analog_pin = analog_pins[pin]
        pins[analog_pin].value = value

        emit('analog-read', pin, value)
        emit("analog-read-#{pin}", value)
      end

    when DIGITAL_MESSAGE_RANGE
      port           = byte & 0x0F
      first_bitmask  = bytes.next
      second_bitmask = bytes.next
      port_value     = first_bitmask | (second_bitmask << 7)

      8.times do |i|
        pin_number = 8 * port + i
        if pin = pins[pin_number] and pin.mode == INPUT
          value = (port_value >> (i & 0x07)) & 0x01
          pin.value = value
          emit('digital-read', pin_number, value)
          emit("digital-read-#{pin_number}", value)
        end
      end

    when START_SYSEX
      current_buffer = [byte]
      begin
        current_buffer.push(bytes.next)
      end until current_buffer.last == END_SYSEX

      command = current_buffer[1]

      case command
      when CAPABILITY_RESPONSE
        supported_modes = 0
        n = 0

        current_buffer.slice(2, current_buffer.length - 3).each do |byte|
          if byte == 127
            modes = []
            # the pin modes
            [ INPUT, OUTPUT, ANALOG, PWM, SERVO ].each do |mode|
               modes.push(mode) unless (supported_modes & (1 << mode)).zero?
            end

            @pins.push(Pin.new(modes, OUTPUT, 0))

            supported_modes = 0
            n = 0
            next
          end

          supported_modes |= (1 << byte) if n.zero?

          n ^= 1
        end

        emit('capability_query')

      when ANALOG_MAPPING_RESPONSE
        pin_index = 0

        current_buffer.slice(2, current_buffer.length - 3).each do |byte|

          @pins[pin_index].analog_channel = byte

          @analog_pins.push(pin_index) unless byte == 127

          pin_index += 1
        end

        emit('analog_mapping_query')

      when PIN_STATE_RESPONSE
        pin       = pins[current_buffer[2]]
        pin.mode  = current_buffer[3]
        pin.value = current_buffer[4]

        pin.value |= (current_buffer[5] << 7) if current_buffer.size > 6

        pin.value |= (current_buffer[6] << 14) if current_buffer.size > 7

      when FIRMWARE_QUERY
        @firmware_name = current_buffer.slice(4, current_buffer.length - 5).reject { |b| b.zero? }.map(&:chr).join
        emit('firmware_query')

      else
        puts 'bad byte'
      end
    end
  end
rescue StopIteration
  # do nadda
end

#query_analog_mappingObject

Public: Ask the Arduino which pins (used with pin mode message) correspond to the analog channels.

Returns nothing.



377
378
379
# File 'lib/firmata/board.rb', line 377

def query_analog_mapping
  write(START_SYSEX, ANALOG_MAPPING_QUERY, END_SYSEX)
end

#query_capabilitiesObject

Public: Ask the Arduino about its capabilities and current state.

Returns nothing.



370
371
372
# File 'lib/firmata/board.rb', line 370

def query_capabilities
  write(START_SYSEX, CAPABILITY_QUERY, END_SYSEX)
end

#query_firmwareObject

Public: Ask the Ardution for its firmware name.

Returns nothing.



354
355
356
# File 'lib/firmata/board.rb', line 354

def query_firmware
  write(FIRMWARE_QUERY)
end

#query_pin_state(pin) ⇒ Object

Public: Ask the Arduino for the current configuration of any pin.

pin - The Integer pin to query on the board.

Returns nothing.



363
364
365
# File 'lib/firmata/board.rb', line 363

def query_pin_state(pin)
  write(START_SYSEX, PIN_STATE_QUERY, pin.to_i, END_SYSEX)
end

#readObject

Internal: Read data from the underlying serial port.

Returns String data read for serial port.



139
140
141
142
# File 'lib/firmata/board.rb', line 139

def read
  serial_port.read_nonblock(4096)
rescue EOFError
end

#read_and_processObject

Public: Read the serial port and process the results

Returns nothing.



263
264
265
# File 'lib/firmata/board.rb', line 263

def read_and_process
  process(read)
end

#report_versionObject

Public: Ask the Arduino to report its version.

Returns nothing.



347
348
349
# File 'lib/firmata/board.rb', line 347

def report_version
  write(REPORT_VERSION)
end

#resetObject

Public: Send a SYSTEM_RESET to the Arduino

Returns nothing.



270
271
272
# File 'lib/firmata/board.rb', line 270

def reset
  write(SYSTEM_RESET)
end

#set_pin_mode(pin, mode) ⇒ Object

Public: Set the mode for a pin.

pin - The Integer pin to set. mode - The Fixnum mode (INPUT, OUTPUT, ANALOG, PWM or SERVO)

Examples

set_pin_mode(13, OUTPUT)

Returns nothing.



284
285
286
287
# File 'lib/firmata/board.rb', line 284

def set_pin_mode(pin, mode)
  pins[pin].mode = mode
  write(PIN_MODE, pin, mode)
end

#toggle_pin_reporting(pin, state = HIGH, mode = REPORT_DIGITAL) ⇒ Object

Public: Toggle pin reporting on or off.

pin - The Integer pin to toggle. mode - The Integer mode the pin will report. The valid values are

REPORT_DIGITAL or REPORT_ANALOG (default: REPORT_DIGITAL).

state - The Integer state to toggle the pin. The valid value are

HIGH or LOW (default: HIGH)

Returns nothing.



390
391
392
# File 'lib/firmata/board.rb', line 390

def toggle_pin_reporting(pin, state = HIGH, mode = REPORT_DIGITAL)
  write(mode | pin, state)
end

#versionObject

Public: The major and minor firmware version on the board. Will report as “0.0” if report_version command has not been run.

Returns String the firmware version as “minor.major”.



340
341
342
# File 'lib/firmata/board.rb', line 340

def version
  [@major_version, @minor_version].join('.')
end

#write(*commands) ⇒ Object

Internal: Write data to the underlying serial port.

commands - Zero or more byte commands to be written.

Examples

write(START_SYSEX, CAPABILITY_QUERY, END_SYSEX)

Returns nothing.



132
133
134
# File 'lib/firmata/board.rb', line 132

def write(*commands)
  serial_port.write_nonblock(commands.map(&:chr).join)
end