prremote 0.4.2 → 0.4.3

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: e8eeb5a287e2d90f2c3fdab1324a778be063eee3bb8eeb5577803113478f7afe
4
- data.tar.gz: f58b759474589038e3c4f5b4fc5c0504543fa2c03f4fde9957a30fe2d6f198f1
3
+ metadata.gz: 27b1b34fbd6bbb46d3adb571005dafe869b286b7bc2babc547af1c72cd7f9cd6
4
+ data.tar.gz: 6f35395ede42d344d2b5de27502fe20631551a94ba1c6ea115abefccd4cda9d0
5
5
  SHA512:
6
- metadata.gz: bec989326521193f9abcdd90794d79d2acefe8f9d41721507188cd267dc9fbb085c75bc953e7c14960f6ccdb6106da8f5cc276f4a8a7b861038092beeb74af57
7
- data.tar.gz: f5087a5ab44764509e10b7c70b4b1214223cebdf3ff8f72adee7740297a8deeb7b37c8729a27511484630521ba8da20c55102174e8333728ffd36c39fbd37b12
6
+ metadata.gz: 9097fa9e4df160a9bf2bf4b4667d56a0fc3c8600e359d568b9aa028a9f3affb899b3e7a7ad2f9ae0204b824df378cbaa4f6c2935214807b504d70d59014eeee7
7
+ data.tar.gz: 0be3d04ce9b4bd1b07cb1d690b4a844d33c9f6e76c6594b270714214eef1c9662a2eb2151d26ee1e1123a8c6ea0c6c8b8b11caf552d1f62901d5cdd394a2038f
data/README.md CHANGED
@@ -77,7 +77,7 @@ prremote install -b picow --version 0.1.1 # specify a runtime version
77
77
 
78
78
  The firmware is downloaded from GitHub Releases on first use and cached in `~/.prremote/runtime/`. Subsequent installs use the cache.
79
79
 
80
- Pico boards: put the device into BOOTSEL mode (hold BOOTSEL, connect USB, release) when prompted. The drive appears as `RPI-RP2` on Pico / Pico W and as `RP2350` on Pico 2.
80
+ Pico boards: if the board is already running the prremote runtime, `install` reboots it into BOOTSEL mode by itself (a 1200 bps touch on its serial port). Otherwise — a fresh board, or one running something else — put it into BOOTSEL mode (hold BOOTSEL, connect USB, release) when prompted. The drive appears as `RPI-RP2` on Pico / Pico W and as `RP2350` on Pico 2.
81
81
 
82
82
  ESP32 (classic): no button dance and no extra tools needed — the firmware is written over the serial port by prremote's pure-Ruby implementation of the Espressif bootloader protocol (the chip is reset into its boot ROM automatically, and the write is verified with an on-chip MD5).
83
83
 
@@ -183,8 +183,8 @@ Show the gem version, mrbc version, and the connected device's runtime version.
183
183
 
184
184
  ```bash
185
185
  prremote version
186
- # prremote: 0.4.2
187
- # runtime: 0.4.2 board=picow (/dev/tty.usbmodem101)
186
+ # prremote: 0.4.3
187
+ # runtime: 0.4.3 board=picow (/dev/tty.usbmodem101)
188
188
  # mrbc: mruby 4.0.0 (2026-04-20) (/opt/homebrew/bin/mrbc)
189
189
  ```
190
190
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.2
1
+ 0.4.3
@@ -14,6 +14,15 @@ module Prremote
14
14
  # The UF2 bootloader exposes this file on the BOOTSEL volume.
15
15
  BOOTSEL_MARKER = 'INFO_UF2.TXT'.freeze
16
16
 
17
+ # pico-sdk's stdio_usb reboots into the BOOTSEL boot ROM when the host
18
+ # sets this baud rate (PICO_STDIO_USB_RESET_MAGIC_BAUD_RATE), so a board
19
+ # already running the runtime can be flashed without the button.
20
+ RESET_BAUD = 1200
21
+
22
+ # How long to wait for the BOOTSEL drive after the 1200 bps touch before
23
+ # falling back to the manual instructions.
24
+ AUTO_BOOTSEL_TIMEOUT = 10
25
+
17
26
  # Boards whose install path has not been run on real hardware yet.
18
27
  UNVERIFIED_BOARDS = %w[pico2].freeze
19
28
 
@@ -38,14 +47,7 @@ module Prremote
38
47
  uf2_path = RuntimeManager.fetch(@version, @board)
39
48
 
40
49
  warn_unverified
41
- puts "Put the #{device_label} into BOOTSEL mode:"
42
- puts ' 1. Hold the BOOTSEL button'
43
- puts ' 2. Connect USB (or press RUN while holding BOOTSEL)'
44
- puts " 3. Release BOOTSEL — #{volume_name} should appear as a USB drive"
45
- puts
46
- puts "Waiting for #{volume_name}..."
47
-
48
- volume = wait_for_volume
50
+ volume = mounted_volume || auto_bootsel || manual_bootsel
49
51
  puts "Copying firmware to #{volume}..."
50
52
  FileUtils.cp(uf2_path, File.join(volume, File.basename(uf2_path)))
51
53
 
@@ -57,6 +59,55 @@ module Prremote
57
59
 
58
60
  private
59
61
 
62
+ # A board already sitting in BOOTSEL needs no reboot.
63
+ def mounted_volume
64
+ volume = volume_paths.find { |p| mounted?(p) }
65
+ puts "Found #{volume_name} at #{volume}." if volume
66
+ volume
67
+ end
68
+
69
+ # Reboots a board running the runtime into BOOTSEL with a 1200 bps
70
+ # touch. nil when there is no Pico port or the drive never shows up
71
+ # (no runtime on the board, a wedged runtime, or a WSL drive that still
72
+ # needs mounting), so the caller falls back to the button.
73
+ def auto_bootsel
74
+ port = @port || Detector.find_pico_port
75
+ return nil unless port
76
+
77
+ puts "Rebooting #{device_label} on #{port} into BOOTSEL mode..."
78
+ touch_reset_baud(port)
79
+ puts "Waiting for #{volume_name}..."
80
+ wait_for_volume(timeout: AUTO_BOOTSEL_TIMEOUT)
81
+ rescue RuntimeError
82
+ puts "#{volume_name} did not appear; falling back to manual BOOTSEL."
83
+ puts
84
+ nil
85
+ end
86
+
87
+ # Opening the port at the magic baud rate is the whole trigger. The board
88
+ # drops off USB right away, so errors while closing are expected.
89
+ def touch_reset_baud(port)
90
+ serial = Serial.new(port, RESET_BAUD)
91
+ rescue RubySerial::Error, SystemCallError => e
92
+ puts "Could not open #{port} at #{RESET_BAUD} bps: #{e.message}" if @verbose
93
+ ensure
94
+ begin
95
+ serial&.close
96
+ rescue RubySerial::Error, SystemCallError
97
+ nil
98
+ end
99
+ end
100
+
101
+ def manual_bootsel
102
+ puts "Put the #{device_label} into BOOTSEL mode:"
103
+ puts ' 1. Hold the BOOTSEL button'
104
+ puts ' 2. Connect USB (or press RUN while holding BOOTSEL)'
105
+ puts " 3. Release BOOTSEL — #{volume_name} should appear as a USB drive"
106
+ puts
107
+ puts "Waiting for #{volume_name}..."
108
+ wait_for_volume
109
+ end
110
+
60
111
  # No BOOTSEL dance on ESP32: the flasher toggles DTR/RTS to enter the
61
112
  # boot ROM by itself, so flashing works over the normal serial port.
62
113
  def install_esp32
@@ -155,10 +206,12 @@ module Prremote
155
206
  false
156
207
  end
157
208
 
209
+ # The marker, not the directory, is the test: macOS can leave an empty
210
+ # /Volumes/RPI-RP2 behind after the board reboots off a copy.
158
211
  def wait_for_volume(timeout: 60)
159
212
  deadline = Time.now + timeout
160
213
  loop do
161
- path = volume_paths.find { |p| File.directory?(p) }
214
+ path = volume_paths.find { |p| mounted?(p) }
162
215
  return path if path
163
216
  raise "Timed out waiting for #{volume_name} volume (#{timeout}s)" if Time.now > deadline
164
217
 
@@ -44,6 +44,17 @@ module Prremote
44
44
  '`usbipd list`, then `usbipd attach --wsl --busid <BUSID>`.'
45
45
  end
46
46
 
47
+ def self.find_pico_port
48
+ new.find_pico_port
49
+ end
50
+
51
+ # A port whose USB vendor is Raspberry Pi — the only kind the 1200 bps
52
+ # BOOTSEL touch applies to. nil when none (or the vendor can't be read).
53
+ def find_pico_port
54
+ label = KNOWN_VENDORS.dig(R2P2_VENDOR_IDS.first, :label)
55
+ serial_ports.find { |p| port_label(p) == label }
56
+ end
57
+
47
58
  def find_device
48
59
  candidates = serial_ports
49
60
  return candidates.first if candidates.size == 1
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.4.2
4
+ version: 0.4.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - ITO Yosei