ruby-nxt 0.8.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.
@@ -0,0 +1,72 @@
1
+ # ruby-nxt Control Mindstorms NXT via Bluetooth Serial Port Connection
2
+ # Copyright (C) 2006 Tony Buser <tbuser@gmail.com> - http://juju.org
3
+ #
4
+ # This program is free software; you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation; either version 2 of the License
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with this program; if not, write to the Free Software Foundation,
15
+ # Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
16
+
17
+ # Implements the "Rotation Sensor" block in NXT-G (using an nxt motor as rotation sensor)
18
+ class Commands::RotationSensor
19
+
20
+ attr_accessor :port, :trigger_direction, :trigger_point, :comparison
21
+
22
+ def initialize(nxt)
23
+ @nxt = nxt
24
+
25
+ # defaults the same as NXT-G
26
+ @port = :a
27
+ @trigger_direction = :forward
28
+ @trigger_point = 360
29
+ @comparison = ">"
30
+ reset
31
+ @internal_counter = degrees
32
+ end
33
+
34
+ # returns true or false based on comparison type and difference between last time reset point
35
+ def logic
36
+ @trigger_direction == :forward ? trigger = @trigger_point : trigger = -@trigger_point
37
+ case @comparison
38
+ when ">"
39
+ degrees >= trigger ? true : false
40
+ when "<"
41
+ degrees <= trigger ? true : false
42
+ end
43
+ end
44
+
45
+ # resets the value_scaled property, use this to reset the degrees counter
46
+ def reset
47
+ @nxt.reset_motor_position(NXTComm.const_get("MOTOR_#{@port.to_s.upcase}"))
48
+ end
49
+
50
+ # returns the number of degrees moved since last reset point
51
+ def degrees
52
+ @internal_counter = rotation_count
53
+ @internal_counter
54
+ end
55
+
56
+ # attempts to determine direction based on last time position was requested (may not be accurate)
57
+ def direction
58
+ case true
59
+ when rotation_count > @internal_counter
60
+ "forwards"
61
+ when rotation_count < @internal_counter
62
+ "backwards"
63
+ when rotation_count == @internal_counter
64
+ "stopped"
65
+ end
66
+ end
67
+
68
+ # attempt to return the output_state requested
69
+ def method_missing(cmd)
70
+ @nxt.get_output_state(NXTComm.const_get("MOTOR_#{@port.to_s.upcase}"))[cmd]
71
+ end
72
+ end
@@ -0,0 +1,102 @@
1
+ # ruby-nxt Control Mindstorms NXT via Bluetooth Serial Port Connection
2
+ # Copyright (C) 2006 Tony Buser <tbuser@gmail.com> - http://juju.org
3
+ #
4
+ # This program is free software; you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation; either version 2 of the License
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with this program; if not, write to the Free Software Foundation,
15
+ # Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
16
+
17
+ # Implements the "Sound" block in NXT-G
18
+ class Commands::Sound
19
+
20
+ attr_accessor :action
21
+ attr_accessor :control
22
+ attr_accessor :volume # TODO not sure how to set this with direct commands?
23
+ attr_accessor :repeat
24
+ attr_accessor :file
25
+ attr_accessor :note
26
+ attr_accessor :duration
27
+ attr_accessor :wait
28
+
29
+ def initialize(nxt)
30
+ @nxt = nxt
31
+
32
+ # defaults the same as NXT-G
33
+ @action = :file
34
+ @control = :play
35
+ @volume = 75
36
+ @repeat = false
37
+ @file = "Good Job.rso"
38
+ @note = "C"
39
+ @duration = 0.5
40
+ @wait = true
41
+ end
42
+
43
+ # execute the Sound command based on the properties specified
44
+ def start
45
+ if @action == :file
46
+ @nxt.play_sound_file(@file,@repeat)
47
+ else
48
+ @nxt.play_tone(@note.to_freq,(@duration * 1000).to_i)
49
+ end
50
+
51
+ # TODO figure out a logical way to repeat a tone without blocking execution
52
+
53
+ if @wait
54
+ if @action == :tone
55
+ sleep(@duration)
56
+ else
57
+ # TODO don't know how to sleep until a sound file finishes
58
+ end
59
+ end
60
+ end
61
+
62
+ # stop the Sound command
63
+ def stop
64
+ @nxt.stop_sound_playback
65
+ end
66
+
67
+ end
68
+
69
+ class String
70
+ # converts a note string to equiv frequency in Hz
71
+ # TODO need to get a better range...
72
+ def to_freq
73
+ case self.downcase
74
+ when "c"
75
+ 523
76
+ when "c#"
77
+ 554
78
+ when "d"
79
+ 587
80
+ when "d#"
81
+ 622
82
+ when "e"
83
+ 659
84
+ when "f"
85
+ 698
86
+ when "f#"
87
+ 740
88
+ when "g"
89
+ 784
90
+ when "g#"
91
+ 830
92
+ when "a"
93
+ 880
94
+ when "a#"
95
+ 923
96
+ when "b"
97
+ 988
98
+ else
99
+ raise "Unknown Note: #{self}"
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,67 @@
1
+ # ruby-nxt Control Mindstorms NXT via Bluetooth Serial Port Connection
2
+ # Copyright (C) 2006 Tony Buser <tbuser@gmail.com> - http://juju.org
3
+ #
4
+ # This program is free software; you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation; either version 2 of the License
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with this program; if not, write to the Free Software Foundation,
15
+ # Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
16
+
17
+ require "nxt_comm"
18
+ require "commands/mixins/sensor"
19
+
20
+ # Implements the "Sound Sensor" block in NXT-G
21
+ class Commands::SoundSensor
22
+
23
+ include Commands::Mixins::Sensor
24
+
25
+ attr_reader :port, :mode
26
+ attr_accessor :trigger_point, :comparison
27
+
28
+ def initialize(nxt)
29
+ @nxt = nxt
30
+
31
+ # defaults the same as NXT-G
32
+ @port = 2
33
+ @trigger_point = 50
34
+ @comparison = ">"
35
+ @mode = "dba"
36
+ set_mode
37
+ end
38
+
39
+ def mode=(mode)
40
+ @mode = mode
41
+ set_mode
42
+ end
43
+
44
+ # scaled value read from sensor
45
+ def sound_level
46
+ value_scaled
47
+ end
48
+
49
+ # returns the raw value of the sensor
50
+ def raw_value
51
+ value_raw
52
+ end
53
+
54
+ # sets up the sensor port
55
+ def set_mode
56
+ @nxt.set_input_mode(
57
+ NXTComm.const_get("SENSOR_#{@port}"),
58
+ NXTComm.const_get("SOUND_#{@mode.upcase}"),
59
+ NXTComm::PCTFULLSCALEMODE
60
+ )
61
+ end
62
+
63
+ # attempt to return the input_value requested
64
+ def method_missing(cmd)
65
+ @nxt.get_input_values(NXTComm.const_get("SENSOR_#{@port}"))[cmd]
66
+ end
67
+ end
@@ -0,0 +1,84 @@
1
+ # ruby-nxt Control Mindstorms NXT via Bluetooth Serial Port Connection
2
+ # Copyright (C) 2006 Tony Buser <tbuser@gmail.com> - http://juju.org
3
+ #
4
+ # This program is free software; you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation; either version 2 of the License
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with this program; if not, write to the Free Software Foundation,
15
+ # Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
16
+
17
+ # require File.dirname(File.expand_path(__FILE__))+'/../nxt_comm'
18
+ # require File.dirname(File.expand_path(__FILE__))+'/mixins/sensor'
19
+ require "nxt_comm"
20
+ require "commands/mixins/sensor"
21
+
22
+ # Implements the "Touch Sensor" block in NXT-G
23
+ class Commands::TouchSensor
24
+
25
+ include Commands::Mixins::Sensor
26
+
27
+ attr_reader :port, :action
28
+
29
+ def initialize(nxt)
30
+ @nxt = nxt
31
+
32
+ # defaults the same as NXT-G
33
+ @port = 1
34
+ @action = :pressed
35
+ set_mode
36
+ end
37
+
38
+ def action=(action)
39
+ @action = action
40
+ set_mode
41
+ end
42
+ alias trigger_point= action=
43
+
44
+ def comparison=(op)
45
+ raise NotImplementedError, "Cannot assign a comparison operator for this sensor type."
46
+ end
47
+
48
+ # returns true or false based on action type
49
+ def logic
50
+ case @action
51
+ when :pressed
52
+ value_scaled > 0 ? true : false
53
+ when :released
54
+ value_scaled > 0 ? false : true
55
+ when :bumped
56
+ value_scaled > 0 ? true : false
57
+ end
58
+ end
59
+
60
+ # returns the raw value of the sensor
61
+ def raw_value
62
+ value_raw
63
+ end
64
+
65
+ # resets the value_scaled property, use this to reset the sensor when in :bumped mode
66
+ def reset
67
+ @nxt.reset_input_scaled_value(NXTComm.const_get("SENSOR_#{@port}"))
68
+ end
69
+
70
+ # sets up the sensor port
71
+ def set_mode
72
+ @action == :bumped ? mode = NXTComm::PERIODCOUNTERMODE : mode = NXTComm::BOOLEANMODE
73
+ @nxt.set_input_mode(
74
+ NXTComm.const_get("SENSOR_#{@port}"),
75
+ NXTComm::SWITCH,
76
+ mode
77
+ )
78
+ end
79
+
80
+ # attempt to return the input_value requested
81
+ def method_missing(cmd)
82
+ @nxt.get_input_values(NXTComm.const_get("SENSOR_#{@port}"))[cmd]
83
+ end
84
+ end
@@ -0,0 +1,84 @@
1
+ # ruby-nxt Control Mindstorms NXT via Bluetooth Serial Port Connection
2
+ # Copyright (C) 2006 Tony Buser <tbuser@gmail.com> - http://juju.org
3
+ #
4
+ # This program is free software; you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation; either version 2 of the License
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with this program; if not, write to the Free Software Foundation,
15
+ # Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
16
+
17
+ require "nxt_comm"
18
+ require "ultrasonic_comm"
19
+ require "commands/mixins/sensor"
20
+
21
+ # Implements the "Ultrasonic Sensor" block in NXT-G
22
+ class Commands::UltrasonicSensor
23
+
24
+ include Commands::Mixins::Sensor
25
+
26
+ # Exception thrown by distance! when the sensor cannot determine the distance.
27
+ class UnmeasurableDistance < Exception; end
28
+
29
+ attr_reader :port
30
+ attr_accessor :mode, :trigger_point, :comparison
31
+
32
+ def initialize(nxt)
33
+ @nxt = nxt
34
+
35
+ # defaults the same as NXT-G
36
+ @port = 4
37
+ @trigger_point = 50
38
+ @comparison = "<"
39
+ @mode = :inches
40
+ set_mode
41
+ end
42
+
43
+ # returns distance in requested mode (:inches or :centimeters)
44
+ def distance
45
+ @nxt.ls_write(NXTComm.const_get("SENSOR_#{@port}"), UltrasonicComm.read_measurement_byte(0))
46
+
47
+ # Keep checking until we have data to read
48
+ while @nxt.ls_get_status(NXTComm.const_get("SENSOR_#{@port}")) < 1
49
+ sleep(0.1)
50
+ # TODO: implement timeout so we don't get stuck if the expected data never comes
51
+ end
52
+
53
+ distance = @nxt.ls_read(NXTComm.const_get("SENSOR_#{@port}"))[:data][0]
54
+
55
+ if @mode == :centimeters
56
+ distance.to_i
57
+ else
58
+ (distance * 0.3937008).to_i
59
+ end
60
+ end
61
+ alias value_scaled distance
62
+
63
+ # returns the distance in requested mode; raises an UnmeasurableDistance exception
64
+ # when the distance cannot be measured (i.e. when the distance == 255, which is
65
+ # the code the sensor returns when it cannot get a distance reading)
66
+ def distance!
67
+ d = distance
68
+ raise UnmeasurableDistance if d == 255
69
+ return d
70
+ end
71
+
72
+ # sets up the sensor port
73
+ def set_mode
74
+ @nxt.set_input_mode(
75
+ NXTComm.const_get("SENSOR_#{@port}"),
76
+ NXTComm::LOWSPEED_9V,
77
+ NXTComm::RAWMODE
78
+ )
79
+ # clear buffer
80
+ @nxt.ls_read(NXTComm.const_get("SENSOR_#{@port}"))
81
+ # set sensor to continuously send pings
82
+ @nxt.ls_write(NXTComm.const_get("SENSOR_#{@port}"), UltrasonicComm.continuous_measurement_command)
83
+ end
84
+ end
@@ -0,0 +1,235 @@
1
+ # ruby-nxt Control Mindstorms NXT via Bluetooth Serial Port Connection
2
+ # Copyright (C) 2006 Matt Zukowski <matt@roughest.net>
3
+ #
4
+ # This program is free software; you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation; either version 2 of the License
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with this program; if not, write to the Free Software Foundation,
15
+ # Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
16
+
17
+ require "yaml"
18
+ require "brick"
19
+
20
+ # Controls an NXT motor.
21
+ #
22
+ # Example:
23
+ #
24
+ # m = Motor.new('a')
25
+ # m.forward(:degrees => 180, :power => 50)
26
+ #
27
+ class Motor < Brick
28
+
29
+ POLL_INTERVAL = 0.5
30
+
31
+ attr_accessor :ratio
32
+
33
+ def initialize(nxt, port)
34
+ super(nxt, port)
35
+
36
+ @port = formalize_motor_port_name(port)
37
+
38
+ @ratio = 0
39
+ end
40
+
41
+ def name
42
+ case self.port
43
+ when NXTComm::MOTOR_A then 'a'
44
+ when NXTComm::MOTOR_B then 'b'
45
+ when NXTComm::MOTOR_C then 'c'
46
+ end
47
+ end
48
+
49
+ ### high level commands #####################################################
50
+
51
+ # Rotate the motor forwards.
52
+ # See Motor#run.
53
+ # Examples:
54
+ #
55
+ # m.forward(:degrees => 360, :power => 20)
56
+ #
57
+ # The above rotates the motor 360 degrees forward at 20% power.
58
+ #
59
+ # m.forward(:time => 5)
60
+ #
61
+ # The above rotates the motor forwards for 5 seconds at 25% power
62
+ # (because 25% is the default).
63
+ def forward(options)
64
+ debug(options.to_yaml, :forward)
65
+ options[:direction] = 1
66
+ return run(options)
67
+ end
68
+
69
+ # Rotate the motor backwards.
70
+ # See Motor#forward and Motor#run.
71
+ def backward(options)
72
+ debug(options, :backward)
73
+ options[:direction] = -1
74
+ return run(options)
75
+ end
76
+
77
+ # Return the current state of the motor as a hash.
78
+ # See NXTComm#get_output_state for info on what data is available here.
79
+ def read_state
80
+ @log.debug(:read_state) { "getting state"}
81
+ state = @nxt.get_output_state(@port)
82
+ @log.debug(:read_state) { "got state" }
83
+
84
+ debug(state, :state)
85
+
86
+ return state
87
+ end
88
+
89
+
90
+ ### low level commands ######################################################
91
+
92
+ # Low-level command for initiating motor rotation.
93
+ # Options is a hash with the following keys:
94
+ # [+:power+] Power from 0 to 100. Default is 25.
95
+ # [+:time+] Maximum time to run the motor in seconds.
96
+ # By default there is no time limit.
97
+ # If :degrees is also specified then the motor
98
+ # will only turn as far as :degrees and will stop and
99
+ # wait out the remaining :time has expired.
100
+ # [+:regulate+] False to disable power regulation. It is true (i.e. on)
101
+ # by default.
102
+ # [+:degrees+] The maximum tachometer degrees to turn before automatically
103
+ # stopping. Use negative values for backward movement (need to
104
+ # double check that this is in fact true)
105
+ # [+:direction+] Direction in which the motor should move. 1 for forward,
106
+ # -1 for backward. The default is 1 (i.e. forward).
107
+ # [+:wait_until_complete+] If true, the motor will block further commands
108
+ # until this command is complete. This is true by default when
109
+ # :degrees or :time is specified, false by default otherwise.
110
+ # NOTE: currently this setting is always on and cannot be turned off
111
+ # when :time is specified... this will probably be fixed in the future
112
+ # [+:brake_on_stop+] If true, the motor will try to hard brake when the
113
+ # command completes (otherwise when the command finishes the
114
+ # motor may continue to coast for a while -- especially at higher
115
+ # power levels). This is true by default when :degrees or :time
116
+ # is specified, false by default otherwise.
117
+ #
118
+ #
119
+ # ====Examples:
120
+ #
121
+ # Rotate backward up to 90 degrees:
122
+ #
123
+ # m.run(:degrees => 90, :direction => -1)
124
+ #
125
+ # Rotate forward for 8 seconds at 100% power:
126
+ #
127
+ # m.run(:time => 8, :power => 100)
128
+ #
129
+ # Forward for 3 seconds up to 180 degrees at 50% power. If the 180 degree
130
+ # rotation takes only 1 second to complete, the motor will
131
+ # sit there and wait out the full 3 seconds anyway:
132
+ #
133
+ # m.run(:power => 50, :degrees => 180, :time => 3)
134
+ #
135
+ # Rotate forward indefinitely (until Motor#stop is called).
136
+ #
137
+ # m.run
138
+ #
139
+ def run(options)
140
+ debug(options, :run)
141
+
142
+ if options[:power]
143
+ power = options[:power].to_i.abs
144
+ else
145
+ power = 25
146
+ end
147
+
148
+ time = options[:time] || nil
149
+ regulate = options[:regulate] || true
150
+ regulation_mode = options[:regulation_mode] || "speed"
151
+ degrees = options[:degrees] || 0
152
+ ratio = options[:ratio] || self.ratio
153
+ direction = options[:direction] || 1 # 1 is forward, -1 is backward
154
+
155
+ brake_on_stop = options.has_key?(:time) || options.has_key?(:degrees)
156
+ wait_until_complete = options.has_key?(:time) || options.has_key?(:degrees)
157
+
158
+ brake_on_stop = options[:brake_on_stop] if options.has_key?(:brake_on_stop)
159
+
160
+ wait_until_complete = options[:wait_until_complete] if options.has_key?(:wait_until_complete)
161
+ # FIXME: wait_until_complete MUST be true if a time period is specified, otherwise we have no way of
162
+ # enforcing the time limit (this is a problem with the way threading is implemented...)
163
+ wait_until_complete = true if options.has_key?(:time)
164
+
165
+ power = direction * power
166
+
167
+ mode = NXTComm::MOTORON
168
+ mode |= NXTComm::BRAKE if brake_on_stop
169
+ mode |= NXTComm::REGULATED if regulate
170
+
171
+ if regulate
172
+ case regulation_mode
173
+ when "idle"
174
+ regulation_mode = NXTComm::REGULATION_MODE_IDLE
175
+ when "speed"
176
+ regulation_mode = NXTComm::REGULATION_MODE_MOTOR_SPEED
177
+ when "sync"
178
+ regulation_mode = NXTComm::REGULATION_MODE_MOTOR_SYNC
179
+ end
180
+ else
181
+ regulation_mode = NXTComm::REGULATION_MODE_IDLE
182
+ end
183
+
184
+ @log.debug(:run) {"sending run command"}
185
+ @nxt.set_output_state(@port, power, mode, regulation_mode, ratio, NXTComm::MOTOR_RUN_STATE_RUNNING, degrees)
186
+
187
+ if time.nil?
188
+ if wait_until_complete
189
+ @log.debug(:run) {"sleeping until run_state is idle"}
190
+ until read_state[:run_state] == NXTComm::MOTOR_RUN_STATE_IDLE
191
+ sleep(POLL_INTERVAL)
192
+ @log.debug(:run) {"checking run_state again"}
193
+ end
194
+ @log.debug(:run) {"run_state is idle"}
195
+ end
196
+ else
197
+ @log.debug(:run) {"waiting #{time} seconds until stop"}
198
+ sleep(time)
199
+ @log.debug(:run) {"stopping"}
200
+ self.stop
201
+ @log.debug(:run) {"stopped"}
202
+ end
203
+ end
204
+
205
+ # Stop movement.
206
+ def stop
207
+ debug(nil, :stop)
208
+ @nxt.set_output_state(@port, 100, NXTComm::BRAKE, NXTComm::REGULATION_MODE_MOTOR_SPEED, 100, NXTComm::MOTOR_RUN_STATE_IDLE, 0)
209
+ end
210
+
211
+ # Resets the motor's tachometer movement count (i.e. +:degree_count+ in Motor#state).
212
+ def reset_tacho
213
+ @log.debug(:reset_tacho) { "resetting tacho" }
214
+ @nxt.reset_motor_position(@port, false)
215
+ @log.debug(:reset_tacho) { "reset tacho" }
216
+ end
217
+
218
+ private
219
+ def formalize_motor_port_name(port)
220
+ port = port.downcase.intern if port.kind_of? String
221
+
222
+ if port.kind_of? Symbol
223
+ case port
224
+ when :a then return NXTComm::MOTOR_A
225
+ when :b then return NXTComm::MOTOR_B
226
+ when :c then return NXTComm::MOTOR_C
227
+ end
228
+ elsif [NXTComm::MOTOR_A, NXTComm::MOTOR_B, NXTComm::MOTOR_C].include? port
229
+ return port
230
+ else
231
+ raise "'#{port}' is not a valid motor port"
232
+ end
233
+ end
234
+
235
+ end