Class: NXT

Inherits:
Object
  • Object
show all
Defined in:
lib/nxt.rb

Overview

High-level interface for controlling motors and sensors connected to the NXT. Currently only motors and some other misc functionality is implemented.

Examples:

nxt = NXT.new('/dev/tty.NXT-DevB-1')

nxt.motor_a do |m|
  m.forward(:degrees => 180, :power => 15)
end

nxt.motors_bc do |m|
  m.backward(:time => 5, :power => 20)
end

nxt.motors_abc do |m|
  m.reset_tacho
  m.forward(:time => 3, :power => 10)
  puts "Motor #{m.name} moved #{m.read_state[:degree_count]} degrees."
end

nxt.disconnect

Be sure to call NXT#disconnect when done sending commands, otherwise there may be trouble if you try to connect or send commands again afterwards.

Instance Method Summary collapse

Constructor Details

#initialize(dev = $DEV) ⇒ NXT

Initialize the NXT. This creates three Motor instances and one kind of each sensor. It is assumed that the sensors are connected to the standard ports as follows:

  • Port 1: Touch

  • Port 2: Sound

  • Port 3: Light

  • Port 4: Ultrasonic

You can specify the path to the serialport device (e.g. ‘/dev/tty.NXT-DevB-1’) or omit the argument to use the serialport device specified in the global $DEV variable.



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/nxt.rb', line 64

def initialize(dev = $DEV)
  @nxt = NXTComm.new(dev)
  
  @motors = {}
  @motors[:a] = Motor.new(@nxt, :a)
  @motors[:b] = Motor.new(@nxt, :b)
  @motors[:c] = Motor.new(@nxt, :c)
  
  @sensors = {}
  @sensors[1] = TouchSensor.new(@nxt, NXTComm::SENSOR_1)
  @sensors[2] = SoundSensor.new(@nxt, NXTComm::SENSOR_2)
  @sensors[3] = LightSensor.new(@nxt, NXTComm::SENSOR_3)
  @sensors[4] = UltrasonicSensor.new(@nxt, NXTComm::SENSOR_4)
  
  @motor_threads = {}
  @sensor_threads = {}
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method, *args, &block) ⇒ Object



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/nxt.rb', line 82

def method_missing(method, *args, &block)
  name = method.id2name
  if /^motor_([abc])$/ =~ name
    motor($1, block)
  elsif /^motors_([abc]+?)$/ =~ name
    motors($1, block)
  elsif /^sensor_([1234])$/ =~ name
	sensor($1, block)
elsif /^sensor_(touch|sound|light|ultrasonic)$/ =~ name or
		/^(touch|sound|light|ultrasonic)_sensor$/ =~ name
	case $1
		when 'touch'
			sensor(1, block)
		when 'sound'
			sensor(2, block)
		when 'light'
			sensor(3, block)
		when 'ultrasonic'
			sensor(4, block)
		else
			raise "'#{$1}' is not a valid sensor."
	end
  else
  	# if the method is not recognized, we assume it is a low-level NXTComm command
    m = @nxt.method(method)
    m.call(*args)
  end
end

Instance Method Details

#disconnectObject

Waits for all running jobs to finish and cleanly closes all connections to the NXT device. You should always call this when done sending commands to the NXT.



183
184
185
186
187
# File 'lib/nxt.rb', line 183

def disconnect
  @sensor_threads.each {|i,t| t.join}
  @motor_threads.each {|i,t| t.join}
  @nxt.close
end

#motor(id, proc) ⇒ Object

Runs the given proc for the given motor. You should use the motor_x dynamic method instead of calling this directly. For example…

nxt.motor_a {|m| m.forward(:degrees => 180)}

…would rotate motor A by 180 degrees, while…

nxt.motor_b {|m| m.forward(:degrees => 180)}

…would do the same for motor B.



145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/nxt.rb', line 145

def motor(id, proc)
  id = id.intern if id.kind_of? String
  
  # If a thread for this motor is already running, wait until it's finished.
  # In other words, don't try to send another command to the motor if it is already
  # doing something else; wait until it's done and then send.
  # FIXME: I think this blocks the entire program... is that what we really want?
  #        I think it is, but need to think about it more...
  @motor_threads[id].join if (@motor_threads[id] and @motor_threads[id].alive?)
  
  t = Thread.new(@motors[id]) do |m|
    proc.call(m)
  end
  
  @motor_threads[id] = t
end

#motors(which, proc) ⇒ Object

Runs the given proc for multiple motors. You should use the motors_xxx dynamic method instead of calling this directly. For example…

nxt.motors_abc {|m| m.forward(:degrees => 180)}

…would run the given block simultanously on all three motors, while…

nxt.motors_bc {|m| m.forward(:degrees => 180)}

…would only run it on motors B and C.



123
124
125
126
127
128
129
130
131
# File 'lib/nxt.rb', line 123

def motors(which, proc)
  which = which.scan(/\w/) if which.kind_of? String
  which = which.uniq
  which = @motors.keys if which.nil? or which.empty?
  
  which.each do |id|
    motor(id, proc)
  end
end

#sensor(id, proc) ⇒ Object



162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/nxt.rb', line 162

def sensor(id, proc)
	id = id.to_i
	
	@sensor_threads[id].join if (@sensor_threads[id] and @sensor_threads[id].alive?)
	
	t = Thread.new(@sensors[id]) do |m|
		proc.call(m)
	end
	
	# FIXME: this blocks until we get something back from the sensor... probably 
	#        not the smartest way to do this
	t.join
	
	# FIXME: do we need to store the thread? it will always be dead by this point..
	@sensor_threads[id] = t
end