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.
- checksums.yaml +5 -5
- data/README.md +16 -12
- data/Rakefile +9 -12
- data/lib/lego_nxt.rb +36 -0
- data/lib/nxt/commands/base.rb +56 -7
- data/lib/nxt/commands/input.rb +30 -12
- data/lib/nxt/commands/low_speed.rb +47 -0
- data/lib/nxt/commands/output.rb +9 -7
- data/lib/nxt/commands/program.rb +3 -0
- data/lib/nxt/commands/sound.rb +3 -0
- data/lib/nxt/commands/tone.rb +3 -0
- data/lib/nxt/connector/input/base.rb +9 -0
- data/lib/nxt/{connectors → connector}/input/color.rb +3 -0
- data/lib/nxt/{connectors → connector}/input/touch.rb +3 -0
- data/lib/nxt/connector/input/ultrasonic.rb +66 -0
- data/lib/nxt/connector/output/base.rb +9 -0
- data/lib/nxt/connector/output/motor.rb +117 -0
- data/lib/nxt/exceptions.rb +3 -1
- data/lib/nxt/interface/base.rb +18 -0
- data/lib/nxt/{interfaces → interface}/serial_port.rb +13 -10
- data/lib/nxt/{interfaces → interface}/usb.rb +10 -9
- data/lib/nxt/nxt_brick.rb +51 -45
- data/lib/nxt/patches/module.rb +5 -2
- data/lib/nxt/patches/string.rb +6 -17
- data/lib/nxt/protocols/i2c.rb +118 -0
- data/lib/nxt/utils/accessors.rb +8 -7
- data/lib/nxt/utils/assertions.rb +24 -0
- data/spec/matchers.rb +2 -0
- data/spec/nxt/connector/output/motor_spec.rb +52 -0
- data/spec/nxt/interface/serial_port_spec.rb +119 -0
- data/spec/nxt/nxt_brick_spec.rb +189 -120
- data/spec/spec_helper.rb +10 -1
- metadata +193 -59
- data/lib/nxt.rb +0 -27
- data/lib/nxt/connectors/input/ultrasonic.rb +0 -11
- data/lib/nxt/connectors/output/motor.rb +0 -116
- data/lib/nxt/interfaces/base.rb +0 -26
- data/spec/nxt/interfaces/serial_port_spec.rb +0 -73
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f05dd5e79c55b8b179ff31bec9197f5ba3a5e671c1d09fd59123e6cc3fb944f5
|
4
|
+
data.tar.gz: 9b3bcfe2965efa57b1b972d3cdc1752a736e1a515dd48b98ce529831b49f204b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
data/lib/lego_nxt.rb
ADDED
@@ -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'
|
data/lib/nxt/commands/base.rb
CHANGED
@@ -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
|
-
|
24
|
-
|
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(
|
28
|
-
] + payload
|
70
|
+
port_as_byte(port)
|
71
|
+
] + payload)
|
29
72
|
end
|
30
73
|
|
31
|
-
def
|
32
|
-
|
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
|
data/lib/nxt/commands/input.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
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:
|
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
|
71
|
-
send_and_receive(
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
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
|
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
|
data/lib/nxt/commands/output.rb
CHANGED
@@ -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
|
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 = [
|
95
|
+
tacho_limit_as_bytes = [tacho_limit].pack('V').unpack('C4')
|
94
96
|
|
95
97
|
send_and_receive(COMMAND_IDENTIFIER[:set_output_state], [
|
96
|
-
|
97
|
-
MODE[
|
98
|
-
REGULATION_MODE[
|
98
|
+
power,
|
99
|
+
MODE[mode],
|
100
|
+
REGULATION_MODE[regulation_mode],
|
99
101
|
0, # turn ratio
|
100
|
-
RUN_STATE[
|
102
|
+
RUN_STATE[run_state]
|
101
103
|
] + tacho_limit_as_bytes, response_required)
|
102
104
|
end
|
103
105
|
|
104
|
-
def
|
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
|
data/lib/nxt/commands/program.rb
CHANGED
data/lib/nxt/commands/sound.rb
CHANGED
data/lib/nxt/commands/tone.rb
CHANGED
@@ -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
|