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,45 @@
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
+
19
+ require File.dirname(File.expand_path(__FILE__))+'/sensor'
20
+
21
+ class LightSensor < Sensor
22
+
23
+ def initialize(nxt, port = NXTComm::SENSOR_3)
24
+ super(nxt, port)
25
+ use_illuminated_mode
26
+ end
27
+
28
+ # Get the current light level as a float from 0 to 1.0.
29
+ # 1.0 is maximum, 0 is minimum.
30
+ def get_light_level
31
+ # TODO: probably need to calibrate this... light level never really reaches 1023
32
+ (read_data[:value_scaled]).to_f / 1023.to_f
33
+ end
34
+
35
+
36
+ def use_illuminated_mode
37
+ set_input_mode(NXTComm::LIGHT_ACTIVE, NXTComm::RAWMODE)
38
+ end
39
+
40
+
41
+ def use_ambient_mode
42
+ set_input_mode(NXTComm::LIGHT_INACTIVE, NXTComm::RAWMODE)
43
+ end
44
+
45
+ end
@@ -0,0 +1,93 @@
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
+
19
+ require File.dirname(File.expand_path(__FILE__))+'/../brick'
20
+
21
+ # Controls and reads an NXT sensor.
22
+ class Sensor < Brick
23
+
24
+ POLL_INTERVAL = 0.25
25
+
26
+ # TODO: internal sensor numbering is 0x00 to 0x03, but sensor ports on the brick are marked 1 to 4...
27
+ # need to come up with some way to make sure there isn't confusion (or maybe it's okay, since
28
+ # if the user probably never uses the Sensor classes directly, only interacting via the NXT
29
+ # class?)
30
+
31
+
32
+ def initialize(nxt, port)
33
+ super(nxt, port)
34
+ @port = port
35
+ end
36
+
37
+ def name
38
+ "#{@port + 1}"
39
+ end
40
+
41
+ def set_input_mode(type, mode)
42
+ @nxt.set_input_mode(@port, type, mode)
43
+ end
44
+
45
+ def read_data
46
+ data = @nxt.get_input_values(@port)
47
+
48
+ debug(data.inspect, :read_data)
49
+ return data
50
+ end
51
+
52
+ # Continuously evalutes the given block until it returns at
53
+ # least the given value (that is, until the block returns a
54
+ # value equal or greater than the given argument). If no
55
+ # value is specified, the block will be continuously
56
+ # evaluated until it returns true. Optionally, a comparison
57
+ # operator can be specified as the second parameter,
58
+ # otherwise >= is used (or == if expected value is Boolean).
59
+ #
60
+ # Simple example:
61
+ #
62
+ # ts = TouchSensor.new(@nxt)
63
+ # ts.wait_for_event { ts.is_pressed? }
64
+ #
65
+ # Example with an expected value:
66
+ #
67
+ # ls = LightSensor.new(@nxt)
68
+ # ls.wait_for_event(0.55) do
69
+ # ls.get_light_level
70
+ # end
71
+ #
72
+ def wait_for_event(expected = true, operator = nil)
73
+ if operator
74
+ comp = operator
75
+ elsif expected.respond_to? '>='.intern
76
+ comp = ">="
77
+ else
78
+ comp = "=="
79
+ end
80
+
81
+ while true
82
+ value = yield
83
+ return value if eval("value #{comp} expected")
84
+ sleep(POLL_INTERVAL)
85
+ end
86
+ end
87
+
88
+ def off
89
+ # Turns off the sensor.
90
+ set_input_mode(NXTComm::NO_SENSOR, NXTComm::RAWMODE)
91
+ end
92
+
93
+ end
@@ -0,0 +1,48 @@
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
+
19
+ require File.dirname(File.expand_path(__FILE__))+'/sensor'
20
+
21
+ class SoundSensor < Sensor
22
+
23
+ def initialize(nxt, port = NXTComm::SENSOR_2)
24
+ super(nxt, port)
25
+ use_adjusted_mode
26
+ end
27
+
28
+ # Get the current sound level as a float from 0 to 1.0.
29
+ # 1.0 is maximum, 0 is minimum.
30
+ def get_sound_level
31
+ # TODO: should probably do some basic calibration here...
32
+ read_data[:value_normal] / 1023.to_f
33
+ end
34
+
35
+ # Sound level measurement is NOT adjusted to match the psychoacoustic properties
36
+ # of human hearing. Sounds that may not be loud to the human ear may show up as loud
37
+ # and vice versa.
38
+ def use_unadjusted_mode
39
+ set_input_mode(NXTComm::SOUND_DB, NXTComm::RAWMODE)
40
+ end
41
+
42
+ # Sound level measurement is adjusted to match the psychoacoustic properties of
43
+ # human hearing. This is on by default.
44
+ def use_adjusted_mode
45
+ set_input_mode(NXTComm::SOUND_DBA, NXTComm::RAWMODE)
46
+ end
47
+
48
+ end
@@ -0,0 +1,35 @@
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
+
19
+ require File.dirname(File.expand_path(__FILE__))+'/sensor'
20
+
21
+ class TouchSensor < Sensor
22
+
23
+ def initialize(nxt, port = NXTComm::SENSOR_1)
24
+ super(nxt, port)
25
+ set_input_mode(NXTComm::SWITCH, NXTComm::BOOLEANMODE)
26
+ end
27
+
28
+ # Returns true if the touch sensor is pressed, false otherwise.
29
+ # (The sensor seems to read as "pressed" when it is pushed in about half way)
30
+ def is_pressed?
31
+ read_data[:value_scaled] > 0
32
+ end
33
+ alias_method :pressed?, :is_pressed?
34
+
35
+ end
@@ -0,0 +1,95 @@
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 File.dirname(File.expand_path(__FILE__))+'/sensor'
18
+ require File.dirname(File.expand_path(__FILE__))+'/../ultrasonic_comm'
19
+
20
+ class UltrasonicSensor < Sensor
21
+
22
+ INCHES_PER_CM = 0.3937008
23
+
24
+ def initialize(nxt, port = NXTComm::SENSOR_4)
25
+ super(nxt, port)
26
+
27
+ # The Ultrasonic sensor is digital and unlike the other sensors it
28
+ # uses the lowspeed communication protocol.
29
+ set_input_mode(NXTComm::LOWSPEED_9V, NXTComm::RAWMODE)
30
+
31
+ # Read the sensor in case there was some garbage data in the buffer waiting to be read
32
+ @nxt.ls_read(@port)
33
+
34
+ # Set the sensor to continuously send pings
35
+ @nxt.ls_write(@port, UltrasonicComm.continuous_measurement_command)
36
+ end
37
+
38
+ # Return the measured distance in the default units (the default units being centimeters).
39
+ # A value of 255 is returned when the sensor cannot get an accurate reading (because
40
+ # the object is out of range, or there is too much interference, etc.)
41
+ # Note that the sensor's real-world range is at best 175 cm. At larger distances it
42
+ # will almost certainly just return 255.
43
+ def get_distance
44
+ @nxt.ls_write(@port, UltrasonicComm.read_measurement_byte(0))
45
+
46
+ # Keep checking until we have 1 byte of data to read
47
+ while @nxt.ls_get_status(@port)[0] < 1
48
+ sleep(0.1)
49
+ @nxt.ls_write(@port, UltrasonicComm.read_measurement_byte(0))
50
+ # TODO: implement timeout so we don't get stuck if the expected data never comes
51
+ end
52
+
53
+ resp = @nxt.ls_read(@port)
54
+ # TODO: probably need a better error message here...
55
+ raise "ls_read returned more than one byte!" if resp[:bytes_read] > 1
56
+ raise "ls_read did not return any data!" if resp[:bytes_read] < 1
57
+
58
+ # If the sensor cannot determine the distance, it will return
59
+ # 0xff (255)... this usually means that the object is out of
60
+ # sensor range, but it can also mean that there is too much
61
+ # interference or that the object is too close to the sensor.
62
+ # I considered returning nil or false under such cases, but
63
+ # this makes numeric comparison (i.e. greather than/less than)
64
+ # more difficult
65
+ d = resp[:data][0]
66
+ end
67
+ alias_method :get_distance_in_cm, :get_distance
68
+
69
+ # Return the measured distance in inches.
70
+ def get_distance_in_inches
71
+ get_distance.to_f * INCHES_PER_CM
72
+ end
73
+
74
+ # Same as get_distance, but raises an UnmeasurableDistance
75
+ # exception when the sensor cannot accurately determine
76
+ # the distance.
77
+ def get_distance!
78
+ d = get_distance
79
+ if d == 255
80
+ raise UnmeasurableDistance
81
+ else
82
+ return d
83
+ end
84
+ end
85
+ alias_method :get_distance_in_cm!, :get_distance!
86
+
87
+ def get_distance_in_inches!
88
+ get_distance!.to_f * INCHES_PER_CM
89
+ end
90
+
91
+ # Exception thrown by get_distance! and related methods when
92
+ # the sensor cannot determine the distance.
93
+ class UnmeasurableDistance < Exception; end
94
+
95
+ end
@@ -0,0 +1,102 @@
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
+
18
+ # Low-level interface for communicating with the NXT's Ultrasonic
19
+ # sensor. Unlike the other sensors, the Ultrasonic sensor is digital
20
+ # and uses the low-speed I2C protocol for communication. This is
21
+ # defined in Appendix 7 of the Lego Mindstorms NXT SDK.
22
+ class UltrasonicComm
23
+
24
+ @@i2c_dev = 0x02
25
+
26
+ # first value is the i2c address, second value is the expected number of bytes returned
27
+ @@const_codes = {
28
+ 'read_version' => [0x00, 8],
29
+ 'read_product_id' => [0x08, 8],
30
+ 'read_sensor_type' => [0x10, 8],
31
+ 'read_factory_zero' => [0x11, 1],
32
+ 'read_factory_scale_factor' => [0x12, 1],
33
+ 'read_factory_scale_divisor' => [0x13, 1],
34
+ 'read_measurement_units' => [0x14, 7],
35
+ }
36
+
37
+ # value is the i2c address (all of these ops always expect to return 1 byte)
38
+ @@var_codes = {
39
+ 'read_continuous_measurements_interval' => 0x40,
40
+ 'read_command_state' => 0x41,
41
+ 'read_measurement_byte_0' => 0x42,
42
+ 'read_measurement_byte_1' => 0x43,
43
+ 'read_measurement_byte_2' => 0x44,
44
+ 'read_measurement_byte_3' => 0x45,
45
+ 'read_measurement_byte_4' => 0x46,
46
+ 'read_measurement_byte_5' => 0x47,
47
+ 'read_measurement_byte_6' => 0x48,
48
+ 'read_measurement_byte_7' => 0x49,
49
+ 'read_actual_zero' => 0x50,
50
+ 'read_actual_scale_factor' => 0x51,
51
+ 'read_actual_scale_divisor' => 0x52,
52
+ }
53
+
54
+ # first value is the i2c address, second value is the command
55
+ @@cmd_codes = {
56
+ 'off_command' => [0x41, 0x00],
57
+ 'single_shot_command' => [0x41, 0x01],
58
+ 'continuous_measurement_command' => [0x41, 0x02],
59
+ 'event_capture_command' => [0x41, 0x03],
60
+ 'request_warm_reset' => [0x41, 0x04],
61
+ 'set_continuous_measurement_interval' => [0x40],
62
+ 'set_actual_zero' => [0x50],
63
+ 'set_actual_scale_factor' => [0x51],
64
+ 'set_actual_scale_divisor' => [0x52]
65
+ }
66
+
67
+
68
+ def self.read_measurement_byte(num)
69
+ eval "self.read_measurement_byte_#{num}"
70
+ end
71
+
72
+ def self.method_missing(name, *args)
73
+ name = name.to_s
74
+ if @@const_codes.has_key? name
75
+ type = :const
76
+ op = @@const_codes[name]
77
+ addr = op[0]
78
+ rx_len = op[1]
79
+ elsif @@var_codes.has_key? name
80
+ type = :var
81
+ op = @@var_codes[name]
82
+ addr = op
83
+ rx_len = 1
84
+ elsif @@cmd_codes.has_key? name
85
+ type = :cmd
86
+ op = @@cmd_codes[name]
87
+ addr = op[0]
88
+ if op[1] then value = op[1]
89
+ elsif args[0] then value = args[0]
90
+ else raise "Missing argument for command #{name}" end
91
+ rx_len = 0
92
+ else
93
+ raise "Unknown ultrasonic sensor command: #{name}"
94
+ end
95
+
96
+ data = [@@i2c_dev, addr]
97
+ data += [value] if type == :cmd
98
+
99
+ [data.size, rx_len] + data
100
+ end
101
+
102
+ end
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby -w
2
+
3
+ require "nxt_comm"
4
+
5
+ $DEBUG = true
6
+
7
+ @nxt = NXTComm.new("/dev/tty.NXT-DevB-1")
8
+
9
+ @nxt.message_write(1,180)
10
+ @nxt.message_write(1,0)
@@ -0,0 +1,89 @@
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
+ begin
18
+ require 'rubygems'
19
+ require_gem "term-ansicolor"
20
+ $COLOUR = true
21
+ rescue LoadError
22
+ $COLOUR = false
23
+ puts "Please install term-ansicolor gem to get colour output."
24
+ puts "Colour output will be disabled..."
25
+ puts
26
+ end
27
+
28
+ module InteractiveTestHelper
29
+ def info(msg)
30
+ msg = Term::ANSIColor.white(msg) if $COLOUR
31
+ puts "#{msg}"
32
+ end
33
+
34
+ def notice(msg)
35
+ msg = Term::ANSIColor.yellow(msg) if $COLOUR
36
+ puts "#{msg}"
37
+ end
38
+
39
+ def prompt(msg)
40
+ msg = Term::ANSIColor.cyan(msg) if $COLOUR
41
+ puts "#{msg}"
42
+ return gets
43
+ end
44
+
45
+ def pass(msg)
46
+ msg = Term::ANSIColor.green(msg) if $COLOUR
47
+ msg = Term::ANSIColor.bold(msg) if $COLOUR
48
+ puts "#{msg}"
49
+ end
50
+
51
+ def fail(msg)
52
+ msg = Term::ANSIColor.red(msg) if $COLOUR
53
+ msg = Term::ANSIColor.red(msg) if $COLOUR
54
+ puts "#{msg}"
55
+ end
56
+
57
+ def puts_sameline(msg)
58
+ puts "#{msg}"
59
+ puts "\e[2A"
60
+ end
61
+
62
+ def meter(value, label = "Level", threshold = nil, max = 100, min = 0)
63
+ width = 40
64
+ if value.nil?
65
+ bars = "?" * width
66
+ spaces = ""
67
+ percent_formatted = " ??? "
68
+ else
69
+ if value >= max
70
+ percent = 1.0
71
+ else
72
+ percent = ((value - min) / max.to_f)
73
+ end
74
+ bars = "|" * (percent * width)
75
+ spaces = " " * ((1 - percent) * width)
76
+
77
+ percent_formatted = "%5.1f" % (percent * 100)
78
+ end
79
+ meterbar = bars + spaces
80
+
81
+ if threshold
82
+ threshold_pos = (((threshold - min) / max.to_f) * 40).to_i
83
+ meterbar[threshold_pos] = "!"
84
+ end
85
+
86
+ puts_sameline " #{label}: (#{percent_formatted}%) [#{min}]#{meterbar}[#{max}]"
87
+ end
88
+
89
+ end