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,11 @@
1
+ require_relative '../../direct_command_reply'
2
+
3
+ class SetOutputStateReply < DirectCommandReply
4
+ def initialize(bytes)
5
+ super(bytes)
6
+ end
7
+
8
+ def validate_bytes(bytes)
9
+ raise ArgumentError, "must be a reply for a SetOutputState command" unless bytes[1] == 0x04
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ require_relative '../../direct_command_reply'
2
+
3
+ class StartProgramReply < DirectCommandReply
4
+ def initialize(bytes)
5
+ super(bytes)
6
+ end
7
+
8
+ # override
9
+ def validate_bytes(bytes)
10
+ super(bytes)
11
+ raise ArgumentError, "must be a reply for a StartProgram command" unless bytes[1] == 0x00
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ require_relative '../../direct_command_reply'
2
+
3
+ class StopProgramReply < DirectCommandReply
4
+ def initialize(bytes)
5
+ super(bytes)
6
+ end
7
+
8
+ # override
9
+ def validate_bytes(bytes)
10
+ super(bytes)
11
+ raise ArgumentError, "must be a reply for a StopProgram command" unless bytes[1] == 0x01
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ require_relative '../../direct_command_reply'
2
+
3
+ class StopSoundPlaybackReply < DirectCommandReply
4
+ def initialize(bytes)
5
+ super(bytes)
6
+ end
7
+
8
+ # override
9
+ def validate_bytes(bytes)
10
+ super(bytes)
11
+ raise ArgumentError, "must be a reply for a StopSoundPlayback command" unless bytes[1] == 0x0C
12
+ end
13
+ end
@@ -0,0 +1,8 @@
1
+ require_relative '../direct_command'
2
+
3
+ class ResetMotorPosition < DirectCommand
4
+ def initialize(require_response=true)
5
+ super
6
+ @command = 0x0A
7
+ end
8
+ end
@@ -0,0 +1,101 @@
1
+ require_relative '../direct_command'
2
+
3
+ class SetInputMode < DirectCommand
4
+
5
+ class << self
6
+ def input_ports; INPUT_PORTS.keys; end
7
+ def sensor_types; SENSOR_TYPES.keys; end
8
+ def sensor_modes; SENSOR_MODES.keys; end
9
+ end
10
+
11
+ attr_reader :input_port, :sensor_type, :sensor_mode
12
+
13
+ def initialize(input_port, sensor_type, sensor_mode, response_required=true)
14
+ super(response_required)
15
+ @command = 0x05
16
+
17
+ validate_arguments(input_port, sensor_type, sensor_mode)
18
+ @input_port = input_port
19
+ @sensor_type = sensor_type
20
+ @sensor_mode = sensor_mode
21
+ end
22
+
23
+ INPUT_PORTS = {
24
+ one: 0x00,
25
+ two: 0x01,
26
+ three: 0x02,
27
+ four: 0x03
28
+ }
29
+
30
+ SENSOR_TYPES = {}
31
+ [:no_sensor,
32
+ :switch,
33
+ :temperature,
34
+ :reflection,
35
+ :angle,
36
+ :light_active,
37
+ :light_inactive,
38
+ :sound_db,
39
+ :sound_dba,
40
+ :custom,
41
+ :lowspeed,
42
+ :lowspeed_9v,
43
+ :highspeed,
44
+ :colorfull,
45
+ :colorred,
46
+ :colorgreen,
47
+ :colorblue,
48
+ :colornone
49
+ ].each_with_index do |key, index|
50
+ SENSOR_TYPES[key] = index
51
+ end
52
+
53
+ SENSOR_MODES = {
54
+ :rawmode => 0x00,
55
+ :booleanmode => 0x20,
56
+ :transitioncntmode => 0x40,
57
+ :periodcountermode => 0x60,
58
+ :pctfullscalemode => 0x80,
59
+ :celsiusmode => 0xA0,
60
+ :fahrenheitmode => 0xC0,
61
+ :anglestepmode => 0xE0,
62
+ :slopemask => 0x1F,
63
+ :modemask => 0xE0
64
+ }
65
+
66
+ def sensor_types
67
+ self.class.sensor_types
68
+ end
69
+
70
+ def sensor_modes
71
+ self.class.sensor_modes
72
+ end
73
+
74
+ def input_ports
75
+ self.class.input_ports
76
+ end
77
+
78
+ def as_bytes
79
+ super << INPUT_PORTS[@input_port] << SENSOR_TYPES[@sensor_type] << SENSOR_MODES[@sensor_mode]
80
+ end
81
+
82
+ private
83
+ def validate_arguments(input_port, sensor_type, sensor_mode)
84
+ validate_input_port(input_port)
85
+ validate_sensor_type(sensor_type)
86
+ validate_sensor_mode(sensor_mode)
87
+ end
88
+
89
+ def validate_input_port(input_port)
90
+ raise ArgumentError, "Invalid input port" unless input_ports.include? input_port
91
+ end
92
+
93
+ def validate_sensor_type(sensor_type)
94
+ raise ArgumentError, "Invalid sensor type" unless sensor_types.include? sensor_type
95
+ end
96
+
97
+ def validate_sensor_mode(sensor_mode)
98
+ raise ArgumentError, "Invalid sensor mode" unless sensor_modes.include? sensor_mode
99
+ end
100
+
101
+ end
@@ -0,0 +1,29 @@
1
+ require_relative '../direct_command'
2
+ require_relative './output_state'
3
+ require 'forwardable'
4
+
5
+ class SetOutputState < DirectCommand
6
+ extend Forwardable
7
+
8
+ def initialize(output_state, wait_for_reply=true)
9
+ super(wait_for_reply)
10
+ @command = 0x04
11
+
12
+ raise ArgumentError, "output_state must be a valid OutputState object" if output_state.nil?
13
+ @output_state = output_state
14
+ end
15
+
16
+ def as_bytes
17
+ super.concat(@output_state.as_bytes)
18
+ end
19
+
20
+ # follow Law of Demeter and hide internal access to output_state
21
+ def_delegators :@output_state, :port, :power, :mode_flags, :regulation_mode,
22
+ :turn_ratio, :run_state, :tacho_limit
23
+
24
+ private
25
+ def output_state
26
+ @output_state
27
+ end
28
+ end
29
+
@@ -0,0 +1,30 @@
1
+ require_relative '../direct_command'
2
+ require_relative '../message_translator'
3
+
4
+ class StartProgram < DirectCommand
5
+ include MessageTranslator
6
+
7
+ attr_reader :name
8
+
9
+ def initialize(program_name, require_response=true)
10
+ raise ArgumentError, "program_name is required" if program_name.nil?
11
+
12
+ super(require_response)
13
+ @command = 0x00 # startprogram
14
+ @name = adjust_program_name(program_name)
15
+ end
16
+
17
+ def as_bytes
18
+ bytes = super
19
+ bytes.concat program_name_as_bytes
20
+ end
21
+
22
+ private
23
+ def program_name_as_bytes
24
+ string_as_bytes(@name)
25
+ end
26
+
27
+ def adjust_program_name(name)
28
+ add_default_extension_if_missing(name, 'rxe')
29
+ end
30
+ end
@@ -0,0 +1,9 @@
1
+ require_relative '../direct_command'
2
+
3
+ class StopProgram < DirectCommand
4
+ def initialize(require_response=true)
5
+ super
6
+ @command = 0x01
7
+ end
8
+
9
+ end
@@ -0,0 +1,8 @@
1
+ require_relative '../direct_command'
2
+
3
+ class StopSoundPlayback < DirectCommand
4
+ def initialize(require_response=true)
5
+ super(require_response)
6
+ @command= 0x0C
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ require_relative "../respondable_telegram"
2
+
3
+ class DirectCommand < RespondableTelegram
4
+ def initialize(require_response=true)
5
+ super
6
+ @type = 0x00
7
+ end
8
+ end
@@ -0,0 +1,10 @@
1
+ require_relative '../reply'
2
+
3
+ class DirectCommandReply < Reply
4
+
5
+ def initialize(bytes)
6
+ super
7
+ end
8
+
9
+ end
10
+
@@ -0,0 +1,46 @@
1
+ module MessageTranslator
2
+ FILENAME_MAX_LENGTH = 20 # 15.3 + null terminator
3
+
4
+ def string_as_bytes(string, max_length=FILENAME_MAX_LENGTH)
5
+ # pad out to max length with null characters and then
6
+ # convert all to ASCIIZ (null-terminated ascii string)
7
+ string.ljust(max_length, "\0").unpack('C*')
8
+ end
9
+
10
+ def string_from_bytes(bytes)
11
+ # convert from ASCIIZ (null-terminated ascii), removing all the
12
+ # trailing null characters
13
+ bytes.pack("C*").strip
14
+ end
15
+
16
+ def add_default_extension_if_missing(filename, default_extension)
17
+ /.+\.[A-Za-z0-9]{3}$/.match(filename).nil? ? "#{filename}.#{default_extension}" : filename
18
+ end
19
+
20
+ def boolean_as_bytes(boolean)
21
+ boolean ? [0xff] : [0x00]
22
+ end
23
+
24
+ def boolean_from_bytes(bytes)
25
+ bytes == [0x00] ? false : true
26
+ end
27
+
28
+ def integer_as_uword_bytes(integer)
29
+ integer_as_bytes(integer, 4) # make sure number is 4 bytes, or 2, 2 digit hex values
30
+ end
31
+
32
+ def integer_as_ulong_bytes(integer)
33
+ integer_as_bytes(integer, 8) # (4 bytes as 2 digit hex values each)
34
+ end
35
+
36
+ def integer_as_bytes(integer, bytes_length)
37
+ uword_byte_length = bytes_length
38
+ bytes_string = integer.to_s(16).rjust(uword_byte_length, "0")
39
+ [bytes_string].pack('H*').bytes.to_a.reverse # reverse because it's MSB
40
+ end
41
+
42
+ def integer_from_bytes(bytes)
43
+ bytes.reverse.pack('C*').unpack('H*')[0].hex
44
+ end
45
+
46
+ end
@@ -0,0 +1,8 @@
1
+ require_relative '../system_command'
2
+
3
+ class GetDeviceInfo < SystemCommand
4
+ def initialize
5
+ super
6
+ @command = 0x9B
7
+ end
8
+ end
@@ -0,0 +1,41 @@
1
+ require_relative '../../../reply'
2
+
3
+ class GetDeviceInfoReply < Reply
4
+ attr_reader :nxt_brick_name, :bt_address,
5
+ :signal_strength, :free_user_flash_memory_bytes
6
+
7
+ def initialize(bytes)
8
+ super
9
+ raise ArgumentError, "must be a GetDeviceInfoReply (0x9B)" unless bytes[1] == 0x9B
10
+
11
+ @nxt_brick_name = parse_brick_name(bytes)
12
+ @bt_address = parse_bt_address(bytes)
13
+ @signal_strength = parse_signal_strength(bytes)
14
+ @free_user_flash_memory_bytes = parse_free_user_flash_memory(bytes)
15
+ end
16
+
17
+ private
18
+ def parse_brick_name(bytes)
19
+ bytes[3..17].pack("C*").strip
20
+ end
21
+
22
+ def parse_bt_address(bytes)
23
+ # skip the null terminator, so 18-23 with 24 at null terminator
24
+ address = bytes[18..23].map { | byte | byte.to_s(8).rjust(2, "0") }
25
+ "btspp://#{address.join('')}"
26
+ end
27
+
28
+ def parse_signal_strength(bytes)
29
+ signal_strength_bytes = bytes[25..28]
30
+ convert_byte_array_of_little_endian_to_number(signal_strength_bytes)
31
+ end
32
+
33
+ def parse_free_user_flash_memory(bytes)
34
+ free_memory_bytes = bytes[29..32]
35
+ convert_byte_array_of_little_endian_to_number(free_memory_bytes)
36
+ end
37
+
38
+ def convert_byte_array_of_little_endian_to_number(byte_array)
39
+ byte_array.pack('C*').unpack('v*')[0]
40
+ end
41
+ end
@@ -0,0 +1,8 @@
1
+ require_relative '../respondable_telegram'
2
+
3
+ class SystemCommand < RespondableTelegram
4
+ def initialize(response_required=true)
5
+ super
6
+ @type = 0x01
7
+ end
8
+ end
File without changes
File without changes
File without changes
@@ -0,0 +1,10 @@
1
+ require_relative './reply'
2
+
3
+ class NoMessageReply < Reply
4
+ # override to return empty string
5
+ def message
6
+ ''
7
+ end
8
+ end
9
+
10
+
@@ -0,0 +1,82 @@
1
+ require_relative './telegram'
2
+
3
+ class Reply < Telegram
4
+ COMMAND_TYPE = 0x02
5
+ SUCCESS = 0x00
6
+ STATUS_MESSAGES = {
7
+ SUCCESS => 'Success',
8
+ # base error messages
9
+ 0x81 => 'No more handles',
10
+ 0x82 => 'No space',
11
+ 0x83 => 'No more files',
12
+ 0x84 => 'End of file expected',
13
+ 0x85 => 'End of file',
14
+ 0x86 => 'Not a linear file',
15
+ 0x87 => 'File not found',
16
+ 0x88 => 'Handle all ready closed',
17
+ 0x89 => 'No linear space',
18
+ 0x8A => 'Undefined error',
19
+ 0x8B => 'File is busy',
20
+ 0x8C => 'No write buffers',
21
+ 0x8D => 'Append not possible',
22
+ 0x8E => 'File is full',
23
+ 0x8F => 'File exists',
24
+ 0x90 => 'Module not found',
25
+ 0x91 => 'Out of boundary',
26
+ 0x92 => 'Illegal file name',
27
+ 0x93 => 'Illegal handle',
28
+
29
+ # command replies
30
+ 0x20 => 'Pending communication transaction in progress',
31
+ 0x40 => 'Specified mailbox queue is empty',
32
+ 0xBD => 'Request failed (i.e., specified file not found)',
33
+ 0xBE => 'Unknown command opcode',
34
+ 0xBF => 'Insane packet',
35
+ 0xC0 => 'Data contains out-of-range values',
36
+ 0xDD => 'Communication bus error',
37
+ 0xDE => 'No free memory in communication buffer',
38
+ 0xDF => 'Specified channel/connection is not valid',
39
+ 0xE0 => 'Specified channel/connection not configured or busy',
40
+ 0xEC => 'No active program',
41
+ 0xED => 'Illegal size specified',
42
+ 0xEE => 'Illegal mailbox queue ID specified',
43
+ 0xEF => 'Attempted to access invalid field of a structure',
44
+ 0xF0 => 'Bad input or output specified',
45
+ 0xFB => 'Insufficient memory available',
46
+ 0xFF => 'Bad arguments'
47
+ }
48
+
49
+ attr_accessor :type, :command, :status, :message_bytes, :status_description
50
+
51
+ def initialize(bytes=nil)
52
+ validate_bytes(bytes)
53
+ @type = bytes[0]
54
+ @command = bytes[1]
55
+ @status = bytes[2]
56
+ @message_bytes = bytes[3..-1]
57
+ set_status_description
58
+ end
59
+
60
+
61
+ def success?
62
+ status == SUCCESS
63
+ end
64
+
65
+
66
+ def message
67
+ # override in subclasses to translate bytes to something meaningful
68
+ message_bytes
69
+ end
70
+
71
+
72
+ private
73
+ def validate_bytes(bytes)
74
+ raise ArgumentError, "must be non-nil" if bytes.nil?
75
+ raise ArgumentError, "must have a type, command, and status" unless bytes.size && bytes.size >= 3
76
+ raise ArgumentError, "must be a reply telegram" unless bytes[0] == COMMAND_TYPE
77
+ end
78
+
79
+ def set_status_description
80
+ @status_description = STATUS_MESSAGES.include?(status) ? STATUS_MESSAGES[status] : ''
81
+ end
82
+ end