lego-nxt 0.2.0 → 0.3.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 (38) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +16 -12
  3. data/Rakefile +9 -12
  4. data/lib/lego_nxt.rb +36 -0
  5. data/lib/nxt/commands/base.rb +56 -7
  6. data/lib/nxt/commands/input.rb +30 -12
  7. data/lib/nxt/commands/low_speed.rb +47 -0
  8. data/lib/nxt/commands/output.rb +9 -7
  9. data/lib/nxt/commands/program.rb +3 -0
  10. data/lib/nxt/commands/sound.rb +3 -0
  11. data/lib/nxt/commands/tone.rb +3 -0
  12. data/lib/nxt/connector/input/base.rb +9 -0
  13. data/lib/nxt/{connectors → connector}/input/color.rb +3 -0
  14. data/lib/nxt/{connectors → connector}/input/touch.rb +3 -0
  15. data/lib/nxt/connector/input/ultrasonic.rb +66 -0
  16. data/lib/nxt/connector/output/base.rb +9 -0
  17. data/lib/nxt/connector/output/motor.rb +117 -0
  18. data/lib/nxt/exceptions.rb +3 -1
  19. data/lib/nxt/interface/base.rb +18 -0
  20. data/lib/nxt/{interfaces → interface}/serial_port.rb +13 -10
  21. data/lib/nxt/{interfaces → interface}/usb.rb +10 -9
  22. data/lib/nxt/nxt_brick.rb +51 -45
  23. data/lib/nxt/patches/module.rb +5 -2
  24. data/lib/nxt/patches/string.rb +6 -17
  25. data/lib/nxt/protocols/i2c.rb +118 -0
  26. data/lib/nxt/utils/accessors.rb +8 -7
  27. data/lib/nxt/utils/assertions.rb +24 -0
  28. data/spec/matchers.rb +2 -0
  29. data/spec/nxt/connector/output/motor_spec.rb +52 -0
  30. data/spec/nxt/interface/serial_port_spec.rb +119 -0
  31. data/spec/nxt/nxt_brick_spec.rb +189 -120
  32. data/spec/spec_helper.rb +10 -1
  33. metadata +193 -59
  34. data/lib/nxt.rb +0 -27
  35. data/lib/nxt/connectors/input/ultrasonic.rb +0 -11
  36. data/lib/nxt/connectors/output/motor.rb +0 -116
  37. data/lib/nxt/interfaces/base.rb +0 -26
  38. data/spec/nxt/interfaces/serial_port_spec.rb +0 -73
@@ -1,29 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Some patches that extend the default Ruby String class with some useful
4
+ # methods.
1
5
  class String
2
6
  # Convert the given string to a hexadecimal representation of the same data.
3
7
  # This method is non-destructive, it will return a new copy of the string
4
8
  # convered to hex.
5
9
  def to_hex_str
6
10
  str = ''
7
- self.each_byte {|b| str << '0x%02x ' % b}
8
- str
9
- end
10
-
11
- #
12
- def from_hex_str_two
13
- data = self.split(' ')
14
- str = ''
15
- data.each {|h| eval "str += '%c' % #{h}"}
11
+ each_byte { |b| str << format('0x%02x ', b) }
16
12
  str
17
13
  end
18
14
 
19
15
  def from_hex_str
20
- data = self.split(' ')
21
- str = ''
22
-
23
- data.each do |h|
24
- str += '%c' % h
25
- end
26
-
27
- str
16
+ unpack('C*')
28
17
  end
29
18
  end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NXT
4
+ module Protocols
5
+ # Communication to the NXT brick for digital sensors is done using the I2C
6
+ # protocol. This module implements communication in that fashion,
7
+ # abstracing the messages
8
+ # we wish to send from the underlying protocol for ease-of-use.
9
+ module I2C
10
+ COMMAND_IDENTIFIER = {
11
+ ls_get_status: 0x0E,
12
+ ls_write: 0x0F,
13
+ ls_read: 0x10
14
+ }.freeze
15
+
16
+ I2C_HEADER = 0x02
17
+
18
+ # Format is I2C address, followed by the number of bytes expected in the response.
19
+ I2C_CONSTANT_OPS = {
20
+ read_version: [0x00, 8],
21
+ read_product_id: [0x08, 8],
22
+ read_sensor_type: [0x10, 8],
23
+ read_factory_zero: [0x11, 1],
24
+ read_factory_scale_factor: [0x12, 1],
25
+ read_factory_scale_divisor: [0x13, 1],
26
+ read_measurement_units: [0x14, 7]
27
+ }.freeze
28
+
29
+ # Format is the I2C address (all variable operations expect a 1 byte reponse).
30
+ I2C_VARIABLE_OPS = {
31
+ read_continuous_measurements_interval: 0x40,
32
+ read_command_state: 0x41,
33
+ read_measurement_byte_zero: 0x42,
34
+ read_measurement_byte_one: 0x43,
35
+ read_measurement_byte_two: 0x44,
36
+ read_measurement_byte_three: 0x45,
37
+ read_measurement_byte_four: 0x46,
38
+ read_measurement_byte_five: 0x47,
39
+ read_measurement_byte_six: 0x48,
40
+ read_measurement_byte_seven: 0x49,
41
+ read_actual_zero: 0x50,
42
+ read_actual_scale_factor: 0x51,
43
+ read_actual_scale_divisor: 0x52
44
+ }.freeze
45
+
46
+ # Format is I2C address, followed by the command. If the array is only
47
+ # one member large, then the second byte is instead a value passed at
48
+ # run-time, eg. the length of the interval to run the continuous
49
+ # measuring on the ultrasonic sensor for.
50
+ I2C_COMMANDS = {
51
+ off_command: [0x41, 0x00],
52
+ single_shot_command: [0x41, 0x01],
53
+ continuous_measurement_command: [0x41, 0x02],
54
+ event_capture_command: [0x41, 0x03],
55
+ request_warm_reset: [0x41, 0x04],
56
+ set_continuous_measurement_interval: [0x40],
57
+ set_actual_zero: [0x50],
58
+ set_actual_scale_factor: [0x51],
59
+ set_actual_scale_divisor: [0x52]
60
+ }.freeze
61
+
62
+ def self.method_missing(name, *args)
63
+ if I2C_CONSTANT_OPS.key?(name)
64
+ run_constant_op(name)
65
+ elsif I2C_VARIABLE_OPS.key?(name)
66
+ run_variable_op(name)
67
+ elsif I2C_COMMANDS.key?(name)
68
+ run_command(name, *args)
69
+ else
70
+ super
71
+ end
72
+ end
73
+
74
+ def self.respond_to_missing?(name, include_private = false)
75
+ I2C_CONSTANT_OPS.key?(name) ||
76
+ I2C_VARIABLE_OPS.key?(name) ||
77
+ I2C_COMMANDS.key?(name) ||
78
+ super
79
+ end
80
+
81
+ class << self
82
+ private
83
+
84
+ def run_constant_op(name)
85
+ op = I2C_CONSTANT_OPS[name]
86
+ addr = op[0]
87
+ rx_len = op[1]
88
+
89
+ [0, rx_len, I2C_HEADER, addr]
90
+ end
91
+
92
+ def run_variable_op(name)
93
+ op = I2C_VARIABLE_OPS[name]
94
+ addr = op
95
+ rx_len = 1
96
+
97
+ [0, rx_len, I2C_HEADER, addr]
98
+ end
99
+
100
+ def run_command(name, *args)
101
+ op = I2C_COMMANDS[name]
102
+ addr = op[0]
103
+ rx_len = 0
104
+
105
+ if op[1]
106
+ data = [op[1]]
107
+ elsif args[0]
108
+ data = [args[0]]
109
+ else
110
+ raise "Missing argument for command #{name}"
111
+ end
112
+
113
+ [data.size, rx_len, I2C_HEADER, addr] + data
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
@@ -1,15 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module NXT
2
4
  module Utils
5
+ # Utilities related to creating accessors that have some conditonal guards
6
+ # when setting values.
3
7
  module Accessors
8
+ include NXT::Utils::Assertions
9
+
4
10
  def attr_setter(name, options)
5
11
  define_method("#{name}=") do |value|
6
- if options.include?(:is)
7
- raise TypeError.new('Expected value to be a number') unless duration.is_a?(options[:is])
8
- end
9
-
10
- if options.include?(:is_key_in) && !options[:is_key_in].include?(value)
11
- raise TypeError.new("Expected value to be one of: :#{options[:is_key_in].keys.join(', :')}")
12
- end
12
+ assert_type(name, value, options[:is]) if options.include?(:is)
13
+ assert_in(name, value, options[:is_key_in]) if options.include?(:is_key_in)
13
14
 
14
15
  instance_variable_set("@#{name}", value)
15
16
  self
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NXT
4
+ module Utils
5
+ # Utilities related to asserting values are what is expected.
6
+ module Assertions
7
+ def assert_in(name, value, values)
8
+ raise(TypeError, "Expected #{name} to be one of: :#{values.join(', :')}") unless values.include?(value)
9
+ end
10
+
11
+ def assert_type(name, value, type)
12
+ raise(TypeError, "Expected #{name} to be of type #{type}") unless value.is_a?(type)
13
+ end
14
+
15
+ def assert_responds_to(name, value, *methods)
16
+ valid = methods.all? do |method|
17
+ value.respond_to?(method)
18
+ end
19
+
20
+ raise(TypeError, "Expected #{name} to respond to: #{methods.join(', ')}") unless valid
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rspec/expectations'
2
4
 
3
5
  RSpec::Matchers.define :have_constant do |expected|
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe NXT::Connector::Output::Motor do
6
+ subject(:motor) { described_class.new(port, interface) }
7
+
8
+ let(:port) { :a }
9
+ let(:interface) { nil }
10
+
11
+ describe 'constants' do
12
+ it 'has a DURATION_TYPE constant' do
13
+ expect(motor).to have_constant(:DURATION_TYPE)
14
+ end
15
+
16
+ it 'has a DURATION_AFTER constant' do
17
+ expect(motor).to have_constant(:DURATION_AFTER)
18
+ end
19
+
20
+ it 'has a DIRECTION constant' do
21
+ expect(motor).to have_constant(:DIRECTION)
22
+ end
23
+ end
24
+
25
+ describe 'accessors' do
26
+ it 'has read accessor for @port' do
27
+ expect(motor).to respond_to(:port)
28
+ end
29
+
30
+ it 'has write accessor for @port' do
31
+ expect(motor).to respond_to(:port=)
32
+ end
33
+
34
+ it 'has read accessor for @interface' do
35
+ expect(motor).to respond_to(:interface)
36
+ end
37
+
38
+ it 'has write accessor for @interface' do
39
+ expect(motor).to respond_to(:interface=)
40
+ end
41
+ end
42
+
43
+ describe '#initialize' do
44
+ it 'sets the port to the incomming argument' do
45
+ expect(motor.port).to equal(port)
46
+ end
47
+
48
+ it 'sets the interface to the incomming argument' do
49
+ expect(motor.interface).to equal(interface)
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe NXT::Interface::SerialPort do
6
+ subject(:serial_port) { described_class.new(device) }
7
+
8
+ let(:device) { '/dev/zero' }
9
+
10
+ describe 'constants' do
11
+ it 'has a BAUD_RATE constant' do
12
+ expect(serial_port).to have_constant(:BAUD_RATE)
13
+ end
14
+
15
+ it 'has a DATA_BITS constant' do
16
+ expect(serial_port).to have_constant(:DATA_BITS)
17
+ end
18
+
19
+ it 'has a STOP_BITS constant' do
20
+ expect(serial_port).to have_constant(:STOP_BITS)
21
+ end
22
+
23
+ it 'has a PARITY constant' do
24
+ expect(serial_port).to have_constant(:PARITY)
25
+ end
26
+
27
+ it 'has a READ_TIMEOUT constant' do
28
+ expect(serial_port).to have_constant(:READ_TIMEOUT)
29
+ end
30
+ end
31
+
32
+ describe 'accessors' do
33
+ it 'has read accessor for @dev' do
34
+ expect(serial_port).to respond_to(:dev)
35
+ end
36
+
37
+ it 'has write accessor for @dev' do
38
+ expect(serial_port).to respond_to(:dev=)
39
+ end
40
+ end
41
+
42
+ describe '#initialize' do
43
+ it 'sets the device to the incomming argument' do
44
+ expect(serial_port.dev).to equal(device)
45
+ end
46
+
47
+ it 'raises an exception when trying to connect to invalid dev files' do
48
+ expect do
49
+ serial_port.class.new('/dev/foobar')
50
+ end.to raise_exception(InvalidDeviceError)
51
+ end
52
+ end
53
+
54
+ describe '#connect' do
55
+ it 'raises an exception when the SerialPort connection failed' do
56
+ expect do
57
+ serial_port.connect
58
+ end.to raise_exception(SerialPortConnectionError, "The #{device} device is not a valid SerialPort")
59
+ end
60
+
61
+ it 'raises an exception when the SerialPort connection is nil' do
62
+ allow(::SerialPort).to receive(:new).and_return(nil)
63
+ expect do
64
+ serial_port.connect
65
+ end.to raise_exception(SerialPortConnectionError, "Could not establish a SerialPort connection to #{device}")
66
+ end
67
+
68
+ it 'sets the flow control and read timeout when the connection is established' do
69
+ serial_port_stub = double
70
+ allow(serial_port_stub).to receive(:flow_control=).with(::SerialPort::HARD).once
71
+ allow(serial_port_stub).to receive(:read_timeout=).with(serial_port.class::READ_TIMEOUT).once
72
+ allow(::SerialPort).to receive(:new).and_return(serial_port_stub)
73
+
74
+ serial_port.connect
75
+ end
76
+ end
77
+
78
+ describe '#disconnect' do
79
+ let(:connection_klass) do
80
+ Class.new do
81
+ def initialize(closed)
82
+ @closed = closed
83
+ end
84
+
85
+ def closed?
86
+ @closed
87
+ end
88
+ end
89
+ end
90
+
91
+ it 'closes the connection if connected' do
92
+ connection = connection_klass.new(false)
93
+ allow(connection).to receive(:close)
94
+ serial_port.instance_variable_set(:@connection, connection)
95
+
96
+ serial_port.disconnect
97
+
98
+ expect(connection).to have_received(:close)
99
+ end
100
+
101
+ it 'does not close the connection if already disconnected' do
102
+ connection = connection_klass.new(true)
103
+ allow(connection).to receive(:close)
104
+ serial_port.instance_variable_set(:@connection, connection)
105
+
106
+ serial_port.disconnect
107
+
108
+ expect(connection).not_to have_received(:close)
109
+ end
110
+ end
111
+
112
+ # describe '#send' do
113
+
114
+ # end
115
+
116
+ # describe '#receive' do
117
+
118
+ # end
119
+ end
@@ -1,205 +1,274 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe NXTBrick do
4
- before do
5
- @interface = stub(
6
- is_a?: true
7
- )
8
- end
9
-
10
- subject do
11
- NXTBrick.new(@interface)
12
- end
6
+ subject(:nxt_brick) { described_class.new(:usb) }
13
7
 
14
8
  describe 'accessors' do
15
- it 'should have read/write accessors for @interface' do
16
- should respond_to(:interface)
17
- should respond_to(:interface=)
9
+ it 'has a read accessor for @interface' do
10
+ expect(nxt_brick).to respond_to(:interface)
11
+ end
12
+
13
+ it 'has write accessor for @interface' do
14
+ expect(nxt_brick).to respond_to(:interface=)
15
+ end
16
+
17
+ it 'has a read accessor for @a' do
18
+ expect(nxt_brick).to respond_to(:a)
19
+ end
20
+
21
+ it 'has a write accessor for @a' do
22
+ expect(nxt_brick).not_to respond_to(:a=)
23
+ end
24
+
25
+ it 'has a read accessor for @b' do
26
+ expect(nxt_brick).to respond_to(:b)
27
+ end
28
+
29
+ it 'has a write accessor for @b' do
30
+ expect(nxt_brick).not_to respond_to(:b=)
31
+ end
32
+
33
+ it 'has a read accessor for @c' do
34
+ expect(nxt_brick).to respond_to(:c)
35
+ end
36
+
37
+ it 'has a write accessor for @c' do
38
+ expect(nxt_brick).not_to respond_to(:c=)
18
39
  end
19
40
 
20
- it 'should have read/write accessors for @options' do
21
- should respond_to(:options)
22
- should respond_to(:options=)
41
+ it 'has a read accessor for @one' do
42
+ expect(nxt_brick).to respond_to(:one)
23
43
  end
24
44
 
25
- it 'should have a read accessor for @a' do
26
- should respond_to(:a)
27
- should_not respond_to(:a=)
45
+ it 'has a write accessor for @one' do
46
+ expect(nxt_brick).not_to respond_to(:one=)
28
47
  end
29
48
 
30
- it 'should have a read accessor for @b' do
31
- should respond_to(:b)
32
- should_not respond_to(:b=)
49
+ it 'has a read accessor for @two' do
50
+ expect(nxt_brick).to respond_to(:two)
33
51
  end
34
52
 
35
- it 'should have a read accessor for @c' do
36
- should respond_to(:c)
37
- should_not respond_to(:c=)
53
+ it 'has a write accessor for @two' do
54
+ expect(nxt_brick).not_to respond_to(:two=)
38
55
  end
39
56
 
40
- it 'should have a read accessor for @one' do
41
- should respond_to(:one)
42
- should_not respond_to(:one=)
57
+ it 'has a read accessor for @three' do
58
+ expect(nxt_brick).to respond_to(:three)
43
59
  end
44
60
 
45
- it 'should have a read accessor for @two' do
46
- should respond_to(:two)
47
- should_not respond_to(:two=)
61
+ it 'has a write accessor for @three' do
62
+ expect(nxt_brick).not_to respond_to(:three=)
48
63
  end
49
64
 
50
- it 'should have a read accessor for @three' do
51
- should respond_to(:three)
52
- should_not respond_to(:three=)
65
+ it 'has a read accessor for @four' do
66
+ expect(nxt_brick).to respond_to(:four)
53
67
  end
54
68
 
55
- it 'should have a read accessor for @four' do
56
- should respond_to(:four)
57
- should_not respond_to(:four=)
69
+ it 'has a write accessor for @four' do
70
+ expect(nxt_brick).not_to respond_to(:four=)
58
71
  end
59
72
 
60
- it 'should have a read accessor for @port_identifiers' do
61
- should respond_to(:port_identifiers)
62
- should_not respond_to(:port_identifiers=)
73
+ it 'has a read accessor for @port_identifiers' do
74
+ expect(nxt_brick).to respond_to(:port_identifiers)
75
+ end
76
+
77
+ it 'has a write accessor for @port_identifiers' do
78
+ expect(nxt_brick).not_to respond_to(:port_identifiers=)
63
79
  end
64
80
  end
65
81
 
66
82
  describe '#initialize' do
67
- it 'should raise an exception if an invalid type of interface is given' do
68
- interface_stub = stub()
69
- interface_stub.should_receive(:is_a?).with(NXT::Interface::Base).once.and_return(false)
83
+ it 'raises an exception if an invalid type of interface is given' do
70
84
  expect do
71
- NXTBrick.new(interface_stub)
85
+ described_class.new(:foobar)
72
86
  end.to raise_exception(InvalidInterfaceError)
73
87
  end
74
88
 
75
- it 'should set the interface to the incomming argument' do
76
- subject.interface.should equal(@interface)
89
+ it 'sets the interface to the incomming argument' do
90
+ expect(nxt_brick.interface).to be_an_instance_of(NXT::Interface::Usb)
77
91
  end
78
92
 
79
- it 'should set the options to the incomming argument' do
80
- options_stub = stub()
81
- nxt = NXTBrick.new(@interface, options_stub)
82
- nxt.options.should equal(options_stub)
83
- end
84
-
85
- it 'should call yield if given a block, passing self' do
93
+ it 'calls yield if given a block, passing self' do
86
94
  block_called = false
87
95
 
88
- NXTBrick.new(@interface) do |nxt|
96
+ # rubocop:disable RSpec/AnyInstance
97
+ allow_any_instance_of(NXT::Interface::Usb).to receive(:connect)
98
+ # rubocop:enable RSpec/AnyInstance
99
+
100
+ described_class.new(:usb) do |_nxt|
89
101
  block_called = true
90
- nxt.should be_an_instance_of(NXTBrick)
91
102
  end
92
103
 
93
- block_called.should be_true
104
+ expect(block_called).to be true
94
105
  end
95
- end
96
106
 
97
- describe '#add' do
98
- it 'should raise an exception if an invalid type of port is given' do
99
- expect do
100
- subject.add('not a symbol', :symbol, Class)
101
- end.to raise_exception(TypeError, 'Expected port to be a Symbol')
107
+ it 'passes self if given a block' do
108
+ # rubocop:disable RSpec/AnyInstance
109
+ allow_any_instance_of(NXT::Interface::Usb).to receive(:connect)
110
+ # rubocop:enable RSpec/AnyInstance
111
+
112
+ described_class.new(:usb) do |nxt|
113
+ expect(nxt).to be_an_instance_of(described_class)
114
+ end
102
115
  end
116
+ end
103
117
 
104
- it 'should raise an exception if an invalid type of identifier is given' do
105
- expect do
106
- subject.add(:symbol, 'not a symbol', Class)
107
- end.to raise_exception(TypeError, 'Expected identifier to be a Symbol')
118
+ describe '#connect' do
119
+ let(:interface) { Object.new }
120
+
121
+ before do
122
+ nxt_brick.instance_variable_set(:@interface, interface)
108
123
  end
109
124
 
110
- it 'should raise an exception if an invalid type of klass is given' do
111
- expect do
112
- subject.add(:symbol, :symbol, 'not a class')
113
- end.to raise_exception(TypeError, 'Expected klass to be a Class')
125
+ it 'calls connect on the interface' do
126
+ allow(interface).to receive(:connect)
127
+
128
+ nxt_brick.connect
129
+
130
+ expect(interface).to have_received(:connect)
114
131
  end
132
+ end
115
133
 
116
- it 'should raise an exception if an invalid port number or letter is given' do
117
- expect do
118
- subject.add(:invalid_port, :symbol, Class)
119
- end.to raise_exception(TypeError, 'Expected port to be one of: :a, :b, :c, :one, :two, :three, :four')
134
+ describe '#disconnect' do
135
+ let(:interface) { Object.new }
136
+
137
+ before do
138
+ nxt_brick.instance_variable_set(:@interface, interface)
120
139
  end
121
140
 
122
- it 'should create a new instance of the passed klass and store it in the attribute for the given port' do
123
- port = :a
124
- class_stub = Class.new
125
- class_return_stub = stub()
141
+ it 'calls disconnect on the interface' do
142
+ allow(interface).to receive(:disconnect)
126
143
 
127
- class_stub.should_receive(:new) do
128
- class_return_stub
129
- end.with(port).once()
144
+ nxt_brick.disconnect
130
145
 
131
- subject.add(port, :hello, class_stub)
146
+ expect(interface).to have_received(:disconnect)
147
+ end
148
+ end
132
149
 
133
- subject.send(port).should equal(class_return_stub)
150
+ describe '#add' do
151
+ let(:port) { :a }
152
+ let(:identifier) { :hello }
153
+ let(:class_stub) do
154
+ Class.new do
155
+ def initialize(*_args)
156
+ nil
157
+ end
158
+ end
134
159
  end
135
160
 
136
- it 'should raise an exception if the port given is already set' do
137
- port = :a
161
+ it 'raises an exception if an invalid port number or letter is given' do
162
+ expect do
163
+ nxt_brick.add(:invalid_port, identifier, class_stub)
164
+ end.to raise_exception(TypeError, 'Expected port to be one of: :a, :b, :c, :one, :two, :three, :four')
165
+ end
138
166
 
139
- class_stub = Class.new
140
- class_stub.stub(:new)
141
- subject.stub(:hello)
142
- subject.instance_variable_set(:"@#{port}", 'some value already there')
167
+ it 'raises an exception if an invalid type of identifier is given' do
168
+ expect do
169
+ nxt_brick.add(port, 123, class_stub)
170
+ end.to raise_exception(TypeError, 'Expected identifier to respond to: to_sym')
171
+ end
143
172
 
173
+ it 'raises an exception if an invalid type of class is given' do
144
174
  expect do
145
- subject.add(port, :hello, class_stub)
146
- end.to raise_error(PortTakenError, "Port #{port} is already set, call remove first")
175
+ nxt_brick.add(port, identifier, 'not a class')
176
+ end.to raise_exception(TypeError, 'Expected klass to be of type Class')
147
177
  end
148
178
 
149
- it 'should raise an exception if trying to use an identifier that is the name of a defined methodz' do
150
- port = :a
151
- identifier = :hello
179
+ it 'raises an exception if trying to use an identifier that is the name of a defined method' do
180
+ expect do
181
+ nxt_brick.add(port, :add, class_stub)
182
+ end.to raise_error(
183
+ InvalidIdentifierError,
184
+ 'Cannot use identifier add, a method on NXTBrick is already using it.'
185
+ )
186
+ end
152
187
 
153
- class_stub = Class.new
154
- class_stub.stub(:new)
155
- subject.stub(identifier)
188
+ it 'raises an exception if the port given is already set' do
189
+ nxt_brick.add(port, identifier, class_stub)
156
190
 
157
191
  expect do
158
- subject.add(port, :hello, class_stub)
159
- end.to raise_error(InvalidIdentifierError, "Cannot use identifier #{identifier}, a method on NXTBrick is already using it.")
192
+ nxt_brick.add(port, identifier, class_stub)
193
+ end.to raise_error(PortTakenError, "Port #{port} is already set, call remove first")
160
194
  end
161
195
 
162
- it 'should set up the port identifiers correctly' do
163
- port = :a
164
- identifier = :hello_world
165
- class_stub = Class.new
166
- class_stub.stub(:new)
167
-
168
- subject.add(port, identifier, class_stub)
196
+ it 'sets up the port if the given port is not alread in use' do
197
+ nxt_brick.add(port, identifier, class_stub)
169
198
 
170
- subject.port_identifiers[identifier].should equal(port)
199
+ expect(nxt_brick.send(port)).not_to eq(nil)
171
200
  end
172
201
  end
173
202
 
174
203
  describe '#remove' do
175
- it 'should raise an exception if an invalid type of identifier is given' do
204
+ it 'raises an exception if an invalid type of identifier is given' do
176
205
  expect do
177
- subject.remove('not a symbol')
178
- end.to raise_exception(TypeError, 'Expected identifier to be a Symbol')
206
+ nxt_brick.remove(123)
207
+ end.to raise_exception(TypeError, 'Expected identifier to respond to: to_sym')
208
+ end
209
+
210
+ it 'removes any matching identifiers' do
211
+ identifier = :hello_world
212
+ port_identifiers = {}
213
+ nxt_brick.instance_variable_set(:@port_identifiers, port_identifiers)
214
+
215
+ allow(port_identifiers).to receive(:delete)
216
+
217
+ nxt_brick.remove(identifier)
218
+
219
+ expect(port_identifiers).to have_received(:delete).with(identifier).once
179
220
  end
180
221
 
181
- it 'should remove any matching identifiers' do
222
+ it 'returns a boolean true if it removed anything' do
182
223
  identifier = :hello_world
183
224
  port_identifiers = {}
184
- subject.instance_variable_set(:@port_identifiers, port_identifiers)
225
+ port_identifiers[identifier] = true
226
+ nxt_brick.instance_variable_set(:@port_identifiers, port_identifiers)
185
227
 
186
- port_identifiers.should_receive(:delete).with(identifier).once()
187
- subject.remove(identifier)
228
+ return_value = nxt_brick.remove(identifier)
229
+ expect(return_value).to be true
188
230
  end
189
231
 
190
- it 'should return a boolean indicating whether it removed anything' do
232
+ it 'removes the port if it did delete something' do
191
233
  identifier = :hello_world
192
234
  port_identifiers = {}
193
235
  port_identifiers[identifier] = true
194
- subject.instance_variable_set(:@port_identifiers, port_identifiers)
236
+ nxt_brick.instance_variable_set(:@port_identifiers, port_identifiers)
237
+
238
+ nxt_brick.remove(identifier)
239
+
240
+ expect(port_identifiers).not_to include(identifier)
241
+ end
195
242
 
196
- return_value = subject.remove(identifier)
197
- return_value.should be_true
243
+ it 'returns a boolean false if it did not remove anything' do
244
+ return_value = nxt_brick.remove(:hello_world)
245
+ expect(return_value).to be false
246
+ end
247
+ end
198
248
 
199
- port_identifiers.should_not include(identifier)
249
+ describe '#define_port_handler_method' do
250
+ let(:port) { :a }
251
+ let(:identifier) { :hello }
252
+ let(:class_stub) do
253
+ Class.new do
254
+ def initialize(*_args)
255
+ nil
256
+ end
257
+ end
258
+ end
259
+
260
+ it 'stores it in the attribute for the given port' do
261
+ class_return_stub = double
262
+ allow(class_stub).to receive(:new) { class_return_stub }
263
+
264
+ nxt_brick.send(:define_port_handler_method, port, identifier, class_stub)
265
+
266
+ expect(nxt_brick.send(port)).to equal(class_return_stub)
267
+ end
200
268
 
201
- return_value = subject.remove(identifier)
202
- return_value.should be_false
269
+ it 'sets up the port identifiers correctly' do
270
+ nxt_brick.send(:define_port_handler_method, port, identifier, class_stub)
271
+ expect(nxt_brick.port_identifiers[identifier]).to equal(port)
203
272
  end
204
273
  end
205
274
  end