sphero_pwn 0.0.0 → 0.0.1

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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/bin/sphero_diagnostics.rb +71 -0
  4. data/bin/sphero_map_flash.rb +82 -0
  5. data/bin/sphero_save_flash_block.rb +47 -0
  6. data/bin/sphero_set_flag.rb +38 -0
  7. data/bin/sphero_soft_reboot.rb +33 -0
  8. data/bin/sphero_toggle_user_hack.rb +36 -0
  9. data/lib/sphero_pwn.rb +24 -14
  10. data/lib/sphero_pwn/asyncs/flash_block.rb +26 -0
  11. data/lib/sphero_pwn/channel.rb +72 -29
  12. data/lib/sphero_pwn/commands/boot_main_app.rb +17 -0
  13. data/lib/sphero_pwn/commands/enter_bootloader.rb +15 -0
  14. data/lib/sphero_pwn/commands/get_device_mode.rb +35 -0
  15. data/lib/sphero_pwn/commands/get_flash_block.rb +34 -0
  16. data/lib/sphero_pwn/commands/get_permanent_flags.rb +44 -0
  17. data/lib/sphero_pwn/commands/get_versions.rb +12 -10
  18. data/lib/sphero_pwn/commands/is_page_blank.rb +36 -0
  19. data/lib/sphero_pwn/commands/l1_diagnostics.rb +0 -1
  20. data/lib/sphero_pwn/commands/l2_diagnostics.rb +50 -0
  21. data/lib/sphero_pwn/commands/set_device_mode.rb +25 -0
  22. data/lib/sphero_pwn/commands/set_permanent_flags.rb +29 -0
  23. data/lib/sphero_pwn/session.rb +12 -4
  24. data/sphero_pwn.gemspec +130 -0
  25. data/test/command_test.rb +10 -0
  26. data/test/commands/boot_main_app_test.rb +11 -0
  27. data/test/commands/enter_bootloader_test.rb +33 -0
  28. data/test/commands/get_device_mode_test.rb +40 -0
  29. data/test/commands/get_flash_block_test.rb +101 -0
  30. data/test/commands/get_permanent_flags_test.rb +48 -0
  31. data/test/commands/get_versions_test.rb +10 -1
  32. data/test/commands/is_page_blank_test.rb +65 -0
  33. data/test/commands/l2_diagnostics_test.rb +58 -0
  34. data/test/commands/set_device_mode_test.rb +56 -0
  35. data/test/commands/set_permanent_flags_test.rb +55 -0
  36. data/test/data/enter_bootloader.txt +4 -0
  37. data/test/data/get_device_mode.txt +2 -0
  38. data/test/data/get_factory_config.txt +2 -0
  39. data/test/data/get_permanent_flags.txt +2 -0
  40. data/test/data/get_soul.txt +2 -0
  41. data/test/data/get_version.txt +2 -2
  42. data/test/data/is_page_blank.txt +6 -0
  43. data/test/data/l2_diagnostics.txt +8 -0
  44. data/test/data/ping.txt +2 -2
  45. data/test/data/set_device_mode.txt +12 -0
  46. data/test/data/set_permanent_flags.txt +8 -0
  47. metadata +44 -3
@@ -0,0 +1,17 @@
1
+ # Asks the robot's bootloader to boot the main application.
2
+ #
3
+ # This command is only valid when the robot is executing the bootloader.
4
+ class SpheroPwn::Commands::BootMainApp < SpheroPwn::Command
5
+ def initialize
6
+ super 0x01, 0x04, nil
7
+ end
8
+
9
+ # @see {SpheroPwn::Command#response_class}
10
+ def response_class
11
+ SpheroPwn::Commands::BootMainApp::Response
12
+ end
13
+ end
14
+
15
+ # The response to a boot main application command.
16
+ class SpheroPwn::Commands::BootMainApp::Response < SpheroPwn::Response
17
+ end
@@ -0,0 +1,15 @@
1
+ # Asks the robot to jump into its bootloader.
2
+ class SpheroPwn::Commands::EnterBootloader < SpheroPwn::Command
3
+ def initialize
4
+ super 0x00, 0x30, nil
5
+ end
6
+
7
+ # @see {SpheroPwn::Command#response_class}
8
+ def response_class
9
+ SpheroPwn::Commands::EnterBootloader::Response
10
+ end
11
+ end
12
+
13
+ # The response to an enter bootloader command.
14
+ class SpheroPwn::Commands::EnterBootloader::Response < SpheroPwn::Response
15
+ end
@@ -0,0 +1,35 @@
1
+ # Asks the robot about the versions of its software stack components.
2
+ class SpheroPwn::Commands::GetDeviceMode < SpheroPwn::Command
3
+ def initialize
4
+ super 0x02, 0x44, nil
5
+ end
6
+
7
+ # @see {SpheroPwn::Command#response_class}
8
+ def response_class
9
+ SpheroPwn::Commands::GetDeviceMode::Response
10
+ end
11
+ end
12
+
13
+ # The development mode of the robot.
14
+ class SpheroPwn::Commands::GetDeviceMode::Response < SpheroPwn::Response
15
+ # @return {Symbol} the device's mode; can be :normal or :user_hack
16
+ attr_reader :mode
17
+
18
+ # @see {SpheroPwn::Response#initialize}
19
+ def initialize(code_byte, sequence_byte, data_bytes)
20
+ super
21
+
22
+ if code == :ok
23
+ @mode = case data_bytes[0]
24
+ when 0x00
25
+ :normal
26
+ when 0x01
27
+ :user_hack
28
+ else
29
+ :unknown
30
+ end
31
+ else
32
+ @mode = :error
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,34 @@
1
+ # Asks the robot to send a configuration block from the flash memory.
2
+ class SpheroPwn::Commands::GetFlashBlock < SpheroPwn::Command
3
+ # @param {Symbol} block_type :soul, :factory_config or :user_config
4
+ def initialize(block_type)
5
+ case block_type
6
+ when :soul
7
+ command_id = 0x46
8
+ data_bytes = nil
9
+ when :factory_config
10
+ command_id = 0x40
11
+ data_bytes = [0x00]
12
+ when :user_config
13
+ command_id = 0x40
14
+ data_bytes = [0x01]
15
+ when /^block_/
16
+ command_id = 0x40
17
+ data_bytes = block_type.to_s.split('_')[1..-1].
18
+ map { |char| char.to_i(16) }
19
+ else
20
+ raise ArgumentError, "Unimplemented block type #{block_type.inspect}"
21
+ end
22
+
23
+ super 0x02, command_id, data_bytes
24
+ end
25
+
26
+ # @see {SpheroPwn::Command#response_class}
27
+ def response_class
28
+ SpheroPwn::Commands::GetFlashBlock::Response
29
+ end
30
+ end
31
+
32
+ # The response to a get flash block command.
33
+ class SpheroPwn::Commands::GetFlashBlock::Response < SpheroPwn::Response
34
+ end
@@ -0,0 +1,44 @@
1
+ # Obtains the robot's configuration flags that persist across power cycles.
2
+ class SpheroPwn::Commands::GetPermanentFlags < SpheroPwn::Command
3
+ def initialize
4
+ super 0x02, 0x36, nil
5
+ end
6
+
7
+ # @see {SpheroPwn::Command#response_class}
8
+ def response_class
9
+ SpheroPwn::Commands::GetPermanentFlags::Response
10
+ end
11
+
12
+ # @return {Hash<Number, Symbol>} symbolic values for the returned flags
13
+ FLAGS = {
14
+ 0x01 => :no_sleep_while_charging,
15
+ 0x02 => :vector_drive,
16
+ 0x04 => :no_leveling_while_charging,
17
+ 0x08 => :tail_led_always_on,
18
+ 0x10 => :motion_timeouts,
19
+ 0x20 => :demo_mode,
20
+ 0x40 => :light_double_tap,
21
+ 0x80 => :heavy_double_tap,
22
+ 0x100 => :gyro_max_async,
23
+ }.freeze
24
+ end
25
+
26
+ # The robot's configuration flags that persist across power cycles.
27
+ class SpheroPwn::Commands::GetPermanentFlags::Response < SpheroPwn::Response
28
+ # @return {Hash<Symbol, Boolean>} maps developer-friendly flag names to
29
+ # whether the corresponding bits were set in the flags field
30
+ attr_reader :flags
31
+
32
+ # @see {SpheroPwn::Response#initialize}
33
+ def initialize(code_byte, sequence_byte, data_bytes)
34
+ super
35
+
36
+ @flags = {}
37
+ if code == :ok
38
+ flags_number = data_bytes[0, 4].pack('C*').unpack('N').first
39
+ SpheroPwn::Commands::GetPermanentFlags::FLAGS.each do |mask, name|
40
+ @flags[name] = (flags_number & mask) != 0
41
+ end
42
+ end
43
+ end
44
+ end
@@ -21,16 +21,18 @@ class SpheroPwn::Commands::GetVersions::Response < SpheroPwn::Response
21
21
  super
22
22
 
23
23
  @versions = {}
24
- response_version = data_bytes[0]
25
- if response_version >= 1
26
- @versions.merge! model: data_bytes[1], hardware: data_bytes[2],
27
- sphero_app: { version: data_bytes[3], revision: data_bytes[4] },
28
- bootloader: self.class.parse_packed_nibble(data_bytes[5]),
29
- basic: self.class.parse_packed_nibble(data_bytes[6]),
30
- macros: self.class.parse_packed_nibble(data_bytes[7])
31
- end
32
- if response_version >= 2
33
- @versions.merge! api: { major: data_bytes[8], minor: data_bytes[9] }
24
+ if code == :ok
25
+ response_version = data_bytes[0]
26
+ if response_version >= 1
27
+ @versions.merge! model: data_bytes[1], hardware: data_bytes[2],
28
+ sphero_app: { version: data_bytes[3], revision: data_bytes[4] },
29
+ bootloader: self.class.parse_packed_nibble(data_bytes[5]),
30
+ basic: self.class.parse_packed_nibble(data_bytes[6]),
31
+ macros: self.class.parse_packed_nibble(data_bytes[7])
32
+ end
33
+ if response_version >= 2
34
+ @versions.merge! api: { major: data_bytes[8], minor: data_bytes[9] }
35
+ end
34
36
  end
35
37
  end
36
38
 
@@ -0,0 +1,36 @@
1
+ # Asks the robot's bootloader if a flash page is blank.
2
+ #
3
+ # This command is only valid when the robot is executing the bootloader.
4
+ class SpheroPwn::Commands::IsPageBlank < SpheroPwn::Command
5
+ # @param {Number} page_number the flash page that the command will ask the
6
+ # robot to look at
7
+ def initialize(page_number)
8
+ if page_number > 255
9
+ raise ArgumentError, "Page number #{page_number} exceeds 255"
10
+ end
11
+ super 0x01, 0x05, [page_number]
12
+ end
13
+
14
+ # @see {SpheroPwn::Command#response_class}
15
+ def response_class
16
+ SpheroPwn::Commands::IsPageBlank::Response
17
+ end
18
+ end
19
+
20
+ # The response to a boot main application command.
21
+ class SpheroPwn::Commands::IsPageBlank::Response < SpheroPwn::Response
22
+ # @return {Boolean} true if the flash memory page is blank
23
+ attr_reader :is_blank
24
+ alias_method :is_blank?, :is_blank
25
+
26
+ # @see {SpheroPwn::Response#initialize}
27
+ def initialize(code_byte, sequence_byte, data_bytes)
28
+ super
29
+
30
+ if code == :ok
31
+ @is_blank = data_bytes[0] > 0
32
+ else
33
+ @is_blank = nil
34
+ end
35
+ end
36
+ end
@@ -13,4 +13,3 @@ end
13
13
  # The response to an L1 diagnostics command.
14
14
  class SpheroPwn::Commands::L1Diagnostics::Response < SpheroPwn::Response
15
15
  end
16
-
@@ -0,0 +1,50 @@
1
+ # Asks the robot for its debugging counters.
2
+ #
3
+ # This command only works when the robot is in user hacking mode.
4
+ class SpheroPwn::Commands::L2Diagnostics < SpheroPwn::Command
5
+ def initialize
6
+ super 0x00, 0x41, nil
7
+ end
8
+
9
+ # @see {SpheroPwn::Command#response_class}
10
+ def response_class
11
+ SpheroPwn::Commands::L2Diagnostics::Response
12
+ end
13
+ end
14
+
15
+ # The response to an L2 diagnostics command.
16
+ class SpheroPwn::Commands::L2Diagnostics::Response < SpheroPwn::Response
17
+ # @return {Hash<Symbol, Number>} debugging information counters
18
+ attr_reader :counters
19
+
20
+ # @see {SpheroPwn::Response#initialize}
21
+ def initialize(code_byte, sequence_byte, data_bytes)
22
+ super
23
+
24
+ @counters = {}
25
+ if code == :ok
26
+ data_string = data_bytes.pack('C*')
27
+ response_version = data_bytes[0x02]
28
+ if response_version >= 1
29
+ @counters.merge! received_good: data_string[0x03, 4].unpack('N'),
30
+ reserved1: data_bytes[0x02],
31
+ bad_device_id: data_string[0x07, 4].unpack('N'),
32
+ bad_data_length: data_string[0x0B, 4].unpack('N'),
33
+ bad_command_id: data_string[0x0F, 4].unpack('N'),
34
+ bad_checksum: data_string[0x13, 4].unpack('N'),
35
+ rx_buffer_overrun: data_string[0x17, 4].unpack('N'),
36
+ transmitted: data_string[0x1B, 4].unpack('N'),
37
+ tx_buffer_overrun: data_string[0x1F, 4].unpack('N'),
38
+ last_boot_reason: data_bytes[0x23],
39
+ boots_by_reason: data_string[0x24, 32].unpack('n*'),
40
+ reserved2: data_string[0x44, 2].unpack('n'),
41
+ charge_count: data_string[0x46, 2].unpack('n'),
42
+ seconds_since_charge: data_string[0x48, 2].unpack('n'),
43
+ seconds_on: data_string[0x4A, 4].unpack('N'),
44
+ distance_rolled: data_string[0x4E, 4].unpack('N'),
45
+ i2c_failures: data_string[0x52, 2].unpack('n'),
46
+ gyro_adjusts: data_string[0x54, 4].unpack('N')
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,25 @@
1
+ # Asks the robot about the versions of its software stack components.
2
+ class SpheroPwn::Commands::SetDeviceMode < SpheroPwn::Command
3
+ def initialize(mode)
4
+
5
+ mode_byte = case mode
6
+ when :normal
7
+ 0x00
8
+ when :user_hack
9
+ 0x01
10
+ else
11
+ raise ArgumentError, "Unimplemented mode #{mode.inspect}"
12
+ end
13
+
14
+ super 0x02, 0x42, [mode_byte]
15
+ end
16
+
17
+ # @see {SpheroPwn::Command#response_class}
18
+ def response_class
19
+ SpheroPwn::Commands::SetDeviceMode::Response
20
+ end
21
+ end
22
+
23
+ # The versions of a robot's software stack.
24
+ class SpheroPwn::Commands::SetDeviceMode::Response < SpheroPwn::Response
25
+ end
@@ -0,0 +1,29 @@
1
+ # Sets the robot's configuration flags that persist across power cycles.
2
+ class SpheroPwn::Commands::SetPermanentFlags < SpheroPwn::Command
3
+ # @param {Hash<Symbol, Boolean>} maps developer-friendly flag names to
4
+ # whether the corresponding bits will be set in the flags field
5
+ def initialize(new_flags)
6
+ flags_number = 0
7
+ new_flags.each do |name, value|
8
+ mask = SpheroPwn::Commands::SetPermanentFlags::FLAGS[name]
9
+ if mask.nil?
10
+ raise ArgumentError, "Unknown flag #{name.inspect}"
11
+ end
12
+ flags_number |= mask if value
13
+ end
14
+
15
+ super 0x02, 0x35, [flags_number].pack('N').unpack('C*')
16
+ end
17
+
18
+ # @see {SpheroPwn::Command#response_class}
19
+ def response_class
20
+ SpheroPwn::Commands::SetPermanentFlags::Response
21
+ end
22
+
23
+ # @return {Hash<Symbol, Number>} numbers for the symbolic values
24
+ FLAGS = SpheroPwn::Commands::GetPermanentFlags::FLAGS.invert.freeze
25
+ end
26
+
27
+ # The robot's configuration flags that persist across power cycles.
28
+ class SpheroPwn::Commands::SetPermanentFlags::Response < SpheroPwn::Response
29
+ end
@@ -65,14 +65,18 @@ class SpheroPwn::Session
65
65
  # @return {Response, Async} the response read from the channel; can be nil if
66
66
  # no message was received or if the checksum verification failed
67
67
  def recv_message
68
- return nil if @channel.recv_bytes(1).ord != 0xFF
68
+ start_of_packet = @channel.recv_bytes 1
69
+ return nil if start_of_packet.empty? || start_of_packet.ord != 0xFF
69
70
 
70
71
  packet_type = @channel.recv_bytes 1
72
+ return nil if packet_type.empty?
71
73
  case packet_type.ord
72
74
  when 0xFF
73
75
  read_response
74
76
  when 0xFE
75
77
  read_async_message
78
+ else
79
+ nil
76
80
  end
77
81
  end
78
82
 
@@ -103,12 +107,14 @@ class SpheroPwn::Session
103
107
  def read_response
104
108
  header_bytes = @channel.recv_bytes(3).unpack('C*')
105
109
  response_code, sequence, data_length = *header_bytes
110
+ return nil unless data_length
106
111
 
107
112
  # It may seem that it'd be better to look up the sequence number and bail
108
113
  # early if we don't find it. However, in order to avoid misleading error
109
114
  # messages, we don't want to touch anything in the message until we know
110
115
  # that the checksum is valid.
111
- data_bytes = @channel.recv_bytes(data_length).unpack('C*')
116
+ return nil unless data = @channel.recv_bytes(data_length)
117
+ data_bytes = data.unpack 'C*'
112
118
  checksum = data_bytes.pop
113
119
  unless self.class.valid_checksum?(header_bytes, data_bytes, checksum)
114
120
  return nil
@@ -130,13 +136,15 @@ class SpheroPwn::Session
130
136
  def read_async_message
131
137
  header_bytes = @channel.recv_bytes(3).unpack('C*')
132
138
  class_id, length_msb, length_lsb = *header_bytes
133
- data_length = (length_msb << 8) | length_lsb
139
+ return nil unless length_msb && length_lsb
134
140
 
135
141
  # It may seem that it'd be better to look up the sequence number and bail
136
142
  # early if we don't find it. However, in order to avoid misleading error
137
143
  # messages, we don't want to touch anything in the message until we know
138
144
  # that the checksum is valid.
139
- data_bytes = @channel.recv_bytes(data_length).unpack('C*')
145
+ data_length = (length_msb << 8) | length_lsb
146
+ return nil unless data = @channel.recv_bytes(data_length)
147
+ data_bytes = data.unpack 'C*'
140
148
  checksum = data_bytes.pop
141
149
  unless self.class.valid_checksum?(header_bytes, data_bytes, checksum)
142
150
  return nil
@@ -0,0 +1,130 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+ # stub: sphero_pwn 0.0.1 ruby lib
6
+
7
+ Gem::Specification.new do |s|
8
+ s.name = "sphero_pwn"
9
+ s.version = "0.0.1"
10
+
11
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
+ s.require_paths = ["lib"]
13
+ s.authors = ["Victor Costan"]
14
+ s.date = "2015-12-15"
15
+ s.description = "This library is currently focused on reverse-engineering\n the undocumented parts of Sphero"
16
+ s.email = "victor@costan.us"
17
+ s.executables = ["sphero_diagnostics.rb", "sphero_map_flash.rb", "sphero_save_flash_block.rb", "sphero_set_flag.rb", "sphero_soft_reboot.rb", "sphero_toggle_user_hack.rb"]
18
+ s.extra_rdoc_files = [
19
+ "LICENSE.txt",
20
+ "README.markdown"
21
+ ]
22
+ s.files = [
23
+ ".document",
24
+ ".travis.yml",
25
+ "Gemfile",
26
+ "Gemfile.lock",
27
+ "LICENSE.txt",
28
+ "README.markdown",
29
+ "Rakefile",
30
+ "VERSION",
31
+ "bin/sphero_diagnostics.rb",
32
+ "bin/sphero_map_flash.rb",
33
+ "bin/sphero_save_flash_block.rb",
34
+ "bin/sphero_set_flag.rb",
35
+ "bin/sphero_soft_reboot.rb",
36
+ "bin/sphero_toggle_user_hack.rb",
37
+ "lib/sphero_pwn.rb",
38
+ "lib/sphero_pwn/async.rb",
39
+ "lib/sphero_pwn/asyncs.rb",
40
+ "lib/sphero_pwn/asyncs/flash_block.rb",
41
+ "lib/sphero_pwn/asyncs/l1_diagnostics.rb",
42
+ "lib/sphero_pwn/channel.rb",
43
+ "lib/sphero_pwn/channel_recorder.rb",
44
+ "lib/sphero_pwn/command.rb",
45
+ "lib/sphero_pwn/commands.rb",
46
+ "lib/sphero_pwn/commands/boot_main_app.rb",
47
+ "lib/sphero_pwn/commands/enter_bootloader.rb",
48
+ "lib/sphero_pwn/commands/get_device_mode.rb",
49
+ "lib/sphero_pwn/commands/get_flash_block.rb",
50
+ "lib/sphero_pwn/commands/get_permanent_flags.rb",
51
+ "lib/sphero_pwn/commands/get_versions.rb",
52
+ "lib/sphero_pwn/commands/is_page_blank.rb",
53
+ "lib/sphero_pwn/commands/l1_diagnostics.rb",
54
+ "lib/sphero_pwn/commands/l2_diagnostics.rb",
55
+ "lib/sphero_pwn/commands/ping.rb",
56
+ "lib/sphero_pwn/commands/set_device_mode.rb",
57
+ "lib/sphero_pwn/commands/set_permanent_flags.rb",
58
+ "lib/sphero_pwn/replay_channel.rb",
59
+ "lib/sphero_pwn/response.rb",
60
+ "lib/sphero_pwn/session.rb",
61
+ "lib/sphero_pwn/test_channel.rb",
62
+ "sphero_pwn.gemspec",
63
+ "test/channel_recorder_test.rb",
64
+ "test/command_test.rb",
65
+ "test/commands/boot_main_app_test.rb",
66
+ "test/commands/enter_bootloader_test.rb",
67
+ "test/commands/get_device_mode_test.rb",
68
+ "test/commands/get_flash_block_test.rb",
69
+ "test/commands/get_permanent_flags_test.rb",
70
+ "test/commands/get_versions_test.rb",
71
+ "test/commands/is_page_blank_test.rb",
72
+ "test/commands/l1_diagnostics_test.rb",
73
+ "test/commands/l2_diagnostics_test.rb",
74
+ "test/commands/ping_test.rb",
75
+ "test/commands/set_device_mode_test.rb",
76
+ "test/commands/set_permanent_flags_test.rb",
77
+ "test/data/enter_bootloader.txt",
78
+ "test/data/get_device_mode.txt",
79
+ "test/data/get_factory_config.txt",
80
+ "test/data/get_permanent_flags.txt",
81
+ "test/data/get_soul.txt",
82
+ "test/data/get_version.txt",
83
+ "test/data/is_page_blank.txt",
84
+ "test/data/l1_diagnostics.txt",
85
+ "test/data/l2_diagnostics.txt",
86
+ "test/data/ping.txt",
87
+ "test/data/set_device_mode.txt",
88
+ "test/data/set_permanent_flags.txt",
89
+ "test/helper.rb",
90
+ "test/replay_channel_test.rb",
91
+ "test/response_test.rb",
92
+ "test/session_test.rb",
93
+ "test/sphero_pwn_test.rb"
94
+ ]
95
+ s.homepage = "http://github.com/pwnall/sphero_pwn"
96
+ s.licenses = ["MIT"]
97
+ s.rubygems_version = "2.4.5.1"
98
+ s.summary = "Wrapper for Sphero's bluetooth protocol"
99
+
100
+ if s.respond_to? :specification_version then
101
+ s.specification_version = 4
102
+
103
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
104
+ s.add_runtime_dependency(%q<rubyserial>, [">= 0.2.4"])
105
+ s.add_development_dependency(%q<bundler>, [">= 1.3.5"])
106
+ s.add_development_dependency(%q<jeweler>, [">= 2.0.1"])
107
+ s.add_development_dependency(%q<minitest>, [">= 5.8.3"])
108
+ s.add_development_dependency(%q<mocha>, [">= 1.1.0"])
109
+ s.add_development_dependency(%q<simplecov>, [">= 0"])
110
+ s.add_development_dependency(%q<yard>, [">= 0.8.7.6"])
111
+ else
112
+ s.add_dependency(%q<rubyserial>, [">= 0.2.4"])
113
+ s.add_dependency(%q<bundler>, [">= 1.3.5"])
114
+ s.add_dependency(%q<jeweler>, [">= 2.0.1"])
115
+ s.add_dependency(%q<minitest>, [">= 5.8.3"])
116
+ s.add_dependency(%q<mocha>, [">= 1.1.0"])
117
+ s.add_dependency(%q<simplecov>, [">= 0"])
118
+ s.add_dependency(%q<yard>, [">= 0.8.7.6"])
119
+ end
120
+ else
121
+ s.add_dependency(%q<rubyserial>, [">= 0.2.4"])
122
+ s.add_dependency(%q<bundler>, [">= 1.3.5"])
123
+ s.add_dependency(%q<jeweler>, [">= 2.0.1"])
124
+ s.add_dependency(%q<minitest>, [">= 5.8.3"])
125
+ s.add_dependency(%q<mocha>, [">= 1.1.0"])
126
+ s.add_dependency(%q<simplecov>, [">= 0"])
127
+ s.add_dependency(%q<yard>, [">= 0.8.7.6"])
128
+ end
129
+ end
130
+