rufirmata 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
File without changes
data/README.rdoc ADDED
File without changes
@@ -0,0 +1,6 @@
1
+ class Arduino < Rufirmata::Board
2
+ def initialize(serial_port_id)
3
+ super(serial_port_id, :board_type=>:arduino)
4
+ start_listening
5
+ end
6
+ end
@@ -0,0 +1,177 @@
1
+ module Rufirmata
2
+
3
+ class Board
4
+ include Observables::Base
5
+
6
+ attr_reader :serial_port, :name, :analog, :digital, :digital_ports, :board_type,
7
+ :taken,:firmata_version,:firmware, :listening
8
+
9
+ def initialize(serial_port, options={})
10
+ options[:serial_port] = serial_port
11
+ @serial_port ||= Rufirmata.create_serial_port(options)
12
+ @name = options[:name] || serial_port
13
+ @board_type =
14
+ case options[:board_type]
15
+ when Hash : options[:board_type]
16
+ when Symbol : Rufirmata::BOARD_TYPES[options[:board_type]]
17
+ else Rufirmata::BOARD_TYPES[:arduino]
18
+ end
19
+ @taken = { :analog => { }, :digital =>{ } }
20
+ @listening = false
21
+ initialize_layout
22
+ end
23
+
24
+ def to_s
25
+ "Board #{name} on #{serial_port}"
26
+ end
27
+
28
+ def write_command(*commands)
29
+ message = commands.shift.chr
30
+ commands.each { |command| message += command.chr }
31
+ @serial_port.write(message)
32
+ end
33
+
34
+ def write(data)
35
+ @serial_port.write(data)
36
+ end
37
+
38
+ # Sends a SysEx msg.
39
+ # :arg sysex_cmd: A sysex command byteggg
40
+ # :arg data: A list of data values
41
+ def send_sysex(sysex_cmd, data=[])
42
+ write_command Rufirmata::START_SYSEX
43
+ write_command sysex_cmd
44
+ data.each do |b|
45
+ begin
46
+ byte = b.chr
47
+ rescue
48
+ byte = (b >> 7).chr
49
+ end#TODO send multiple bytes
50
+ write(byte)
51
+ end
52
+ write_command Rufirmata::END_SYSEX
53
+ end
54
+
55
+ def close
56
+ @listening = false
57
+ @serial_port.close()
58
+ end
59
+ alias stop close
60
+
61
+ def start_listening
62
+ @listening = true
63
+ @listener = Thread.new do
64
+ while @listening
65
+ iterate
66
+ sleep 0.001
67
+ end
68
+ end
69
+
70
+ end
71
+
72
+ # Reads and handles data from the microcontroller over the serial port.
73
+ #This method should be called in a main loop, or in an
74
+ #:class:`Iterator` instance to keep this boards pin values up to date
75
+ def iterate
76
+ data = @serial_port.getc
77
+ return unless data
78
+
79
+ received_data = []
80
+
81
+ if data < Rufirmata::START_SYSEX
82
+ command = data & 0xF0
83
+ handler = find_handler(command)
84
+ return unless handler
85
+ received_data << (data & 0x0F)
86
+ while received_data.length < method(handler).arity
87
+ received_data << @serial_port.getc
88
+ end
89
+
90
+ elsif data == Rufirmata::START_SYSEX
91
+
92
+ data = @serial_port.getc
93
+ handler = find_handler(data)
94
+ return unless handler
95
+ data = @serial_port.getc
96
+ while data != Rufirmata::END_SYSEX
97
+ received_data << data
98
+ data = @serial_port.getc
99
+ end
100
+
101
+ else
102
+ handler = find_handler(data)
103
+ return unless handler
104
+ while received_data.length < method(handler).arity
105
+ received_data << @serial_port.getc
106
+ end
107
+ end
108
+
109
+ send(handler,*received_data) if handler
110
+
111
+ end
112
+
113
+ private
114
+
115
+ def find_handler(command)
116
+ case command
117
+ when Rufirmata::ANALOG_MESSAGE : :handle_analog_message
118
+ when Rufirmata::DIGITAL_MESSAGE : :handle_digital_message
119
+ when Rufirmata::REPORT_VERSION : :handle_report_version
120
+ when Rufirmata::REPORT_FIRMWARE : :handle_report_firmware
121
+ else return
122
+ end
123
+ end
124
+
125
+ def handle_analog_message(pin_number, lsb, msb)
126
+ value = (((msb << 7) + lsb).to_f / 1023).prec(4)
127
+ self.analog[pin_number].value = value if self.analog[pin_number].reporting
128
+ end
129
+
130
+ #Digital messages always go by the whole port. This means we have a
131
+ # bitmask wich we update the port.
132
+ def handle_digital_message(port_number, lsb, msb)
133
+ mask = (msb << 7) + lsb
134
+ self.digital_ports[port_number].update(mask)
135
+ end
136
+
137
+ def handle_report_version(major, minor)
138
+ @firmata_version = [major,minor]
139
+ end
140
+
141
+ def handle_report_firmware(*data)
142
+ major = data.shift
143
+ minor = data.shift
144
+ @firmata_version = [major,minor]
145
+ # TODO this is more complicated, values is send as 7 bit bytes
146
+ @firmware = data.map{|byte|byte.chr}.join('')
147
+ end
148
+
149
+ def initialize_layout
150
+ @analog, @digital, @digital_ports = [], [], []
151
+
152
+ @board_type[:analog_pins].each do |pin|
153
+ @analog << Rufirmata::Pin.new(self, pin, Rufirmata::ANALOG)
154
+ end
155
+
156
+ digital_pins = @board_type[:digital_pins]
157
+
158
+ if @board_type[:use_ports]
159
+ (0...digital_pins.length/7).each { |port| @digital_ports << Port.new(self, port)}
160
+ else
161
+ (0..digital_pins).each do |pin|
162
+ @digital << Rufirmata::Pin.new(self, pin, Rufirmata::DIGITAL)
163
+ end
164
+ end
165
+
166
+ (@digital + @analog).each do |pin|
167
+ pin.set_observer do |sender, type, args|
168
+ notifier.publish type, args.merge(:pin=>sender)
169
+ end
170
+ end
171
+
172
+ end #init layout
173
+
174
+
175
+ end #board
176
+
177
+ end #rufirmata
@@ -0,0 +1,5 @@
1
+ class Float
2
+ def prec(x)
3
+ sprintf("%.0" + x.to_i.to_s + "f", self).to_f
4
+ end
5
+ end
@@ -0,0 +1,123 @@
1
+ module Rufirmata
2
+ #A Pin representation
3
+ class Pin
4
+ include Observables::Base
5
+
6
+ attr_reader :board, :pin_number, :pin_type, :mode, :port, :pwm, :reporting, :value
7
+
8
+ def initialize(board,pin_number,pin_type,port = nil)
9
+ @board = board
10
+ @port = port
11
+ @pin_number = pin_number
12
+ @pin_type = pin_type
13
+ @reporting = false
14
+ @value = nil
15
+ @pwm = false
16
+ @mode = Rufirmata::INPUT
17
+ if pin_type == Rufirmata::DIGITAL
18
+ @pwm = board.board_type[:pwm_pins].include?(pin_number)
19
+ @mode = board.board_type[:disabled_pins].include?(pin_number) ?
20
+ Rufirmata::UNAVAILABLE : Rufirmata::OUTPUT
21
+ end
22
+ end
23
+
24
+ def to_s
25
+ type = self.pin_type == Rufirmata::ANALOG ? "Analog" : "Digital"
26
+ "#{type} pin #{pin_number}"
27
+ end
28
+
29
+
30
+ # Set the mode of operation for the pin
31
+ # Can be one of the pin modes: INPUT, OUTPUT, ANALOG or PWM
32
+ def mode=(mode)
33
+ #Can be Rufirmata::INPUT, OUTPUT, ANALOG, PWM or UNAVAILABLE
34
+ return if @mode == mode #Nothing is changing, so nothing to do
35
+
36
+ raise "#{to_s} does not have PWM capabilities" if mode == Rufirmata::PWM and !pwm
37
+ raise "#{to_s} cannot be used through Firmata" if @mode == Rufirmata::UNAVAILABLE
38
+
39
+ changing :pin_mode_changed, :changes=>{ :from=>@mode, :to=>mode } do
40
+ @mode = mode
41
+ unless mode == Rufirmata::UNAVAILABLE
42
+ board.write_command(Rufirmata::SET_PIN_MODE, pin_number, mode)
43
+ enable_reporting if mode == Rufirmata::INPUT
44
+ end
45
+ end
46
+
47
+ end
48
+
49
+ def reporting=(reporting)
50
+ return if @reporting == reporting
51
+ changing :reporting_changed, :changes=>{ :from=>@reporting, :to=>reporting } do
52
+ @reporting = reporting
53
+ end
54
+ end
55
+
56
+ def value=(new_value)
57
+ return if @value == new_value
58
+ changing :value_changed, :changes=>{ :from=>@value, :to=>new_value } do
59
+ @value = new_value
60
+ end
61
+ end
62
+
63
+ #Set an input pin to report values
64
+ def enable_reporting
65
+ raise "#{to_s} is not an input and therefore cannot report" unless mode == Rufirmata::INPUT
66
+ if pin_type == Rufirmata::ANALOG
67
+ self.reporting = true
68
+ board.write_command(Rufirmata::REPORT_ANALOG + pin_number, 1)
69
+ elsif port
70
+ port.enable_reporting
71
+ end
72
+ end
73
+
74
+ # Disable the reporting of an input pin
75
+ def disable_reporting
76
+ if pin_type == Rufirmata::ANALOG
77
+ @reporting = false
78
+ board.write_command(Rufirmata::REPORT_ANALOG + pin_number, 0)
79
+ elsif port
80
+ port.disable_reporting
81
+ end
82
+ end
83
+
84
+ #Returns the output value of the pin. This value is updated by the
85
+ #boards `Board.iterate` method. Value is always in the range 0.0 - 1.0
86
+ def read
87
+ raise "Cannot read pin #{to_s} because it is marked as UNAVAILABLE" if mode == Rufirmata::UNAVAILABLE
88
+ value
89
+ end
90
+
91
+ #Output a voltage from the pin
92
+ #
93
+ # :arg value: Uses value as a boolean if the pin is in output mode, or
94
+ # expects a float from 0 to 1 if the pin is in PWM mode.
95
+ def write(new_value)
96
+ raise "#{to_s} cannot be used through Firmata" if mode == Rufirmata::UNAVAILABLE
97
+ raise "#{to_s} is set up as an INPUT and therefore cannot be written to" if mode == Rufirmata::INPUT
98
+ if (new_value != value)
99
+ @value = new_value
100
+ if mode == Rufirmata::OUTPUT
101
+ port ? port.write() :
102
+ board.write_command(Rufirmata::DIGITAL_MESSAGE, pin_number, value)
103
+ elsif mode == Rufirmata::PWM
104
+ val = (@value * 255).to_i
105
+ board.write_command(Rufirmata::ANALOG_MESSAGE + pin_number, val % 128, val >> 7)
106
+ end
107
+ end
108
+ end #write
109
+
110
+ def send_sysex(sysex_cmd, data=[])
111
+ #Sends a SysEx message.
112
+ # :arg sysex_cmd: A sysex command byte
113
+ # :arg data: A list of data values
114
+ board.write_command(Rufirmata::START_SYSEX)
115
+ board.write_command(systex_cmd)
116
+ data.each do |byte|
117
+ byte = begin; byte.chr; rescue RangeError; (byte >> 7).chr; end
118
+ board.write(byte)
119
+ end
120
+ board.write_command(Rufirmata::END_SYSEX)
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,62 @@
1
+ module Rufirmata
2
+ #An 8-bit port on the board
3
+ class Port
4
+
5
+ attr_reader :board, :port_number, :pins
6
+ attr_accessor :reporting
7
+
8
+ def initialize(board, port_number)
9
+ @board = board
10
+ @port_number = port_number
11
+ @reporting = false
12
+ @pins = []
13
+ (0..7).each do |pin|
14
+ pin_num = pin + port_number * 8
15
+ pins << (pin = Rufirmata::Pin.new(board, pin_num, Rufirmata::DIGITAL, self))
16
+ board.digital << pin
17
+ end
18
+ end
19
+
20
+ def to_s; "Digital Port #{port_number}"; end
21
+
22
+ #Enable reporting of values for the whole port
23
+ def enable_reporting
24
+ @reporting = true
25
+ board.write_command(Rufirmata::REPORT_DIGITAL + port_number, 1)
26
+ pins.each {|pin|pin.reporting = true}
27
+ end
28
+
29
+ #Disable reporting of values for the whole port
30
+ def disable_reporting
31
+ @reporting = false
32
+ board.write_command(Rufirmata::REPORT_DIGITAL + port_number, 0)
33
+ end
34
+
35
+ #Set the output pins of the port to the correct state
36
+ def write
37
+ mask = 0
38
+ pins.each do |pin|
39
+ if (pin.mode == Rufirmata::OUTPUT)
40
+ if (pin.value == 1)
41
+ pin_nr = pin.pin_number - port_number * 8
42
+ mask |= 1 << pin_nr
43
+ end
44
+ end
45
+ end
46
+ board.write_command(Rufirmata::DIGITAL_MESSAGE + port_number, mask % 128, mask >> 7)
47
+ end
48
+
49
+ #Update the values for the pins marked as input with the mask
50
+ def update(mask)
51
+ if reporting
52
+ pins.each do |pin|
53
+ if pin.mode == Rufirmata::INPUT
54
+ pin_nr = pin.pin_number - port_number * 8
55
+ pin.value = (mask & (1 << pin_nr)) > 1
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ end
62
+ end
@@ -0,0 +1,3 @@
1
+ module Rufirmata
2
+ VERSION = "0.0.1"
3
+ end
data/lib/rufirmata.rb ADDED
@@ -0,0 +1,99 @@
1
+ require 'rubygems'
2
+ require 'serialport'
3
+ require 'observables'
4
+
5
+ base_dir = File.dirname(__FILE__)
6
+ [
7
+ 'version',
8
+ 'extensions',
9
+ 'pin',
10
+ 'port',
11
+ 'board',
12
+ 'arduino'
13
+ ].each {|req| require File.join(base_dir,'rufirmata',req)}
14
+
15
+ module Rufirmata
16
+ # Message command bytes - straight from Firmata.h
17
+ DIGITAL_MESSAGE = 0x90 # send data for a digital pin
18
+ ANALOG_MESSAGE = 0xE0 # send data for an analog pin (or PWM)
19
+ DIGITAL_PULSE = 0x91 # SysEx command to send a digital pulse
20
+
21
+ # PULSE_MESSAGE = 0xA0 # proposed pulseIn/Out msg (SysEx)
22
+ # SHIFTOUT_MESSAGE = 0xB0 # proposed shiftOut msg (SysEx)
23
+ REPORT_ANALOG = 0xC0 # enable analog input by pin #
24
+ REPORT_DIGITAL = 0xD0 # enable digital input by port pair
25
+ START_SYSEX = 0xF0 # start a MIDI SysEx msg
26
+ SET_PIN_MODE = 0xF4 # set a pin to INPUT/OUTPUT/PWM/etc
27
+ END_SYSEX = 0xF7 # end a MIDI SysEx msg
28
+ REPORT_VERSION = 0xF9 # report firmware version
29
+ SYSTEM_RESET = 0xFF # reset from MIDI
30
+ QUERY_FIRMWARE = 0x79 # query the firmware name
31
+
32
+ # extended command set using sysex (0-127/0x00-0x7F)
33
+ # 0x00-0x0F reserved for user-defined commands */
34
+ SERVO_CONFIG = 0x70 # set max angle, minPulse, maxPulse, freq
35
+ STRING_DATA = 0x71 # a string message with 14-bits per char
36
+ SHIFT_DATA = 0x75 # a bitstream to/from a shift register
37
+ I2C_REQUEST = 0x76 # send an I2C read/write request
38
+ I2C_REPLY = 0x77 # a reply to an I2C read request
39
+ I2C_CONFIG = 0x78 # config I2C settings such as delay times and power pins
40
+ REPORT_FIRMWARE = 0x79 # report name and version of the firmware
41
+ SAMPLING_INTERVAL = 0x7A # set the poll rate of the main loop
42
+ SYSEX_NON_REALTIME = 0x7E # MIDI Reserved for non-realtime messages
43
+ SYSEX_REALTIME = 0x7F # MIDI Reserved for realtime messages
44
+
45
+
46
+ # Pin modes.
47
+ # except from UNAVAILABLE taken from Firmata.h
48
+ UNAVAILABLE = -1
49
+ INPUT = 0 # as defined in wiring.h
50
+ OUTPUT = 1 # as defined in wiring.h
51
+ ANALOG = 2 # analog pin in analogInput mode
52
+ PWM = 3 # digital pin in PWM output mode
53
+
54
+ # Pin types
55
+ DIGITAL = OUTPUT # same as OUTPUT below
56
+ # ANALOG is already defined above
57
+
58
+ BOARD_TYPES = {
59
+ :arduino => {
60
+ :digital_pins => (0..13).to_a,
61
+ :analog_pins => (0..5).to_a,
62
+ :pwm_pins => [3, 5, 6, 9, 10, 11],
63
+ :use_ports => true,
64
+ :disabled_pins => [0, 1, 14, 15] #Rx, Tx, Crystal
65
+ },
66
+ :arduino_mega => {
67
+ :digital_pins => (0..53).to_a,
68
+ :analog_pins => (0..15).to_a,
69
+ :pwm_pints => (2..14).to_a,
70
+ :use_ports => true,
71
+ :disabled_pins => [0, 1, 14, 15] #Rx, Tx, Crystal
72
+ }
73
+ }
74
+
75
+ class << self
76
+ attr_reader :serial_ports
77
+
78
+ def create_serial_port(options={})
79
+ @serial_ports ||= {}
80
+ options = {
81
+ :serial_port => "/dev/ttyUSB0",
82
+ :baud_rate => 57600,
83
+ :parity => SerialPort::NONE,
84
+ :data_bits => 8,
85
+ :stop_bits => 1
86
+ }.merge(options)
87
+
88
+ sp = options[:serial_port]
89
+ @serial_ports[options] =
90
+ SerialPort.new(sp,
91
+ options[:baud_rate],
92
+ options[:data_bits],
93
+ options[:stop_bits],
94
+ options[:parity])
95
+ end
96
+
97
+ end
98
+
99
+ end
@@ -0,0 +1,228 @@
1
+ require "spec_helper"
2
+
3
+ describe Rufirmata::Board do
4
+ before(:each) do
5
+ Rufirmata.stub(:create_serial_port) { FakeSerial.new }
6
+ @board = Rufirmata::Board.new("/dev/ttyUSB0")
7
+ end
8
+
9
+ describe "handlers" do
10
+ it "should return nil for a failed analog read" do
11
+ @board.analog[3].reporting = true
12
+ @board.analog[3].read.should be_nil
13
+ end
14
+
15
+ # This sould set it correctly. 1023 (127, 7 in to 7 bit bytes) is the
16
+ # max value an analog pin will send and it should result in a value 1
17
+ it "should handle an analog message" do
18
+ @board.analog[3].reporting = true
19
+ @board.send(:handle_analog_message,3,127,7)
20
+ @board.analog[3].read.should == 1.0
21
+ end
22
+
23
+ # A digital message sets the value for a whole port. We will set pin
24
+ # 5 (That is on port 0) to 1 to test if this is working.
25
+ it "should handle a digital message" do
26
+ @board.digital_ports[0].reporting = true
27
+ @board.digital[5].mode = Rufirmata::INPUT
28
+ mask = 0
29
+ mask |= 1 << 5 # set the bit for pin 5 to to 1
30
+ @board.digital[5].read.should be_nil
31
+ @board.send(:handle_digital_message, 0, mask % 128, mask >> 7)
32
+ @board.digital[5].read.should be_true
33
+ end
34
+
35
+ it "should handle report version" do
36
+ @board.firmata_version.should be_nil
37
+ @board.send(:handle_report_version,2,1)
38
+ @board.firmata_version.should == [2,1]
39
+ end
40
+
41
+ it "should handle report firmware" do
42
+ @board.firmware.should be_nil
43
+ data = [2,1]
44
+ "Firmware_name".each_byte { |b|data << b}
45
+ @board.send(:handle_report_firmware,*data)
46
+ @board.firmware.should == 'Firmware_name'
47
+ @board.firmata_version.should == [2,1]
48
+ end
49
+
50
+ # type command channel first byte second byte
51
+ # ---------------------------------------------------------------------------
52
+ # analog I/O message 0xE0 pin # LSB(bits 0-6) MSB(bits 7-13)
53
+ describe "incoming analog message" do
54
+ it "should ignore incoming data when reporting=false" do
55
+ @board.serial_port.write_as_chars Rufirmata::ANALOG_MESSAGE + 4, 127, 7
56
+ @board.iterate()
57
+ @board.analog[4].read.should be_nil
58
+ end
59
+
60
+ it "should set incoming data on the pin value when reporting=true" do
61
+ @board.analog[4].enable_reporting
62
+ @board.serial_port.clear
63
+ @board.serial_port.write_as_chars Rufirmata::ANALOG_MESSAGE + 4, 127, 7
64
+ @board.iterate
65
+ @board.analog[4].read.should == 1.0
66
+ end
67
+ end
68
+
69
+ # type command channel first byte second byte
70
+ # ---------------------------------------------------------------------------
71
+ # digital I/O message 0x90 port LSB(bits 0-6) MSB(bits 7-13)
72
+ describe "incoming digital message" do
73
+ it "should set incoming digital data for input pins" do
74
+ @board.digital[9].mode = Rufirmata::INPUT
75
+ @board.serial_port.clear
76
+ mask = 0; mask |= 1 << (9 - 8) #set the bit for pin 9 to 1
77
+ @board.digital[9].read.should be_nil
78
+ @board.serial_port.write_as_chars Rufirmata::DIGITAL_MESSAGE + 1, mask % 128, mask >> 7
79
+ @board.iterate
80
+ @board.digital[9].read.should be_true
81
+ end
82
+ end
83
+
84
+ # version report format
85
+ # -------------------------------------------------
86
+ # 0 version report header (0xF9) (MIDI Undefined)
87
+ # 1 major version (0-127)
88
+ # 2 minor version (0-127)
89
+ describe "incoming report version" do
90
+ it "should set the firmata version" do
91
+ @board.serial_port.write_as_chars Rufirmata::REPORT_VERSION, 2, 1
92
+ @board.iterate
93
+ @board.firmata_version.should == [2,1]
94
+ end
95
+ end
96
+
97
+ # Receive Firmware Name and Version (after query)
98
+ # 0 START_SYSEX (0xF0)
99
+ # 1 queryFirmware (0x79)
100
+ # 2 major version (0-127)
101
+ # 3 minor version (0-127)
102
+ # 4 first 7-bits of firmware name
103
+ # 5 second 7-bits of firmware name
104
+ # x ...for as many bytes as it needs)
105
+ # 6 END_SYSEX (0xF7)
106
+ describe "incoming report firmware" do
107
+ before(:each) do
108
+ @board.serial_port.write_as_chars Rufirmata::START_SYSEX, Rufirmata::REPORT_FIRMWARE, 2, 1
109
+ @board.serial_port.write 'Firmware_name'.split(//)
110
+ @board.serial_port.write_as_chars Rufirmata::END_SYSEX
111
+ @board.iterate
112
+ end
113
+ it "should have set the appropriate firmware name" do
114
+ @board.firmware.should == 'Firmware_name'
115
+ end
116
+ it "should have set the firmata version" do
117
+ @board.firmata_version.should == [2,1]
118
+ end
119
+ end
120
+
121
+ # type command channel first byte second byte
122
+ # ---------------------------------------------------------------------------
123
+ # report analog pin 0xC0 pin # disable/enable(0/1) - n/a -
124
+ describe "report analog" do
125
+ it "should write an enable reporting command to the serial port" do
126
+ @board.analog[1].enable_reporting
127
+ @board.serial_port.should have_received_bytes( 0xC0 + 1, 1 )
128
+ @board.analog[1].reporting.should be_true
129
+ end
130
+ it "should write a disable reporting command to the serial port" do
131
+ @board.analog[1].reporting = true
132
+ @board.analog[1].disable_reporting
133
+ @board.serial_port.should have_received_bytes( 0xC0 + 1, 0 )
134
+ @board.analog[1].reporting.should be_false
135
+ end
136
+ end
137
+
138
+ # type command channel first byte second byte
139
+ # ---------------------------------------------------------------------------
140
+ # report digital port 0xD0 port disable/enable(0/1) - n/a -
141
+ describe "report digital" do
142
+ before(:each){ @board.digital[8].mode = Rufirmata::INPUT; @board.serial_port.clear }
143
+ # This should enable reporting of whole port 1
144
+ it "should write an enable reporting command for the port" do
145
+ @board.digital[8].enable_reporting
146
+ @board.serial_port.should have_received_bytes( 0xD0 + 1, 1 )
147
+ @board.digital[8].reporting.should be_true
148
+ end
149
+
150
+ it "should write a disable reporting command for the port" do
151
+ @board.digital[8].reporting = true
152
+ @board.digital[8].disable_reporting
153
+ @board.serial_port.should have_received_bytes( 0xD0 + 1, 0 )
154
+ end
155
+ end
156
+
157
+ # Generic Sysex Message
158
+ # 0 START_SYSEX (0xF0)
159
+ # 1 sysex command (0x00-0x7F)
160
+ # x between 0 and MAX_DATA_BYTES 7-bit bytes of arbitrary data
161
+ # last END_SYSEX (0xF7)
162
+ describe "SYSEX" do
163
+ it "should send a sysex message" do
164
+ @board.send_sysex 0x79, [1,2,3]
165
+ @board.serial_port.should have_received_bytes 0xF0, 0x79, 1, 2, 3, 0xF7
166
+ end
167
+
168
+ it "should receive a sysex message" do
169
+ @board.serial_port.write [0xF0, 0x79, 2, 1, 'a'[0], 'b'[0], 'c'[0], 0xF7].map{ |b|b.chr}
170
+ while @board.serial_port.length > 0
171
+ @board.iterate
172
+ end
173
+ @board.firmata_version.should == [2,1]
174
+ @board.firmware.should == 'abc'
175
+ end
176
+ end
177
+
178
+ describe "sending extraneous data" do
179
+ it "should be ignored" do
180
+ @board.analog[4].enable_reporting
181
+ @board.serial_port.clear
182
+ #Garbage
183
+ @board.serial_port.write_as_chars( *(0..10).to_a )
184
+ #Set analog port 4 to 1
185
+ @board.serial_port.write_as_chars Rufirmata::ANALOG_MESSAGE + 4, 127, 7
186
+ #More garbage
187
+ @board.serial_port.write_as_chars( *(0..10).to_a )
188
+ while @board.serial_port.length > 0
189
+ @board.iterate
190
+ end
191
+ @board.analog[4].read.should == 1.0
192
+ end
193
+ end
194
+
195
+ describe "notifications" do
196
+ before :each do
197
+ @changes = []
198
+ @board.subscribe do |type,args|
199
+ @changes << [type,args]
200
+ end
201
+ end
202
+
203
+
204
+ describe "when a pin is changed" do
205
+ before(:each){@board.analog[0].value = 1.1}
206
+
207
+ it "should issue before and after change notifications" do
208
+ @changes.length.should == 2
209
+ @changes[0][0].should == :before_value_changed
210
+ @changes[1][0].should == :after_value_changed
211
+ end
212
+
213
+ it "should pass the pin in the event args" do
214
+ @changes[0][1][:pin].should == @board.analog[0]
215
+ end
216
+
217
+ it "should provide the previous and new values of the pin" do
218
+ @changes.each do |c|
219
+ c[1][:changes].should == { :from=>nil, :to=>1.1}
220
+ end
221
+ end
222
+
223
+ end
224
+ end
225
+ end
226
+
227
+ end
228
+
data/spec/pin_spec.rb ADDED
@@ -0,0 +1,89 @@
1
+ require "spec_helper"
2
+
3
+ describe Rufirmata::Pin do
4
+ before(:each) do
5
+ Rufirmata.stub(:create_serial_port) { FakeSerial.new }
6
+ @board = Rufirmata::Board.new("/dev/ttyUSB0")
7
+ end
8
+
9
+ describe "notifications" do
10
+ before :each do
11
+ @changes = []
12
+ @pin = Rufirmata::Pin.new(@board, 2, Rufirmata::DIGITAL)
13
+ @pin.subscribe do |type, args|
14
+ @changes << [type, args]
15
+ end
16
+ end
17
+
18
+ describe "#mode" do
19
+ before(:each) { @pin.mode = Rufirmata::INPUT }
20
+
21
+ it "should issue a change notice before and after the change" do
22
+ @changes.count.should == 2
23
+ @changes[0][0].should == :before_pin_mode_changed
24
+ @changes[1][0].should == :after_pin_mode_changed
25
+ end
26
+
27
+ it "should notify changes to the pin mode" do
28
+ @changes.each do |c|
29
+ c[1].changes[:from].should == Rufirmata::OUTPUT
30
+ c[1].changes[:to].should == Rufirmata::INPUT
31
+ end
32
+ end
33
+
34
+ it "should not issue a notification if nothing changed" do
35
+ @changes.clear
36
+ @pin.mode = Rufirmata::INPUT
37
+ @changes.length.should == 0
38
+ end
39
+
40
+ end
41
+
42
+ describe "#reporting" do
43
+ before(:each){ @pin.reporting = true }
44
+
45
+ it "should issue a change before and after reporting changed" do
46
+ @changes.length.should == 2
47
+ @changes[0][0].should == :before_reporting_changed
48
+ @changes[1][0].should == :after_reporting_changed
49
+ end
50
+
51
+ it "should notify changes to reporting" do
52
+ @changes.each do |c|
53
+ c[1].changes[:from].should == false
54
+ c[1].changes[:to].should == true
55
+ end
56
+ end
57
+
58
+ it "should not issue a change when not changed" do
59
+ @changes.clear
60
+ @pin.reporting = true
61
+ @changes.length.should == 0
62
+ end
63
+ end
64
+
65
+ describe "#reporting" do
66
+ before(:each){ @pin.value = 1 }
67
+
68
+ it "should issue a change before and after value changed" do
69
+ @changes.length.should == 2
70
+ @changes[0][0].should == :before_value_changed
71
+ @changes[1][0].should == :after_value_changed
72
+ end
73
+
74
+ it "should notify changes to value" do
75
+ @changes.each do |c|
76
+ c[1].changes[:from].should == nil
77
+ c[1].changes[:to].should == 1
78
+ end
79
+ end
80
+
81
+ it "should not issue a change when not changed" do
82
+ @changes.clear
83
+ @pin.value = 1
84
+ @changes.length.should == 0
85
+ end
86
+ end
87
+
88
+ end
89
+ end
@@ -0,0 +1,7 @@
1
+ require "spec_helper"
2
+
3
+ describe Rufirmata do
4
+ it "should exist" do
5
+ Rufirmata::VERSION.should_not be_nil
6
+ end
7
+ end
@@ -0,0 +1,46 @@
1
+ require 'rubygems'
2
+
3
+ $:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
4
+
5
+ require 'rufirmata'
6
+ require 'rspec'
7
+
8
+ # Requires supporting files with custom matchers and macros, etc,
9
+ # in ./support/ and its subdirectories.
10
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
11
+ Dir["#{File.dirname(__FILE__)}/stubs/**/*.rb"].each {|f| require f}
12
+
13
+ require 'rspec/expectations'
14
+
15
+ RSpec::Matchers.define :have_received_bytes do |*expected|
16
+ match do |actual|
17
+ res = actual.read()
18
+ serial_msg = res
19
+ until res.nil? or res.empty?
20
+ res = actual.read()
21
+ serial_msg += res
22
+ end
23
+ expected = expected.map{|b|b.chr}.join('')
24
+ actual.instance_eval "def last_received; '#{serial_msg.inspect}'; end"
25
+ expected == serial_msg
26
+ end
27
+ failure_message_for_should do |actual|
28
+ "expected to receive #{expected.inspect} but received #{actual.last_received}"
29
+ end
30
+ end
31
+
32
+ Rspec.configure do |config|
33
+ # == Mock Framework
34
+
35
+ # If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
36
+ #
37
+ # config.mock_with :mocha
38
+ # config.mock_with :flexmock
39
+ # config.mock_with :rr
40
+ config.mock_with :rspec
41
+
42
+ config.before(:each) do
43
+
44
+ end
45
+
46
+ end
@@ -0,0 +1,50 @@
1
+
2
+ # A Mock object for ruby's Serial. Functions as a fifo-stack. Push to
3
+ # it with ``write``, read from it with ``read``.
4
+ #
5
+ # >>> s = Serial.new('someport', 4800)
6
+ # >>> s.read()
7
+ # ''
8
+ # >>> s.write(chr(100))
9
+ # >>> s.write('blaat')
10
+ # >>> s.write(100000)
11
+ # >>> s.read(2)
12
+ # ['d', 'blaat']
13
+ # >>> s.read()
14
+ # 100000
15
+ # >>> s.read()
16
+ # ''
17
+ # >>> s.read(2)
18
+ # ['', '']
19
+ # >>> s.close()
20
+ class FakeSerial < Array
21
+
22
+ def getc
23
+ read[0]
24
+ end
25
+
26
+ def read(count=1)
27
+ if count > 1
28
+ val = []
29
+ [i..count].each do |i|
30
+ val << (shift || '')
31
+ end
32
+ else
33
+ val = (shift || '')
34
+ end
35
+ val
36
+ end
37
+
38
+ def write(value)
39
+ (self << value).flatten!
40
+ end
41
+
42
+ def write_as_chars(*vals)
43
+ write vals.map{ |v| v.chr }
44
+ end
45
+
46
+ def close
47
+ clear()
48
+ end
49
+
50
+ end
metadata ADDED
@@ -0,0 +1,136 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rufirmata
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Nathan Stults
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-11-27 00:00:00 -08:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: ruby-serialport
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: observables
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 3
44
+ segments:
45
+ - 0
46
+ version: "0"
47
+ type: :runtime
48
+ version_requirements: *id002
49
+ - !ruby/object:Gem::Dependency
50
+ name: rspec
51
+ prerelease: false
52
+ requirement: &id003 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ~>
56
+ - !ruby/object:Gem::Version
57
+ hash: 1
58
+ segments:
59
+ - 2
60
+ - 1
61
+ version: "2.1"
62
+ type: :development
63
+ version_requirements: *id003
64
+ - !ruby/object:Gem::Dependency
65
+ name: fuubar
66
+ prerelease: false
67
+ requirement: &id004 !ruby/object:Gem::Requirement
68
+ none: false
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ hash: 3
73
+ segments:
74
+ - 0
75
+ version: "0"
76
+ type: :development
77
+ version_requirements: *id004
78
+ description: A Ruby port of pyFirmata, software for interfacing with firmata-enabled microcontrollers
79
+ email: hereiam@sonic.net
80
+ executables: []
81
+
82
+ extensions: []
83
+
84
+ extra_rdoc_files: []
85
+
86
+ files:
87
+ - lib/rufirmata.rb
88
+ - lib/rufirmata/extensions.rb
89
+ - lib/rufirmata/pin.rb
90
+ - lib/rufirmata/arduino.rb
91
+ - lib/rufirmata/board.rb
92
+ - lib/rufirmata/port.rb
93
+ - lib/rufirmata/version.rb
94
+ - spec/stubs/fake_serial.rb
95
+ - spec/spec_helper.rb
96
+ - spec/board_spec.rb
97
+ - spec/pin_spec.rb
98
+ - spec/rufirmata_spec.rb
99
+ - LICENSE
100
+ - README.rdoc
101
+ has_rdoc: true
102
+ homepage: http://github.com/PlasticLizard/rufirmata
103
+ licenses: []
104
+
105
+ post_install_message:
106
+ rdoc_options: []
107
+
108
+ require_paths:
109
+ - lib
110
+ required_ruby_version: !ruby/object:Gem::Requirement
111
+ none: false
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ hash: 3
116
+ segments:
117
+ - 0
118
+ version: "0"
119
+ required_rubygems_version: !ruby/object:Gem::Requirement
120
+ none: false
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ hash: 3
125
+ segments:
126
+ - 0
127
+ version: "0"
128
+ requirements: []
129
+
130
+ rubyforge_project: "[none]"
131
+ rubygems_version: 1.3.7
132
+ signing_key:
133
+ specification_version: 3
134
+ summary: A ruby firmata client for interfacing with microcontollers running firmata compatible firmware
135
+ test_files: []
136
+