rufirmata 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+