nxt 0.3.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (109) hide show
  1. checksums.yaml +15 -0
  2. data/README.md +45 -0
  3. data/lib/communication/bluetooth_communication.rb +85 -0
  4. data/lib/communication/serial_port_profile.rb +71 -0
  5. data/lib/nxt.rb +111 -20
  6. data/lib/ruby-nxt.sublime-project +8 -0
  7. data/lib/ruby-nxt.sublime-workspace +288 -0
  8. data/lib/telegrams/commands/direct/get_battery_level.rb +8 -0
  9. data/lib/telegrams/commands/direct/get_current_program_name.rb +8 -0
  10. data/lib/{nxt/commands/sound.rb → telegrams/commands/direct/get_input_values.rb} +0 -0
  11. data/lib/{nxt/commands/tone.rb → telegrams/commands/direct/keep_alive.rb} +0 -0
  12. data/lib/telegrams/commands/direct/ls_get_status.rb +0 -0
  13. data/lib/telegrams/commands/direct/ls_read.rb +0 -0
  14. data/lib/telegrams/commands/direct/ls_write.rb +0 -0
  15. data/lib/telegrams/commands/direct/message_read.rb +0 -0
  16. data/lib/telegrams/commands/direct/message_write.rb +0 -0
  17. data/lib/telegrams/commands/direct/output_state.rb +229 -0
  18. data/lib/telegrams/commands/direct/play_sound_file.rb +38 -0
  19. data/lib/telegrams/commands/direct/play_tone.rb +34 -0
  20. data/lib/telegrams/commands/direct/replies/get_battery_level_reply.rb +28 -0
  21. data/lib/telegrams/commands/direct/replies/get_current_program_name_reply.rb +30 -0
  22. data/lib/telegrams/commands/direct/replies/play_sound_file_reply.rb +13 -0
  23. data/lib/telegrams/commands/direct/replies/play_tone_reply.rb +14 -0
  24. data/lib/telegrams/commands/direct/replies/reset_motor_position_reply.rb +13 -0
  25. data/lib/telegrams/commands/direct/replies/set_input_mode_reply.rb +11 -0
  26. data/lib/telegrams/commands/direct/replies/set_output_state_reply.rb +11 -0
  27. data/lib/telegrams/commands/direct/replies/start_program_reply.rb +13 -0
  28. data/lib/telegrams/commands/direct/replies/stop_program_reply.rb +13 -0
  29. data/lib/telegrams/commands/direct/replies/stop_sound_playback_reply.rb +13 -0
  30. data/lib/telegrams/commands/direct/reset_input_scaled_value.rb +0 -0
  31. data/lib/telegrams/commands/direct/reset_motor_position.rb +8 -0
  32. data/lib/telegrams/commands/direct/set_input_mode.rb +101 -0
  33. data/lib/telegrams/commands/direct/set_output_state.rb +29 -0
  34. data/lib/telegrams/commands/direct/start_program.rb +30 -0
  35. data/lib/telegrams/commands/direct/stop_program.rb +9 -0
  36. data/lib/telegrams/commands/direct/stop_sound_playback.rb +8 -0
  37. data/lib/telegrams/commands/direct_command.rb +8 -0
  38. data/lib/telegrams/commands/direct_command_reply.rb +10 -0
  39. data/lib/telegrams/commands/message_translator.rb +46 -0
  40. data/lib/telegrams/commands/system/get_device_info.rb +8 -0
  41. data/lib/telegrams/commands/system/replies/get_device_info_reply.rb +41 -0
  42. data/lib/telegrams/commands/system_command.rb +8 -0
  43. data/lib/telegrams/messages/error.rb +0 -0
  44. data/lib/telegrams/messages/message.rb +0 -0
  45. data/lib/telegrams/messages/success.rb +0 -0
  46. data/lib/telegrams/no_message_reply.rb +10 -0
  47. data/lib/telegrams/reply.rb +82 -0
  48. data/lib/telegrams/respondable_telegram.rb +29 -0
  49. data/lib/telegrams/telegram.rb +14 -0
  50. data/spec/communication/bluetooth_communication_spec.rb +170 -0
  51. data/spec/communication/serial_port_profile_spec.rb +139 -0
  52. data/spec/helper.rb +1 -0
  53. data/spec/nxt_spec.rb +438 -0
  54. data/spec/telegrams/commands/direct/get_battery_level_spec.rb +26 -0
  55. data/spec/telegrams/commands/direct/get_current_program_name_spec.rb +26 -0
  56. data/spec/telegrams/commands/direct/output_state_spec.rb +198 -0
  57. data/spec/telegrams/commands/direct/play_sound_file_spec.rb +75 -0
  58. data/spec/telegrams/commands/direct/play_tone_spec.rb +63 -0
  59. data/spec/telegrams/commands/direct/replies/get_battery_level_reply_spec.rb +40 -0
  60. data/spec/telegrams/commands/direct/replies/get_current_program_name_reply_spec.rb +33 -0
  61. data/spec/telegrams/commands/direct/replies/play_sound_file_reply_spec.rb +13 -0
  62. data/spec/telegrams/commands/direct/replies/play_tone_reply_spec.rb +14 -0
  63. data/spec/telegrams/commands/direct/replies/reset_motor_position_reply_spec.rb +13 -0
  64. data/spec/telegrams/commands/direct/replies/set_input_mode_reply_spec.rb +12 -0
  65. data/spec/telegrams/commands/direct/replies/set_output_state_reply_spec.rb +12 -0
  66. data/spec/telegrams/commands/direct/replies/start_program_reply_spec.rb +12 -0
  67. data/spec/telegrams/commands/direct/replies/stop_program_reply_spec.rb +13 -0
  68. data/spec/telegrams/commands/direct/replies/stop_sound_playback_reply_spec.rb +13 -0
  69. data/spec/telegrams/commands/direct/reset_motor_position_spec.rb +31 -0
  70. data/spec/telegrams/commands/direct/set_input_mode_spec.rb +122 -0
  71. data/spec/telegrams/commands/direct/set_output_state_spec.rb +72 -0
  72. data/spec/telegrams/commands/direct/start_program_spec.rb +58 -0
  73. data/spec/telegrams/commands/direct/stop_program_spec.rb +34 -0
  74. data/spec/telegrams/commands/direct/stop_sound_playback_spec.rb +34 -0
  75. data/spec/telegrams/commands/direct_command_reply_spec.rb +7 -0
  76. data/spec/telegrams/commands/direct_command_spec.rb +34 -0
  77. data/spec/telegrams/commands/system/get_device_info_spec.rb +16 -0
  78. data/spec/telegrams/commands/system/replies/get_device_info_reply_spec.rb +63 -0
  79. data/spec/telegrams/commands/system_command_spec.rb +26 -0
  80. data/spec/telegrams/no_message_reply_spec.rb +12 -0
  81. data/spec/telegrams/reply_spec.rb +63 -0
  82. data/spec/telegrams/respondable_telegram_spec.rb +66 -0
  83. data/spec/telegrams/telegram_spec.rb +38 -0
  84. metadata +97 -116
  85. data/README.markdown +0 -52
  86. data/Rakefile +0 -35
  87. data/lib/nxt/commands/base.rb +0 -51
  88. data/lib/nxt/commands/input.rb +0 -60
  89. data/lib/nxt/commands/output.rb +0 -105
  90. data/lib/nxt/commands/program.rb +0 -70
  91. data/lib/nxt/connectors/base.rb +0 -35
  92. data/lib/nxt/connectors/input/color.rb +0 -30
  93. data/lib/nxt/connectors/input/touch.rb +0 -11
  94. data/lib/nxt/connectors/input/ultrasonic.rb +0 -11
  95. data/lib/nxt/connectors/output/motor.rb +0 -114
  96. data/lib/nxt/errors.rb +0 -25
  97. data/lib/nxt/exceptions.rb +0 -26
  98. data/lib/nxt/interfaces/base.rb +0 -36
  99. data/lib/nxt/interfaces/serial_port.rb +0 -88
  100. data/lib/nxt/interfaces/usb.rb +0 -8
  101. data/lib/nxt/nxt_brick.rb +0 -167
  102. data/lib/nxt/patches/module.rb +0 -22
  103. data/lib/nxt/patches/string.rb +0 -29
  104. data/lib/nxt/utils/accessors.rb +0 -24
  105. data/spec/matchers.rb +0 -7
  106. data/spec/nxt/connectors/output/motor_spec.rb +0 -55
  107. data/spec/nxt/interfaces/serial_port_spec.rb +0 -73
  108. data/spec/nxt/nxt_brick_spec.rb +0 -199
  109. data/spec/spec_helper.rb +0 -4
@@ -1,11 +0,0 @@
1
- module NXT
2
- module Connector
3
- module Input
4
- class Touch
5
- def initialize(port)
6
- @port = port
7
- end
8
- end
9
- end
10
- end
11
- end
@@ -1,11 +0,0 @@
1
- module NXT
2
- module Connector
3
- module Input
4
- class Ultrasonic
5
- def initialize(port)
6
- @port = port
7
- end
8
- end
9
- end
10
- end
11
- end
@@ -1,114 +0,0 @@
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, :brake
17
- attr_combined_accessor :direction, :forwards
18
-
19
- attr_setter :direction, is_in: DIRECTION
20
- attr_setter :duration_after, is_in: DURATION_AFTER
21
- attr_setter :duration_type, is_in: DURATION_TYPE
22
-
23
- def initialize(port, interface)
24
- @port = port
25
- @interface = interface
26
- end
27
-
28
- def duration=(duration, options = {})
29
- raise TypeError.new('Expected duration to be a number') unless duration.is_a?(Integer)
30
- @duration = duration
31
-
32
- self.duration_type = options[:type] if options.include?(:type)
33
-
34
- if options.include?(:after)
35
- raise TypeError.new('The after option is only available when the unit duration is in seconds.') if self.duration_type != :seconds
36
- self.duration_after = options[:after]
37
- end
38
-
39
- self
40
- end
41
-
42
- def forwards
43
- self.direction = :forwards
44
- self
45
- end
46
-
47
- def backwards
48
- self.direction = :backwards
49
- self
50
- end
51
-
52
- def stop(type = :coast)
53
- self.power = 0
54
- self.mode = :coast
55
-
56
- self.move
57
- end
58
-
59
- # takes block for response, or can return the response instead.
60
- def move
61
- response_required = (self.duration > 0 && self.duration_type != :seconds)
62
-
63
- set_motor_output_state(response_required)
64
-
65
- if self.duration > 0 && self.duration_type == :seconds
66
- sleep(self.duration)
67
- self.reset
68
- self.stop(self.duration_after)
69
- else
70
- self.reset
71
- end
72
- end
73
-
74
- def reset
75
- self.duration = 0
76
- self.direction = :forwards
77
- self.power = 75
78
- self.mode = :motor_on
79
- self.regulation_mode = :idle
80
- self.run_state = :running
81
- self.tacho_limit = 0
82
- end
83
-
84
-
85
- private
86
- def set_motor_output_state(response_required=true)
87
- original_tacho_limit = self.tacho_limit
88
- adjust_tacho_limit
89
-
90
- original_power = self.power
91
- adjust_power
92
-
93
- set_output_state(response_required)
94
-
95
- self.power = original_power
96
- self.tacho_limit = original_tacho_limit
97
- end
98
-
99
- def adjust_tacho_limit
100
- case self.duration_type
101
- when :rotations
102
- self.tacho_limit = self.duration * 360
103
- when :degrees
104
- self.tacho_limit = self.duration
105
- end
106
- end
107
-
108
- def adjust_power
109
- self.power *= -1 if self.direction == :backwards
110
- end
111
- end
112
- end
113
- end
114
- end
@@ -1,25 +0,0 @@
1
- module NXT
2
- module Errors
3
- SUCCESS = 0x00
4
- CODES = {
5
- SUCCESS => 'Success',
6
- 0x20 => 'Pending communication transaction in progress',
7
- 0x40 => 'Specified mailbox queue is empty',
8
- 0xBD => 'Request failed (i.e., specified file not found)',
9
- 0xBE => 'Unknown command opcode',
10
- 0xBF => 'Insane packet',
11
- 0xC0 => 'Data contains out-of-range values',
12
- 0xDD => 'Communication bus error',
13
- 0xDE => 'No free memory in communication buffer',
14
- 0xDF => 'Specified channel/connection is not valid',
15
- 0xE0 => 'Specified channel/connection not configured or busy',
16
- 0xEC => 'No active program',
17
- 0xED => 'Illegal size specified',
18
- 0xEE => 'Illegal mailbox queue ID specified',
19
- 0xEF => 'Attempted to access invalid field of a structure',
20
- 0xF0 => 'Bad input or output specified',
21
- 0xFB => 'Insufficient memory available',
22
- 0xFF => 'Bad arguments'
23
- }
24
- end
25
- end
@@ -1,26 +0,0 @@
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
-
23
- # Raised when a command returns a reply message error
24
- class CommandError < RuntimeError; end
25
- end
26
- end
@@ -1,36 +0,0 @@
1
- require_relative '../commands/base' #TODO: bad code smell here
2
-
3
- module NXT
4
- module Interface
5
- class Base
6
- def send_and_receive(msg, response_required = true)
7
- if response_required
8
- msg[0] = msg[0] | (response_required ? 0x00 : 0x80)
9
- end
10
-
11
- self.send(msg)
12
-
13
- if response_required
14
- response = self.receive
15
- response
16
- end
17
- end
18
-
19
- def send
20
- raise InterfaceNotImplemented.new('The #send method must be implemented.')
21
- end
22
-
23
- def receive
24
- raise InterfaceNotImplemented.new('The #receive method must be implemented.')
25
- end
26
-
27
- def connect
28
- raise InterfaceNotImplemented.new('The #connect method must be implemented')
29
- end
30
-
31
- def disconnect
32
- raise InterfaceNotImplemented.new('The #disconnect method must be implemented')
33
- end
34
- end
35
- end
36
- end
@@ -1,88 +0,0 @@
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
- actual_length = length.unpack('v')[0]
73
- response_bytes = @connection.sysread(actual_length)
74
- response_array = response_bytes.unpack('C*')
75
- response = {
76
- :reply => response_array[0] == 0x02,
77
- :for_command => response_array[1],
78
- :status => response_array[2]
79
- }
80
- response[:message] = NXT::Errors::CODES[response[:status]]
81
- response[:for_command_name] = NXT::Command::Base::COMMANDS[response[:for_command]]
82
- response[:data] = response_array[3..-1] if response_array.size > 3
83
-
84
- response
85
- end
86
- end
87
- end
88
- end
@@ -1,8 +0,0 @@
1
- require 'usb'
2
-
3
- module NXT
4
- module Interface
5
- class USB < Base
6
- end
7
- end
8
- end
@@ -1,167 +0,0 @@
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
- def start_program(name)
150
- NXT::Command::Output::Program.start(self.interface, name)
151
- end
152
-
153
- def stop_program
154
- NXT::Command::Output::Program.stop(self.interface)
155
- end
156
-
157
- def running_program_name
158
- NXT::Command::Output::Program.get_running_name(self.interface)
159
- end
160
-
161
- private
162
- def validate_interface(interface_type_name_string)
163
- unless NXT::Interface.constants.include?(interface_type_name_string.to_sym)
164
- raise InvalidInterfaceError.new("There is no interface of type #{interface_type_name_string}.")
165
- end
166
- end
167
- end