nxt 0.3.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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