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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6b51f0b12bbd766e4ad620a00be1d1841b90fe72
4
- data.tar.gz: c23728318daf4e7e42d756b195461c39f3cdd0be
3
+ metadata.gz: 9eac1b907fc6e5963161d6572b72c2d8f2ff241d
4
+ data.tar.gz: ae48c0c7bb5ca8a3c851bbaf512a7612e5caee5b
5
5
  SHA512:
6
- metadata.gz: 033e26a33d83a0e8a8ee49f38a43a69b75f975dbf81e2252055d1383aa5e96b275ebfb8ef1a49aa0d7d8961073eff0ec9d6f05e378528ac6f9cb492409cf0caf
7
- data.tar.gz: 409bd796603489498346cab6f01caff53231c78a9e05d34a3302a0eb4f1b24208e7b1ef591da29791b56b4f6789f212cdd8ae83a53c6e1627317759b3906a242
6
+ metadata.gz: 33bf8e533323091af250cccdeefbec7abb09ad2d71901ce9c7c61cc2aa4d35eb64b66cde688c28c8a088c7d2624f219e6eac4f89728ecf4da41dcd73c0faa421
7
+ data.tar.gz: f6357fc7cda85e4c77d25787827e99251f4304f3f9e34cc5b6f7dcfa45746545b037c2b07d01284311123d255b3b89982b42242f26c1bad5d5f5bd254c81cf74
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.0
1
+ 0.0.1
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/sphero_pwn.rb'
4
+
5
+ require 'pp'
6
+
7
+ unless ARGV.length == 1
8
+ puts <<END_USAGE
9
+ Usage: #{$PROGRAM_NAME} /dev/bluetooth-device
10
+
11
+ The block name can be soul|factory_config|user_config.
12
+ END_USAGE
13
+ exit 1
14
+ end
15
+
16
+ rfconn_path = ARGV[0]
17
+ channel = SpheroPwn::Channel.new rfconn_path
18
+ session = SpheroPwn::Session.new channel
19
+
20
+ session.send_command SpheroPwn::Commands::L1Diagnostics.new
21
+ response = nil
22
+ async = nil
23
+ while response.nil? || async.nil?
24
+ message = session.recv_message
25
+ if message.nil?
26
+ sleep 0.05
27
+ next
28
+ end
29
+
30
+ if message.kind_of? SpheroPwn::Response
31
+ response = message
32
+ if response.code == :ok
33
+ puts "Queued command to L1 diagnostics.\n"
34
+ else
35
+ puts "Failed to get L1 diagnostics. Code: #{response.code}\n"
36
+ exit 1
37
+ end
38
+ else
39
+ async = message
40
+ puts "L1 diagnostics:\n"
41
+ puts message.text
42
+ end
43
+ end
44
+
45
+ session.send_command SpheroPwn::Commands::L2Diagnostics.new
46
+ response = nil
47
+ async = nil
48
+ while response.nil? || async.nil?
49
+ message = session.recv_message
50
+ if message.nil?
51
+ sleep 0.05
52
+ next
53
+ end
54
+
55
+ if message.kind_of? SpheroPwn::Response
56
+ response = message
57
+ if response.code == :ok
58
+ puts "Queued command to L2 diagnostics.\n"
59
+ else
60
+ puts "Failed to get L2 diagnostics. Code: #{response.code}\n"
61
+ exit 1
62
+ end
63
+ else
64
+ async = message
65
+ puts "L2 diagnostics:\n"
66
+ pp message.counters
67
+ end
68
+ end
69
+
70
+ session.close
71
+ exit 0
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/sphero_pwn.rb'
4
+
5
+ unless ARGV.length == 1
6
+ puts <<END_USAGE
7
+ Usage: #{$PROGRAM_NAME} /dev/bluetooth-device
8
+ END_USAGE
9
+ exit 1
10
+ end
11
+
12
+ rfconn_path = ARGV[0]
13
+ channel = SpheroPwn::Channel.new rfconn_path
14
+ session = SpheroPwn::Session.new channel
15
+
16
+ session.send_command SpheroPwn::Commands::EnterBootloader.new
17
+ response = session.recv_until_response
18
+ unless response.code == :ok
19
+ puts "Failed to enter bootloader. Code: #{response.code}\n"
20
+ exit 1
21
+ end
22
+
23
+ page_map = []
24
+ first_good = 0
25
+ first_bad = 0
26
+
27
+ begin
28
+ loop do
29
+ print "Probing page #{first_good}... "
30
+ session.send_command SpheroPwn::Commands::IsPageBlank.new(first_good)
31
+ response = session.recv_until_response
32
+ case response.code
33
+ when :ok
34
+ page_map[first_good] = response.is_blank? ? '.' : '*'
35
+ puts(response.is_blank? ? 'blank' : 'used')
36
+ break
37
+ when :bad_page
38
+ page_map[first_good] = 'X'
39
+ puts 'bad'
40
+ first_good += 1
41
+ next
42
+ else
43
+ puts "Failed to get page status. Code: #{response.code}\n"
44
+ exit 1
45
+ end
46
+ end
47
+
48
+ first_bad = first_good + 1
49
+ loop do
50
+ print "Probing page #{first_bad}... "
51
+ session.send_command SpheroPwn::Commands::IsPageBlank.new(first_bad)
52
+ response = session.recv_until_response
53
+ case response.code
54
+ when :ok
55
+ page_map[first_bad] = response.is_blank? ? '.' : '*'
56
+ puts(response.is_blank? ? 'blank' : 'used')
57
+ first_bad += 1
58
+ next
59
+ when :bad_page
60
+ page_map[first_bad] = 'X'
61
+ puts 'bad'
62
+ break
63
+ else
64
+ puts "Failed to get page status. Code: #{response.code}\n"
65
+ exit 1
66
+ end
67
+ end
68
+ ensure
69
+ puts "First valid page: #{first_good}\n"
70
+ puts "Last valid page: #{first_bad - 1}\n"
71
+ puts "Page map:\n#{page_map.join('')}\n"
72
+
73
+ session.send_command SpheroPwn::Commands::BootMainApp.new
74
+ response = session.recv_until_response
75
+ unless response.code == :ok
76
+ puts "Failed to boot main application. Code: #{response.code}\n"
77
+ exit 1
78
+ end
79
+ end
80
+
81
+ session.close
82
+ exit 0
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/sphero_pwn.rb'
4
+
5
+ unless ARGV.length == 3
6
+ puts <<END_USAGE
7
+ Usage: #{$PROGRAM_NAME} /dev/bluetooth-device block_name flash_file.bin
8
+
9
+ The block name can be soul|factory_config|user_config.
10
+ END_USAGE
11
+ exit 1
12
+ end
13
+
14
+ rfconn_path = ARGV[0]
15
+ channel = SpheroPwn::Channel.new rfconn_path
16
+ session = SpheroPwn::Session.new channel
17
+
18
+ session.send_command SpheroPwn::Commands::GetFlashBlock.new ARGV[1].to_sym
19
+
20
+ response = nil
21
+ async = nil
22
+ while response.nil? || async.nil?
23
+ message = session.recv_message
24
+ if message.nil?
25
+ sleep 0.05
26
+ next
27
+ end
28
+
29
+ if message.kind_of? SpheroPwn::Response
30
+ response = message
31
+ if response.code == :ok
32
+ puts "Queued command to get #{ARGV[1]} block.\n"
33
+ else
34
+ puts "Failed to get #{ARGV[1]} block. Code: #{response.code}\n"
35
+ exit 1
36
+ end
37
+ else
38
+ async = message
39
+ File.open ARGV[2], 'wb' do |f|
40
+ f.write async.data_bytes.pack('C*')
41
+ end
42
+ puts "Wrote #{ARGV[1]} block to #{ARGV[2]}\n"
43
+ end
44
+ end
45
+
46
+ session.close
47
+ exit 0
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/sphero_pwn.rb'
4
+
5
+ unless ARGV.length == 3
6
+ puts <<END_USAGE
7
+ Usage: #{$PROGRAM_NAME} /dev/bluetooth-device flag_name {true|false}
8
+
9
+ Known flags: #{SpheroPwn::Commands::SetPermanentFlags::FLAGS.keys.join(' ')}
10
+ END_USAGE
11
+ exit 1
12
+ end
13
+
14
+ rfconn_path = ARGV[0]
15
+ channel = SpheroPwn::Channel.new rfconn_path
16
+ session = SpheroPwn::Session.new channel
17
+
18
+ session.send_command SpheroPwn::Commands::GetPermanentFlags.new
19
+ response = session.recv_until_response
20
+ unless response.code == :ok
21
+ puts "Failed to retrieve current flags. Code: #{response.code}\n"
22
+ exit 1
23
+ end
24
+ puts "Current flags: #{response.flags.inspect}\n"
25
+
26
+ new_flags = response.flags.merge({ ARGV[1].to_sym => (ARGV[2] == 'true') })
27
+ puts "New flags: #{new_flags}\n"
28
+
29
+ session.send_command SpheroPwn::Commands::SetPermanentFlags.new(new_flags)
30
+ response = session.recv_until_response
31
+ unless response.code == :ok
32
+ puts "Failed to set new flags. Code: #{response.code}\n"
33
+ exit 1
34
+ end
35
+
36
+ puts "New flags set successfully\n"
37
+ session.close
38
+ exit 0
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/sphero_pwn.rb'
4
+
5
+ unless ARGV.length == 1
6
+ puts <<END_USAGE
7
+ Usage: #{$PROGRAM_NAME} /dev/bluetooth-device
8
+ END_USAGE
9
+ exit 1
10
+ end
11
+
12
+ rfconn_path = ARGV[0]
13
+ channel = SpheroPwn::Channel.new rfconn_path
14
+ session = SpheroPwn::Session.new channel
15
+
16
+ session.send_command SpheroPwn::Commands::EnterBootloader.new
17
+ response = session.recv_until_response
18
+ unless response.code == :ok
19
+ puts "Failed to enter bootloader. Code: #{response.code}\n"
20
+ exit 1
21
+ end
22
+ puts "Entered bootloader\n"
23
+
24
+ session.send_command SpheroPwn::Commands::BootMainApp.new
25
+ response = session.recv_until_response
26
+ unless response.code == :ok
27
+ puts "Failed to boot main application. Code: #{response.code}\n"
28
+ exit 1
29
+ end
30
+ puts "Entered main app\n"
31
+
32
+ session.close
33
+ exit 0
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/sphero_pwn.rb'
4
+
5
+ unless ARGV.length == 1
6
+ puts <<END_USAGE
7
+ Usage: #{$PROGRAM_NAME} /dev/bluetooth-device
8
+ END_USAGE
9
+ exit 1
10
+ end
11
+
12
+ rfconn_path = ARGV[0]
13
+ channel = SpheroPwn::Channel.new rfconn_path
14
+ session = SpheroPwn::Session.new channel
15
+
16
+ session.send_command SpheroPwn::Commands::GetDeviceMode.new
17
+ response = session.recv_until_response
18
+ unless response.code == :ok
19
+ puts "Failed to retrieve device mode. Code: #{response.code}\n"
20
+ exit 1
21
+ end
22
+ puts "Current device mode: #{response.mode}\n"
23
+
24
+ new_mode = (response.mode == :normal) ? :user_hack : :normal
25
+ puts "Switching device to mode: #{new_mode}\n"
26
+
27
+ session.send_command SpheroPwn::Commands::SetDeviceMode.new(new_mode)
28
+ response = session.recv_until_response
29
+ unless response.code == :ok
30
+ puts "Failed to set device mode. Code: #{response.code}\n"
31
+ exit 1
32
+ end
33
+
34
+ puts "New mode set successfully\n"
35
+ session.close
36
+ exit 0
@@ -2,19 +2,29 @@
2
2
  module SpheroPwn
3
3
  end
4
4
 
5
- require 'sphero_pwn/async.rb'
6
- require 'sphero_pwn/asyncs.rb'
7
- require 'sphero_pwn/channel.rb'
8
- require 'sphero_pwn/channel_recorder.rb'
9
- require 'sphero_pwn/command.rb'
10
- require 'sphero_pwn/commands.rb'
11
- require 'sphero_pwn/replay_channel.rb'
12
- require 'sphero_pwn/response.rb'
13
- require 'sphero_pwn/session.rb'
14
- require 'sphero_pwn/test_channel.rb'
5
+ require_relative './sphero_pwn/async.rb'
6
+ require_relative './sphero_pwn/asyncs.rb'
7
+ require_relative './sphero_pwn/channel.rb'
8
+ require_relative './sphero_pwn/channel_recorder.rb'
9
+ require_relative './sphero_pwn/command.rb'
10
+ require_relative './sphero_pwn/commands.rb'
11
+ require_relative './sphero_pwn/replay_channel.rb'
12
+ require_relative './sphero_pwn/response.rb'
13
+ require_relative './sphero_pwn/session.rb'
14
+ require_relative './sphero_pwn/test_channel.rb'
15
15
 
16
- require 'sphero_pwn/asyncs/l1_diagnostics.rb'
16
+ require_relative './sphero_pwn/asyncs/flash_block.rb'
17
+ require_relative './sphero_pwn/asyncs/l1_diagnostics.rb'
17
18
 
18
- require 'sphero_pwn/commands/get_versions.rb'
19
- require 'sphero_pwn/commands/l1_diagnostics.rb'
20
- require 'sphero_pwn/commands/ping.rb'
19
+ require_relative './sphero_pwn/commands/boot_main_app.rb'
20
+ require_relative './sphero_pwn/commands/enter_bootloader.rb'
21
+ require_relative './sphero_pwn/commands/get_device_mode.rb'
22
+ require_relative './sphero_pwn/commands/get_flash_block.rb'
23
+ require_relative './sphero_pwn/commands/get_permanent_flags.rb'
24
+ require_relative './sphero_pwn/commands/get_versions.rb'
25
+ require_relative './sphero_pwn/commands/is_page_blank.rb'
26
+ require_relative './sphero_pwn/commands/l1_diagnostics.rb'
27
+ require_relative './sphero_pwn/commands/l2_diagnostics.rb'
28
+ require_relative './sphero_pwn/commands/ping.rb'
29
+ require_relative './sphero_pwn/commands/set_device_mode.rb'
30
+ require_relative './sphero_pwn/commands/set_permanent_flags.rb'
@@ -0,0 +1,26 @@
1
+ # The result of a get flash block request.
2
+ #
3
+ # This is an asynchronous message because it's too long to fit into the command
4
+ # response structure.
5
+ class SpheroPwn::Asyncs::FlashBlock < SpheroPwn::Async
6
+ # @return {String} the text form of the diagnostics
7
+ attr_reader :text
8
+
9
+ def initialize(data_bytes)
10
+ super
11
+ end
12
+ end
13
+
14
+ class SpheroPwn::Asyncs::FlashBlock::Config < SpheroPwn::Asyncs::FlashBlock
15
+ def self.id_code
16
+ 0x04
17
+ end
18
+ end
19
+ SpheroPwn::Asyncs.register SpheroPwn::Asyncs::FlashBlock::Config
20
+
21
+ class SpheroPwn::Asyncs::FlashBlock::Soul < SpheroPwn::Asyncs::FlashBlock
22
+ def self.id_code
23
+ 0x0D
24
+ end
25
+ end
26
+ SpheroPwn::Asyncs.register SpheroPwn::Asyncs::FlashBlock::Soul
@@ -10,29 +10,17 @@ class SpheroPwn::Channel
10
10
  #
11
11
  # @param {String} rfconn_path the path to the device file connecting to the
12
12
  # robot's Bluetooth RFCONN service
13
- def initialize(rfconn_path)
14
- give_up_at = Time.now + 15
15
- @port = nil
16
- while @port.nil?
17
- begin
18
- @port = Serial.new rfconn_path, 115200
19
- rescue RubySerial::Exception => e
20
- raise e unless e.message == 'EBUSY'
21
- raise e if Time.now >= give_up_at
22
- end
23
- end
24
-
13
+ # @param {Hash} options the options below
14
+ # @option {Number} connect_timeout retry for this number of seconds when the
15
+ # connection gets refused with EBUSY
16
+ def initialize(rfconn_path, options = {})
17
+ @connect_timeout = options[:connect_timeout] || 15
18
+ @read_timeout = options[:read_timeout] || 30
19
+ @read_backoff = options[:read_backoff] || 0.2
25
20
  @send_queue = Queue.new
26
- @send_thread = Thread.new @send_queue do
27
- send_queue = @send_queue
28
21
 
29
- loop do
30
- bytes = send_queue.pop
31
- break if bytes == :close
32
-
33
- @port.write bytes
34
- end
35
- end
22
+ @port = connect rfconn_path, @connect_timeout
23
+ @send_thread = spawn_sending_thread @send_queue, @port
36
24
  end
37
25
 
38
26
  # @param {String} bytes a binary-encoded string of bytes to be sent to the
@@ -43,15 +31,27 @@ class SpheroPwn::Channel
43
31
  self
44
32
  end
45
33
 
46
- # @param {Integer} count the number of bytes to be read from the RFCONN port
47
- def recv_bytes(count)
48
- retries_left = 100_000
49
- while retries_left > 0
50
- bytes = @port.read count
51
- return bytes unless bytes.empty?
52
- retries_left -= 1
34
+ # @param {Integer} byte_count the number of bytes to be read from the RFCONN
35
+ # port
36
+ # @return {String} a binary-encoded string of bytes retrieved from the RFCONN
37
+ # port; the string may have fewer bytes than requested
38
+ def recv_bytes(byte_count)
39
+ buffer = ''.encode Encoding::BINARY
40
+
41
+ last_byte_at = Time.now
42
+ loop do
43
+ new_bytes = @port.read byte_count - buffer.length
44
+ buffer.concat new_bytes
45
+ break if buffer.length == byte_count
46
+
47
+ if new_bytes.empty?
48
+ break if Time.now - last_byte_at >= @read_timeout
49
+ sleep @read_backoff
50
+ else
51
+ last_byte_at = Time.now
52
+ end
53
53
  end
54
- nil
54
+ buffer
55
55
  end
56
56
 
57
57
  # Gracefully shuts down the communication channel with the robot.
@@ -62,4 +62,47 @@ class SpheroPwn::Channel
62
62
  @port.close
63
63
  self
64
64
  end
65
+
66
+ # Connects to an RFCONN serial port.
67
+ #
68
+ # @param {String} rfconn_path the path to the device file connecting to the
69
+ # robot's Bluetooth RFCONN service
70
+ # @param {Number} timeout the number of seconds to retry connecting when
71
+ # getting EBUSY
72
+ # @return {Serial} the connected port
73
+ def connect(rfconn_path, timeout)
74
+ give_up_at = Time.now + timeout
75
+ port = nil
76
+ while port.nil?
77
+ begin
78
+ port = Serial.new rfconn_path, 115200, 8
79
+ rescue RubySerial::Exception => e
80
+ raise e unless e.message == 'EBUSY'
81
+ raise e if Time.now >= give_up_at
82
+ end
83
+ end
84
+ port
85
+ end
86
+ private :connect
87
+
88
+ # Creates a thread that reads data from a queue and writes it to an IO.
89
+ #
90
+ # The thread expects to pop String instances from the queue, and will
91
+ # write them to the IO. When the thread pops the :close symbol, it stops
92
+ # executing.
93
+ #
94
+ # @param {Queue} send_queue the queue that the tread will read data from
95
+ # @param {IO} io the IO that the data bytes will be written to
96
+ # @return {Thread} the newly created thread
97
+ def spawn_sending_thread(send_queue, io)
98
+ Thread.new do
99
+ loop do
100
+ bytes = send_queue.pop
101
+ break if bytes == :close
102
+
103
+ io.write bytes
104
+ end
105
+ end
106
+ end
107
+ private :spawn_sending_thread
65
108
  end