sphero_pwn 0.0.0 → 0.0.1

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