ruby-nxt 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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