rpremote 0.2.0 → 0.4.0
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/CHANGELOG.md +21 -0
- data/README.ja.md +17 -3
- data/README.md +17 -3
- data/RELEASING.md +2 -2
- data/THIRD_PARTY_NOTICES.md +4 -0
- data/lib/rpremote/bootsel_command.rb +124 -0
- data/lib/rpremote/cli.rb +60 -9
- data/lib/rpremote/config.rb +2 -0
- data/lib/rpremote/deploy_command.rb +191 -0
- data/lib/rpremote/dfu_command.rb +65 -3
- data/lib/rpremote/flash_command.rb +4 -1
- data/lib/rpremote/flasher.rb +49 -11
- data/lib/rpremote/help.rb +64 -11
- data/lib/rpremote/mrbgems.rb +41 -12
- data/lib/rpremote/nuke_firmware.rb +71 -0
- data/lib/rpremote/picoruby_source_patch.rb +23 -6
- data/lib/rpremote/recursive_copy.rb +84 -0
- data/lib/rpremote/runner.rb +33 -6
- data/lib/rpremote/setup_command.rb +4 -1
- data/lib/rpremote/shell.rb +9 -5
- data/lib/rpremote/terminal.rb +13 -1
- data/lib/rpremote/version.rb +1 -1
- data/lib/rpremote.rb +4 -0
- data/patches/picoruby-3.4.5-bootsel.patch +71 -0
- data/patches/picoruby-4.0.3-bootsel.patch +71 -0
- data/sig/rpremote.rbs +50 -2
- metadata +7 -1
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "optparse"
|
|
4
|
+
require_relative "bootsel_command"
|
|
5
|
+
require_relative "recursive_copy"
|
|
6
|
+
|
|
7
|
+
module Rpremote
|
|
8
|
+
class DeployCommand
|
|
9
|
+
SHELL_READY_TIMEOUT = 2.0
|
|
10
|
+
RETRY_INTERVAL = 0.25
|
|
11
|
+
|
|
12
|
+
class Error < Rpremote::Error; end
|
|
13
|
+
|
|
14
|
+
# The command intentionally keeps the six deployment stages visible in execution order.
|
|
15
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
16
|
+
def self.run(args, defaults:, output: $stdout, error: $stderr, services: {})
|
|
17
|
+
options = parse_options(args, defaults)
|
|
18
|
+
raise ArgumentError, "usage: rpremote deploy PATH [options]" unless args.length == 1
|
|
19
|
+
|
|
20
|
+
project = File.expand_path(args.first)
|
|
21
|
+
library, remote_library, source = project_files(project)
|
|
22
|
+
Language.validate!(options[:language])
|
|
23
|
+
|
|
24
|
+
builder = services.fetch(:builder) { Builder.new }
|
|
25
|
+
flasher = services.fetch(:flasher, Flasher)
|
|
26
|
+
serial = services.fetch(:serial, Serial)
|
|
27
|
+
device = services.fetch(:device, Device)
|
|
28
|
+
runner = services.fetch(:runner, Runner)
|
|
29
|
+
target_options = options.slice(:language, :language_version, :board, :cache_dir, :firmware)
|
|
30
|
+
firmware_path = Target.new(**target_options).firmware_path(root: Dir.pwd)
|
|
31
|
+
|
|
32
|
+
output.puts("deploy build: #{firmware_path}")
|
|
33
|
+
builder.build(
|
|
34
|
+
**target_options,
|
|
35
|
+
mrbgems: options[:mrbgems],
|
|
36
|
+
output: output,
|
|
37
|
+
error: error
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
flash_service = flasher.new(timeout: options[:timeout])
|
|
41
|
+
bootsel_mount = resolve_bootsel_mount(options, flash_service, output, services)
|
|
42
|
+
|
|
43
|
+
mount = options[:mount] || "an automatically detected RP2350 BOOTSEL volume"
|
|
44
|
+
output.puts("deploy flash: #{firmware_path} to #{mount}; this replaces persistent board firmware")
|
|
45
|
+
result = flash_service.flash(
|
|
46
|
+
firmware_path,
|
|
47
|
+
mount: bootsel_mount,
|
|
48
|
+
port: options[:port]
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
connection_options = options.merge(port: result.port)
|
|
52
|
+
output.puts("deploy connect: waiting for R2P2 Shell on #{result.port}")
|
|
53
|
+
wait_for_shell(
|
|
54
|
+
port: result.port, baud: options[:baud], timeout: options[:timeout], serial: serial,
|
|
55
|
+
shell: services.fetch(:shell, Shell),
|
|
56
|
+
sleeper: services.fetch(:sleeper) { ->(seconds) { sleep(seconds) } },
|
|
57
|
+
clock: services.fetch(:clock) { -> { Process.clock_gettime(Process::CLOCK_MONOTONIC) } }
|
|
58
|
+
)
|
|
59
|
+
output.puts("deploy connect: ready on #{result.port}")
|
|
60
|
+
|
|
61
|
+
if File.directory?(library)
|
|
62
|
+
output.puts("deploy push: #{library} -> :#{remote_library}")
|
|
63
|
+
recursive_copy = services.fetch(:recursive_copy) do
|
|
64
|
+
RecursiveCopy.new(output: output, serial: serial, device: device)
|
|
65
|
+
end
|
|
66
|
+
recursive_copy.call(library, ":#{remote_library}", connection_options)
|
|
67
|
+
else
|
|
68
|
+
output.puts("deploy push: skipped; directory not found: #{library}")
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
output.puts("deploy run: #{source} on #{result.port}")
|
|
72
|
+
source_data = prepend_mrbgem_requires(File.binread(source), options[:mrbgems])
|
|
73
|
+
run_output = serial.open(result.port, baud: options[:baud]) do |port|
|
|
74
|
+
runner.new(port, timeout: options[:timeout]).run(
|
|
75
|
+
source_data, output: output, cleanup: false, remote_path: Runner::DEPLOY_REMOTE_PATH
|
|
76
|
+
)
|
|
77
|
+
end
|
|
78
|
+
output.puts("deploy run: completed; output bytes: #{run_output.to_s.bytesize}")
|
|
79
|
+
rescue Errno::ENOENT => e
|
|
80
|
+
raise ArgumentError, e.message
|
|
81
|
+
end
|
|
82
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
|
83
|
+
|
|
84
|
+
def self.project_files(project)
|
|
85
|
+
raise ArgumentError, "project directory not found: #{project}" unless File.directory?(project)
|
|
86
|
+
|
|
87
|
+
name = File.basename(project)
|
|
88
|
+
library = File.join(project, "lib", name)
|
|
89
|
+
source = File.join(project, "main.rb")
|
|
90
|
+
raise ArgumentError, "project entry file not found: #{source}" unless File.file?(source)
|
|
91
|
+
|
|
92
|
+
[library, File.join("/lib", name), source]
|
|
93
|
+
end
|
|
94
|
+
private_class_method :project_files
|
|
95
|
+
|
|
96
|
+
def self.prepend_mrbgem_requires(source, definition)
|
|
97
|
+
return source if definition == false
|
|
98
|
+
|
|
99
|
+
Mrbgems.new(path: definition || Mrbgems::DEFAULT_PATH, cwd: Dir.pwd).prepend_requires(source)
|
|
100
|
+
end
|
|
101
|
+
private_class_method :prepend_mrbgem_requires
|
|
102
|
+
|
|
103
|
+
def self.wait_for_shell(context)
|
|
104
|
+
port = context.fetch(:port)
|
|
105
|
+
baud = context.fetch(:baud)
|
|
106
|
+
timeout = context.fetch(:timeout)
|
|
107
|
+
serial = context.fetch(:serial)
|
|
108
|
+
shell = context.fetch(:shell)
|
|
109
|
+
sleeper = context.fetch(:sleeper)
|
|
110
|
+
clock = context.fetch(:clock)
|
|
111
|
+
deadline = clock.call + timeout
|
|
112
|
+
loop do
|
|
113
|
+
serial.open(port, baud:) do |connection|
|
|
114
|
+
remaining = deadline - clock.call
|
|
115
|
+
raise Shell::TimeoutError, "R2P2 Shell is not ready" unless remaining.positive?
|
|
116
|
+
|
|
117
|
+
shell.new(connection, timeout: [remaining, SHELL_READY_TIMEOUT].min).synchronize!
|
|
118
|
+
end
|
|
119
|
+
return
|
|
120
|
+
rescue Serial::ConfigurationError, Shell::TimeoutError, IOError, SystemCallError
|
|
121
|
+
raise Error, "timed out waiting for R2P2 Shell after flash" if clock.call >= deadline
|
|
122
|
+
|
|
123
|
+
sleeper.call(RETRY_INTERVAL)
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
private_class_method :wait_for_shell
|
|
127
|
+
|
|
128
|
+
def self.resolve_bootsel_mount(options, flash_service, output, services)
|
|
129
|
+
mounted = flash_service.find_mounted(options[:mount])
|
|
130
|
+
if mounted
|
|
131
|
+
output.puts("deploy bootsel: already mounted at #{mounted}; skipping serial reset")
|
|
132
|
+
return mounted
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
port = options[:port] || "an automatically detected R2P2 port"
|
|
136
|
+
output.puts("deploy bootsel: requesting USB BOOTSEL mode through #{port}")
|
|
137
|
+
BootselCommand.enter(
|
|
138
|
+
mount: options[:mount], port: options[:port], baud: options[:baud], timeout: options[:timeout],
|
|
139
|
+
output: output, services: services.slice(:flasher, :serial, :device, :shell)
|
|
140
|
+
)
|
|
141
|
+
end
|
|
142
|
+
private_class_method :resolve_bootsel_mount
|
|
143
|
+
|
|
144
|
+
def self.parse_options(args, defaults)
|
|
145
|
+
options = default_options(defaults)
|
|
146
|
+
option_parser(options).parse!(args)
|
|
147
|
+
validate_options!(options)
|
|
148
|
+
options
|
|
149
|
+
end
|
|
150
|
+
private_class_method :parse_options
|
|
151
|
+
|
|
152
|
+
def self.default_options(defaults)
|
|
153
|
+
{
|
|
154
|
+
language: defaults.fetch(:language, Target::DEFAULT_LANGUAGE),
|
|
155
|
+
language_version: defaults.fetch(:language_version, Target::DEFAULT_LANGUAGE_VERSION),
|
|
156
|
+
board: defaults.fetch(:board, Target::DEFAULT_BOARD),
|
|
157
|
+
cache_dir: defaults.fetch(:cache, Target::DEFAULT_CACHE_DIR),
|
|
158
|
+
firmware: defaults[:firmware],
|
|
159
|
+
mrbgems: defaults[:mrbgems],
|
|
160
|
+
mount: defaults[:mount],
|
|
161
|
+
port: defaults[:port],
|
|
162
|
+
baud: defaults.fetch(:baud, Serial::BAUD_RATE),
|
|
163
|
+
timeout: defaults.fetch(:timeout, Flasher::DEFAULT_TIMEOUT)
|
|
164
|
+
}
|
|
165
|
+
end
|
|
166
|
+
private_class_method :default_options
|
|
167
|
+
|
|
168
|
+
def self.option_parser(options)
|
|
169
|
+
OptionParser.new do |parser|
|
|
170
|
+
parser.on("--language LANGUAGE") { |value| options[:language] = value }
|
|
171
|
+
parser.on("--language-version VERSION") { |value| options[:language_version] = value }
|
|
172
|
+
parser.on("--board BOARD") { |value| options[:board] = value }
|
|
173
|
+
parser.on("--cache DIR") { |value| options[:cache_dir] = value }
|
|
174
|
+
parser.on("--firmware FILE") { |value| options[:firmware] = value }
|
|
175
|
+
parser.on("--mrbgems FILE") { |value| options[:mrbgems] = value }
|
|
176
|
+
parser.on("--no-mrbgems") { options[:mrbgems] = false }
|
|
177
|
+
parser.on("--mount DIR") { |value| options[:mount] = value }
|
|
178
|
+
parser.on("--port PORT") { |value| options[:port] = value }
|
|
179
|
+
parser.on("--baud RATE", Integer) { |value| options[:baud] = value }
|
|
180
|
+
parser.on("--timeout SECONDS", Float) { |value| options[:timeout] = value }
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
private_class_method :option_parser
|
|
184
|
+
|
|
185
|
+
def self.validate_options!(options)
|
|
186
|
+
raise ArgumentError, "--baud must be positive" unless options[:baud].positive?
|
|
187
|
+
raise ArgumentError, "--timeout must be positive" unless options[:timeout].positive?
|
|
188
|
+
end
|
|
189
|
+
private_class_method :validate_options!
|
|
190
|
+
end
|
|
191
|
+
end
|
data/lib/rpremote/dfu_command.rb
CHANGED
|
@@ -8,16 +8,21 @@ module Rpremote
|
|
|
8
8
|
TYPES = { ".rb" => "RUBY", ".mrb" => "RITE" }.freeze
|
|
9
9
|
|
|
10
10
|
def self.run(args, defaults:, output: $stdout, services: {})
|
|
11
|
-
default_services = {
|
|
11
|
+
default_services = {
|
|
12
|
+
serial: Serial, device: Device, modem: PicoModem, runner: Runner,
|
|
13
|
+
compiler: DfuCompiler
|
|
14
|
+
}
|
|
12
15
|
services = default_services.merge(services)
|
|
13
16
|
subcommand = args.shift
|
|
14
17
|
case subcommand
|
|
15
18
|
when "app" then stage(args, defaults, output, services)
|
|
16
19
|
when "compile" then compile(args, defaults, output, services[:compiler])
|
|
17
20
|
when "status" then status(args, defaults, output, services)
|
|
21
|
+
when "remove" then remove(args, defaults, output, services)
|
|
18
22
|
else
|
|
19
23
|
raise ArgumentError,
|
|
20
|
-
"usage: rpremote dfu app FILE [options], dfu compile FILE [options],
|
|
24
|
+
"usage: rpremote dfu app FILE [options], dfu compile FILE [options], " \
|
|
25
|
+
"dfu status [options], or dfu remove [options]"
|
|
21
26
|
end
|
|
22
27
|
end
|
|
23
28
|
|
|
@@ -52,7 +57,7 @@ module Rpremote
|
|
|
52
57
|
private_class_method :compile
|
|
53
58
|
|
|
54
59
|
def self.status(args, defaults, output, services)
|
|
55
|
-
options =
|
|
60
|
+
options = parse_connection_options(args, defaults)
|
|
56
61
|
raise ArgumentError, "usage: rpremote dfu status [options]" unless args.empty?
|
|
57
62
|
|
|
58
63
|
port_path = services[:device].main_port(options[:port])
|
|
@@ -71,6 +76,45 @@ module Rpremote
|
|
|
71
76
|
end
|
|
72
77
|
private_class_method :status
|
|
73
78
|
|
|
79
|
+
def self.remove(args, defaults, output, services)
|
|
80
|
+
options = parse_connection_options(args, defaults)
|
|
81
|
+
raise ArgumentError, "usage: rpremote dfu remove [options]" unless args.empty?
|
|
82
|
+
|
|
83
|
+
port_path = services[:device].main_port(options[:port])
|
|
84
|
+
code = <<~'RUBY'
|
|
85
|
+
require "dfu"
|
|
86
|
+
# A confirmed DFU launcher may have returned after leaving a Sandbox
|
|
87
|
+
# worker alive. Stop known Processing workers before waiting for the
|
|
88
|
+
# Shell prompt; deleting their source files does not stop RAM tasks.
|
|
89
|
+
begin
|
|
90
|
+
$imu_processing_stream.close if $imu_processing_stream
|
|
91
|
+
$imu_processing_stream = nil
|
|
92
|
+
rescue Exception
|
|
93
|
+
$imu_processing_stream = nil
|
|
94
|
+
end
|
|
95
|
+
begin
|
|
96
|
+
$mpu6050_processing_stream.close if $mpu6050_processing_stream
|
|
97
|
+
$mpu6050_processing_stream = nil
|
|
98
|
+
rescue Exception
|
|
99
|
+
$mpu6050_processing_stream = nil
|
|
100
|
+
end
|
|
101
|
+
DFU::Meta.recover
|
|
102
|
+
DFU::Meta.save(DFU::Meta.deep_copy(DFU::Meta::DEFAULT))
|
|
103
|
+
%w[a b].each do |slot|
|
|
104
|
+
%w[rb mrb].each do |ext|
|
|
105
|
+
path = "#{ENV['HOME']}/app_#{slot}.#{ext}"
|
|
106
|
+
File.unlink(path) if File.exist?(path)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
RUBY
|
|
110
|
+
output.puts("removing all DFU boot applications from #{port_path}; this permanently clears both A/B slots")
|
|
111
|
+
services[:serial].open(port_path, baud: options[:baud]) do |port|
|
|
112
|
+
services[:runner].new(port, timeout: options[:timeout]).run(code)
|
|
113
|
+
end
|
|
114
|
+
output.puts("removed all DFU boot applications; run `rpremote reset` to stop the running application")
|
|
115
|
+
end
|
|
116
|
+
private_class_method :remove
|
|
117
|
+
|
|
74
118
|
def self.parse_app_options(args, defaults)
|
|
75
119
|
options = {
|
|
76
120
|
port: defaults[:port],
|
|
@@ -91,6 +135,24 @@ module Rpremote
|
|
|
91
135
|
end
|
|
92
136
|
private_class_method :parse_app_options
|
|
93
137
|
|
|
138
|
+
def self.parse_connection_options(args, defaults)
|
|
139
|
+
options = {
|
|
140
|
+
port: defaults[:port],
|
|
141
|
+
baud: defaults.fetch(:baud, Serial::BAUD_RATE),
|
|
142
|
+
timeout: defaults.fetch(:timeout, DEFAULT_TIMEOUT)
|
|
143
|
+
}
|
|
144
|
+
OptionParser.new do |parser|
|
|
145
|
+
parser.on("--port PORT") { |value| options[:port] = value }
|
|
146
|
+
parser.on("--baud RATE", Integer) { |value| options[:baud] = value }
|
|
147
|
+
parser.on("--timeout SECONDS", Float) { |value| options[:timeout] = value }
|
|
148
|
+
end.parse!(args)
|
|
149
|
+
raise ArgumentError, "--baud must be positive" unless options[:baud].positive?
|
|
150
|
+
raise ArgumentError, "--timeout must be positive" unless options[:timeout].positive?
|
|
151
|
+
|
|
152
|
+
options
|
|
153
|
+
end
|
|
154
|
+
private_class_method :parse_connection_options
|
|
155
|
+
|
|
94
156
|
def self.parse_compile_options(args, defaults)
|
|
95
157
|
options = {
|
|
96
158
|
language: defaults.fetch(:language, Target::DEFAULT_LANGUAGE),
|
|
@@ -33,7 +33,10 @@ module Rpremote
|
|
|
33
33
|
}
|
|
34
34
|
OptionParser.new do |parser|
|
|
35
35
|
parser.on("--cache DIR") { |value| options[:cache_dir] = value }
|
|
36
|
-
parser.on("--firmware FILE")
|
|
36
|
+
parser.on("--firmware FILE") do |value|
|
|
37
|
+
options[:firmware] = value
|
|
38
|
+
options[:firmware_explicit] = true
|
|
39
|
+
end
|
|
37
40
|
parser.on("--language LANGUAGE") { |value| options[:language] = value }
|
|
38
41
|
parser.on("--language-version VERSION") { |value| options[:language_version] = value }
|
|
39
42
|
parser.on("--board BOARD") { |value| options[:board] = value }
|
data/lib/rpremote/flasher.rb
CHANGED
|
@@ -36,30 +36,57 @@ module Rpremote
|
|
|
36
36
|
raise ArgumentError, "timeout must be positive" unless @timeout.positive?
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
-
def flash(uf2_path, mount: nil, port: nil)
|
|
39
|
+
def flash(uf2_path, mount: nil, port: nil, wait_for_port: true)
|
|
40
40
|
validate_firmware!(uf2_path)
|
|
41
41
|
target = find_mount(mount)
|
|
42
42
|
destination = File.join(target, File.basename(uf2_path))
|
|
43
43
|
copy_firmware(uf2_path, destination)
|
|
44
44
|
wait_until("BOOTSEL drive to disappear") { !mount_probe.call(target) }
|
|
45
|
+
return Result.new(mount: target, destination: destination, port: nil) unless wait_for_port
|
|
46
|
+
|
|
45
47
|
detected_port = wait_until("R2P2 serial port to appear") { detect_port(port) }
|
|
46
48
|
Result.new(mount: target, destination: destination, port: detected_port)
|
|
47
49
|
end
|
|
48
50
|
|
|
49
51
|
def find_mount(explicit_mount = nil)
|
|
50
|
-
|
|
52
|
+
mounted = find_mounted(explicit_mount)
|
|
53
|
+
return mounted if mounted
|
|
51
54
|
|
|
52
|
-
|
|
53
|
-
|
|
55
|
+
raise MountNotFoundError,
|
|
56
|
+
"Pico 2 BOOTSEL drive not found; reconnect it while holding BOOTSEL or use --mount DIR"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def find_mounted(explicit_mount = nil)
|
|
60
|
+
if explicit_mount
|
|
61
|
+
path = File.expand_path(explicit_mount)
|
|
62
|
+
return unless Dir.exist?(path)
|
|
63
|
+
|
|
64
|
+
return validate_mount!(path)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
matches = bootsel_mounts
|
|
68
|
+
return if matches.empty?
|
|
69
|
+
return matches.first if matches.one?
|
|
70
|
+
|
|
71
|
+
raise MountNotFoundError, "multiple Pico 2 BOOTSEL drives found; use --mount DIR"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def wait_for_mount(mount: nil)
|
|
75
|
+
if mount
|
|
76
|
+
path = File.expand_path(mount)
|
|
77
|
+
return wait_until("Pico 2 BOOTSEL drive to appear") do
|
|
78
|
+
next unless Dir.exist?(path)
|
|
79
|
+
next unless valid_target?(path)
|
|
80
|
+
|
|
81
|
+
path
|
|
82
|
+
end
|
|
54
83
|
end
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
84
|
+
|
|
85
|
+
wait_until("Pico 2 BOOTSEL drive to appear") do
|
|
86
|
+
matches = bootsel_mounts
|
|
87
|
+
raise MountNotFoundError, "multiple Pico 2 BOOTSEL drives found; use --mount DIR" if matches.length > 1
|
|
88
|
+
|
|
60
89
|
matches.first
|
|
61
|
-
else
|
|
62
|
-
raise MountNotFoundError, "multiple Pico 2 BOOTSEL drives found; use --mount DIR"
|
|
63
90
|
end
|
|
64
91
|
end
|
|
65
92
|
|
|
@@ -90,8 +117,19 @@ module Rpremote
|
|
|
90
117
|
false
|
|
91
118
|
end
|
|
92
119
|
|
|
120
|
+
def bootsel_mounts
|
|
121
|
+
Dir.glob(File.join(volumes_root, "*")).select do |path|
|
|
122
|
+
Dir.exist?(path) && valid_target?(path)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
93
126
|
def copy_firmware(source, destination)
|
|
94
127
|
FileUtils.copy_file(source, destination)
|
|
128
|
+
rescue Errno::ENXIO
|
|
129
|
+
# RP2350 may reboot and detach its BOOTSEL volume before macOS finishes
|
|
130
|
+
# closing the destination. The disappearance and serial reconnect checks
|
|
131
|
+
# in #flash determine whether the transfer actually completed.
|
|
132
|
+
nil
|
|
95
133
|
rescue SystemCallError => e
|
|
96
134
|
raise Error, "failed to copy UF2 firmware: #{e.message}"
|
|
97
135
|
end
|
data/lib/rpremote/help.rb
CHANGED
|
@@ -9,9 +9,14 @@ module Rpremote
|
|
|
9
9
|
build: "rpremote build [--language LANGUAGE] [--language-version VERSION] [--board BOARD] " \
|
|
10
10
|
"[--firmware FILE] [--cache DIR] [--mrbgems FILE|--no-mrbgems]",
|
|
11
11
|
build_clean: "rpremote build clean",
|
|
12
|
+
bootsel: "rpremote bootsel [--reset-flash-memory] [--mount DIR] [--port PORT] [--baud RATE] [--timeout SEC]",
|
|
13
|
+
deploy: "rpremote deploy PATH [--language LANGUAGE] [--language-version VERSION] [--board BOARD] " \
|
|
14
|
+
"[--firmware FILE] [--cache DIR] [--mrbgems FILE|--no-mrbgems] [--mount DIR] " \
|
|
15
|
+
"[--port PORT] [--baud RATE] [--timeout SEC]",
|
|
12
16
|
dfu_app: "rpremote dfu app FILE [--type ruby|rite] [--port PORT] [--baud RATE] [--timeout SEC]",
|
|
13
17
|
dfu_compile: "rpremote dfu compile FILE [--output FILE] [--language LANGUAGE] [--language-version VERSION] [--cache DIR]",
|
|
14
18
|
dfu_status: "rpremote dfu status [--port PORT] [--baud RATE] [--timeout SEC]",
|
|
19
|
+
dfu_remove: "rpremote dfu remove [--port PORT] [--baud RATE] [--timeout SEC]",
|
|
15
20
|
mrbgems: "rpremote mrbgems SUBCOMMAND [--file FILE] [--lockfile FILE]",
|
|
16
21
|
flash: "rpremote flash [--firmware FILE] [--language LANGUAGE] [--language-version VERSION] " \
|
|
17
22
|
"[--board BOARD] [--cache DIR] [--mount DIR] [--port PORT] [--timeout SEC]",
|
|
@@ -19,12 +24,13 @@ module Rpremote
|
|
|
19
24
|
"[--cache DIR] [--firmware FILE] [--mrbgems FILE|--no-mrbgems] [--mount DIR] " \
|
|
20
25
|
"[--port PORT] [--baud RATE] [--timeout SEC]",
|
|
21
26
|
ports: "rpremote ports",
|
|
22
|
-
run: "rpremote run FILE [--port PORT] [--baud RATE] [--timeout SEC] [--language LANGUAGE]",
|
|
27
|
+
run: "rpremote run FILE [--port PORT] [--baud RATE] [--timeout SEC] [--reset-on-timeout] [--language LANGUAGE]",
|
|
23
28
|
monitor: "rpremote monitor [--port PORT] [--baud RATE] [--timeout SEC]",
|
|
24
29
|
repl: "rpremote repl [--port PORT] [--baud RATE] [--timeout SEC]",
|
|
25
30
|
exec: "rpremote exec CODE [--port PORT] [--baud RATE] [--timeout SEC] [--language LANGUAGE]",
|
|
26
31
|
reset: "rpremote reset [--port PORT] [--baud RATE] [--timeout SEC]",
|
|
27
|
-
fs_cp: "rpremote fs cp SOURCE DESTINATION [--port PORT] [--baud RATE] [--timeout SEC]",
|
|
32
|
+
fs_cp: "rpremote fs cp SOURCE DESTINATION [--recursive] [--port PORT] [--baud RATE] [--timeout SEC]",
|
|
33
|
+
fs_push: "rpremote fs push LOCAL_DIR :/REMOTE_DIR [--port PORT] [--baud RATE] [--timeout SEC]",
|
|
28
34
|
fs_cat: "rpremote fs cat :/REMOTE/PATH [--port PORT] [--baud RATE] [--timeout SEC]",
|
|
29
35
|
fs_ls: "rpremote fs ls :/REMOTE/PATH [--port PORT] [--baud RATE] [--timeout SEC]",
|
|
30
36
|
fs_rm: "rpremote fs rm :/REMOTE/PATH [--port PORT] [--baud RATE] [--timeout SEC]",
|
|
@@ -38,9 +44,12 @@ module Rpremote
|
|
|
38
44
|
#{COMMAND_USAGE.fetch(:setup)}
|
|
39
45
|
#{COMMAND_USAGE.fetch(:build)}
|
|
40
46
|
#{COMMAND_USAGE.fetch(:build_clean)}
|
|
47
|
+
#{COMMAND_USAGE.fetch(:bootsel)}
|
|
48
|
+
#{COMMAND_USAGE.fetch(:deploy)}
|
|
41
49
|
#{COMMAND_USAGE.fetch(:dfu_app)}
|
|
42
50
|
#{COMMAND_USAGE.fetch(:dfu_compile)}
|
|
43
51
|
#{COMMAND_USAGE.fetch(:dfu_status)}
|
|
52
|
+
#{COMMAND_USAGE.fetch(:dfu_remove)}
|
|
44
53
|
#{COMMAND_USAGE.fetch(:mrbgems).sub("SUBCOMMAND", "check|list|lock|update")}
|
|
45
54
|
#{COMMAND_USAGE.fetch(:flash)}
|
|
46
55
|
#{COMMAND_USAGE.fetch(:config_show)}
|
|
@@ -51,6 +60,7 @@ module Rpremote
|
|
|
51
60
|
#{COMMAND_USAGE.fetch(:exec)}
|
|
52
61
|
#{COMMAND_USAGE.fetch(:reset)}
|
|
53
62
|
#{COMMAND_USAGE.fetch(:fs_cp)}
|
|
63
|
+
#{COMMAND_USAGE.fetch(:fs_push)}
|
|
54
64
|
#{COMMAND_USAGE.fetch(:fs_cat)}
|
|
55
65
|
#{COMMAND_USAGE.fetch(:fs_ls)}
|
|
56
66
|
#{COMMAND_USAGE.fetch(:fs_rm)}
|
|
@@ -66,11 +76,11 @@ module Rpremote
|
|
|
66
76
|
--language-version VERSION
|
|
67
77
|
use R2P2/PicoRuby 4.0.3 or 3.4.2 (default: 4.0.3)
|
|
68
78
|
--cache DIR use another project cache directory
|
|
69
|
-
--mrbgems FILE use an explicit Mrbgems definition during build
|
|
70
|
-
--no-mrbgems build without the automatically detected Mrbgems
|
|
79
|
+
--mrbgems FILE use an explicit Mrbgems definition during build or deploy
|
|
80
|
+
--no-mrbgems build or deploy without the automatically detected Mrbgems
|
|
71
81
|
--board BOARD
|
|
72
82
|
select pico2 or pico2_w (default: pico2)
|
|
73
|
-
--firmware FILE build to, or flash from, this UF2 path
|
|
83
|
+
--firmware FILE build to, or deploy or flash from, this UF2 path
|
|
74
84
|
--language LANGUAGE
|
|
75
85
|
select the remote language (default: picoruby)
|
|
76
86
|
--config FILE use another configuration file
|
|
@@ -93,6 +103,8 @@ module Rpremote
|
|
|
93
103
|
case command
|
|
94
104
|
when "setup" then setup_text
|
|
95
105
|
when "build" then args.first == "clean" ? build_clean_text : build_text
|
|
106
|
+
when "bootsel" then bootsel_text
|
|
107
|
+
when "deploy" then deploy_text
|
|
96
108
|
when "dfu" then dfu_text(args.first)
|
|
97
109
|
when "mrbgems" then mrbgems_text(args.first)
|
|
98
110
|
when "flash" then flash_text
|
|
@@ -112,7 +124,7 @@ module Rpremote
|
|
|
112
124
|
<<~HELP
|
|
113
125
|
Usage: #{COMMAND_USAGE.fetch(:setup)}
|
|
114
126
|
|
|
115
|
-
Creates config/setting.json when it does not exist, then downloads and prepares PicoRuby source.
|
|
127
|
+
Creates config/setting.json when it does not exist, then downloads and prepares PicoRuby source and the official Raspberry Pi nuke_universal.uf2 firmware.
|
|
116
128
|
Options: --language LANGUAGE (picoruby), --language-version VERSION (4.0.3), --cache DIR (firmware), --force.
|
|
117
129
|
This changes the project configuration and source cache but does not connect to a board.
|
|
118
130
|
HELP
|
|
@@ -136,15 +148,40 @@ module Rpremote
|
|
|
136
148
|
HELP
|
|
137
149
|
end
|
|
138
150
|
|
|
151
|
+
def self.deploy_text
|
|
152
|
+
<<~HELP
|
|
153
|
+
Usage: #{COMMAND_USAGE.fetch(:deploy)}
|
|
154
|
+
|
|
155
|
+
Builds the selected custom UF2, enters BOOTSEL when needed, flashes the firmware, and waits for the R2P2 Shell to become ready.
|
|
156
|
+
If PATH/lib/NAME exists, it copies it to :/lib/NAME, where NAME is the final component of PATH. It then runs PATH/main.rb and preserves its Shell job, so hardware output remains active until the next command.
|
|
157
|
+
The stages run in order and stop at the first failure. Flashing replaces persistent board firmware.
|
|
158
|
+
deploy requires PicoRuby 4.x firmware (currently 4.0.3).
|
|
159
|
+
Options combine build, BOOTSEL, flash, library-copy, and run settings. The first install of firmware with automatic BOOTSEL still requires holding BOOTSEL.
|
|
160
|
+
HELP
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def self.bootsel_text
|
|
164
|
+
<<~HELP
|
|
165
|
+
Usage: #{COMMAND_USAGE.fetch(:bootsel)}
|
|
166
|
+
|
|
167
|
+
Asks the running R2P2 firmware to restart as an RP2350 BOOTSEL USB volume, then waits for that volume to appear.
|
|
168
|
+
By default it does not write firmware. This requires firmware built by a current rpremote; use physical BOOTSEL once to install it first.
|
|
169
|
+
--reset-flash-memory copies the official Raspberry Pi nuke_universal.uf2 in BOOTSEL mode and erases all external flash memory.
|
|
170
|
+
After resetting flash memory, wait for BOOTSEL to reappear and run `rpremote flash` to install R2P2 again.
|
|
171
|
+
HELP
|
|
172
|
+
end
|
|
173
|
+
|
|
139
174
|
def self.dfu_text(subcommand)
|
|
140
175
|
return dfu_app_text if subcommand == "app"
|
|
141
176
|
return dfu_compile_text if subcommand == "compile"
|
|
142
177
|
return dfu_status_text if subcommand == "status"
|
|
178
|
+
return dfu_remove_text if subcommand == "remove"
|
|
143
179
|
|
|
144
180
|
<<~HELP
|
|
145
181
|
Usage: #{COMMAND_USAGE.fetch(:dfu_app)}
|
|
146
182
|
#{COMMAND_USAGE.fetch(:dfu_compile)}
|
|
147
183
|
#{COMMAND_USAGE.fetch(:dfu_status)}
|
|
184
|
+
#{COMMAND_USAGE.fetch(:dfu_remove)}
|
|
148
185
|
|
|
149
186
|
Stages and inspects PicoModem DFU applications. Run `rpremote dfu SUBCOMMAND --help` for details.
|
|
150
187
|
HELP
|
|
@@ -178,6 +215,16 @@ module Rpremote
|
|
|
178
215
|
HELP
|
|
179
216
|
end
|
|
180
217
|
|
|
218
|
+
def self.dfu_remove_text
|
|
219
|
+
<<~HELP
|
|
220
|
+
Usage: #{COMMAND_USAGE.fetch(:dfu_remove)}
|
|
221
|
+
|
|
222
|
+
Permanently removes the Ruby source and bytecode applications from both DFU A/B slots and resets their metadata.
|
|
223
|
+
Run `rpremote reset` afterward to stop the application already running in RAM. It does not remove /home/app.rb,
|
|
224
|
+
/home/app.mrb, other files, embedded mrbgems, or R2P2 firmware.
|
|
225
|
+
HELP
|
|
226
|
+
end
|
|
227
|
+
|
|
181
228
|
def self.mrbgems_text(subcommand)
|
|
182
229
|
action = subcommand || "check|list|lock|update"
|
|
183
230
|
<<~HELP
|
|
@@ -217,8 +264,10 @@ module Rpremote
|
|
|
217
264
|
<<~HELP
|
|
218
265
|
Usage: #{COMMAND_USAGE.fetch(:run)}
|
|
219
266
|
|
|
220
|
-
Uploads FILE to R2P2, relays output, then removes the temporary remote file. Defaults are the automatic CDC 0 port,
|
|
221
|
-
115200 baud, 20
|
|
267
|
+
Uploads FILE to R2P2, or main.rb when FILE is a directory, relays output, then removes the temporary remote file. Defaults are the automatic CDC 0 port,
|
|
268
|
+
115200 baud, a 20-second idle timeout, and picoruby. Output from the running program resets the idle timeout.
|
|
269
|
+
It exits nonzero when compatible R2P2 firmware reports a Ruby exception.
|
|
270
|
+
--reset-on-timeout resets R2P2 after the run times out and the serial connection has closed.
|
|
222
271
|
HELP
|
|
223
272
|
end
|
|
224
273
|
|
|
@@ -227,7 +276,8 @@ module Rpremote
|
|
|
227
276
|
Usage: #{COMMAND_USAGE.fetch(:exec)}
|
|
228
277
|
|
|
229
278
|
Runs short Ruby CODE through a temporary remote file, then removes it. Defaults are the automatic CDC 0 port,
|
|
230
|
-
115200 baud, 20
|
|
279
|
+
115200 baud, a 20-second idle timeout, and picoruby. Output from the running program resets the idle timeout.
|
|
280
|
+
It exits nonzero when compatible R2P2 firmware reports a Ruby exception.
|
|
231
281
|
HELP
|
|
232
282
|
end
|
|
233
283
|
|
|
@@ -256,10 +306,11 @@ module Rpremote
|
|
|
256
306
|
end
|
|
257
307
|
|
|
258
308
|
def self.fs_text(subcommand)
|
|
259
|
-
return fs_subcommand_text(subcommand) if %w[cp cat ls rm mkdir].include?(subcommand)
|
|
309
|
+
return fs_subcommand_text(subcommand) if %w[cp push cat ls rm mkdir].include?(subcommand)
|
|
260
310
|
|
|
261
311
|
<<~HELP
|
|
262
312
|
Usage: #{COMMAND_USAGE.fetch(:fs_cp)}
|
|
313
|
+
#{COMMAND_USAGE.fetch(:fs_push)}
|
|
263
314
|
#{COMMAND_USAGE.fetch(:fs_cat)}
|
|
264
315
|
#{COMMAND_USAGE.fetch(:fs_ls)}
|
|
265
316
|
#{COMMAND_USAGE.fetch(:fs_rm)}
|
|
@@ -274,7 +325,9 @@ module Rpremote
|
|
|
274
325
|
effect = case subcommand
|
|
275
326
|
when "rm" then "Deletes the remote path permanently."
|
|
276
327
|
when "mkdir" then "Creates a remote directory."
|
|
277
|
-
|
|
328
|
+
when "cp" then "Transfers one file, or recursively uploads a local directory with --recursive."
|
|
329
|
+
when "push" then "Creates missing remote directories and recursively uploads a local directory."
|
|
330
|
+
else "Reads remote files."
|
|
278
331
|
end
|
|
279
332
|
<<~HELP
|
|
280
333
|
Usage: #{usage}
|