nxt 0.2.0
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/README.markdown +52 -0
- data/Rakefile +35 -0
- data/lib/nxt.rb +27 -0
- data/lib/nxt/commands/base.rb +28 -0
- data/lib/nxt/commands/input.rb +0 -0
- data/lib/nxt/commands/output.rb +105 -0
- data/lib/nxt/commands/program.rb +0 -0
- data/lib/nxt/commands/sound.rb +0 -0
- data/lib/nxt/commands/tone.rb +0 -0
- data/lib/nxt/connectors/input/color.rb +11 -0
- data/lib/nxt/connectors/input/touch.rb +11 -0
- data/lib/nxt/connectors/input/ultrasonic.rb +11 -0
- data/lib/nxt/connectors/output/motor.rb +126 -0
- data/lib/nxt/exceptions.rb +23 -0
- data/lib/nxt/interfaces/base.rb +34 -0
- data/lib/nxt/interfaces/serial_port.rb +76 -0
- data/lib/nxt/interfaces/usb.rb +8 -0
- data/lib/nxt/nxt_brick.rb +155 -0
- data/lib/nxt/patches/module.rb +22 -0
- data/lib/nxt/patches/string.rb +29 -0
- data/lib/nxt/utils/accessors.rb +20 -0
- data/spec/matchers.rb +7 -0
- data/spec/nxt/interfaces/serial_port_spec.rb +73 -0
- data/spec/nxt/nxt_brick_spec.rb +199 -0
- data/spec/spec_helper.rb +4 -0
- metadata +184 -0
data/README.markdown
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# NXT
|
2
|
+
|
3
|
+
Control a Lego NXT 2.0 brick using Ruby code. This library works by piping
|
4
|
+
commands over a serialport connection to the brick, allowing you to write Ruby
|
5
|
+
scripts to control your bot. This means you can use both the Bluetooth and USB
|
6
|
+
serial ports provided on the brick as interfaces within your code.
|
7
|
+
|
8
|
+
This project used to be based on "ruby-nxt", and Tony Buser's subsequent rewrite
|
9
|
+
"nxt". It is now a complete rewrite, based heavily in some parts on the
|
10
|
+
aforesaid projects internally, but with a brand new external API that should
|
11
|
+
prove cleaner and easier to work with.
|
12
|
+
|
13
|
+
This code implements direct command, as outlined in "Appendix 2-LEGO MINDSTORMS
|
14
|
+
NXT Direct Commands.pdf". Not all functionality is implemented yet!
|
15
|
+
|
16
|
+
## Getting Started
|
17
|
+
|
18
|
+
### Connect to Your NXT Brick
|
19
|
+
|
20
|
+
In order to start coding with your NXT, you'll need to set up either a
|
21
|
+
Bluetooth or USB connection to it. Follow one of the below set of steps, and
|
22
|
+
make a note of the `/dev/*` address you end up using to point to the NXT.
|
23
|
+
|
24
|
+
### Creating a Bluetooth Serialport Connection
|
25
|
+
|
26
|
+
For instructions on creating a bluetooth serialport connection:
|
27
|
+
|
28
|
+
* Linux: http://tonybuser.com/bluetooth-serial-port-to-nxt-in-linux
|
29
|
+
* Max OSX: http://tonybuser.com/bluetooth-serial-port-to-nxt-in-osx
|
30
|
+
* Windows: http://tonybuser.com/ruby-serialportnxt-on-windows
|
31
|
+
|
32
|
+
### Creating a USB Serialport Connection
|
33
|
+
|
34
|
+
TODO
|
35
|
+
|
36
|
+
Once you have your NXT Connected
|
37
|
+
|
38
|
+
## Documentation and Examples
|
39
|
+
|
40
|
+
The NXT project has been heavily documented using nice, clean, human readable
|
41
|
+
markdown. YARD is used to generated the docs, and the options have been included
|
42
|
+
in our .yardopts file, so simply run a YARD server to read them:
|
43
|
+
|
44
|
+
yard server
|
45
|
+
|
46
|
+
This documents the API, both internal and external. For bite-sized chunks of NXT
|
47
|
+
code that is much more appropriate for beginners,
|
48
|
+
[have a look at the examples](https://github.com/nathankleyn/nxt/tree/master/examples).
|
49
|
+
|
50
|
+
In addition to this, you might find the tests quite helpful. There are currently
|
51
|
+
only RSpec unit tests, which can be found in the `spec` directory; the plan is
|
52
|
+
to add some decent feature tests soon.
|
data/Rakefile
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/clean'
|
3
|
+
require 'rdoc/task'
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
|
6
|
+
RDoc::Task.new do |rdoc|
|
7
|
+
files = ['README.markdown', 'lib/**/*.rb']
|
8
|
+
rdoc.rdoc_files.add(files)
|
9
|
+
rdoc.main = 'README.markdown'
|
10
|
+
rdoc.title = 'ruby_events Docs'
|
11
|
+
rdoc.rdoc_dir = 'doc/rdoc'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
end
|
14
|
+
|
15
|
+
RSpec::Core::RakeTask.new(:spec)
|
16
|
+
|
17
|
+
desc 'NXT related tasks'
|
18
|
+
namespace :nxt do
|
19
|
+
desc 'Detect a connected NXT brick within /dev.'
|
20
|
+
task :detect do
|
21
|
+
unless $DEV ||= ENV['NXT'] || ENV['DEV']
|
22
|
+
begin
|
23
|
+
devices = Dir['/dev/*NXT*']
|
24
|
+
if devices.size > 0
|
25
|
+
$DEV = devices[0]
|
26
|
+
puts "Detected a NXT brick at '#{$DEV}'."
|
27
|
+
else
|
28
|
+
puts 'Could not detect any connected NXT bricks.'
|
29
|
+
end
|
30
|
+
rescue
|
31
|
+
# FIXME: The /dev directory isn't there, possibly running on Windows.
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/nxt.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__))
|
2
|
+
|
3
|
+
require 'active_support/inflector'
|
4
|
+
|
5
|
+
require 'nxt/utils/accessors'
|
6
|
+
|
7
|
+
require 'nxt/patches/module'
|
8
|
+
|
9
|
+
require 'nxt/exceptions'
|
10
|
+
|
11
|
+
require 'nxt/interfaces/base'
|
12
|
+
require 'nxt/interfaces/usb'
|
13
|
+
require 'nxt/interfaces/serial_port'
|
14
|
+
|
15
|
+
require 'nxt/commands/base'
|
16
|
+
require 'nxt/commands/input'
|
17
|
+
require 'nxt/commands/output'
|
18
|
+
require 'nxt/commands/program'
|
19
|
+
require 'nxt/commands/sound'
|
20
|
+
require 'nxt/commands/tone'
|
21
|
+
|
22
|
+
require 'nxt/connectors/input/color'
|
23
|
+
require 'nxt/connectors/input/touch'
|
24
|
+
require 'nxt/connectors/input/ultrasonic'
|
25
|
+
require 'nxt/connectors/output/motor'
|
26
|
+
|
27
|
+
require 'nxt/nxt_brick'
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module NXT
|
2
|
+
module Command
|
3
|
+
module Base
|
4
|
+
private
|
5
|
+
|
6
|
+
COMMAND_TYPES = {
|
7
|
+
direct: 0x00,
|
8
|
+
system: 0x01,
|
9
|
+
reply: 0x02
|
10
|
+
}.freeze
|
11
|
+
|
12
|
+
PORTS = {
|
13
|
+
a: 0x00,
|
14
|
+
b: 0x01,
|
15
|
+
c: 0x02,
|
16
|
+
one: 0x00,
|
17
|
+
two: 0x01,
|
18
|
+
three: 0x02,
|
19
|
+
four: 0x03,
|
20
|
+
all: 0xFF
|
21
|
+
}
|
22
|
+
|
23
|
+
def port_as_byte(port)
|
24
|
+
PORTS[port]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
File without changes
|
@@ -0,0 +1,105 @@
|
|
1
|
+
module NXT
|
2
|
+
module Command
|
3
|
+
# An implementation of all the output related NXT commands:
|
4
|
+
#
|
5
|
+
# * setoutputstate
|
6
|
+
# * getoutputstate
|
7
|
+
#
|
8
|
+
# This is used predominantly to interface with the servo-motor connectors
|
9
|
+
# that come prepackaged with NXT kits.
|
10
|
+
#
|
11
|
+
# This class can also be used to talk to other third-party accessories
|
12
|
+
# connected in the output ports on the NXT brick.
|
13
|
+
#
|
14
|
+
# This class does not actually talk to the chosen interface for the NXT
|
15
|
+
# brick. Instead, it outputs messages in byte arrays ready to be serialised
|
16
|
+
# to the brick over the appropriate interface from within the {NXT::Brick}
|
17
|
+
# class.
|
18
|
+
module Output
|
19
|
+
include NXT::Command::Base
|
20
|
+
extend NXT::Utils::Accessors
|
21
|
+
|
22
|
+
@@command_type = COMMAND_TYPES[:direct]
|
23
|
+
@@command_identifier = 0x04
|
24
|
+
|
25
|
+
# The mode enum. This is a list of possible values when setting the mode
|
26
|
+
# byte.
|
27
|
+
#
|
28
|
+
# Reference: Appendix 2, Page 6
|
29
|
+
MODE = {
|
30
|
+
# Motor will rotate freely.
|
31
|
+
# NOTE: This is not documented in the Appendixes.
|
32
|
+
coast: 0x00,
|
33
|
+
# Turn on the specified motor.
|
34
|
+
motor_on: 0x01,
|
35
|
+
# Use run/brake instead of run/float in PWM. This means the voltage is
|
36
|
+
# not allowed to float between PWM pulses, improving accuracy at the
|
37
|
+
# expense of greater power usage.
|
38
|
+
brake: 0x02,
|
39
|
+
# Turns on the regulation. This is required when setting a regulation
|
40
|
+
# mode setting.
|
41
|
+
regulated: 0x04
|
42
|
+
}.freeze
|
43
|
+
|
44
|
+
# The regulation mode enum. This is a list of possible values when
|
45
|
+
# setting the regulation mode byte.
|
46
|
+
#
|
47
|
+
# Reference: Appendix 2, Page 6
|
48
|
+
REGULATION_MODE = {
|
49
|
+
# No regulation will be enabled.
|
50
|
+
idle: 0x00,
|
51
|
+
# Power control will be enabled on specific output.
|
52
|
+
motor_speed: 0x01,
|
53
|
+
# Synchronisation will be enabled. This requires two output ports to
|
54
|
+
# have this enabled before it will work.
|
55
|
+
motor_sync: 0x02
|
56
|
+
}.freeze
|
57
|
+
|
58
|
+
# The run state enum. This is a list of possible values when setting the
|
59
|
+
# run state byte.
|
60
|
+
#
|
61
|
+
# Reference: Appendix 2, Page 6
|
62
|
+
RUN_STATE = {
|
63
|
+
# Output will be idle.
|
64
|
+
idle: 0x00,
|
65
|
+
# Output will ramp-up to the desired speed.
|
66
|
+
ramp_up: 0x10,
|
67
|
+
# Output will be running.
|
68
|
+
running: 0x20,
|
69
|
+
# Output will ramp-down to the desired speed.
|
70
|
+
ramp_down: 0x40
|
71
|
+
}.freeze
|
72
|
+
|
73
|
+
attr_combined_accessor :power, 75
|
74
|
+
attr_combined_accessor :mode, :motor_on
|
75
|
+
attr_combined_accessor :regulation_mode, :idle
|
76
|
+
attr_combined_accessor :run_state, :running
|
77
|
+
attr_combined_accessor :tacho_limit, 0
|
78
|
+
|
79
|
+
attr_setter :power, is: Integer
|
80
|
+
attr_setter :mode, is_key_in: MODE
|
81
|
+
attr_setter :regulation_mode, is_key_in: REGULATION_MODE
|
82
|
+
attr_setter :run_state, is_key_in: RUN_STATE
|
83
|
+
attr_setter :tacho_limit, is: Integer
|
84
|
+
|
85
|
+
def set_output_state(response_required = false)
|
86
|
+
# Pack this value into a 32-bit unsigned little-endian binary string,
|
87
|
+
# then unpack it into 4 8 bit unsigned integer chunks. We are
|
88
|
+
# converting the passed in value to a little endian, unsigned long
|
89
|
+
# value.
|
90
|
+
tacho_limit_as_bytes = [self.tacho_limit].pack('V').unpack('C4')
|
91
|
+
|
92
|
+
@interface.send_and_receive([
|
93
|
+
@@command_type,
|
94
|
+
@@command_identifier,
|
95
|
+
port_as_byte(self.port),
|
96
|
+
self.power,
|
97
|
+
MODE[self.mode],
|
98
|
+
REGULATION_MODE[self.regulation_mode],
|
99
|
+
0, # turn ratio
|
100
|
+
RUN_STATE[self.run_state]
|
101
|
+
] + tacho_limit_as_bytes, response_required)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
File without changes
|
File without changes
|
File without changes
|
@@ -0,0 +1,126 @@
|
|
1
|
+
module NXT
|
2
|
+
module Connector
|
3
|
+
module Output
|
4
|
+
class Motor
|
5
|
+
include NXT::Command::Output
|
6
|
+
extend NXT::Utils::Accessors
|
7
|
+
|
8
|
+
DURATION_TYPE = [:seconds, :degrees, :rotations].freeze
|
9
|
+
DURATION_AFTER = [:coast, :brake].freeze
|
10
|
+
DIRECTION = [:forwards, :backwards].freeze
|
11
|
+
|
12
|
+
attr_accessor :port, :interface
|
13
|
+
|
14
|
+
attr_combined_accessor :duration, 0
|
15
|
+
attr_combined_accessor :duration_type, :seconds
|
16
|
+
attr_combined_accessor :duration_after, :stop
|
17
|
+
attr_combined_accessor :direction, :forwards
|
18
|
+
|
19
|
+
attr_setter :direction, is_key_in: DIRECTION
|
20
|
+
|
21
|
+
def initialize(port, interface)
|
22
|
+
@port = port
|
23
|
+
@interface = interface
|
24
|
+
end
|
25
|
+
|
26
|
+
def duration=(duration, options = {})
|
27
|
+
raise TypeError.new('Expected duration to be a number') unless duration.is_a?(Integer)
|
28
|
+
@duration = duration
|
29
|
+
|
30
|
+
if options.include?(:type)
|
31
|
+
type = options[:type]
|
32
|
+
|
33
|
+
unless DURATION_TYPE.include?(type)
|
34
|
+
raise TypeError.new("Expected duration type to be one of: :#{DURATION_TYPE.join(', :')}")
|
35
|
+
end
|
36
|
+
|
37
|
+
@duration_type = type
|
38
|
+
else
|
39
|
+
@duration_type = :seconds
|
40
|
+
end
|
41
|
+
|
42
|
+
if options.include?(:after)
|
43
|
+
if @duration_type == :seconds
|
44
|
+
after = options[:after]
|
45
|
+
|
46
|
+
unless DURATION_AFTER.include?(after)
|
47
|
+
raise TypeError.new("Expected after option to be one of: :#{DURATION_AFTER.join(', :')}")
|
48
|
+
end
|
49
|
+
|
50
|
+
@duration_after = after
|
51
|
+
else
|
52
|
+
raise TypeError.new('The after option is only available when the unit duration is in seconds.')
|
53
|
+
end
|
54
|
+
else
|
55
|
+
@duration_after = :stop
|
56
|
+
end
|
57
|
+
|
58
|
+
case @duration_type
|
59
|
+
when :rotations
|
60
|
+
self.tacho_limit = @duration * 360
|
61
|
+
when :degrees
|
62
|
+
self.tacho_limit = @duration
|
63
|
+
end
|
64
|
+
|
65
|
+
self
|
66
|
+
end
|
67
|
+
|
68
|
+
def forwards
|
69
|
+
self.direction = :forwards
|
70
|
+
self
|
71
|
+
end
|
72
|
+
|
73
|
+
def backwards
|
74
|
+
self.direction = :backwards
|
75
|
+
self
|
76
|
+
end
|
77
|
+
|
78
|
+
def stop(type = :coast)
|
79
|
+
self.power = 0
|
80
|
+
self.mode = :coast
|
81
|
+
|
82
|
+
self.move
|
83
|
+
end
|
84
|
+
|
85
|
+
# takes block for response, or can return the response instead.
|
86
|
+
def move
|
87
|
+
response_required = false
|
88
|
+
|
89
|
+
if self.duration > 0 && self.duration_type != :seconds
|
90
|
+
response_required = true
|
91
|
+
end
|
92
|
+
|
93
|
+
set_output_state(response_required)
|
94
|
+
|
95
|
+
if self.duration > 0 && self.duration_type == :seconds
|
96
|
+
sleep(self.duration)
|
97
|
+
self.reset
|
98
|
+
self.stop(self.duration_after)
|
99
|
+
else
|
100
|
+
self.reset
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def reset
|
105
|
+
self.duration = 0
|
106
|
+
self.direction = :forwards
|
107
|
+
self.power = 75
|
108
|
+
self.mode = :motor_on
|
109
|
+
self.regulation_mode = :idle
|
110
|
+
self.run_state = :running
|
111
|
+
self.tacho_limit = 0
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# overrides
|
116
|
+
def set_output_state(response_required=true)
|
117
|
+
original_power = self.power
|
118
|
+
self.power *= -1 if self.direction == :backwards
|
119
|
+
|
120
|
+
super
|
121
|
+
|
122
|
+
self.power = original_power
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module NXT
|
2
|
+
module Exceptions
|
3
|
+
# Raised when a class has not implemented a method from the base class
|
4
|
+
# that is required to be overriden.
|
5
|
+
class InterfaceNotImplemented < NotImplementedError; end
|
6
|
+
|
7
|
+
# Raised when an invalid interface is attempted to be constructed.
|
8
|
+
class InvalidInterfaceError < TypeError; end
|
9
|
+
|
10
|
+
# Raised when a port is attempted to be named when it already has been.
|
11
|
+
class PortTakenError < TypeError; end
|
12
|
+
|
13
|
+
# Raised when an invalid name is attempted to be given to a port.
|
14
|
+
class InvalidIdentifierError < TypeError; end
|
15
|
+
|
16
|
+
# Raised when the device file attempted to be used for communication is
|
17
|
+
# for whatever reason does not exist or is not correct.
|
18
|
+
class InvalidDeviceError < TypeError; end
|
19
|
+
|
20
|
+
# Raised when communication with a Serial Port connection fails.
|
21
|
+
class SerialPortConnectionError < RuntimeError; end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module NXT
|
2
|
+
module Interface
|
3
|
+
class Base
|
4
|
+
def send_and_receive(msg, response_required = true)
|
5
|
+
if response_required
|
6
|
+
msg[0] = msg[0] | (response_required ? 0x80 : 0x00)
|
7
|
+
end
|
8
|
+
|
9
|
+
self.send(msg)
|
10
|
+
|
11
|
+
if response_required
|
12
|
+
response = self.receive
|
13
|
+
response
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def send
|
18
|
+
raise InterfaceNotImplemented.new('The #send method must be implemented.')
|
19
|
+
end
|
20
|
+
|
21
|
+
def receive
|
22
|
+
raise InterfaceNotImplemented.new('The #receive method must be implemented.')
|
23
|
+
end
|
24
|
+
|
25
|
+
def connect
|
26
|
+
raise InterfaceNotImplemented.new('The #connect method must be implemented')
|
27
|
+
end
|
28
|
+
|
29
|
+
def disconnect
|
30
|
+
raise InterfaceNotImplemented.new('The #disconnect method must be implemented')
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'serialport'
|
2
|
+
|
3
|
+
module NXT
|
4
|
+
module Interface
|
5
|
+
class SerialPort < Base
|
6
|
+
include NXT::Exceptions
|
7
|
+
|
8
|
+
attr_reader :dev
|
9
|
+
|
10
|
+
BAUD_RATE = 57600
|
11
|
+
DATA_BITS = 8
|
12
|
+
STOP_BITS = 1
|
13
|
+
PARITY = ::SerialPort::NONE
|
14
|
+
READ_TIMEOUT = 5000
|
15
|
+
|
16
|
+
def initialize(dev)
|
17
|
+
self.dev = (dev)
|
18
|
+
end
|
19
|
+
|
20
|
+
def dev=(dev)
|
21
|
+
raise InvalidDeviceError unless File.exists?(dev)
|
22
|
+
@dev = dev
|
23
|
+
end
|
24
|
+
|
25
|
+
def connect
|
26
|
+
@connection = ::SerialPort.new(@dev, BAUD_RATE, DATA_BITS, STOP_BITS, PARITY)
|
27
|
+
|
28
|
+
if !@connection.nil?
|
29
|
+
@connection.flow_control = ::SerialPort::HARD
|
30
|
+
@connection.read_timeout = READ_TIMEOUT
|
31
|
+
else
|
32
|
+
raise SerialPortConnectionError.new("Could not establish a SerialPort connection to #{dev}")
|
33
|
+
end
|
34
|
+
|
35
|
+
@connection
|
36
|
+
rescue ArgumentError
|
37
|
+
raise SerialPortConnectionError.new("The #{dev} device is not a valid SerialPort")
|
38
|
+
end
|
39
|
+
|
40
|
+
def disconnect
|
41
|
+
@connection.close if @connection && !@connection.closed?
|
42
|
+
end
|
43
|
+
|
44
|
+
def connected?
|
45
|
+
!@connection.closed?
|
46
|
+
end
|
47
|
+
|
48
|
+
def send(msg)
|
49
|
+
# The expected data package structure for NXT Bluetooth communication is:
|
50
|
+
#
|
51
|
+
# [Length Byte 1, Length Byte 2, Command Type, Command, ...]
|
52
|
+
#
|
53
|
+
# So here we calculate the two leading length bytes, and rely on the
|
54
|
+
# passed in argument to give us the rest of the message to send.
|
55
|
+
#
|
56
|
+
# Note that the length is stored in Little Endian ie. LSB -> MSB
|
57
|
+
#
|
58
|
+
# Reference: Appendix 1, Page 22
|
59
|
+
msg = [(msg.length & 255), (msg.length >> 8)] + msg
|
60
|
+
|
61
|
+
msg.each do |b|
|
62
|
+
@connection.putc(b)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def receive
|
67
|
+
# This gets the length of the received data from the header that was sent
|
68
|
+
# to us. We unpack it, as it's stored as a 16-bit Little Endian number.
|
69
|
+
#
|
70
|
+
# Reference: Appendix 1, Page 22
|
71
|
+
length = @connection.sysread(2)
|
72
|
+
@connection.sysread(length.unpack('v')[0])
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
# This class is the entry point for end-users creating their own list of
|
2
|
+
# commands to execute remotely on a Lego NXT brick.
|
3
|
+
#
|
4
|
+
# An instance of this class provides all the endpoints necessary to:
|
5
|
+
#
|
6
|
+
# * educate the API on the connected input and output devices; and,
|
7
|
+
# * access these input and output devices and run commands using them.
|
8
|
+
#
|
9
|
+
# @example Creating an instance using a block, with one motor output.
|
10
|
+
#
|
11
|
+
# NXTBrick.new(interface) do |nxt|
|
12
|
+
# nxt.add_motor_output(:a, :front_left)
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# @example Creating an instance without a block, with one motor and one light sensor.
|
16
|
+
#
|
17
|
+
# nxt = NXTBrick.new(interface)
|
18
|
+
# nxt.add_motor_output(:a, :front_left)
|
19
|
+
# # ...
|
20
|
+
# nxt.disconnect
|
21
|
+
class NXTBrick
|
22
|
+
include NXT::Exceptions
|
23
|
+
|
24
|
+
# An enumeration of possible ports, both input and output, that the NXT brick
|
25
|
+
# can have connectors attached to.
|
26
|
+
PORTS = [:a, :b, :c, :one, :two, :three, :four]
|
27
|
+
|
28
|
+
# Get the instance of the interface that this runner class is using to connect
|
29
|
+
# to the NXT brick.
|
30
|
+
attr_accessor :interface
|
31
|
+
|
32
|
+
# Accessors for output ports on the NXT brick. These will be populated with
|
33
|
+
# the appropriate instances of their respective output connectors.
|
34
|
+
attr_reader :a, :b, :c
|
35
|
+
|
36
|
+
# Accessors for input ports on the NXT brick. These will be populated with the
|
37
|
+
# appropriate instances of their respective input connectors.
|
38
|
+
attr_reader :one, :two, :three, :four
|
39
|
+
|
40
|
+
# We mandate that all added port connections have an identifier associated
|
41
|
+
# with it. This is so that code is not fragile when port swapping needs to
|
42
|
+
# be done.
|
43
|
+
attr_reader :port_identifiers
|
44
|
+
|
45
|
+
def initialize(interface_type, *interface_args)
|
46
|
+
@port_identifiers = {}
|
47
|
+
interface_type = interface_type.to_s.classify
|
48
|
+
|
49
|
+
validate_interface(interface_type)
|
50
|
+
self.interface = NXT::Interface.const_get(interface_type).new(*interface_args)
|
51
|
+
|
52
|
+
if block_given?
|
53
|
+
begin
|
54
|
+
self.connect
|
55
|
+
yield(self)
|
56
|
+
ensure
|
57
|
+
self.disconnect
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Connect using the given interface to the NXT brick.
|
63
|
+
def connect
|
64
|
+
self.interface.connect
|
65
|
+
end
|
66
|
+
|
67
|
+
# Close the connection to the NXT brick, and dispose of any resources that
|
68
|
+
# this instance of NXTBrick is using. Any commands run against this runner
|
69
|
+
# after calling disconnect will fail.
|
70
|
+
def disconnect
|
71
|
+
self.interface.disconnect
|
72
|
+
end
|
73
|
+
|
74
|
+
# Add a new connector instance, binding a specific identifier to the given
|
75
|
+
# port.
|
76
|
+
#
|
77
|
+
# If the given port already is bound, an exception will be thrown. The
|
78
|
+
# instance given though can be of any class, presuming it talks the
|
79
|
+
# correct language.
|
80
|
+
#
|
81
|
+
# @param Symbol port The port to bind to.
|
82
|
+
# @param Symbol identifier The identifier to associate with this port.
|
83
|
+
# @param Class klass The Class to instantiate as the instance of this
|
84
|
+
# port. There is no limitation on what type this can
|
85
|
+
# be, though it must be able to hook in correctly
|
86
|
+
# with the NXT library.
|
87
|
+
def add(port, identifier, klass)
|
88
|
+
raise TypeError.new('Expected port to be a Symbol') unless port.is_a?(Symbol)
|
89
|
+
raise TypeError.new('Expected identifier to be a Symbol') unless identifier.is_a?(Symbol)
|
90
|
+
raise TypeError.new('Expected klass to be a Class') unless klass.is_a?(Class)
|
91
|
+
|
92
|
+
unless PORTS.include?(port)
|
93
|
+
raise TypeError.new("Expected port to be one of: :#{PORTS.join(', :')}")
|
94
|
+
end
|
95
|
+
|
96
|
+
port_variable = :"@#{port}"
|
97
|
+
|
98
|
+
if !self.respond_to?(identifier)
|
99
|
+
# Makes a new instance of the class and pushes it into our instance variable
|
100
|
+
# for the given port.
|
101
|
+
self.instance_variable_set(port_variable, klass.new(port, self.interface))
|
102
|
+
|
103
|
+
# Given that that succeeded, all that remains is to add the identifier
|
104
|
+
# to our lookup Hash. We'll use this Hash later on within method_missing.
|
105
|
+
@port_identifiers[identifier] = port
|
106
|
+
|
107
|
+
# Define a method on the eigenclass of this instance.
|
108
|
+
(class << self; self; end).send(:define_method, identifier) do
|
109
|
+
self.instance_variable_get(port_variable)
|
110
|
+
end
|
111
|
+
else
|
112
|
+
if !self.instance_variable_get(port_variable).nil?
|
113
|
+
raise PortTakenError.new("Port #{port} is already set, call remove first")
|
114
|
+
else
|
115
|
+
raise InvalidIdentifierError.new("Cannot use identifier #{identifier}, a method on #{self.class} is already using it.")
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# Remove the assigned (if any) connector instance from the given
|
121
|
+
# identifier.
|
122
|
+
#
|
123
|
+
# @param Symbol identifier The identifier to search for and remove.
|
124
|
+
def remove(identifier)
|
125
|
+
raise TypeError.new('Expected identifier to be a Symbol') unless identifier.is_a?(Symbol)
|
126
|
+
!!@port_identifiers.delete(identifier)
|
127
|
+
end
|
128
|
+
|
129
|
+
# This will dynamically add methods like:
|
130
|
+
#
|
131
|
+
# * add_light_input
|
132
|
+
# * add_motor_output
|
133
|
+
# * add_ultrasonic_input
|
134
|
+
#
|
135
|
+
# This means they don't have to provide the class each and every time. For
|
136
|
+
# connectors they have added themselves, it's likely best to use the
|
137
|
+
# {#add} method.
|
138
|
+
NXT::Connector.constants.each do |type_const|
|
139
|
+
NXT::Connector.const_get(type_const).constants.each do |const|
|
140
|
+
# We don't use a splat here for the args, because that way when
|
141
|
+
# people don't pass in the correct number of params, it says helpfully
|
142
|
+
# '1 of 2' args passed (or something similar).
|
143
|
+
define_method("add_#{const.to_s.underscore}_#{type_const.to_s.underscore}") do |port, identifier|
|
144
|
+
self.add(port, identifier, NXT::Connector.const_get(type_const).const_get(const))
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
private
|
150
|
+
def validate_interface(interface_type_name_string)
|
151
|
+
unless NXT::Interface.constants.include?(interface_type_name_string.to_sym)
|
152
|
+
raise InvalidInterfaceError.new("There is no interface of type #{interface_type_name_string}.")
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class Module
|
2
|
+
# Creates an invariant accessor that allows getting and setting from the same
|
3
|
+
# endpoint. It will operate in getter mode if you don't pass any arguments
|
4
|
+
# when calling it, otherwise it will work in setter mode. Useful when needing
|
5
|
+
# to chain methods (you can't chain standard attr_writer methods because
|
6
|
+
# of the `= something` part).
|
7
|
+
def attr_combined_accessor(sym, default = nil)
|
8
|
+
define_method(sym) do |*args|
|
9
|
+
if args.empty?
|
10
|
+
instance_var = :"@#{sym}"
|
11
|
+
if (value = self.instance_variable_get(instance_var))
|
12
|
+
value
|
13
|
+
else
|
14
|
+
self.instance_variable_set(instance_var, default)
|
15
|
+
default
|
16
|
+
end
|
17
|
+
else
|
18
|
+
send(:"#{sym}=", *args)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class String
|
2
|
+
# Convert the given string to a hexadecimal representation of the same data.
|
3
|
+
# This method is non-destructive, it will return a new copy of the string
|
4
|
+
# convered to hex.
|
5
|
+
def to_hex_str
|
6
|
+
str = ''
|
7
|
+
self.each_byte {|b| str << '0x%02x ' % b}
|
8
|
+
str
|
9
|
+
end
|
10
|
+
|
11
|
+
#
|
12
|
+
def from_hex_str_two
|
13
|
+
data = self.split(' ')
|
14
|
+
str = ''
|
15
|
+
data.each {|h| eval "str += '%c' % #{h}"}
|
16
|
+
str
|
17
|
+
end
|
18
|
+
|
19
|
+
def from_hex_str
|
20
|
+
data = self.split(' ')
|
21
|
+
str = ''
|
22
|
+
|
23
|
+
data.each do |h|
|
24
|
+
str += '%c' % h
|
25
|
+
end
|
26
|
+
|
27
|
+
str
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module NXT
|
2
|
+
module Utils
|
3
|
+
module Accessors
|
4
|
+
def attr_setter(name, options)
|
5
|
+
define_method("#{name}=") do |value|
|
6
|
+
if options.include?(:is)
|
7
|
+
raise TypeError.new('Expected value to be a number') unless duration.is_a?(options[:is])
|
8
|
+
end
|
9
|
+
|
10
|
+
if options.include?(:is_key_in) && !options[:is_key_in].include?(value)
|
11
|
+
raise TypeError.new("Expected value to be one of: :#{options[:is_key_in].keys.join(', :')}")
|
12
|
+
end
|
13
|
+
|
14
|
+
instance_variable_set("@#{name}", value)
|
15
|
+
self
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/spec/matchers.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe NXT::Interface::SerialPort do
|
4
|
+
before do
|
5
|
+
@device = '/dev/zero'
|
6
|
+
@bad_device = 'hello world'
|
7
|
+
end
|
8
|
+
|
9
|
+
subject do
|
10
|
+
NXT::Interface::SerialPort.new(@device)
|
11
|
+
end
|
12
|
+
|
13
|
+
describe 'constants' do
|
14
|
+
it 'should have a BAUD_RATE constant' do
|
15
|
+
should have_constant(:BAUD_RATE)
|
16
|
+
should have_constant(:DATA_BITS)
|
17
|
+
should have_constant(:STOP_BITS)
|
18
|
+
should have_constant(:PARITY)
|
19
|
+
should have_constant(:READ_TIMEOUT)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe 'accessors' do
|
24
|
+
it 'should have read/write accessor for @dev' do
|
25
|
+
should respond_to(:dev)
|
26
|
+
should respond_to(:dev=)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe '#initialize' do
|
31
|
+
it 'should set the device to the incomming argument' do
|
32
|
+
subject.dev.should equal(@device)
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'should raise an exception when trying to connect to invalid dev files' do
|
36
|
+
expect do
|
37
|
+
serial_port = subject.class.new(@bad_device)
|
38
|
+
end.to raise_exception(InvalidDeviceError)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe '#connect' do
|
43
|
+
it 'should raise an exception when the SerialPort connection failed' do
|
44
|
+
expect do
|
45
|
+
subject.connect
|
46
|
+
end.to raise_exception(SerialPortConnectionError, "The #{@device} device is not a valid SerialPort")
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'should raise an exception when the SerialPort connection is nil' do
|
50
|
+
::SerialPort.should_receive(:new).and_return(nil)
|
51
|
+
expect do
|
52
|
+
subject.connect
|
53
|
+
end.to raise_exception(SerialPortConnectionError, "Could not establish a SerialPort connection to #{@device}")
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'should set the flow control and read timeout when the connection is established' do
|
57
|
+
serial_port_stub = stub()
|
58
|
+
serial_port_stub.should_receive(:flow_control=).with(::SerialPort::HARD).once
|
59
|
+
serial_port_stub.should_receive(:read_timeout=).with(subject.class::READ_TIMEOUT).once
|
60
|
+
::SerialPort.should_receive(:new).and_return(serial_port_stub)
|
61
|
+
|
62
|
+
subject.connect
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe '#send' do
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
describe '#receive' do
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,199 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module NXT
|
4
|
+
module Interface
|
5
|
+
class MockInterface < Base
|
6
|
+
def connect; end
|
7
|
+
def disconnect; end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe NXTBrick do
|
13
|
+
before do
|
14
|
+
@interface = :mock_interface
|
15
|
+
end
|
16
|
+
|
17
|
+
subject do
|
18
|
+
NXTBrick.new(@interface)
|
19
|
+
end
|
20
|
+
|
21
|
+
describe 'accessors' do
|
22
|
+
it 'should have read/write accessors for @interface' do
|
23
|
+
should respond_to(:interface)
|
24
|
+
should respond_to(:interface=)
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should have a read accessor for @a' do
|
28
|
+
should respond_to(:a)
|
29
|
+
should_not respond_to(:a=)
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should have a read accessor for @b' do
|
33
|
+
should respond_to(:b)
|
34
|
+
should_not respond_to(:b=)
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should have a read accessor for @c' do
|
38
|
+
should respond_to(:c)
|
39
|
+
should_not respond_to(:c=)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should have a read accessor for @one' do
|
43
|
+
should respond_to(:one)
|
44
|
+
should_not respond_to(:one=)
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'should have a read accessor for @two' do
|
48
|
+
should respond_to(:two)
|
49
|
+
should_not respond_to(:two=)
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'should have a read accessor for @three' do
|
53
|
+
should respond_to(:three)
|
54
|
+
should_not respond_to(:three=)
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'should have a read accessor for @four' do
|
58
|
+
should respond_to(:four)
|
59
|
+
should_not respond_to(:four=)
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'should have a read accessor for @port_identifiers' do
|
63
|
+
should respond_to(:port_identifiers)
|
64
|
+
should_not respond_to(:port_identifiers=)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe '#initialize' do
|
69
|
+
it 'should raise an exception if an invalid type of interface is given' do
|
70
|
+
expect do
|
71
|
+
NXTBrick.new(:foobar_biz_buzz)
|
72
|
+
end.to raise_exception(InvalidInterfaceError)
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'should set the interface to the incomming argument' do
|
76
|
+
subject.interface.class.should equal(NXT::Interface::MockInterface)
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'should call yield if given a block, passing self' do
|
80
|
+
block_called = false
|
81
|
+
|
82
|
+
NXTBrick.new(@interface) do |nxt|
|
83
|
+
block_called = true
|
84
|
+
nxt.should be_an_instance_of(NXTBrick)
|
85
|
+
end
|
86
|
+
|
87
|
+
block_called.should be_true
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe '#add' do
|
92
|
+
it 'should raise an exception if an invalid type of port is given' do
|
93
|
+
expect do
|
94
|
+
subject.add('not a symbol', :symbol, Class)
|
95
|
+
end.to raise_exception(TypeError, 'Expected port to be a Symbol')
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'should raise an exception if an invalid type of identifier is given' do
|
99
|
+
expect do
|
100
|
+
subject.add(:symbol, 'not a symbol', Class)
|
101
|
+
end.to raise_exception(TypeError, 'Expected identifier to be a Symbol')
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'should raise an exception if an invalid type of klass is given' do
|
105
|
+
expect do
|
106
|
+
subject.add(:symbol, :symbol, 'not a class')
|
107
|
+
end.to raise_exception(TypeError, 'Expected klass to be a Class')
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'should raise an exception if an invalid port number or letter is given' do
|
111
|
+
expect do
|
112
|
+
subject.add(:invalid_port, :symbol, Class)
|
113
|
+
end.to raise_exception(TypeError, 'Expected port to be one of: :a, :b, :c, :one, :two, :three, :four')
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'should create a new instance of the passed klass and store it in the attribute for the given port' do
|
117
|
+
port = :a
|
118
|
+
class_stub = Class.new
|
119
|
+
class_return_stub = stub()
|
120
|
+
|
121
|
+
class_stub.should_receive(:new) do
|
122
|
+
class_return_stub
|
123
|
+
end.with(port, anything).once()
|
124
|
+
|
125
|
+
subject.add(port, :hello, class_stub)
|
126
|
+
|
127
|
+
subject.send(port).should equal(class_return_stub)
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'should raise an exception if the port given is already set' do
|
131
|
+
port = :a
|
132
|
+
|
133
|
+
class_stub = Class.new
|
134
|
+
class_stub.stub(:new)
|
135
|
+
subject.stub(:hello)
|
136
|
+
subject.instance_variable_set(:"@#{port}", 'some value already there')
|
137
|
+
|
138
|
+
expect do
|
139
|
+
subject.add(port, :hello, class_stub)
|
140
|
+
end.to raise_error(PortTakenError, "Port #{port} is already set, call remove first")
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'should raise an exception if trying to use an identifier that is the name of a defined methodz' do
|
144
|
+
port = :a
|
145
|
+
identifier = :hello
|
146
|
+
|
147
|
+
class_stub = Class.new
|
148
|
+
class_stub.stub(:new)
|
149
|
+
subject.stub(identifier)
|
150
|
+
|
151
|
+
expect do
|
152
|
+
subject.add(port, :hello, class_stub)
|
153
|
+
end.to raise_error(InvalidIdentifierError, "Cannot use identifier #{identifier}, a method on NXTBrick is already using it.")
|
154
|
+
end
|
155
|
+
|
156
|
+
it 'should set up the port identifiers correctly' do
|
157
|
+
port = :a
|
158
|
+
identifier = :hello_world
|
159
|
+
class_stub = Class.new
|
160
|
+
class_stub.stub(:new)
|
161
|
+
|
162
|
+
subject.add(port, identifier, class_stub)
|
163
|
+
|
164
|
+
subject.port_identifiers[identifier].should equal(port)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
describe '#remove' do
|
169
|
+
it 'should raise an exception if an invalid type of identifier is given' do
|
170
|
+
expect do
|
171
|
+
subject.remove('not a symbol')
|
172
|
+
end.to raise_exception(TypeError, 'Expected identifier to be a Symbol')
|
173
|
+
end
|
174
|
+
|
175
|
+
it 'should remove any matching identifiers' do
|
176
|
+
identifier = :hello_world
|
177
|
+
port_identifiers = {}
|
178
|
+
subject.instance_variable_set(:@port_identifiers, port_identifiers)
|
179
|
+
|
180
|
+
port_identifiers.should_receive(:delete).with(identifier).once()
|
181
|
+
subject.remove(identifier)
|
182
|
+
end
|
183
|
+
|
184
|
+
it 'should return a boolean indicating whether it removed anything' do
|
185
|
+
identifier = :hello_world
|
186
|
+
port_identifiers = {}
|
187
|
+
port_identifiers[identifier] = true
|
188
|
+
subject.instance_variable_set(:@port_identifiers, port_identifiers)
|
189
|
+
|
190
|
+
return_value = subject.remove(identifier)
|
191
|
+
return_value.should be_true
|
192
|
+
|
193
|
+
port_identifiers.should_not include(identifier)
|
194
|
+
|
195
|
+
return_value = subject.remove(identifier)
|
196
|
+
return_value.should be_false
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,184 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: nxt
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Tony Heupel
|
9
|
+
- Nathan Kleyn <nathan@unfinitydesign.com>
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2013-03-03 00:00:00.000000000 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: serialport
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ~>
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 1.0.4
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ~>
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
version: 1.0.4
|
31
|
+
- !ruby/object:Gem::Dependency
|
32
|
+
name: activesupport
|
33
|
+
requirement: !ruby/object:Gem::Requirement
|
34
|
+
none: false
|
35
|
+
requirements:
|
36
|
+
- - ~>
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: 3.2.2
|
39
|
+
type: :runtime
|
40
|
+
prerelease: false
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ~>
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 3.2.2
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: ruby-usb
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.2.1
|
55
|
+
type: :runtime
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ~>
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: 0.2.1
|
63
|
+
- !ruby/object:Gem::Dependency
|
64
|
+
name: rspec
|
65
|
+
requirement: !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ~>
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: 2.8.0
|
71
|
+
type: :development
|
72
|
+
prerelease: false
|
73
|
+
version_requirements: !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - ~>
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: 2.8.0
|
79
|
+
- !ruby/object:Gem::Dependency
|
80
|
+
name: pry
|
81
|
+
requirement: !ruby/object:Gem::Requirement
|
82
|
+
none: false
|
83
|
+
requirements:
|
84
|
+
- - ~>
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: 0.9.8.4
|
87
|
+
type: :development
|
88
|
+
prerelease: false
|
89
|
+
version_requirements: !ruby/object:Gem::Requirement
|
90
|
+
none: false
|
91
|
+
requirements:
|
92
|
+
- - ~>
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: 0.9.8.4
|
95
|
+
- !ruby/object:Gem::Dependency
|
96
|
+
name: yard
|
97
|
+
requirement: !ruby/object:Gem::Requirement
|
98
|
+
none: false
|
99
|
+
requirements:
|
100
|
+
- - ~>
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: 0.7.5
|
103
|
+
type: :development
|
104
|
+
prerelease: false
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
106
|
+
none: false
|
107
|
+
requirements:
|
108
|
+
- - ~>
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 0.7.5
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: redcarpet
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
none: false
|
115
|
+
requirements:
|
116
|
+
- - ~>
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: 2.1.0
|
119
|
+
type: :development
|
120
|
+
prerelease: false
|
121
|
+
version_requirements: !ruby/object:Gem::Requirement
|
122
|
+
none: false
|
123
|
+
requirements:
|
124
|
+
- - ~>
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
version: 2.1.0
|
127
|
+
description: Control an NXT 2.0 See http://github.com/nathankleyn/nxt for more information.
|
128
|
+
email: tony@heupel.org
|
129
|
+
executables: []
|
130
|
+
extensions: []
|
131
|
+
extra_rdoc_files:
|
132
|
+
- README.markdown
|
133
|
+
files:
|
134
|
+
- README.markdown
|
135
|
+
- Rakefile
|
136
|
+
- lib/nxt/commands/base.rb
|
137
|
+
- lib/nxt/commands/input.rb
|
138
|
+
- lib/nxt/commands/output.rb
|
139
|
+
- lib/nxt/commands/program.rb
|
140
|
+
- lib/nxt/commands/sound.rb
|
141
|
+
- lib/nxt/commands/tone.rb
|
142
|
+
- lib/nxt/connectors/input/color.rb
|
143
|
+
- lib/nxt/connectors/input/touch.rb
|
144
|
+
- lib/nxt/connectors/input/ultrasonic.rb
|
145
|
+
- lib/nxt/connectors/output/motor.rb
|
146
|
+
- lib/nxt/exceptions.rb
|
147
|
+
- lib/nxt/interfaces/base.rb
|
148
|
+
- lib/nxt/interfaces/serial_port.rb
|
149
|
+
- lib/nxt/interfaces/usb.rb
|
150
|
+
- lib/nxt/nxt_brick.rb
|
151
|
+
- lib/nxt/patches/module.rb
|
152
|
+
- lib/nxt/patches/string.rb
|
153
|
+
- lib/nxt/utils/accessors.rb
|
154
|
+
- lib/nxt.rb
|
155
|
+
- spec/matchers.rb
|
156
|
+
- spec/nxt/interfaces/serial_port_spec.rb
|
157
|
+
- spec/nxt/nxt_brick_spec.rb
|
158
|
+
- spec/spec_helper.rb
|
159
|
+
homepage: http://github.com/tchype/nxt
|
160
|
+
licenses: []
|
161
|
+
post_install_message:
|
162
|
+
rdoc_options: []
|
163
|
+
require_paths:
|
164
|
+
- lib
|
165
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
166
|
+
none: false
|
167
|
+
requirements:
|
168
|
+
- - ! '>='
|
169
|
+
- !ruby/object:Gem::Version
|
170
|
+
version: '0'
|
171
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
172
|
+
none: false
|
173
|
+
requirements:
|
174
|
+
- - ! '>='
|
175
|
+
- !ruby/object:Gem::Version
|
176
|
+
version: '0'
|
177
|
+
requirements: []
|
178
|
+
rubyforge_project:
|
179
|
+
rubygems_version: 1.8.23
|
180
|
+
signing_key:
|
181
|
+
specification_version: 3
|
182
|
+
summary: Control an NXT 2.0
|
183
|
+
test_files: []
|
184
|
+
has_rdoc:
|