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.
data/lib/nxt.rb CHANGED
@@ -7,6 +7,7 @@ require 'nxt/utils/accessors'
7
7
  require 'nxt/patches/module'
8
8
 
9
9
  require 'nxt/exceptions'
10
+ require 'nxt/errors'
10
11
 
11
12
  require 'nxt/interfaces/base'
12
13
  require 'nxt/interfaces/usb'
@@ -1,6 +1,7 @@
1
1
  module NXT
2
2
  module Command
3
3
  module Base
4
+
4
5
  private
5
6
 
6
7
  COMMAND_TYPES = {
@@ -9,6 +10,28 @@ module NXT
9
10
  reply: 0x02
10
11
  }.freeze
11
12
 
13
+ COMMANDS = {
14
+ 0x00 => 'startprogram',
15
+ 0x01 => 'stopprogram',
16
+ 0x02 => 'playsoundfile',
17
+ 0x03 => 'playtone',
18
+ 0x04 => 'setoutputstate',
19
+ 0x05 => 'setinputmode',
20
+ 0x06 => 'getoutputstate',
21
+ 0x07 => 'getinputvalues',
22
+ 0x08 => 'resetinputscaledvalue',
23
+ 0x09 => 'messagewrite',
24
+ 0x0a => 'resetmotorposition',
25
+ 0x0b => 'getbatterylevel',
26
+ 0x0c => 'stopsoundplayback',
27
+ 0x0d => 'keepalive',
28
+ 0x0e => 'lsgetstatus',
29
+ 0x0f => 'lswrite',
30
+ 0x10 => 'lsread',
31
+ 0x11 => 'getcurrentprogramname',
32
+ 0x13 => 'messageread',
33
+ }
34
+
12
35
  PORTS = {
13
36
  a: 0x00,
14
37
  b: 0x01,
@@ -0,0 +1,60 @@
1
+ module NXT
2
+ module Command
3
+ module Input
4
+ include NXT::Command::Base
5
+ extend NXT::Utils::Accessors
6
+
7
+ @@command_type = COMMAND_TYPES[:direct]
8
+ @@command = 0x05
9
+
10
+ SENSOR_TYPES = {}
11
+ [:no_sensor,
12
+ :switch,
13
+ :temperature,
14
+ :reflection,
15
+ :angle,
16
+ :light_active,
17
+ :light_inactive,
18
+ :sound_db,
19
+ :sound_dba,
20
+ :custom,
21
+ :lowspeed,
22
+ :lowspeed_9v,
23
+ :highspeed,
24
+ :colorfull,
25
+ :colorred,
26
+ :colorgreen,
27
+ :colorblue,
28
+ :colornone
29
+ ].each_with_index do |key, index|
30
+ SENSOR_TYPES[key] = index
31
+ end
32
+
33
+ SENSOR_MODES = {
34
+ :rawmode => 0x00,
35
+ :booleanmode => 0x20,
36
+ :transitioncntmode => 0x40,
37
+ :periodcountermode => 0x60,
38
+ :pctfullscalemode => 0x80,
39
+ :celsiusmode => 0xA0,
40
+ :fahrenheitmode => 0xC0,
41
+ :anglestepmode => 0xE0,
42
+ :slopemask => 0x1F,
43
+ :modemask => 0xE0
44
+ }
45
+
46
+ def set_input_mode(type, mode)
47
+ raise "Invalid sensor type: #{type}" if !type.nil? && !SENSOR_TYPES.include?(type)
48
+
49
+ @interface.send_and_receive([
50
+ @@command_type,
51
+ @@command,
52
+ port_as_byte(@port),
53
+ SENSOR_TYPES[type],
54
+ SENSOR_MODES[mode]
55
+ ])
56
+ end
57
+ end
58
+ end
59
+ end
60
+
@@ -0,0 +1,70 @@
1
+ module NXT
2
+ module Command
3
+ module Output
4
+ class Program
5
+ include NXT::Command::Base
6
+
7
+ @@command_type = COMMAND_TYPES[:direct]
8
+ START_PROGRAM = 0x00
9
+ STOP_PROGRAM = 0x01
10
+ GET_CURRENT_PROGRAM_NAME = 0x11
11
+
12
+ def initialize(interface, name=nil)
13
+ @interface = interface
14
+ @name = name
15
+ end
16
+
17
+ def start
18
+ self.class.start(@interface, @name)
19
+ end
20
+
21
+ def stop
22
+ self.class.stop(@interface)
23
+ end
24
+
25
+
26
+ class << self
27
+
28
+ def start(interface, name)
29
+ response = send_command(interface, START_PROGRAM, name)
30
+
31
+ unless response[:status] == NXT::Errors::SUCCESS
32
+ message = "#{response[:message]}; Try using '.rxe' as the filename extension"
33
+ raise NXT::Exceptions::CommandError.new message
34
+ end
35
+
36
+ response
37
+ end
38
+
39
+ def stop(interface)
40
+ send_command(interface, STOP_PROGRAM)
41
+ end
42
+
43
+ def get_running_name(interface)
44
+ response = send_command(interface, GET_CURRENT_PROGRAM_NAME)
45
+ raise NXT::Exceptions::CommandError.new(response[:message]) if response[:status] != NXT::Errors::SUCCESS
46
+ convert_data_to_string(response[:data])
47
+ end
48
+
49
+ private
50
+ def send_command(interface, command, name=nil)
51
+
52
+ array = [@@command_type, command]
53
+ array.concat convert_string_to_data(name) unless name.nil?
54
+
55
+ response = interface.send_and_receive(array)
56
+ end
57
+
58
+ def convert_data_to_string(data)
59
+ data.pack("C*").strip
60
+ end
61
+
62
+ def convert_string_to_data(name, max_length=20)
63
+ name.ljust(max_length, "\0").unpack("C*")
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+
@@ -0,0 +1,35 @@
1
+ module NXT
2
+ module Connector
3
+ module Base
4
+ SENSOR_TYPES = {}
5
+ [:no_sensor,
6
+ :switch,
7
+ :temperature,
8
+ :reflection,
9
+ :angle,
10
+ :light_active,
11
+ :light_inactive,
12
+ :sound_db,
13
+ :sound_dba,
14
+ :custom,
15
+ :lowspeed,
16
+ :lowspeed_9v,
17
+ :highspeed,
18
+ :colorfull,
19
+ :colorred,
20
+ :colorgreen,
21
+ :colorblue,
22
+ :colornone
23
+ ].each_with_index do |key, index|
24
+ SENSOR_TYPES[key] = index
25
+ end
26
+
27
+ def set_input_mode(interface, type, mode)
28
+ raise "Invalid sensor type: #{mode}" if !mode.nil? && !SENSOR_TYPES.include?(mode)
29
+
30
+
31
+ end
32
+ end
33
+ end
34
+ end
35
+
@@ -2,9 +2,28 @@ module NXT
2
2
  module Connector
3
3
  module Input
4
4
  class Color
5
- def initialize(port)
5
+ include NXT::Command::Input
6
+
7
+ attr_accessor :port, :interface
8
+
9
+ COLORS = {
10
+ :off => :colornone,
11
+ :red => :colorred,
12
+ :green => :colorgreen,
13
+ :blue => :colorblue,
14
+ :all => :colorfull
15
+ }
16
+ def initialize(port, interface)
17
+ @interface = interface
6
18
  @port = port
7
19
  end
20
+
21
+ def set_color(color)
22
+ raise "Invalid color #{color}" unless COLORS.include?(color)
23
+
24
+ set_input_mode(COLORS[color], :booleanmode)
25
+
26
+ end
8
27
  end
9
28
  end
10
29
  end
@@ -13,10 +13,12 @@ module NXT
13
13
 
14
14
  attr_combined_accessor :duration, 0
15
15
  attr_combined_accessor :duration_type, :seconds
16
- attr_combined_accessor :duration_after, :stop
16
+ attr_combined_accessor :duration_after, :brake
17
17
  attr_combined_accessor :direction, :forwards
18
18
 
19
- attr_setter :direction, is_key_in: DIRECTION
19
+ attr_setter :direction, is_in: DIRECTION
20
+ attr_setter :duration_after, is_in: DURATION_AFTER
21
+ attr_setter :duration_type, is_in: DURATION_TYPE
20
22
 
21
23
  def initialize(port, interface)
22
24
  @port = port
@@ -27,39 +29,11 @@ module NXT
27
29
  raise TypeError.new('Expected duration to be a number') unless duration.is_a?(Integer)
28
30
  @duration = duration
29
31
 
30
- if options.include?(:type)
31
- type = options[:type]
32
-
33
- unless DURATION_TYPE.include?(type)
34
- raise TypeError.new("Expected duration type to be one of: :#{DURATION_TYPE.join(', :')}")
35
- end
36
-
37
- @duration_type = type
38
- else
39
- @duration_type = :seconds
40
- end
32
+ self.duration_type = options[:type] if options.include?(:type)
41
33
 
42
34
  if options.include?(:after)
43
- if @duration_type == :seconds
44
- after = options[:after]
45
-
46
- unless DURATION_AFTER.include?(after)
47
- raise TypeError.new("Expected after option to be one of: :#{DURATION_AFTER.join(', :')}")
48
- end
49
-
50
- @duration_after = after
51
- else
52
- raise TypeError.new('The after option is only available when the unit duration is in seconds.')
53
- end
54
- else
55
- @duration_after = :stop
56
- end
57
-
58
- case @duration_type
59
- when :rotations
60
- self.tacho_limit = @duration * 360
61
- when :degrees
62
- self.tacho_limit = @duration
35
+ raise TypeError.new('The after option is only available when the unit duration is in seconds.') if self.duration_type != :seconds
36
+ self.duration_after = options[:after]
63
37
  end
64
38
 
65
39
  self
@@ -84,13 +58,9 @@ module NXT
84
58
 
85
59
  # takes block for response, or can return the response instead.
86
60
  def move
87
- response_required = false
88
-
89
- if self.duration > 0 && self.duration_type != :seconds
90
- response_required = true
91
- end
61
+ response_required = (self.duration > 0 && self.duration_type != :seconds)
92
62
 
93
- set_output_state(response_required)
63
+ set_motor_output_state(response_required)
94
64
 
95
65
  if self.duration > 0 && self.duration_type == :seconds
96
66
  sleep(self.duration)
@@ -110,16 +80,34 @@ module NXT
110
80
  self.run_state = :running
111
81
  self.tacho_limit = 0
112
82
  end
113
- end
114
83
 
115
- # overrides
116
- def set_output_state(response_required=true)
117
- original_power = self.power
118
- self.power *= -1 if self.direction == :backwards
119
84
 
120
- super
85
+ private
86
+ def set_motor_output_state(response_required=true)
87
+ original_tacho_limit = self.tacho_limit
88
+ adjust_tacho_limit
89
+
90
+ original_power = self.power
91
+ adjust_power
92
+
93
+ set_output_state(response_required)
94
+
95
+ self.power = original_power
96
+ self.tacho_limit = original_tacho_limit
97
+ end
121
98
 
122
- self.power = original_power
99
+ def adjust_tacho_limit
100
+ case self.duration_type
101
+ when :rotations
102
+ self.tacho_limit = self.duration * 360
103
+ when :degrees
104
+ self.tacho_limit = self.duration
105
+ end
106
+ end
107
+
108
+ def adjust_power
109
+ self.power *= -1 if self.direction == :backwards
110
+ end
123
111
  end
124
112
  end
125
113
  end
@@ -0,0 +1,25 @@
1
+ module NXT
2
+ module Errors
3
+ SUCCESS = 0x00
4
+ CODES = {
5
+ SUCCESS => 'Success',
6
+ 0x20 => 'Pending communication transaction in progress',
7
+ 0x40 => 'Specified mailbox queue is empty',
8
+ 0xBD => 'Request failed (i.e., specified file not found)',
9
+ 0xBE => 'Unknown command opcode',
10
+ 0xBF => 'Insane packet',
11
+ 0xC0 => 'Data contains out-of-range values',
12
+ 0xDD => 'Communication bus error',
13
+ 0xDE => 'No free memory in communication buffer',
14
+ 0xDF => 'Specified channel/connection is not valid',
15
+ 0xE0 => 'Specified channel/connection not configured or busy',
16
+ 0xEC => 'No active program',
17
+ 0xED => 'Illegal size specified',
18
+ 0xEE => 'Illegal mailbox queue ID specified',
19
+ 0xEF => 'Attempted to access invalid field of a structure',
20
+ 0xF0 => 'Bad input or output specified',
21
+ 0xFB => 'Insufficient memory available',
22
+ 0xFF => 'Bad arguments'
23
+ }
24
+ end
25
+ end
@@ -19,5 +19,8 @@ module NXT
19
19
 
20
20
  # Raised when communication with a Serial Port connection fails.
21
21
  class SerialPortConnectionError < RuntimeError; end
22
+
23
+ # Raised when a command returns a reply message error
24
+ class CommandError < RuntimeError; end
22
25
  end
23
26
  end
@@ -1,9 +1,11 @@
1
+ require_relative '../commands/base' #TODO: bad code smell here
2
+
1
3
  module NXT
2
4
  module Interface
3
5
  class Base
4
6
  def send_and_receive(msg, response_required = true)
5
7
  if response_required
6
- msg[0] = msg[0] | (response_required ? 0x80 : 0x00)
8
+ msg[0] = msg[0] | (response_required ? 0x00 : 0x80)
7
9
  end
8
10
 
9
11
  self.send(msg)
@@ -69,7 +69,19 @@ module NXT
69
69
  #
70
70
  # Reference: Appendix 1, Page 22
71
71
  length = @connection.sysread(2)
72
- @connection.sysread(length.unpack('v')[0])
72
+ actual_length = length.unpack('v')[0]
73
+ response_bytes = @connection.sysread(actual_length)
74
+ response_array = response_bytes.unpack('C*')
75
+ response = {
76
+ :reply => response_array[0] == 0x02,
77
+ :for_command => response_array[1],
78
+ :status => response_array[2]
79
+ }
80
+ response[:message] = NXT::Errors::CODES[response[:status]]
81
+ response[:for_command_name] = NXT::Command::Base::COMMANDS[response[:for_command]]
82
+ response[:data] = response_array[3..-1] if response_array.size > 3
83
+
84
+ response
73
85
  end
74
86
  end
75
87
  end
@@ -146,6 +146,18 @@ class NXTBrick
146
146
  end
147
147
  end
148
148
 
149
+ def start_program(name)
150
+ NXT::Command::Output::Program.start(self.interface, name)
151
+ end
152
+
153
+ def stop_program
154
+ NXT::Command::Output::Program.stop(self.interface)
155
+ end
156
+
157
+ def running_program_name
158
+ NXT::Command::Output::Program.get_running_name(self.interface)
159
+ end
160
+
149
161
  private
150
162
  def validate_interface(interface_type_name_string)
151
163
  unless NXT::Interface.constants.include?(interface_type_name_string.to_sym)
@@ -4,11 +4,15 @@ module NXT
4
4
  def attr_setter(name, options)
5
5
  define_method("#{name}=") do |value|
6
6
  if options.include?(:is)
7
- raise TypeError.new('Expected value to be a number') unless duration.is_a?(options[:is])
7
+ raise TypeError.new('Expected value to be a #{options[:is]}') unless duration.is_a?(options[:is])
8
8
  end
9
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(', :')}")
10
+ if options.include?(:is_in)
11
+ raise TypeError.new("Expected value to be one of: #{options[:is_in].join(', ')}") unless options[:is_in].include?(value)
12
+ end
13
+
14
+ if options.include?(:is_key_in)
15
+ raise TypeError.new("Expected value to be one of: :#{options[:is_key_in].keys.join(', :')}") unless options[:is_key_in].include?(value)
12
16
  end
13
17
 
14
18
  instance_variable_set("@#{name}", value)
@@ -0,0 +1,55 @@
1
+ require 'spec_helper'
2
+
3
+ describe NXT::Connector::Output::Motor do
4
+
5
+ subject do
6
+ NXT::Connector::Output::Motor.new(nil, nil)
7
+ end
8
+
9
+ context '#duration' do
10
+ # TODO: Should be OK to use any number, but documenting current behavior
11
+ it 'should not allow a non-Integer duration' do
12
+ expect do
13
+ subject.duration(0.5)
14
+ end.to raise_exception(TypeError)
15
+ end
16
+
17
+ it 'should allow valid type options' do
18
+ subject.duration(1, :type => :rotations)
19
+ subject.duration(1, :type => :seconds)
20
+ subject.duration(1, :type => :degrees)
21
+ end
22
+
23
+ it 'should not allow invalid type option' do
24
+ expect do
25
+ subject.duration(1, :type => :incorrect_duration_after_type)
26
+ end.to raise_exception(TypeError)
27
+ end
28
+
29
+ it 'should default to seconds as the duration unit' do
30
+ subject.duration(1)
31
+
32
+ subject.duration_type.should == :seconds
33
+ end
34
+
35
+ context "setting the :after option" do
36
+ # TODO: This isn't right...should change this
37
+ # but I'm just documenting current behavior
38
+ it 'should prevent using the :after option unless duration_type is :seconds' do
39
+ expect do
40
+ subject.duration(1, :type => :rotations, :after => :coast)
41
+ end.to raise_exception(TypeError)
42
+ end
43
+
44
+ it 'should allow using the :afer option when the duration type is :seconds' do
45
+ subject.duration(1, :type => :seconds, :after => :coast)
46
+ end
47
+
48
+ it 'should not allow setting an invalid value' do
49
+ expect do
50
+ subject.duration(1, :type => :seconds, :after => :foobar)
51
+ end.to raise_exception(TypeError)
52
+ end
53
+ end
54
+ end
55
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nxt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2013-03-03 00:00:00.000000000 Z
13
+ date: 2013-03-09 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: serialport
@@ -124,7 +124,7 @@ dependencies:
124
124
  - - ~>
125
125
  - !ruby/object:Gem::Version
126
126
  version: 2.1.0
127
- description: Control an NXT 2.0 See http://github.com/nathankleyn/nxt for more information.
127
+ description: Control an NXT 2.0 See http://github.com/tchype/ruby-nxt for more information.
128
128
  email: tony@heupel.org
129
129
  executables: []
130
130
  extensions: []
@@ -139,10 +139,12 @@ files:
139
139
  - lib/nxt/commands/program.rb
140
140
  - lib/nxt/commands/sound.rb
141
141
  - lib/nxt/commands/tone.rb
142
+ - lib/nxt/connectors/base.rb
142
143
  - lib/nxt/connectors/input/color.rb
143
144
  - lib/nxt/connectors/input/touch.rb
144
145
  - lib/nxt/connectors/input/ultrasonic.rb
145
146
  - lib/nxt/connectors/output/motor.rb
147
+ - lib/nxt/errors.rb
146
148
  - lib/nxt/exceptions.rb
147
149
  - lib/nxt/interfaces/base.rb
148
150
  - lib/nxt/interfaces/serial_port.rb
@@ -153,10 +155,11 @@ files:
153
155
  - lib/nxt/utils/accessors.rb
154
156
  - lib/nxt.rb
155
157
  - spec/matchers.rb
158
+ - spec/nxt/connectors/output/motor_spec.rb
156
159
  - spec/nxt/interfaces/serial_port_spec.rb
157
160
  - spec/nxt/nxt_brick_spec.rb
158
161
  - spec/spec_helper.rb
159
- homepage: http://github.com/tchype/nxt
162
+ homepage: http://github.com/tchype/ruby-nxt
160
163
  licenses: []
161
164
  post_install_message:
162
165
  rdoc_options: []