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.
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
@@ -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,14 @@
1
+ class Telegram
2
+ attr_reader :type
3
+
4
+ def initialize()
5
+ end
6
+
7
+ def as_bytes
8
+ [@type]
9
+ end
10
+
11
+ def max_size_in_bytes
12
+ 64
13
+ end
14
+ 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
@@ -0,0 +1 @@
1
+ require 'minitest/autorun'
@@ -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