prremote 0.1.0 → 0.1.2
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 +4 -4
- data/README.md +15 -7
- data/lib/prremote/cli.rb +57 -14
- data/lib/prremote/commands/deploy.rb +1 -0
- data/lib/prremote/commands/install.rb +5 -3
- data/lib/prremote/commands/run.rb +1 -0
- data/lib/prremote/commands/undeploy.rb +52 -3
- data/lib/prremote/commands/watch.rb +1 -1
- data/lib/prremote/mrbc.rb +39 -6
- data/lib/prremote/runtime_manager.rb +11 -12
- data/lib/prremote/version.rb +2 -2
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9069bbf984dd62c31390570c934396581e816ad884511d248ebdf3a40657ae9d
|
|
4
|
+
data.tar.gz: 853acaa0a69a126503b90ce6726958d194cd56fd5abaf559ff759284d26bf1a6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: dff7ed500809d4d457b68ae7483e4d47632c5ced344d0c01237bcec981b589b57794a086508ac99ae7ac28f1b2f351e25dc3c3498b99fb87dac172c43720ebc9
|
|
7
|
+
data.tar.gz: 58fe0f9d4911c33c8ed9456810cef1405784ab9d778d8bb14502fb9832611877e897880ebfbbd84c29b6c69a82bf42add2f020037534fe809908816cff3409ef
|
data/README.md
CHANGED
|
@@ -12,7 +12,12 @@ 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`
|
|
15
|
+
- `mrbc` (mruby 4.x) for `run`, `deploy`, and `eval`
|
|
16
|
+
- macOS: `brew install mruby`
|
|
17
|
+
- Linux: build from source — [github.com/mruby/mruby/releases](https://github.com/mruby/mruby/releases)
|
|
18
|
+
(`sudo apt install mruby` installs mruby 3.x which is **not compatible**)
|
|
19
|
+
- If `mrbc` is not on your PATH, set the `MRBC` environment variable:
|
|
20
|
+
`MRBC=/path/to/mrbc prremote run app.rb`
|
|
16
21
|
|
|
17
22
|
---
|
|
18
23
|
|
|
@@ -43,16 +48,18 @@ prremote run app.rb
|
|
|
43
48
|
|
|
44
49
|
### `install`
|
|
45
50
|
|
|
46
|
-
Flash the prremote runtime firmware to a Pico W.
|
|
51
|
+
Flash the prremote runtime firmware to a Pico W or Pico.
|
|
47
52
|
|
|
48
53
|
```bash
|
|
49
|
-
prremote install
|
|
50
|
-
prremote install --
|
|
54
|
+
prremote install # Pico W (default)
|
|
55
|
+
prremote install --board pico # Pico (no wireless)
|
|
56
|
+
prremote install --version 0.1.1 # specify a runtime version
|
|
57
|
+
prremote install --board pico --version 0.1.1
|
|
51
58
|
```
|
|
52
59
|
|
|
53
60
|
The firmware is downloaded from GitHub Releases on first use and cached in `~/.prremote/runtime/`. Subsequent installs use the cache.
|
|
54
61
|
|
|
55
|
-
Put the
|
|
62
|
+
Put the device into BOOTSEL mode (hold BOOTSEL, connect USB, release) when prompted.
|
|
56
63
|
|
|
57
64
|
---
|
|
58
65
|
|
|
@@ -140,9 +147,9 @@ Show the gem version, mrbc version, and the connected device's runtime version.
|
|
|
140
147
|
|
|
141
148
|
```bash
|
|
142
149
|
prremote version
|
|
143
|
-
# prremote: 0.1.
|
|
150
|
+
# prremote: 0.1.1
|
|
144
151
|
# mrbc: 3.3.0 (/usr/local/bin/mrbc)
|
|
145
|
-
# runtime: 0.1.
|
|
152
|
+
# runtime: 0.1.3
|
|
146
153
|
```
|
|
147
154
|
|
|
148
155
|
---
|
|
@@ -199,4 +206,5 @@ Scripts saved via `deploy` are stored in flash and run automatically on every bo
|
|
|
199
206
|
## Related Projects
|
|
200
207
|
|
|
201
208
|
- [mruby/c](https://github.com/mrubyc/mrubyc) — Lightweight mruby implementation used in the runtime
|
|
209
|
+
- [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
210
|
- [mpremote](https://docs.micropython.org/en/latest/reference/mpremote.html) — MicroPython equivalent (inspiration)
|
data/lib/prremote/cli.rb
CHANGED
|
@@ -20,12 +20,20 @@ module Prremote
|
|
|
20
20
|
true
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
remove_command :tree
|
|
24
|
+
|
|
25
|
+
desc 'install', 'Flash prremote runtime firmware to Pico W or Pico'
|
|
24
26
|
option :version, type: :string, desc: "Firmware version to install (default: #{RUNTIME_VERSION})"
|
|
27
|
+
option :board, type: :string, desc: 'Board type: pico or picow (default: picow)'
|
|
25
28
|
def install
|
|
26
29
|
version = options[:version] || RUNTIME_VERSION
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
board = options[:board] || 'picow'
|
|
31
|
+
unless RuntimeManager::BOARDS.include?(board)
|
|
32
|
+
raise Thor::Error, "Unknown board '#{board}'. Valid values: #{RuntimeManager::BOARDS.join(', ')}"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
Commands::Install.new(version: version, board: board).call
|
|
36
|
+
rescue StandardError => e
|
|
29
37
|
raise Thor::Error, e.message
|
|
30
38
|
end
|
|
31
39
|
|
|
@@ -33,7 +41,7 @@ module Prremote
|
|
|
33
41
|
def run_script(file)
|
|
34
42
|
port = resolve_port
|
|
35
43
|
Commands::Run.new(port: port, baud: options[:baud]).call(file)
|
|
36
|
-
rescue
|
|
44
|
+
rescue StandardError => e
|
|
37
45
|
raise Thor::Error, e.message
|
|
38
46
|
end
|
|
39
47
|
map 'run' => :run_script
|
|
@@ -42,7 +50,7 @@ module Prremote
|
|
|
42
50
|
def deploy(file)
|
|
43
51
|
port = resolve_port
|
|
44
52
|
Commands::Deploy.new(port: port, baud: options[:baud]).call(file)
|
|
45
|
-
rescue
|
|
53
|
+
rescue StandardError => e
|
|
46
54
|
raise Thor::Error, e.message
|
|
47
55
|
end
|
|
48
56
|
|
|
@@ -50,7 +58,7 @@ module Prremote
|
|
|
50
58
|
def undeploy
|
|
51
59
|
port = resolve_port
|
|
52
60
|
Commands::Undeploy.new(port: port, baud: options[:baud]).call
|
|
53
|
-
rescue
|
|
61
|
+
rescue StandardError => e
|
|
54
62
|
raise Thor::Error, e.message
|
|
55
63
|
end
|
|
56
64
|
|
|
@@ -58,7 +66,7 @@ module Prremote
|
|
|
58
66
|
def eval(expr)
|
|
59
67
|
port = resolve_port
|
|
60
68
|
Commands::EvalCmd.new(port: port, baud: options[:baud]).call(expr)
|
|
61
|
-
rescue
|
|
69
|
+
rescue StandardError => e
|
|
62
70
|
raise Thor::Error, e.message
|
|
63
71
|
end
|
|
64
72
|
|
|
@@ -66,7 +74,7 @@ module Prremote
|
|
|
66
74
|
def watch(file)
|
|
67
75
|
port = resolve_port
|
|
68
76
|
Commands::Watch.new(port: port, baud: options[:baud]).call(file)
|
|
69
|
-
rescue
|
|
77
|
+
rescue StandardError => e
|
|
70
78
|
raise Thor::Error, e.message
|
|
71
79
|
end
|
|
72
80
|
|
|
@@ -87,7 +95,7 @@ module Prremote
|
|
|
87
95
|
serial.write("\x03")
|
|
88
96
|
sleep 0.1
|
|
89
97
|
puts 'Reset signal sent.'
|
|
90
|
-
rescue
|
|
98
|
+
rescue StandardError => e
|
|
91
99
|
raise Thor::Error, e.message
|
|
92
100
|
ensure
|
|
93
101
|
serial&.close
|
|
@@ -98,8 +106,13 @@ module Prremote
|
|
|
98
106
|
puts "prremote: #{VERSION}"
|
|
99
107
|
|
|
100
108
|
begin
|
|
109
|
+
major = Mrbc.major_version
|
|
101
110
|
puts "mrbc: #{Mrbc.version} (#{Mrbc.bin})"
|
|
102
|
-
|
|
111
|
+
unless major && major >= Mrbc::REQUIRED_MAJOR
|
|
112
|
+
puts " ** mrbc is too old (need #{Mrbc::REQUIRED_MAJOR}.x) **"
|
|
113
|
+
puts " Hint: the path can be set via MRBC environment variable."
|
|
114
|
+
end
|
|
115
|
+
rescue StandardError => e
|
|
103
116
|
puts "mrbc: (#{e.message})"
|
|
104
117
|
end
|
|
105
118
|
|
|
@@ -108,7 +121,7 @@ module Prremote
|
|
|
108
121
|
|
|
109
122
|
private
|
|
110
123
|
|
|
111
|
-
def fetch_runtime_version
|
|
124
|
+
def fetch_runtime_version # rubocop:disable Metrics/MethodLength,Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
|
|
112
125
|
port = options[:port] || Detector.find_device
|
|
113
126
|
return '(no device connected)' unless port
|
|
114
127
|
|
|
@@ -116,14 +129,44 @@ module Prremote
|
|
|
116
129
|
serial.write("\x03")
|
|
117
130
|
buf = +''
|
|
118
131
|
deadline = Time.now + 5
|
|
132
|
+
|
|
119
133
|
loop do
|
|
120
|
-
|
|
134
|
+
begin
|
|
135
|
+
buf << (serial.read(256) || '').gsub("\r\n", "\n").gsub("\r", '')
|
|
136
|
+
rescue StandardError
|
|
137
|
+
# Watchdog reboot dropped USB; wait for re-enumeration then reopen.
|
|
138
|
+
begin
|
|
139
|
+
serial.close
|
|
140
|
+
rescue StandardError
|
|
141
|
+
nil
|
|
142
|
+
end
|
|
143
|
+
serial = nil
|
|
144
|
+
reopen_deadline = [deadline, Time.now + 8].min
|
|
145
|
+
loop do
|
|
146
|
+
return '(not responding)' if Time.now > reopen_deadline
|
|
147
|
+
|
|
148
|
+
p = options[:port] || Detector.find_device
|
|
149
|
+
if p && File.exist?(p)
|
|
150
|
+
serial = begin
|
|
151
|
+
Serial.new(p, options[:baud])
|
|
152
|
+
rescue StandardError
|
|
153
|
+
nil
|
|
154
|
+
end
|
|
155
|
+
break if serial
|
|
156
|
+
end
|
|
157
|
+
sleep 0.3
|
|
158
|
+
end
|
|
159
|
+
buf = +''
|
|
160
|
+
next
|
|
161
|
+
end
|
|
162
|
+
|
|
121
163
|
return ::Regexp.last_match(1) if buf =~ %r{READY prremote-runtime/([\d.]+)}
|
|
122
|
-
|
|
164
|
+
return '(not responding)' if Time.now > deadline
|
|
123
165
|
|
|
124
166
|
sleep 0.05
|
|
125
167
|
end
|
|
126
|
-
|
|
168
|
+
rescue StandardError => e
|
|
169
|
+
"(#{e.message})"
|
|
127
170
|
ensure
|
|
128
171
|
serial&.close
|
|
129
172
|
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
|
-
|
|
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
|
-
|
|
14
|
-
|
|
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
|
-
|
|
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
|
data/lib/prremote/mrbc.rb
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
require 'open3'
|
|
2
2
|
|
|
3
3
|
module Prremote
|
|
4
|
+
# mrbc lookup order:
|
|
5
|
+
# 1. MRBC env var if set (use this to point at a non-PATH binary)
|
|
6
|
+
# 2. PATH entries, excluding rbenv shims — shims delegate to the project's
|
|
7
|
+
# .ruby-version (typically CRuby) and do not work as a standalone mrbc
|
|
8
|
+
# 3. rbenv direct installs (~/.rbenv/versions/mruby-*/bin/mrbc)
|
|
9
|
+
# The first candidate wins; version compatibility is checked in check_version!.
|
|
4
10
|
module Mrbc
|
|
5
11
|
SHIMS_RE = %r{\.rbenv/shims}
|
|
12
|
+
REQUIRED_MAJOR = 4
|
|
6
13
|
|
|
7
14
|
def self.bin
|
|
8
15
|
return ENV['MRBC'] if ENV['MRBC'] && File.executable?(ENV['MRBC'])
|
|
9
16
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
.map { |d| File.join(d, 'mrbc') }
|
|
13
|
-
.find { |f| File.executable?(f) }
|
|
14
|
-
|
|
15
|
-
found || raise('mrbc not found. Install mruby: brew install mruby')
|
|
17
|
+
(path_candidates + rbenv_candidates).first ||
|
|
18
|
+
raise('mrbc not found. Install mruby 4.x: brew install mruby')
|
|
16
19
|
end
|
|
17
20
|
|
|
18
21
|
def self.version
|
|
@@ -21,5 +24,35 @@ module Prremote
|
|
|
21
24
|
rescue RuntimeError
|
|
22
25
|
'(mrbc not found)'
|
|
23
26
|
end
|
|
27
|
+
|
|
28
|
+
def self.major_version
|
|
29
|
+
version[/\bmruby (\d+)\./, 1]&.to_i
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.check_version!
|
|
33
|
+
return if @version_ok
|
|
34
|
+
|
|
35
|
+
major = major_version
|
|
36
|
+
unless major && major >= REQUIRED_MAJOR
|
|
37
|
+
raise "mrbc is mruby #{major || '?'}.x but mruby #{REQUIRED_MAJOR}.x or newer is required.\n" \
|
|
38
|
+
"Installed: #{version}\n" \
|
|
39
|
+
"Install mruby 4.x: brew install mruby\n" \
|
|
40
|
+
"On Linux: build from source (https://github.com/mruby/mruby/releases)"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
@version_ok = true
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private_class_method def self.path_candidates
|
|
47
|
+
ENV['PATH'].split(File::PATH_SEPARATOR)
|
|
48
|
+
.grep_v(SHIMS_RE)
|
|
49
|
+
.map { |d| File.join(d, 'mrbc') }
|
|
50
|
+
.select { |f| File.executable?(f) }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private_class_method def self.rbenv_candidates
|
|
54
|
+
Dir.glob(File.expand_path('~/.rbenv/versions/mruby-*/bin/mrbc'))
|
|
55
|
+
.select { |f| File.executable?(f) }
|
|
56
|
+
end
|
|
24
57
|
end
|
|
25
58
|
end
|
|
@@ -4,33 +4,32 @@ require 'fileutils'
|
|
|
4
4
|
|
|
5
5
|
module Prremote
|
|
6
6
|
module RuntimeManager
|
|
7
|
-
|
|
8
|
-
BOARD = 'picow'.freeze
|
|
7
|
+
BOARDS = %w[pico picow].freeze
|
|
9
8
|
|
|
10
|
-
def self.uf2_filename(version)
|
|
11
|
-
"prremote-#{
|
|
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
|
|
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
|
data/lib/prremote/version.rb
CHANGED