nxt 0.3.0 → 0.5.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.
- checksums.yaml +15 -0
- data/README.md +45 -0
- data/lib/communication/bluetooth_communication.rb +85 -0
- data/lib/communication/serial_port_profile.rb +71 -0
- data/lib/nxt.rb +111 -20
- data/lib/ruby-nxt.sublime-project +8 -0
- data/lib/ruby-nxt.sublime-workspace +288 -0
- data/lib/telegrams/commands/direct/get_battery_level.rb +8 -0
- data/lib/telegrams/commands/direct/get_current_program_name.rb +8 -0
- data/lib/{nxt/commands/sound.rb → telegrams/commands/direct/get_input_values.rb} +0 -0
- data/lib/{nxt/commands/tone.rb → telegrams/commands/direct/keep_alive.rb} +0 -0
- data/lib/telegrams/commands/direct/ls_get_status.rb +0 -0
- data/lib/telegrams/commands/direct/ls_read.rb +0 -0
- data/lib/telegrams/commands/direct/ls_write.rb +0 -0
- data/lib/telegrams/commands/direct/message_read.rb +0 -0
- data/lib/telegrams/commands/direct/message_write.rb +0 -0
- data/lib/telegrams/commands/direct/output_state.rb +229 -0
- data/lib/telegrams/commands/direct/play_sound_file.rb +38 -0
- data/lib/telegrams/commands/direct/play_tone.rb +34 -0
- data/lib/telegrams/commands/direct/replies/get_battery_level_reply.rb +28 -0
- data/lib/telegrams/commands/direct/replies/get_current_program_name_reply.rb +30 -0
- data/lib/telegrams/commands/direct/replies/play_sound_file_reply.rb +13 -0
- data/lib/telegrams/commands/direct/replies/play_tone_reply.rb +14 -0
- data/lib/telegrams/commands/direct/replies/reset_motor_position_reply.rb +13 -0
- data/lib/telegrams/commands/direct/replies/set_input_mode_reply.rb +11 -0
- data/lib/telegrams/commands/direct/replies/set_output_state_reply.rb +11 -0
- data/lib/telegrams/commands/direct/replies/start_program_reply.rb +13 -0
- data/lib/telegrams/commands/direct/replies/stop_program_reply.rb +13 -0
- data/lib/telegrams/commands/direct/replies/stop_sound_playback_reply.rb +13 -0
- data/lib/telegrams/commands/direct/reset_input_scaled_value.rb +0 -0
- data/lib/telegrams/commands/direct/reset_motor_position.rb +8 -0
- data/lib/telegrams/commands/direct/set_input_mode.rb +101 -0
- data/lib/telegrams/commands/direct/set_output_state.rb +29 -0
- data/lib/telegrams/commands/direct/start_program.rb +30 -0
- data/lib/telegrams/commands/direct/stop_program.rb +9 -0
- data/lib/telegrams/commands/direct/stop_sound_playback.rb +8 -0
- data/lib/telegrams/commands/direct_command.rb +8 -0
- data/lib/telegrams/commands/direct_command_reply.rb +10 -0
- data/lib/telegrams/commands/message_translator.rb +46 -0
- data/lib/telegrams/commands/system/get_device_info.rb +8 -0
- data/lib/telegrams/commands/system/replies/get_device_info_reply.rb +41 -0
- data/lib/telegrams/commands/system_command.rb +8 -0
- data/lib/telegrams/messages/error.rb +0 -0
- data/lib/telegrams/messages/message.rb +0 -0
- data/lib/telegrams/messages/success.rb +0 -0
- data/lib/telegrams/no_message_reply.rb +10 -0
- data/lib/telegrams/reply.rb +82 -0
- data/lib/telegrams/respondable_telegram.rb +29 -0
- data/lib/telegrams/telegram.rb +14 -0
- data/spec/communication/bluetooth_communication_spec.rb +170 -0
- data/spec/communication/serial_port_profile_spec.rb +139 -0
- data/spec/helper.rb +1 -0
- data/spec/nxt_spec.rb +438 -0
- data/spec/telegrams/commands/direct/get_battery_level_spec.rb +26 -0
- data/spec/telegrams/commands/direct/get_current_program_name_spec.rb +26 -0
- data/spec/telegrams/commands/direct/output_state_spec.rb +198 -0
- data/spec/telegrams/commands/direct/play_sound_file_spec.rb +75 -0
- data/spec/telegrams/commands/direct/play_tone_spec.rb +63 -0
- data/spec/telegrams/commands/direct/replies/get_battery_level_reply_spec.rb +40 -0
- data/spec/telegrams/commands/direct/replies/get_current_program_name_reply_spec.rb +33 -0
- data/spec/telegrams/commands/direct/replies/play_sound_file_reply_spec.rb +13 -0
- data/spec/telegrams/commands/direct/replies/play_tone_reply_spec.rb +14 -0
- data/spec/telegrams/commands/direct/replies/reset_motor_position_reply_spec.rb +13 -0
- data/spec/telegrams/commands/direct/replies/set_input_mode_reply_spec.rb +12 -0
- data/spec/telegrams/commands/direct/replies/set_output_state_reply_spec.rb +12 -0
- data/spec/telegrams/commands/direct/replies/start_program_reply_spec.rb +12 -0
- data/spec/telegrams/commands/direct/replies/stop_program_reply_spec.rb +13 -0
- data/spec/telegrams/commands/direct/replies/stop_sound_playback_reply_spec.rb +13 -0
- data/spec/telegrams/commands/direct/reset_motor_position_spec.rb +31 -0
- data/spec/telegrams/commands/direct/set_input_mode_spec.rb +122 -0
- data/spec/telegrams/commands/direct/set_output_state_spec.rb +72 -0
- data/spec/telegrams/commands/direct/start_program_spec.rb +58 -0
- data/spec/telegrams/commands/direct/stop_program_spec.rb +34 -0
- data/spec/telegrams/commands/direct/stop_sound_playback_spec.rb +34 -0
- data/spec/telegrams/commands/direct_command_reply_spec.rb +7 -0
- data/spec/telegrams/commands/direct_command_spec.rb +34 -0
- data/spec/telegrams/commands/system/get_device_info_spec.rb +16 -0
- data/spec/telegrams/commands/system/replies/get_device_info_reply_spec.rb +63 -0
- data/spec/telegrams/commands/system_command_spec.rb +26 -0
- data/spec/telegrams/no_message_reply_spec.rb +12 -0
- data/spec/telegrams/reply_spec.rb +63 -0
- data/spec/telegrams/respondable_telegram_spec.rb +66 -0
- data/spec/telegrams/telegram_spec.rb +38 -0
- metadata +97 -116
- data/README.markdown +0 -52
- data/Rakefile +0 -35
- data/lib/nxt/commands/base.rb +0 -51
- data/lib/nxt/commands/input.rb +0 -60
- data/lib/nxt/commands/output.rb +0 -105
- data/lib/nxt/commands/program.rb +0 -70
- data/lib/nxt/connectors/base.rb +0 -35
- data/lib/nxt/connectors/input/color.rb +0 -30
- data/lib/nxt/connectors/input/touch.rb +0 -11
- data/lib/nxt/connectors/input/ultrasonic.rb +0 -11
- data/lib/nxt/connectors/output/motor.rb +0 -114
- data/lib/nxt/errors.rb +0 -25
- data/lib/nxt/exceptions.rb +0 -26
- data/lib/nxt/interfaces/base.rb +0 -36
- data/lib/nxt/interfaces/serial_port.rb +0 -88
- data/lib/nxt/interfaces/usb.rb +0 -8
- data/lib/nxt/nxt_brick.rb +0 -167
- data/lib/nxt/patches/module.rb +0 -22
- data/lib/nxt/patches/string.rb +0 -29
- data/lib/nxt/utils/accessors.rb +0 -24
- data/spec/matchers.rb +0 -7
- data/spec/nxt/connectors/output/motor_spec.rb +0 -55
- data/spec/nxt/interfaces/serial_port_spec.rb +0 -73
- data/spec/nxt/nxt_brick_spec.rb +0 -199
- data/spec/spec_helper.rb +0 -4
@@ -0,0 +1,29 @@
|
|
1
|
+
require_relative './telegram'
|
2
|
+
|
3
|
+
class RespondableTelegram < Telegram
|
4
|
+
attr_reader :command
|
5
|
+
def initialize(require_response=true)
|
6
|
+
@require_response = require_response
|
7
|
+
@command = nil
|
8
|
+
end
|
9
|
+
|
10
|
+
def require_response?
|
11
|
+
@require_response
|
12
|
+
end
|
13
|
+
|
14
|
+
def require_response=(value)
|
15
|
+
@require_response = value
|
16
|
+
end
|
17
|
+
|
18
|
+
# override
|
19
|
+
def as_bytes
|
20
|
+
[adjust_type_for_require_response, command]
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
def adjust_type_for_require_response
|
25
|
+
# if response is not required, mask the type
|
26
|
+
# with 0x80 to say a response is not required
|
27
|
+
@type | (require_response? ? 0x00 : 0x80)
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,170 @@
|
|
1
|
+
require './spec/helper'
|
2
|
+
|
3
|
+
require './lib/communication/bluetooth_communication'
|
4
|
+
require './lib/telegrams/reply'
|
5
|
+
|
6
|
+
class BluetoothCommunication
|
7
|
+
attr_accessor :profile
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
class ReceiveMessageLength3BluetoothCommunication < BluetoothCommunication
|
12
|
+
def read_length_of_received_message!
|
13
|
+
3
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe BluetoothCommunication do
|
18
|
+
describe "when creating a new bluetooth communication object" do
|
19
|
+
it "must raise an ArgumentError if device is not set" do
|
20
|
+
-> { BluetoothCommunication.new }.must_raise ArgumentError
|
21
|
+
end
|
22
|
+
|
23
|
+
it "must raise an ArgumentError if device is nil" do
|
24
|
+
-> { BluetoothCommunication.new(nil, Object.new) }.must_raise ArgumentError
|
25
|
+
end
|
26
|
+
|
27
|
+
it "must default to using a SerialPortProfile for its profile" do
|
28
|
+
BluetoothCommunication.new('dev').profile.must_be_instance_of SerialPortProfile
|
29
|
+
end
|
30
|
+
|
31
|
+
it "must use the profile passed in if provided" do
|
32
|
+
fake_profile = Object.new
|
33
|
+
BluetoothCommunication.new('dev', fake_profile).profile.must_be(:==, fake_profile)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "when connecting over bluetooth" do
|
38
|
+
it "should use connect using the serial profile" do
|
39
|
+
mock_profile = MiniTest::Mock.new.expect(:connect, nil)
|
40
|
+
BluetoothCommunication.new('dev', mock_profile).connect
|
41
|
+
|
42
|
+
mock_profile.verify
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should disconnect using the serial profile" do
|
46
|
+
mock_profile = MiniTest::Mock.new.expect(:disconnect, nil)
|
47
|
+
BluetoothCommunication.new('dev', mock_profile).disconnect
|
48
|
+
|
49
|
+
mock_profile.verify
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "when sending a message over bluetooth" do
|
54
|
+
before do
|
55
|
+
@mock_profile = MiniTest::Mock.new
|
56
|
+
@communication = BluetoothCommunication.new('dev', @mock_profile)
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
it "must call as_bytes on the telegram being passed in" do
|
61
|
+
message = MiniTest::Mock.new.expect(:as_bytes, [0,1,2])
|
62
|
+
message.expect(:require_response?, false) # don't do anything after send
|
63
|
+
@mock_profile.expect(:send_data_package, nil, [[3,0,0,1,2]])
|
64
|
+
|
65
|
+
@communication.send_message(message)
|
66
|
+
|
67
|
+
message.verify
|
68
|
+
@mock_profile.verify
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "when expecting a response" do
|
72
|
+
before do
|
73
|
+
@fake_command = MiniTest::Mock.new.expect(:require_response?, true)
|
74
|
+
@fake_command.expect(:as_bytes, [0x00, 0x01, 0x00])
|
75
|
+
|
76
|
+
@mock_profile.expect(:send_data_package, nil, [[3,0,0,1,0]])
|
77
|
+
@mock_profile.expect(:receive_data_package, "\x03\x00\x02\x01\x00")
|
78
|
+
end
|
79
|
+
|
80
|
+
it "must call receive_message and return the response if a response is required" do
|
81
|
+
@communication.send_message(@fake_command).must_equal [2,1,0]
|
82
|
+
end
|
83
|
+
|
84
|
+
it "must yield to the passed in block if a response is required" do
|
85
|
+
@communication.send_message(@fake_command) do | response |
|
86
|
+
response[0].must_equal 2
|
87
|
+
response[1].must_equal 1
|
88
|
+
response[2].must_equal 0
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
it "must create a new instance of the class specified as the return type" do
|
93
|
+
reply = @communication.send_message(@fake_command, Reply)
|
94
|
+
reply.type.must_equal 0x02
|
95
|
+
reply.command.must_equal 0x01
|
96
|
+
reply.success?.must_equal true
|
97
|
+
end
|
98
|
+
|
99
|
+
it "must pass an instance of the reply class to the passed in block if a response is required" do
|
100
|
+
@communication.send_message(@fake_command, Reply) do | reply |
|
101
|
+
reply.type.must_equal 2
|
102
|
+
reply.command.must_equal 1
|
103
|
+
reply.status.must_equal 0
|
104
|
+
reply.success?.must_equal true
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe "when not expecting a response" do
|
110
|
+
before do
|
111
|
+
@fake_command = MiniTest::Mock.new.expect(:require_response?, false)
|
112
|
+
@fake_command.expect(:as_bytes, [0x00, 0x01, 0x00])
|
113
|
+
|
114
|
+
@mock_profile.expect(:send_data_package, nil, [[3,0,0,1,0]])
|
115
|
+
# do not expect receive_data_package
|
116
|
+
end
|
117
|
+
|
118
|
+
it "must not call receive_message and do not return a response" do
|
119
|
+
@communication.send_message(@fake_command).must_equal nil
|
120
|
+
end
|
121
|
+
|
122
|
+
|
123
|
+
it "must not yield to the passed in block when a response is not wanted" do
|
124
|
+
@communication.send_message(@fake_command) do | response |
|
125
|
+
raise "Should not call this block!"
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
describe "but passing in a reply class to use" do
|
130
|
+
it "must not call receive_message and do not return a response" do
|
131
|
+
@communication.send_message(@fake_command, Reply).must_equal nil
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
describe "when receiving a message over bluetooth" do
|
138
|
+
|
139
|
+
before do
|
140
|
+
@mock_profile = MiniTest::Mock.new
|
141
|
+
@communication = BluetoothCommunication.new('dev', @mock_profile)
|
142
|
+
end
|
143
|
+
|
144
|
+
describe "when getting the raw bytes from the profile" do
|
145
|
+
it "must call receive_data_package on the profile" do
|
146
|
+
@mock_profile.expect(:receive_data_package, [0x02, 0x11, 0x00])
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
describe "when using read_length_of_received_message! private method" do
|
151
|
+
|
152
|
+
it "must use sysread to get the length of the message" do
|
153
|
+
# first two bytes are the bluetooth header that has the
|
154
|
+
# message length as a 16-bit Little Endian number
|
155
|
+
raw_message = "\x03\x00\x02\x01\x00"
|
156
|
+
@mock_profile.expect(:receive_data_package, raw_message)
|
157
|
+
|
158
|
+
message_parts = @communication.send(:split_message_into_header_and_message, raw_message)
|
159
|
+
@communication.send(:get_length_of_received_message_from_bluetooth_header, message_parts[:header]).must_equal 3
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
it "must retrun the raw bytes from the profile" do
|
164
|
+
communication = ReceiveMessageLength3BluetoothCommunication.new('dev', @mock_profile)
|
165
|
+
@mock_profile.expect(:receive_data_package, "\x03\x00\x02\x01\x00")
|
166
|
+
|
167
|
+
communication.receive_message.must_equal [0x02, 0x01, 0x00]
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
require './spec/helper'
|
2
|
+
|
3
|
+
require './lib/communication/serial_port_profile'
|
4
|
+
|
5
|
+
class FakeSerialPortConnection; end
|
6
|
+
|
7
|
+
# Open up SerialPortProfile to be able to set the connection
|
8
|
+
class SerialPortProfile
|
9
|
+
def connection=(value)
|
10
|
+
@connection = value
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe SerialPortProfile do
|
15
|
+
describe "when creating a new instance" do
|
16
|
+
it "must raise an ArgumentError if the device is nil" do
|
17
|
+
-> { SerialPortProfile.new }.must_raise ArgumentError
|
18
|
+
end
|
19
|
+
|
20
|
+
it "must set the device if it is non-nil" do
|
21
|
+
profile = SerialPortProfile.new('device')
|
22
|
+
profile.instance_eval('@device').must_equal 'device'
|
23
|
+
end
|
24
|
+
|
25
|
+
it "must default the connection class to use to SerialPort" do
|
26
|
+
profile = SerialPortProfile.new('')
|
27
|
+
profile.instance_eval('@connection_class').must_equal(SerialPort)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "must set the connection class if passed in and not nil" do
|
31
|
+
profile = SerialPortProfile.new('', FakeSerialPortConnection)
|
32
|
+
profile.instance_eval('@connection_class').must_equal(FakeSerialPortConnection)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "when establishing the connection" do
|
37
|
+
it "should set the modem settings as required" do
|
38
|
+
mock_connection = MiniTest::Mock.new.expect(:nil?, false)
|
39
|
+
mock_connection.expect(:flow_control=, nil, [SerialPort::HARD])
|
40
|
+
mock_connection.expect(:read_timeout=, nil, [5000])
|
41
|
+
|
42
|
+
mock_connection_class = MiniTest::Mock.new.expect(
|
43
|
+
:new, mock_connection,
|
44
|
+
[
|
45
|
+
'dev',
|
46
|
+
57600, # baud
|
47
|
+
8, # data_bits
|
48
|
+
1, # stop_bits
|
49
|
+
SerialPort::NONE # parity
|
50
|
+
]
|
51
|
+
)
|
52
|
+
|
53
|
+
|
54
|
+
profile = SerialPortProfile.new('dev', mock_connection_class)
|
55
|
+
|
56
|
+
profile.connect
|
57
|
+
mock_connection_class.verify
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe "when closing the connection" do
|
62
|
+
before do
|
63
|
+
@mock_connection = MiniTest::Mock.new
|
64
|
+
@mock_connection_class = MiniTest::Mock.new.expect(:new, @mock_connection,
|
65
|
+
['dev', 57600, 8, 1, SerialPort::NONE]
|
66
|
+
)
|
67
|
+
@mock_connection.expect(:close, nil)
|
68
|
+
|
69
|
+
@profile = SerialPortProfile.new('dev', @mock_connection_class);
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should not call close if there is no connection object" do
|
73
|
+
@mock_connection.expect(:nil?, true)
|
74
|
+
@profile.connection = @mock_connection
|
75
|
+
|
76
|
+
@profile.disconnect
|
77
|
+
|
78
|
+
# Assert that MockExpectationError was raised because close was not called
|
79
|
+
# We dont' want close to be called, ensure that the MockExpectationError for
|
80
|
+
# close IS IN FACT raised.
|
81
|
+
assert_raises(MockExpectationError, "close should be called") do
|
82
|
+
@mock_connection.verify
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should not call close if there is a connection object but it is already closed" do
|
87
|
+
@mock_connection.expect(:nil?, false)
|
88
|
+
@mock_connection.expect(:closed?, true)
|
89
|
+
@profile.connection = @mock_connection
|
90
|
+
|
91
|
+
@profile.disconnect
|
92
|
+
|
93
|
+
# Assert that MockExpectationError was raised because close was not called
|
94
|
+
# We dont' want close to be called, ensure that the MockExpectationError for
|
95
|
+
# close IS IN FACT raised.
|
96
|
+
assert_raises(MockExpectationError, "close should be called") do
|
97
|
+
@mock_connection.verify
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should call close if there is an open connection" do
|
102
|
+
@mock_connection.expect(:nil?, false)
|
103
|
+
@mock_connection.expect(:closed?, false)
|
104
|
+
@profile.connection = @mock_connection
|
105
|
+
|
106
|
+
@profile.disconnect
|
107
|
+
|
108
|
+
@mock_connection.verify
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
describe "when sending a data package" do
|
113
|
+
before do
|
114
|
+
@mock_connection_class = MiniTest::Mock.new
|
115
|
+
@profile = SerialPortProfile.new('dev', @mock_connection_class)
|
116
|
+
|
117
|
+
@mock_connection = MiniTest::Mock.new
|
118
|
+
@profile.connection = @mock_connection
|
119
|
+
end
|
120
|
+
|
121
|
+
it "should send each byte from the package in order" do
|
122
|
+
@mock_connection.expect(:putc, nil, [0x00])
|
123
|
+
@mock_connection.expect(:putc, nil, [0x01])
|
124
|
+
@mock_connection.expect(:putc, nil, [0x02])
|
125
|
+
|
126
|
+
@profile.send_data_package([0x00, 0x01, 0x02])
|
127
|
+
|
128
|
+
@mock_connection.verify
|
129
|
+
end
|
130
|
+
|
131
|
+
it "should receive each byte in order as an array of bytes" do
|
132
|
+
# 64 is the max length in bytes of a message, + 2 for bluetooth overhead
|
133
|
+
@mock_connection.expect(:sysread, [0x00, 0x01, 0x02], [64 + 2])
|
134
|
+
|
135
|
+
@profile.receive_data_package.must_equal [0x00, 0x01, 0x02]
|
136
|
+
@mock_connection.verify
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
data/spec/helper.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'minitest/autorun'
|
data/spec/nxt_spec.rb
ADDED
@@ -0,0 +1,438 @@
|
|
1
|
+
require './spec/helper'
|
2
|
+
require './lib/nxt'
|
3
|
+
|
4
|
+
class TestableNXT < NXT
|
5
|
+
attr_reader :command, :reply_type
|
6
|
+
|
7
|
+
def initialize(device_path, communication=nil)
|
8
|
+
super(device_path, communication)
|
9
|
+
end
|
10
|
+
|
11
|
+
def send_message(command, reply_type=nil)
|
12
|
+
@command = command
|
13
|
+
@reply_type = reply_type
|
14
|
+
super(command, reply_type)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe NXT do
|
19
|
+
describe "when constructing an instance" do
|
20
|
+
it "must raise an ArgumentError if there is no device path" do
|
21
|
+
-> { NXT.new }.must_raise ArgumentError
|
22
|
+
end
|
23
|
+
|
24
|
+
it "must raise an ArgumentError if there is a nil device path" do
|
25
|
+
-> { NXT.new(nil) }.must_raise ArgumentError
|
26
|
+
end
|
27
|
+
|
28
|
+
it "must accept a device path and set it" do
|
29
|
+
NXT.new('/dev/tty.NXT-DevB').device_path.must_equal '/dev/tty.NXT-DevB'
|
30
|
+
end
|
31
|
+
|
32
|
+
it "must default to a communication object of BluetoothCommunication" do
|
33
|
+
NXT.new('some_device').communication.must_be_instance_of BluetoothCommunication
|
34
|
+
end
|
35
|
+
|
36
|
+
it "must accept a communication object and set it" do
|
37
|
+
communication_object = Object.new
|
38
|
+
NXT.new('some_device', communication_object).communication.must_be_same_as communication_object
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "when connecting to a device" do
|
43
|
+
it "must call connect on the communication object" do
|
44
|
+
mock_communication = MiniTest::Mock.new.expect(:connect, nil)
|
45
|
+
NXT.new('some_device', mock_communication).connect
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "when disconnecting from a device" do
|
50
|
+
it "must call disconnect on the communication object" do
|
51
|
+
mock_communication = MiniTest::Mock.new.expect(:disconnect, nil)
|
52
|
+
NXT.new('some_device', mock_communication).disconnect
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe "when calling get_device_info" do
|
57
|
+
before do
|
58
|
+
@command = GetDeviceInfo.new
|
59
|
+
@reply = GetDeviceInfoReply.new([0x02, 0x9B, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0])
|
60
|
+
@communication = MiniTest::Mock.new.expect(:send_message, @reply, [@command, GetDeviceInfoReply])
|
61
|
+
@nxt = NXT.new('device', @communication)
|
62
|
+
end
|
63
|
+
|
64
|
+
it "must call the communication object with a GetDeviceInfo command" do
|
65
|
+
info = @nxt.get_device_info
|
66
|
+
end
|
67
|
+
|
68
|
+
it "must return the appropriate GetDeviceInfoReply object" do
|
69
|
+
@nxt.get_device_info.must_be_same_as @reply
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe "when calling stop_program" do
|
74
|
+
before do
|
75
|
+
@command = StopProgram.new
|
76
|
+
@reply = StopProgramReply.new([0x02, 0x01, 0x00])
|
77
|
+
@communication = MiniTest::Mock.new.expect(:send_message, @reply, [@command, StopProgramReply])
|
78
|
+
@nxt = NXT.new('device', @communication)
|
79
|
+
end
|
80
|
+
|
81
|
+
it "must call the communication object with a StopProgram command" do
|
82
|
+
@nxt.stop_program
|
83
|
+
end
|
84
|
+
|
85
|
+
it "must return the appropriate StopProgramReply object when waiting for a reply" do
|
86
|
+
@nxt.stop_program.must_be_same_as @reply
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe "when calling start_program" do
|
91
|
+
before do
|
92
|
+
@command = StartProgram.new('program.rxe')
|
93
|
+
@reply = StartProgramReply.new([0x02, 0x00, 0x00])
|
94
|
+
@communication = MiniTest::Mock.new.expect(:send_message, @reply, [@command, StartProgramReply])
|
95
|
+
@nxt = TestableNXT.new('device', @communication)
|
96
|
+
end
|
97
|
+
|
98
|
+
it "must call the communication object with a StartProgram command" do
|
99
|
+
@nxt.start_program('program.rxe')
|
100
|
+
@nxt.command.must_be_instance_of StartProgram
|
101
|
+
@nxt.command.name.must_equal "program.rxe"
|
102
|
+
end
|
103
|
+
|
104
|
+
it "must support not including the filename extension" do
|
105
|
+
@nxt.start_program('program')
|
106
|
+
@nxt.command.name.must_equal 'program.rxe'
|
107
|
+
end
|
108
|
+
|
109
|
+
it "must wait for a reply by default" do
|
110
|
+
@nxt.start_program('program.rxe')
|
111
|
+
@nxt.command.require_response?.must_equal true
|
112
|
+
end
|
113
|
+
|
114
|
+
|
115
|
+
it "must return the appropriate StartProgramReply object when waiting for a reply" do
|
116
|
+
@nxt.start_program('program.rxe').must_be_same_as @reply
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
describe "when calling stop_sound_playback" do
|
122
|
+
before do
|
123
|
+
@command = StopSoundPlayback.new
|
124
|
+
@reply = StopSoundPlaybackReply.new([0x02, 0x0C, 0x00])
|
125
|
+
@communication = MiniTest::Mock.new.expect(:send_message, @reply, [@command, StopSoundPlaybackReply])
|
126
|
+
@nxt = NXT.new('device', @communication)
|
127
|
+
end
|
128
|
+
|
129
|
+
it "must call the communication object with a StopSoundPlayback command" do
|
130
|
+
@nxt.stop_sound_playback
|
131
|
+
end
|
132
|
+
|
133
|
+
it "must return the appropriate StopProgramReply object when waiting for a reply" do
|
134
|
+
@nxt.stop_sound_playback.must_be_same_as @reply
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
|
139
|
+
describe "when calling play_sound_file" do
|
140
|
+
before do
|
141
|
+
@command = PlaySoundFile.new('foobar.rso')
|
142
|
+
@reply = PlaySoundFileReply.new([0x02, 0x02, 0x00])
|
143
|
+
@communication = MiniTest::Mock.new.expect(:send_message, @reply, [@command, PlaySoundFileReply])
|
144
|
+
@nxt = TestableNXT.new('device', @communication)
|
145
|
+
end
|
146
|
+
|
147
|
+
it "must call the communication object with a PlaySoundFile command" do
|
148
|
+
@nxt.play_sound_file('sound.mp3')
|
149
|
+
@nxt.command.must_be_instance_of PlaySoundFile
|
150
|
+
@nxt.command.name.must_equal "sound.mp3"
|
151
|
+
end
|
152
|
+
|
153
|
+
it "must wait for a reply by default" do
|
154
|
+
@nxt.play_sound_file('sound.mp3')
|
155
|
+
@nxt.command.require_response?.must_equal true
|
156
|
+
end
|
157
|
+
|
158
|
+
|
159
|
+
it "must return the appropriate PlaySoundFileReply object when waiting for a reply" do
|
160
|
+
@nxt.play_sound_file('sound.mp3').must_be_same_as @reply
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
|
165
|
+
describe "when calling get_current_program_name" do
|
166
|
+
before do
|
167
|
+
@command = GetCurrentProgramName.new
|
168
|
+
# reply with a successful reply for GetCurrentProgramName with filename "foo.bar"
|
169
|
+
@reply = GetCurrentProgramNameReply.new([0x02, 0x11, 0x00,
|
170
|
+
102, 111, 111, 46, 98, 97, 114, 0,
|
171
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
|
172
|
+
@communication = MiniTest::Mock.new.expect(:send_message, @reply, [@command, GetCurrentProgramNameReply])
|
173
|
+
@nxt = NXT.new('device', @communication)
|
174
|
+
end
|
175
|
+
|
176
|
+
it "must call the communication object with a GetCurrentProgramName command" do
|
177
|
+
@nxt.get_current_program_name
|
178
|
+
end
|
179
|
+
|
180
|
+
it "must return the appropriate GetCurrentProgramNameReply object when waiting for a reply" do
|
181
|
+
@nxt.get_current_program_name.must_be_same_as @reply
|
182
|
+
end
|
183
|
+
|
184
|
+
it "must have the right filename as the message" do
|
185
|
+
@nxt.get_current_program_name.program_name.must_equal "foo.bar"
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
describe "when calling play_tone" do
|
190
|
+
|
191
|
+
before do
|
192
|
+
@command = PlayTone.new(400, 1000)
|
193
|
+
@reply = PlayToneReply.new([0x02, 0x03, 0x00])
|
194
|
+
@communication = MiniTest::Mock.new.expect(:send_message, @reply, [@command, PlayToneReply])
|
195
|
+
@nxt = TestableNXT.new('device', @communication)
|
196
|
+
end
|
197
|
+
|
198
|
+
it "must call the communication object with a PlayTone command" do
|
199
|
+
@nxt.play_tone(400, 1000)
|
200
|
+
@nxt.command.must_be_instance_of PlayTone
|
201
|
+
@nxt.command.frequency.must_equal 400
|
202
|
+
@nxt.command.duration.must_equal 1000
|
203
|
+
end
|
204
|
+
|
205
|
+
it "must wait for a reply by default" do
|
206
|
+
@nxt.play_tone(400, 1000)
|
207
|
+
@nxt.command.require_response?.must_equal true
|
208
|
+
end
|
209
|
+
|
210
|
+
it "must return the appropriate PlayToneReply object when waiting for a reply" do
|
211
|
+
@nxt.play_tone(400, 1000).must_be_same_as @reply
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
describe "when calling set_input_mode" do
|
216
|
+
before do
|
217
|
+
@command = SetInputMode.new(:three, :colorred, :booleanmode)
|
218
|
+
@reply = SetInputModeReply.new([0x02, 0x05, 0x00])
|
219
|
+
@communication = MiniTest::Mock.new.expect(:send_message, @reply, [@command, SetInputModeReply])
|
220
|
+
@nxt = TestableNXT.new('device', @communication)
|
221
|
+
end
|
222
|
+
|
223
|
+
it "must call the communication object with a SetInputMode command" do
|
224
|
+
@nxt.set_input_mode(:three, :colorred, :booleanmode)
|
225
|
+
@nxt.command.must_be_instance_of SetInputMode
|
226
|
+
@nxt.command.input_port.must_equal :three
|
227
|
+
@nxt.command.sensor_type.must_equal :colorred
|
228
|
+
@nxt.command.sensor_mode.must_equal :booleanmode
|
229
|
+
end
|
230
|
+
|
231
|
+
it "must wait for a reply by default" do
|
232
|
+
@nxt.set_input_mode(:three, :colorred, :booleanmode)
|
233
|
+
@nxt.command.require_response?.must_equal true
|
234
|
+
end
|
235
|
+
|
236
|
+
|
237
|
+
it "must return the appropriate SetInputModeReply object when waiting for a reply" do
|
238
|
+
@nxt.set_input_mode(:three, :colorred, :booleanmode).must_be_same_as @reply
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
|
243
|
+
describe "when calling set_output_state" do
|
244
|
+
before do
|
245
|
+
@state = OutputState.new(:port => :c, :power => 55, :regulation_mode => :motor_sync)
|
246
|
+
@command = SetOutputState.new(@state)
|
247
|
+
@reply = SetOutputStateReply.new([0x02, 0x04, 0x00])
|
248
|
+
@communication = MiniTest::Mock.new.expect(:send_message, @reply, [@command, SetOutputStateReply])
|
249
|
+
@nxt = TestableNXT.new('device', @communication)
|
250
|
+
end
|
251
|
+
|
252
|
+
it "must call the communication object with a SetOutputState command" do
|
253
|
+
@nxt.set_output_state(@state)
|
254
|
+
@nxt.command.must_be_instance_of SetOutputState
|
255
|
+
@nxt.command.port.must_equal :c
|
256
|
+
@nxt.command.power.must_equal 55
|
257
|
+
@nxt.command.regulation_mode.must_equal :motor_sync
|
258
|
+
end
|
259
|
+
|
260
|
+
it "must wait for a reply by default" do
|
261
|
+
@nxt.set_output_state(OutputState.new)
|
262
|
+
@nxt.command.require_response?.must_equal true
|
263
|
+
end
|
264
|
+
|
265
|
+
|
266
|
+
it "must return the appropriate SetOutputStateReply object when waiting for a reply" do
|
267
|
+
@nxt.set_output_state(OutputState.new).must_be_same_as @reply
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
|
272
|
+
describe "when calling get_battery_level" do
|
273
|
+
before do
|
274
|
+
@command = GetBatteryLevel.new
|
275
|
+
@reply = GetBatteryLevelReply.new([0x02, 0x0B, 0x00, 0x1B, 0x10]) # bytes at indexes 3-4 is the battery level in millivolts
|
276
|
+
@communication = MiniTest::Mock.new.expect(:send_message, @reply, [@command, GetBatteryLevelReply])
|
277
|
+
@nxt = TestableNXT.new('device', @communication)
|
278
|
+
end
|
279
|
+
|
280
|
+
it "must send a GetBatteryLevel command " do
|
281
|
+
@nxt.get_battery_level
|
282
|
+
@nxt.command.must_be_instance_of GetBatteryLevel
|
283
|
+
end
|
284
|
+
|
285
|
+
it "must wait for a reply by default" do
|
286
|
+
@nxt.get_battery_level
|
287
|
+
@nxt.command.require_response?.must_equal true
|
288
|
+
end
|
289
|
+
|
290
|
+
it "must return the appropriate GetBatteryLevelReply object when waiting for a reply" do
|
291
|
+
this_reply = @nxt.get_battery_level
|
292
|
+
this_reply.must_be_same_as @reply
|
293
|
+
this_reply.millivolts.must_equal 4123
|
294
|
+
this_reply.volts.must_equal 4.123
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
describe "when calling reset_motor_position" do
|
299
|
+
before do
|
300
|
+
@command = ResetMotorPosition.new
|
301
|
+
@reply = ResetMotorPositionReply.new([0x02, 0x0A, 0x00])
|
302
|
+
@communication = MiniTest::Mock.new.expect(:send_message, @reply, [@command, ResetMotorPositionReply])
|
303
|
+
@nxt = NXT.new('device', @communication)
|
304
|
+
end
|
305
|
+
|
306
|
+
it "must call the communication object with a ResetMotorPosition command" do
|
307
|
+
@nxt.reset_motor_position
|
308
|
+
end
|
309
|
+
|
310
|
+
it "must return the appropriate ResetMotorPositionReply object when waiting for a reply" do
|
311
|
+
@nxt.reset_motor_position.must_be_same_as @reply
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
|
316
|
+
describe "yet-to-be implemented" do
|
317
|
+
before do
|
318
|
+
@nxt = TestableNXT.new('device')
|
319
|
+
end
|
320
|
+
|
321
|
+
it "must raise NotImplementedError for these methods" do
|
322
|
+
[:get_output_state, :get_input_values, :reset_input_scaled_value,
|
323
|
+
:message_write, :keep_alive,
|
324
|
+
:ls_get_status, :ls_write, :ls_read,
|
325
|
+
:message_read].each do |method|
|
326
|
+
-> { @nxt.send(method) }.must_raise NotImplementedError
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
|
332
|
+
describe "when calling async" do
|
333
|
+
it "must be an instance of NXTAsync" do
|
334
|
+
NXT.new('device_path').async.must_be_instance_of NXTAsync
|
335
|
+
end
|
336
|
+
|
337
|
+
it "must use the same communication and device_path as the parent" do
|
338
|
+
communication = Object.new
|
339
|
+
nxt = NXT.new('device_path', communication)
|
340
|
+
nxt.async.device_path.must_equal nxt.device_path
|
341
|
+
nxt.async.communication.must_equal nxt.communication
|
342
|
+
end
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
|
347
|
+
class MockCommunication
|
348
|
+
def send_message(comamnd, reply_type)
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
class NXTAsync
|
353
|
+
attr_reader :command, :reply_type
|
354
|
+
|
355
|
+
def send_message(command, reply_type=nil)
|
356
|
+
super(command, reply_type)
|
357
|
+
@command = command
|
358
|
+
@reply_type = reply_type
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
describe NXTAsync do
|
363
|
+
before do
|
364
|
+
@communication = MockCommunication.new
|
365
|
+
@async = NXTAsync.new('device', @communication)
|
366
|
+
end
|
367
|
+
|
368
|
+
describe "constructor" do
|
369
|
+
it "must send the device_path and communication object on" do
|
370
|
+
@async.device_path.must_equal 'device'
|
371
|
+
@async.communication.must_equal @communication
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
describe "async" do
|
376
|
+
it "must return itself when asked for async" do
|
377
|
+
@async.async.must_equal @async
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
describe "stop_program" do
|
382
|
+
it "must send a StopProgram command without waiting for reply" do
|
383
|
+
@async.stop_program
|
384
|
+
@async.command.must_be_instance_of StopProgram
|
385
|
+
@async.command.require_response?.must_equal false
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
389
|
+
describe "start_program" do
|
390
|
+
it "must send a StopProgram command without waiting for reply" do
|
391
|
+
@async.start_program('program.rxe')
|
392
|
+
@async.command.must_be_instance_of StartProgram
|
393
|
+
@async.command.require_response?.must_equal false
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
describe "stop_sound_playback" do
|
398
|
+
it "must send a StopSoundPlayback command without waiting for reply" do
|
399
|
+
@async.stop_sound_playback
|
400
|
+
@async.command.must_be_instance_of StopSoundPlayback
|
401
|
+
@async.command.require_response?.must_equal false
|
402
|
+
end
|
403
|
+
end
|
404
|
+
|
405
|
+
describe "play_sound_file" do
|
406
|
+
it "must send a PlaySoundFile command without waiting for reply" do
|
407
|
+
@async.play_sound_file('sound.mp3')
|
408
|
+
@async.command.must_be_instance_of PlaySoundFile
|
409
|
+
@async.command.require_response?.must_equal false
|
410
|
+
end
|
411
|
+
end
|
412
|
+
|
413
|
+
describe "play_tone" do
|
414
|
+
it "must send a PlayTone command without waiting for reply" do
|
415
|
+
@async.play_tone(400, 1000)
|
416
|
+
@async.command.must_be_instance_of PlayTone
|
417
|
+
@async.command.require_response?.must_equal false
|
418
|
+
end
|
419
|
+
end
|
420
|
+
|
421
|
+
describe "set_output_state" do
|
422
|
+
it "must send a SetOutputState command without waiting for a reply" do
|
423
|
+
state = Object.new
|
424
|
+
@async.set_output_state(state)
|
425
|
+
@async.command.instance_eval('output_state').must_equal state
|
426
|
+
@async.command.require_response?.must_equal false
|
427
|
+
end
|
428
|
+
end
|
429
|
+
|
430
|
+
describe "reset_motor_position" do
|
431
|
+
it "must send a ResetMotorPosition command without waiting for reply" do
|
432
|
+
@async.reset_motor_position
|
433
|
+
@async.command.must_be_instance_of ResetMotorPosition
|
434
|
+
@async.command.require_response?.must_equal false
|
435
|
+
end
|
436
|
+
end
|
437
|
+
|
438
|
+
end
|