lego-nxt 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: e972fae0b3659e85c16a77bf735809a97b0c9bf0
4
- data.tar.gz: bc3d2844376e99a2f4fb55f3001beef1a9a36ab1
2
+ SHA256:
3
+ metadata.gz: f05dd5e79c55b8b179ff31bec9197f5ba3a5e671c1d09fd59123e6cc3fb944f5
4
+ data.tar.gz: 9b3bcfe2965efa57b1b972d3cdc1752a736e1a515dd48b98ce529831b49f204b
5
5
  SHA512:
6
- metadata.gz: e08a116ed02b6d2a38856b29e6e23489543bdedcfcf3ade4c0f33c58b835c31f0a1e9ddea0d78cabadca5f8a8829e7c8553b733cc3e39001b9462b15b3b766b1
7
- data.tar.gz: f90c8c66c8f1390d5ed6ef4d41d7ea7268c356de0ee656e35e5fc312013a2308e942b21c2e6c610211d3d97aa5f03ac7fb00f09838375a344239cadd28b441be
6
+ metadata.gz: 718bebac8ff71116d2cc12271cac115697caa88edf1387d4689591a017be8e5e78d2eaf911880691437250cb7e07a2aa5839bbada3f6c9842e0f17d720b17ce1
7
+ data.tar.gz: 1aaefd9d5090f06a656067b4379202368dde9a96b995e8d5fc2ded602d72d5d50592fa25e3a01c91cbfcd351cb9ceb64190f69a7097be28719e8108374b86805
data/README.md CHANGED
@@ -1,6 +1,8 @@
1
- # Lego NXT
1
+ # Lego NXT [![Build Status](https://github.com/nathankleyn/lego-nxt/workflows/Tests/badge.svg)](https://github.com/nathankleyn/lego-nxt/actions?query=workflow%3ATests) [![Coverage Status](https://coveralls.io/repos/github/nathankleyn/attr_combined_accessor/badge.svg?branch=master)](https://coveralls.io/github/nathankleyn/lego-nxt?branch=master) [![RubyGems Version](https://img.shields.io/gem/v/lego-nxt.svg)](https://rubygems.org/gems/lego-nxt)
2
2
 
3
- Control a Lego NXT 2.0 brick using Ruby code. This library works by piping commands over a serialport connection to the brick, allowing you to write Ruby scripts to control your bot. This means you can use both the Bluetooth and USB serial ports provided on the brick as interfaces within your code.
3
+ > **This gem is under heavy development!** A lot of the below code may not work, and is certainly not guranteed to be reliable if it does! If you need to base a working project on this, I recommend [going back to the `ruby-nxt` gem this work is inspired by](https://github.com/zuk/ruby-nxt) for the time being.
4
+
5
+ Control a Lego NXT 2.0 brick using Ruby code. This library works by piping commands over a Bluetooth or USB connection to the brick, allowing you to write Ruby scripts to control your NXT brick.
4
6
 
5
7
  This project used to be based on "ruby-nxt", and Tony Buser's subsequent rewrite "nxt". It is now a complete rewrite, based heavily in some parts on the aforesaid projects internally, but with a brand new external API that should prove cleaner and easier to work with.
6
8
 
@@ -8,11 +10,21 @@ This code implements direct command, as outlined in "Appendix 2-LEGO MINDSTORMS
8
10
 
9
11
  ## Getting Started
10
12
 
13
+ ### Installing This Library
14
+
15
+ Install the gem:
16
+
17
+ ```sh
18
+ gem install lego-nxt
19
+ ```
20
+
21
+ Add it as a Bundler dependency as you see fit!
22
+
11
23
  ### Connect to Your NXT Brick
12
24
 
13
25
  In order to start coding with your NXT, you'll need to set up either a USB or Bluetooth connection to it. Follow one of the below sets of steps; if you go for a Bluetooth connection, you'll need to remember the `/dev/*` address you end up using, as you'll need to provide it when making a connection with this library.
14
26
 
15
- ### Connecting Via USB
27
+ #### Connecting Via USB
16
28
 
17
29
  Simply plug in the NXT, and that's it! This library will take care of enumerating the USB host devices to find the NXT device for you, no effort required on your behalf!
18
30
 
@@ -96,12 +108,4 @@ In addition to this, you might find the tests quite helpful. There are currently
96
108
 
97
109
  ## License
98
110
 
99
- The MIT License
100
-
101
- Copyright (c) 2013 Nathan Kleyn
102
-
103
- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
104
-
105
- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
106
-
107
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
111
+ This project is licensed [via MIT license](LICENSE).
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rake'
2
4
  require 'rake/clean'
3
5
  require 'rdoc/task'
@@ -18,18 +20,13 @@ desc 'NXT related tasks'
18
20
  namespace :nxt do
19
21
  desc 'Detect a connected NXT brick within /dev.'
20
22
  task :detect do
21
- unless $DEV ||= ENV['NXT'] || ENV['DEV']
22
- begin
23
- devices = Dir['/dev/*NXT*']
24
- if devices.size > 0
25
- $DEV = devices[0]
26
- puts "Detected a NXT brick at '#{$DEV}'."
27
- else
28
- puts 'Could not detect any connected NXT bricks.'
29
- end
30
- rescue
31
- # FIXME: The /dev directory isn't there, possibly running on Windows.
32
- end
23
+ unless ENV['NXT'] || ENV['DEV']
24
+ raise "/dev not fount, please ensure you're using a *nix system." unless Dir.exist?('/dev')
25
+
26
+ devices = Dir['/dev/*NXT*']
27
+ raise 'Could not detect any connected NXT bricks.' if devices.empty?
28
+
29
+ puts "Detected a NXT brick at '#{devices.first}'."
33
30
  end
34
31
  end
35
32
  end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
4
+
5
+ require 'active_support/inflector'
6
+
7
+ require 'nxt/patches/module'
8
+ require 'nxt/patches/string'
9
+
10
+ require 'nxt/exceptions'
11
+
12
+ require 'nxt/utils/assertions'
13
+ require 'nxt/utils/accessors'
14
+
15
+ require 'nxt/interface/base'
16
+ require 'nxt/interface/usb'
17
+ require 'nxt/interface/serial_port'
18
+
19
+ require 'nxt/protocols/i2c'
20
+
21
+ require 'nxt/commands/base'
22
+ require 'nxt/commands/input'
23
+ require 'nxt/commands/low_speed'
24
+ require 'nxt/commands/output'
25
+ require 'nxt/commands/program'
26
+ require 'nxt/commands/sound'
27
+ require 'nxt/commands/tone'
28
+
29
+ require 'nxt/connector/input/base'
30
+ require 'nxt/connector/input/color'
31
+ require 'nxt/connector/input/touch'
32
+ require 'nxt/connector/input/ultrasonic'
33
+ require 'nxt/connector/output/base'
34
+ require 'nxt/connector/output/motor'
35
+
36
+ require 'nxt/nxt_brick'
@@ -1,5 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module NXT
2
4
  module Command
5
+ # The base implementation of all commands, providing low-level details that
6
+ # are consistent across all supported types. These are things such as:
7
+ #
8
+ # * Errors.
9
+ # * Sending and receiving of responses.
10
+ # * Addressing the port safely.
11
+ # * Command types.
3
12
  module Base
4
13
  private
5
14
 
@@ -18,18 +27,58 @@ module NXT
18
27
  three: 0x02,
19
28
  four: 0x03,
20
29
  all: 0xFF
21
- }
30
+ }.freeze
31
+
32
+ ERRORS = {
33
+ 'Pending communication transaction in progress' => 0x20,
34
+ 'Specified mailbox queue is empty' => 0x40,
35
+ 'Request failed (i.e. specified file not found)' => 0xBD,
36
+ 'Unknown command opcode' => 0xBE,
37
+ 'Insane packet' => 0xBF,
38
+ 'Data contains out-of-range values' => 0xC0,
39
+ 'Communication bus error' => 0xDD,
40
+ 'No free memory in communication buffer' => 0xDE,
41
+ 'Specified channel/connection is not valid' => 0xDF,
42
+ 'Specified channel/connection not configured or busy' => 0xE0,
43
+ 'No active program' => 0xEC,
44
+ 'Illegal size specified' => 0xED,
45
+ 'Illegal mailbox queue ID specified' => 0xEE,
46
+ 'Attempted to access invalid field of a structure' => 0xEF,
47
+ 'Bad input or output specified' => 0xF0,
48
+ 'Insufficient memory available' => 0xFB,
49
+ 'Bad arguments' => 0xFF
50
+ }.invert.freeze
51
+
52
+ def port_as_byte(port)
53
+ PORTS[port]
54
+ end
55
+
56
+ def send_and_receive(command_identifier, payload = [], response_required: true)
57
+ send(command_identifier, payload, response_required)
58
+ # We bail unless we need to wait for response.
59
+ return unless response_required
22
60
 
23
- def send_and_receive(command_identifier, payload = [], response_required = true)
24
- @interface.send_and_receive([
61
+ receive
62
+ end
63
+
64
+ def send(command_identifier, payload = [], response_required: true)
65
+ command_identifier |= 0x80 unless response_required
66
+
67
+ @interface.send([
25
68
  command_type,
26
69
  command_identifier,
27
- port_as_byte(self.port)
28
- ] + payload, response_required)
70
+ port_as_byte(port)
71
+ ] + payload)
29
72
  end
30
73
 
31
- def port_as_byte(port)
32
- PORTS[port]
74
+ def receive
75
+ response = @interface.receive
76
+
77
+ raise 'Not a valid response package.' unless response[0] == 0x02
78
+ raise 'Not a valid response to the command that was sent.' unless response[1] == command_identifier
79
+ raise ERRORS[response[2]] unless response[2].zero?
80
+
81
+ response[3..]
33
82
  end
34
83
  end
35
84
  end
@@ -1,4 +1,4 @@
1
- require 'nxt/utils/accessors'
1
+ # frozen_string_literal: true
2
2
 
3
3
  module NXT
4
4
  module Command
@@ -17,7 +17,10 @@ module NXT
17
17
  COMMAND_IDENTIFIER = {
18
18
  set_input_mode: 0x05,
19
19
  get_input_values: 0x07,
20
- reset_input_scaled_value: 0x08
20
+ reset_input_scaled_value: 0x08,
21
+ ls_get_status: 0x0E,
22
+ ls_write: 0x0F,
23
+ ls_read: 0x10
21
24
  }.freeze
22
25
 
23
26
  # The sensor type enum. This is a list of possible values when setting the
@@ -54,7 +57,7 @@ module NXT
54
57
  fahrenheit: 0xC0,
55
58
  angle_steps: 0xE0,
56
59
  slope: 0x1F,
57
- mode_mask: 0XE0
60
+ mode_mask: 0x00
58
61
  }.freeze
59
62
 
60
63
  attr_combined_accessor :sensor_type, :no_sensor
@@ -67,22 +70,37 @@ module NXT
67
70
  COMMAND_TYPES[:direct]
68
71
  end
69
72
 
70
- def set_input_mode(response_required = false)
71
- send_and_receive(COMMAND_IDENTIFIER[:set_input_mode], [
72
- self.power,
73
- SENSOR_TYPE[self.sensor_type],
74
- SENSOR_MODE[self.sensor_mode]
75
- ], response_required)
73
+ def update_input_mode(response_required: false)
74
+ send_and_receive(
75
+ COMMAND_IDENTIFIER[:set_input_mode],
76
+ [
77
+ SENSOR_TYPE[sensor_type],
78
+ SENSOR_MODE[sensor_mode]
79
+ ],
80
+ response_required
81
+ )
76
82
  end
77
83
 
78
- def get_input_values
84
+ def input_values
79
85
  # TODO: Parse this response and return hash or something similar.
80
- send_and_receive(COMMAND_IDENTIFIER[:get_input_values])
86
+ send_and_receive(COMMAND_IDENTIFIER[:get_input_values], [], response_required)
81
87
  end
82
88
 
83
89
  def reset_input_scaled_value
84
90
  # TODO: Parse this response and return hash or something similar.
85
- send_and_receive(COMMAND_IDENTIFIER[:reset_input_scaled_value])
91
+ send_and_receive(COMMAND_IDENTIFIER[:reset_input_scaled_value], [], response_required)
92
+ end
93
+
94
+ def ls_get_status(response_required: false)
95
+ send_and_receive(COMMAND_IDENTIFIER[:ls_get_status], [], response_required)
96
+ end
97
+
98
+ def ls_write(bytes, response_required: false)
99
+ send_and_receive(COMMAND_IDENTIFIER[:ls_write], bytes, response_required)
100
+ end
101
+
102
+ def ls_read(response_required: false)
103
+ send_and_receive(COMMAND_IDENTIFIER[:ls_read], [], response_required)
86
104
  end
87
105
  end
88
106
  end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NXT
4
+ module Command
5
+ # An implementation of all the low speed I2C related NXT commands:
6
+ #
7
+ # * LSGETSTATUS
8
+ # * LSWRITE
9
+ # * LSREAD
10
+ #
11
+ # This class can also be used to talk to other third-party accessories
12
+ # connected in the input ports on the NXT brick that talk using I2C
13
+ # low speed master/slave communication.
14
+ module LowSpeed
15
+ include NXT::Command::Base
16
+ extend NXT::Utils::Accessors
17
+
18
+ COMMAND_IDENTIFIER = {
19
+ ls_get_status: 0x0E,
20
+ ls_write: 0x0F,
21
+ ls_read: 0x10
22
+ }.freeze
23
+
24
+ def command_type
25
+ COMMAND_TYPES[:direct]
26
+ end
27
+
28
+ def ls_clear_buffer
29
+ ls_read
30
+ rescue StandardError
31
+ nil
32
+ end
33
+
34
+ def ls_get_status(response_required: false)
35
+ send_and_receive(COMMAND_IDENTIFIER[:ls_get_status], [], response_required)
36
+ end
37
+
38
+ def ls_write(response_required: false)
39
+ send_and_receive(COMMAND_IDENTIFIER[:ls_write], [], response_required)
40
+ end
41
+
42
+ def ls_read(response_required: false)
43
+ send_and_receive(COMMAND_IDENTIFIER[:ls_read], [], response_required)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'nxt/utils/accessors'
2
4
 
3
5
  module NXT
@@ -85,23 +87,23 @@ module NXT
85
87
  COMMAND_TYPES[:direct]
86
88
  end
87
89
 
88
- def set_output_state(response_required = false)
90
+ def update_output_state(response_required: false)
89
91
  # Pack this value into a 32-bit unsigned little-endian binary string,
90
92
  # then unpack it into 4 8 bit unsigned integer chunks. We are
91
93
  # converting the passed in value to a little endian, unsigned long
92
94
  # value.
93
- tacho_limit_as_bytes = [self.tacho_limit].pack('V').unpack('C4')
95
+ tacho_limit_as_bytes = [tacho_limit].pack('V').unpack('C4')
94
96
 
95
97
  send_and_receive(COMMAND_IDENTIFIER[:set_output_state], [
96
- self.power,
97
- MODE[self.mode],
98
- REGULATION_MODE[self.regulation_mode],
98
+ power,
99
+ MODE[mode],
100
+ REGULATION_MODE[regulation_mode],
99
101
  0, # turn ratio
100
- RUN_STATE[self.run_state]
102
+ RUN_STATE[run_state]
101
103
  ] + tacho_limit_as_bytes, response_required)
102
104
  end
103
105
 
104
- def get_output_state
106
+ def output_state
105
107
  # TODO: Parse this response and return hash or something similar.
106
108
  send_and_receive(COMMAND_IDENTIFIER[:get_output_state])
107
109
  end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ # FIXME: Create commands for program.
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ # FIXME: Create commands for sound.
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ # FIXME: Create commands for tone.
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NXT
4
+ module Connector
5
+ # Holds implementations of connectors that are input based.
6
+ module Input
7
+ end
8
+ end
9
+ end
@@ -1,6 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module NXT
2
4
  module Connector
3
5
  module Input
6
+ # Implements the "color" sensor for the NXT 2.0 module.
4
7
  class Color
5
8
  def initialize(port)
6
9
  @port = port
@@ -1,6 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module NXT
2
4
  module Connector
3
5
  module Input
6
+ # Implements the "touch" sensor for the NXT 2.0 module.
4
7
  class Touch
5
8
  def initialize(port)
6
9
  @port = port
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NXT
4
+ module Connector
5
+ module Input
6
+ # Implements the "ultrasonic" sensor for the NXT 2.0 module.
7
+ class Ultrasonic
8
+ include NXT::Command::Input
9
+ include NXT::Command::LowSpeed
10
+ include NXT::Utils::Assertions
11
+ extend NXT::Utils::Accessors
12
+
13
+ # Exception thrown by distance! when the sensor cannot determine the distance.
14
+ class UnmeasurableDistance < RuntimeError; end
15
+
16
+ UNIT = %i[centimeters inches].freeze
17
+
18
+ attr_accessor :port, :interface
19
+
20
+ attr_combined_accessor :unit, :centimeters
21
+
22
+ attr_setter :unit, is_key_in: UNIT
23
+
24
+ def initialize(port, interface)
25
+ @port = port
26
+ @interface = interface
27
+ end
28
+
29
+ def distance
30
+ ls_write(NXT::Protocols::I2C.read_measurement_byte_0)
31
+
32
+ while ls_get_status < 1
33
+ sleep(0.1)
34
+ # TODO: implement timeout so we don't get stuck if the expected data never comes
35
+ end
36
+
37
+ distance = ls_read[0]
38
+
39
+ if @unit == :centimeters
40
+ distance.to_i
41
+ else
42
+ (distance * 0.3937008).to_i
43
+ end
44
+ end
45
+
46
+ def distance!
47
+ d = distance
48
+ raise UnmeasurableDistance if d == 255
49
+
50
+ d
51
+ end
52
+
53
+ def start
54
+ sensor_type(:lowspeed_9v)
55
+ sensor_mode(:raw)
56
+
57
+ update_input_mode
58
+ ls_clear_buffer
59
+
60
+ # set sensor to continuously send pings
61
+ ls_write(NXT::Protocols::I2C.continuous_measurement_command)
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end