prremote 0.1.0 → 0.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 38c2f68eda963d1330c36a056cd57d7f40aa20916aab7b254b190aea3f9cde11
4
- data.tar.gz: 6ef03b383f689c40a43a5449300e648cc693a1c7ebc974661398e2f290272396
3
+ metadata.gz: 8d080a70a00d10af1e419cae964da017bbc6650f85f54f3a18223b81bb204fe7
4
+ data.tar.gz: 158c193d72d4d414726f79183f6016017132077f37f0adc2f3238cd843e5672b
5
5
  SHA512:
6
- metadata.gz: 67e8c6d8ce84d1721f23671269460fa366ab3ceaf4316f535e185a8d4395b919d6cb746197934074a8942752ed389476a0e979d41705f22cb5053a0fbade6531
7
- data.tar.gz: 88100fc592979327716cf747bebbd06c90087c8193c2bb7fc692fab36fb7a3f9d38d68b97c5b5533c3a682ca0ac4e55b39c311ff28c2ac0643aaea3fe1fd3a2f
6
+ metadata.gz: 47d36cf128e0106b732273bad92aeb3af77c97a4e93f167e77d3ea34999578976a75f7babe8e3e910c6cf364422821cca6e5886cc7c7e5fa327a032a7d3bafb9
7
+ data.tar.gz: a63fae436f971e92d97386933706f99396dfcfc1ecf105c4ded5215aa1410d3ba3fadeb49805a9735a7285c1b6202da51dad325438d9b13d444756d48d88906e
data/README.md CHANGED
@@ -12,7 +12,9 @@ Inspired by [mpremote](https://docs.micropython.org/en/latest/reference/mpremote
12
12
 
13
13
  - Ruby 3.x or later
14
14
  - Raspberry Pi Pico W
15
- - `mrbc` in your PATH (for `run`, `deploy`, and `eval`) — install via `brew install mruby` on macOS
15
+ - `mrbc` in your PATH (for `run`, `deploy`, and `eval`)
16
+ - macOS: `brew install mruby`
17
+ - Ubuntu / Raspberry Pi OS: `sudo apt install mruby`
16
18
 
17
19
  ---
18
20
 
@@ -43,16 +45,18 @@ prremote run app.rb
43
45
 
44
46
  ### `install`
45
47
 
46
- Flash the prremote runtime firmware to a Pico W.
48
+ Flash the prremote runtime firmware to a Pico W or Pico.
47
49
 
48
50
  ```bash
49
- prremote install
50
- prremote install --version 0.1.1 # specify a runtime version
51
+ prremote install # Pico W (default)
52
+ prremote install --board pico # Pico (no wireless)
53
+ prremote install --version 0.1.1 # specify a runtime version
54
+ prremote install --board pico --version 0.1.1
51
55
  ```
52
56
 
53
57
  The firmware is downloaded from GitHub Releases on first use and cached in `~/.prremote/runtime/`. Subsequent installs use the cache.
54
58
 
55
- Put the Pico W into BOOTSEL mode (hold BOOTSEL, connect USB, release) when prompted.
59
+ Put the device into BOOTSEL mode (hold BOOTSEL, connect USB, release) when prompted.
56
60
 
57
61
  ---
58
62
 
@@ -140,9 +144,9 @@ Show the gem version, mrbc version, and the connected device's runtime version.
140
144
 
141
145
  ```bash
142
146
  prremote version
143
- # prremote: 0.1.0
147
+ # prremote: 0.1.1
144
148
  # mrbc: 3.3.0 (/usr/local/bin/mrbc)
145
- # runtime: 0.1.2
149
+ # runtime: 0.1.3
146
150
  ```
147
151
 
148
152
  ---
@@ -199,4 +203,5 @@ Scripts saved via `deploy` are stored in flash and run automatically on every bo
199
203
  ## Related Projects
200
204
 
201
205
  - [mruby/c](https://github.com/mrubyc/mrubyc) — Lightweight mruby implementation used in the runtime
206
+ - [picotool](https://github.com/raspberrypi/picotool) — Official Raspberry Pi tool for inspecting and managing Pico devices; useful for checking what's on flash or force-rebooting outside of prremote
202
207
  - [mpremote](https://docs.micropython.org/en/latest/reference/mpremote.html) — MicroPython equivalent (inspiration)
data/lib/prremote/cli.rb CHANGED
@@ -20,12 +20,18 @@ module Prremote
20
20
  true
21
21
  end
22
22
 
23
- desc 'install', 'Flash prremote runtime firmware to Pico W'
23
+ desc 'install', 'Flash prremote runtime firmware to Pico W or Pico'
24
24
  option :version, type: :string, desc: "Firmware version to install (default: #{RUNTIME_VERSION})"
25
+ option :board, type: :string, desc: 'Board type: pico or picow (default: picow)'
25
26
  def install
26
27
  version = options[:version] || RUNTIME_VERSION
27
- Commands::Install.new(version: version).call
28
- rescue RuntimeError => e
28
+ board = options[:board] || 'picow'
29
+ unless RuntimeManager::BOARDS.include?(board)
30
+ raise Thor::Error, "Unknown board '#{board}'. Valid values: #{RuntimeManager::BOARDS.join(', ')}"
31
+ end
32
+
33
+ Commands::Install.new(version: version, board: board).call
34
+ rescue StandardError => e
29
35
  raise Thor::Error, e.message
30
36
  end
31
37
 
@@ -33,7 +39,7 @@ module Prremote
33
39
  def run_script(file)
34
40
  port = resolve_port
35
41
  Commands::Run.new(port: port, baud: options[:baud]).call(file)
36
- rescue RuntimeError => e
42
+ rescue StandardError => e
37
43
  raise Thor::Error, e.message
38
44
  end
39
45
  map 'run' => :run_script
@@ -42,7 +48,7 @@ module Prremote
42
48
  def deploy(file)
43
49
  port = resolve_port
44
50
  Commands::Deploy.new(port: port, baud: options[:baud]).call(file)
45
- rescue RuntimeError => e
51
+ rescue StandardError => e
46
52
  raise Thor::Error, e.message
47
53
  end
48
54
 
@@ -50,7 +56,7 @@ module Prremote
50
56
  def undeploy
51
57
  port = resolve_port
52
58
  Commands::Undeploy.new(port: port, baud: options[:baud]).call
53
- rescue RuntimeError => e
59
+ rescue StandardError => e
54
60
  raise Thor::Error, e.message
55
61
  end
56
62
 
@@ -58,7 +64,7 @@ module Prremote
58
64
  def eval(expr)
59
65
  port = resolve_port
60
66
  Commands::EvalCmd.new(port: port, baud: options[:baud]).call(expr)
61
- rescue RuntimeError => e
67
+ rescue StandardError => e
62
68
  raise Thor::Error, e.message
63
69
  end
64
70
 
@@ -66,7 +72,7 @@ module Prremote
66
72
  def watch(file)
67
73
  port = resolve_port
68
74
  Commands::Watch.new(port: port, baud: options[:baud]).call(file)
69
- rescue RuntimeError => e
75
+ rescue StandardError => e
70
76
  raise Thor::Error, e.message
71
77
  end
72
78
 
@@ -87,7 +93,7 @@ module Prremote
87
93
  serial.write("\x03")
88
94
  sleep 0.1
89
95
  puts 'Reset signal sent.'
90
- rescue RuntimeError => e
96
+ rescue StandardError => e
91
97
  raise Thor::Error, e.message
92
98
  ensure
93
99
  serial&.close
@@ -99,7 +105,7 @@ module Prremote
99
105
 
100
106
  begin
101
107
  puts "mrbc: #{Mrbc.version} (#{Mrbc.bin})"
102
- rescue RuntimeError => e
108
+ rescue StandardError => e
103
109
  puts "mrbc: (#{e.message})"
104
110
  end
105
111
 
@@ -108,7 +114,7 @@ module Prremote
108
114
 
109
115
  private
110
116
 
111
- def fetch_runtime_version
117
+ def fetch_runtime_version # rubocop:disable Metrics/MethodLength,Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
112
118
  port = options[:port] || Detector.find_device
113
119
  return '(no device connected)' unless port
114
120
 
@@ -116,14 +122,44 @@ module Prremote
116
122
  serial.write("\x03")
117
123
  buf = +''
118
124
  deadline = Time.now + 5
125
+
119
126
  loop do
120
- buf << (serial.read(256) || '').gsub("\r\n", "\n").gsub("\r", '')
127
+ begin
128
+ buf << (serial.read(256) || '').gsub("\r\n", "\n").gsub("\r", '')
129
+ rescue StandardError
130
+ # Watchdog reboot dropped USB; wait for re-enumeration then reopen.
131
+ begin
132
+ serial.close
133
+ rescue StandardError
134
+ nil
135
+ end
136
+ serial = nil
137
+ reopen_deadline = [deadline, Time.now + 8].min
138
+ loop do
139
+ return '(not responding)' if Time.now > reopen_deadline
140
+
141
+ p = options[:port] || Detector.find_device
142
+ if p && File.exist?(p)
143
+ serial = begin
144
+ Serial.new(p, options[:baud])
145
+ rescue StandardError
146
+ nil
147
+ end
148
+ break if serial
149
+ end
150
+ sleep 0.3
151
+ end
152
+ buf = +''
153
+ next
154
+ end
155
+
121
156
  return ::Regexp.last_match(1) if buf =~ %r{READY prremote-runtime/([\d.]+)}
122
- break if Time.now > deadline
157
+ return '(not responding)' if Time.now > deadline
123
158
 
124
159
  sleep 0.05
125
160
  end
126
- '(not responding)'
161
+ rescue StandardError => e
162
+ "(#{e.message})"
127
163
  ensure
128
164
  serial&.close
129
165
  end
@@ -3,14 +3,16 @@ require 'fileutils'
3
3
  module Prremote
4
4
  module Commands
5
5
  class Install
6
- def initialize(version: RUNTIME_VERSION)
6
+ def initialize(version: RUNTIME_VERSION, board: 'picow')
7
7
  @version = version
8
+ @board = board
8
9
  end
9
10
 
10
11
  def call
11
- uf2_path = RuntimeManager.fetch(@version)
12
+ uf2_path = RuntimeManager.fetch(@version, @board)
12
13
 
13
- puts 'Put the Pico W into BOOTSEL mode:'
14
+ device_label = @board == 'picow' ? 'Pico W' : 'Pico'
15
+ puts "Put the #{device_label} into BOOTSEL mode:"
14
16
  puts ' 1. Hold the BOOTSEL button'
15
17
  puts ' 2. Connect USB (or press RUN while holding BOOTSEL)'
16
18
  puts ' 3. Release BOOTSEL — RPI-RP2 should appear as a USB drive'
@@ -10,18 +10,67 @@ module Prremote
10
10
 
11
11
  def call
12
12
  serial = Serial.new(@port, @baud)
13
- sleep 0.5
14
- serial.read(4096)
13
+ serial.write("\x03")
14
+
15
+ # \x03 may trigger a watchdog reboot (if a script was running).
16
+ # wait_for_ready returns the active serial (original or reopened).
17
+ serial = wait_for_ready(serial)
15
18
 
16
19
  serial.write(ERASE_MAGIC)
17
20
  wait_for_erased(serial)
18
21
  warn 'Flash erased. Device will no longer auto-run a script on boot.'
19
22
  ensure
20
- serial&.close
23
+ begin
24
+ serial&.close
25
+ rescue StandardError
26
+ nil
27
+ end
21
28
  end
22
29
 
23
30
  private
24
31
 
32
+ # Returns the serial object in READY state (may be a new object after reboot).
33
+ def wait_for_ready(serial) # rubocop:disable Metrics/MethodLength,Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
34
+ buf = +''
35
+ deadline = Time.now + 10
36
+ loop do
37
+ begin
38
+ buf << (serial.read(256) || '').gsub("\r\n", "\n").gsub("\r", '')
39
+ rescue StandardError
40
+ # Watchdog reboot dropped USB; wait for device to re-enumerate.
41
+ begin
42
+ serial.close
43
+ rescue StandardError
44
+ nil
45
+ end
46
+ serial = nil
47
+ reopen_deadline = [deadline, Time.now + 8].min
48
+ loop do
49
+ raise 'Timeout waiting for device to reconnect' if Time.now > reopen_deadline
50
+
51
+ candidate = Detector.find_device || @port
52
+ if candidate && File.exist?(candidate)
53
+ begin
54
+ serial = Serial.new(candidate, @baud)
55
+ @port = candidate
56
+ break
57
+ rescue StandardError
58
+ nil
59
+ end
60
+ end
61
+ sleep 0.3
62
+ end
63
+ buf = +''
64
+ next
65
+ end
66
+
67
+ return serial if buf.include?('READY prremote-runtime/')
68
+ raise 'Timeout waiting for READY' if Time.now > deadline
69
+
70
+ sleep 0.05
71
+ end
72
+ end
73
+
25
74
  def wait_for_erased(serial)
26
75
  buf = +''
27
76
  deadline = Time.now + 30
@@ -34,7 +34,7 @@ module Prremote
34
34
 
35
35
  def run(rb_path)
36
36
  Run.new(port: @port, baud: @baud).call(rb_path)
37
- rescue RuntimeError => e
37
+ rescue StandardError => e
38
38
  warn "Error: #{e.message}"
39
39
  end
40
40
  end
@@ -4,33 +4,32 @@ require 'fileutils'
4
4
 
5
5
  module Prremote
6
6
  module RuntimeManager
7
- REPO = 'lumbermill/prremote'.freeze
8
- BOARD = 'picow'.freeze
7
+ BOARDS = %w[pico picow].freeze
9
8
 
10
- def self.uf2_filename(version)
11
- "prremote-#{BOARD}-runtime-#{version}.uf2"
9
+ def self.uf2_filename(version, board)
10
+ "prremote-#{board}-runtime-#{version}.uf2"
12
11
  end
13
12
 
14
- def self.release_url(version)
15
- "https://github.com/#{REPO}/releases/download/runtime-#{version}/#{uf2_filename(version)}"
13
+ def self.release_url(version, board)
14
+ "https://github.com/lumbermill/prremote/releases/download/runtime-#{version}/#{uf2_filename(version, board)}"
16
15
  end
17
16
 
18
17
  def self.cache_dir
19
18
  File.join(Dir.home, '.prremote', 'runtime')
20
19
  end
21
20
 
22
- def self.cached_path(version)
23
- File.join(cache_dir, uf2_filename(version))
21
+ def self.cached_path(version, board)
22
+ File.join(cache_dir, uf2_filename(version, board))
24
23
  end
25
24
 
26
- def self.fetch(version)
27
- path = cached_path(version)
25
+ def self.fetch(version, board)
26
+ path = cached_path(version, board)
28
27
  return path if File.exist?(path)
29
28
 
30
29
  FileUtils.mkdir_p(cache_dir)
31
- $stderr.print "Downloading #{uf2_filename(version)}..."
30
+ $stderr.print "Downloading #{uf2_filename(version, board)}..."
32
31
  $stderr.flush
33
- download(release_url(version), path)
32
+ download(release_url(version, board), path)
34
33
  warn ' done.'
35
34
  path
36
35
  end
@@ -1,4 +1,4 @@
1
1
  module Prremote
2
- VERSION = '0.1.0'.freeze
3
- RUNTIME_VERSION = '0.1.2'.freeze
2
+ VERSION = '0.1.1'.freeze
3
+ RUNTIME_VERSION = '0.1.3'.freeze
4
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prremote
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - ITO Yosei