rpremote 0.4.0 → 0.6.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 +17 -5
- data/README.ja.md +31 -45
- data/README.md +31 -46
- data/RELEASING.md +6 -5
- data/lib/rpremote/bootsel_command.rb +8 -6
- data/lib/rpremote/builder.rb +1 -1
- data/lib/rpremote/deploy_command.rb +14 -11
- data/lib/rpremote/help.rb +8 -6
- data/lib/rpremote/language_source.rb +19 -2
- data/lib/rpremote/mrbgems.rb +37 -17
- data/lib/rpremote/picoruby_compiler.rb +28 -0
- data/lib/rpremote/picoruby_source_patch.rb +2 -4
- data/lib/rpremote/target.rb +1 -1
- data/lib/rpremote/terminal.rb +0 -2
- data/lib/rpremote/version.rb +1 -1
- data/lib/rpremote.rb +1 -0
- data/patches/picoruby-4.0.3-pwm-sleep-clock.patch +22 -0
- data/sig/rpremote.rbs +11 -0
- data/tasks/firmware_build.rb +10 -2
- metadata +3 -3
- data/patches/picoruby-3.4.5-bootsel.patch +0 -71
- data/patches/picoruby-4.0.3-bootsel.patch +0 -71
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b30b1f27c4bdbb212d69bc30f79342bf2caa6ae5c6787125a9627e53f15a714a
|
|
4
|
+
data.tar.gz: a7ab212fa48b5f58aa11357ffdf52c2f5c8dbf47c2b00c6d3b7279c38a1431d3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6086af03b4459d2c96102e7f884cd888276735ae77d30132189a4191c66892366a7deb51676613966751d343d79d5b53519242ac5171cee9d60044d81a4f7225
|
|
7
|
+
data.tar.gz: dc2851099a3a6a3c019a3cbb8936b4b111f9359d964208163d688d4afb4b4cf27135286949304954f7ea685a215f6373f27e23a3c20c3aa4fb5382e93bec6620
|
data/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Notable changes to the `rpremote` RubyGem will be documented in this file. Repository-level examples, local mrbgems, and support files are documented in the [repository changelog](../../CHANGELOG.md).
|
|
4
|
+
|
|
5
|
+
## Unreleased
|
|
6
|
+
|
|
7
|
+
## 0.6.0 - 2026-09-20
|
|
8
|
+
|
|
9
|
+
- Change the default PicoRuby version to `latest`, backed by the current `master` branch, while retaining `--language-version` for selecting a release tag.
|
|
10
|
+
- Replace the patched R2P2 `bootsel` executable with a temporary PicoModem script that calls `Machine.enter_bootsel` from the embedded `picoruby-bootsel` mrbgem.
|
|
11
|
+
- Support both the legacy `build/host/bin/mrbc` layout and the PicoRuby 4.0.4 `build/mrbc/default/bin/mrbc` layout when building custom firmware.
|
|
12
|
+
- Expand the human-readable executable specifications and validate bilingual documentation structure, links, command coverage, and safety warnings.
|
|
13
|
+
|
|
14
|
+
## 0.5.0 - 2026-09-12
|
|
15
|
+
|
|
16
|
+
- Make `deploy PATH` flash the existing UF2 by default, and add `deploy PATH --build` for the previous build-and-deploy workflow.
|
|
17
|
+
- Keep the RP2350 PWM clock running during scheduler sleep after PWM initialization, preventing buzzer and other PWM output from pausing between scheduler ticks on PicoRuby 4.0.3.
|
|
18
|
+
- Add `auto_require: false` to `Mrbgems` entries so a gem can remain embedded in firmware without being prepended to every `run`, `exec`, and `deploy` source.
|
|
4
19
|
|
|
5
20
|
## 0.4.0 - 2026-08-31
|
|
6
21
|
|
|
7
|
-
- Add the Daisen Kofun PicoRuby project, with 32 WS2812 illumination patterns, a firmware-embedded local mrbgem, LED layout and setlist definitions, bilingual documentation, and host-side regression tests.
|
|
8
22
|
- Check R2P2 filesystem connectivity before non-recursive `fs cp` transfers, and report Shell synchronization timeouts as filesystem connection failures.
|
|
9
23
|
|
|
10
24
|
## 0.3.0 - 2026-08-28
|
|
@@ -20,13 +34,11 @@ All notable changes to this project will be documented in this file.
|
|
|
20
34
|
- Read mrbgem `require_name` values from `Mrbgems.lock` and automatically prepend the corresponding `require` calls to `run`, `exec`, and `deploy`; allow `Mrbgems` entries to specify `require:` explicitly.
|
|
21
35
|
- Change `flash` to accept an explicit UF2 only through `--firmware FILE`; positional UF2 arguments are no longer supported.
|
|
22
36
|
- Improve RP2350 BOOTSEL flashing on macOS by accepting the expected `ENXIO` volume-detach race after a successful copy.
|
|
23
|
-
- Add PicoRuby mrbgem examples for BMI270, HC-SR04 temperature, MAX30102, MPU6050, and WS2812 SPI, plus oximeter and Processing project examples.
|
|
24
37
|
|
|
25
38
|
## 0.2.0 - 2026-08-26
|
|
26
39
|
|
|
27
40
|
- Make `run` and `exec` exit nonzero when compatible R2P2 firmware reports a Ruby exception, while preserving real-time program output.
|
|
28
41
|
- Increase the default command timeout from 10 to 20 seconds.
|
|
29
|
-
- Add a bilingual education example for practical PicoModem DFU application updates, startup confirmation, and A/B-slot rollback.
|
|
30
42
|
|
|
31
43
|
## 0.1.0 - 2026-08-24
|
|
32
44
|
|
|
@@ -36,7 +48,7 @@ All notable changes to this project will be documented in this file.
|
|
|
36
48
|
- Add PicoModem DFU staging, status, compatible bytecode compilation, and A/B-slot rollback support for Ruby and `.mrb` applications.
|
|
37
49
|
- Add binary-safe file transfer and remote filesystem commands, plus `run`, `exec`, `monitor`, `repl`, and reset with reconnect waiting.
|
|
38
50
|
- Add macOS R2P2 serial-port detection for Raspberry Pi Pico 2 and Pico 2 W.
|
|
39
|
-
- Add bilingual
|
|
51
|
+
- Add bilingual package, configuration, firmware, mrbgem, and DFU documentation.
|
|
40
52
|
- Add RBS signatures, automated tests, macOS CI, and release validation with isolated gem installation and CLI smoke testing.
|
|
41
53
|
|
|
42
54
|
### Limitations
|
data/README.ja.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# rpremote
|
|
1
|
+
# rpremote
|
|
2
2
|
|
|
3
3
|
[English](README.md)
|
|
4
4
|
|
|
@@ -26,19 +26,20 @@ gem install rpremote
|
|
|
26
26
|
```sh
|
|
27
27
|
rpremote setup
|
|
28
28
|
rpremote build
|
|
29
|
-
rpremote flash
|
|
29
|
+
rpremote flash
|
|
30
30
|
rpremote run main.rb
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
`setup`はRaspberry Pi公式のリセット用`nuke_universal.uf2`も`firmware/`へダウンロードします。
|
|
34
34
|
|
|
35
|
-
再利用するRubyコードを`lib/NAME`へ置くプロジェクトでは、`deploy
|
|
35
|
+
再利用するRubyコードを`lib/NAME`へ置くプロジェクトでは、`deploy`が既存ファームウェアを書き込み、そのディレクトリをR2P2へコピーしてからエントリーファイルを一時実行します。`--build`を付けると、同じ処理の前にファームウェアをビルドします。
|
|
36
36
|
|
|
37
37
|
```sh
|
|
38
38
|
rpremote deploy path/to/project
|
|
39
|
+
rpremote deploy path/to/project --build
|
|
39
40
|
```
|
|
40
41
|
|
|
41
|
-
既定では`firmware/picoruby-
|
|
42
|
+
既定では`firmware/picoruby-latest/`にソースを準備し、`firmware/picoruby-latest-pico2.uf2`を生成します。対象ボードを明示する必要がある場合は、`rpremote ports`でR2P2のCDC 0ポートを確認してください。
|
|
42
43
|
|
|
43
44
|
## mrbgemを追加する
|
|
44
45
|
|
|
@@ -60,63 +61,48 @@ rpremote build
|
|
|
60
61
|
|
|
61
62
|
`Mrbgems.lock`はGitHubのコミットとローカルgemの内容ハッシュを固定します。`build`は既存のlockを再利用し、`rpremote mrbgems update`だけが新しいコミットを解決します。
|
|
62
63
|
|
|
64
|
+
ファームウェアへ組み込む一方で、すべての`run`、`exec`、`deploy`に先行ロードしないgemには`auto_require: false`を指定できます。アプリケーション側から必要なgemを明示的に`require`してください。
|
|
65
|
+
|
|
63
66
|
## 対象を選択する
|
|
64
67
|
|
|
65
68
|
```sh
|
|
66
|
-
rpremote setup
|
|
67
|
-
rpremote build
|
|
68
|
-
rpremote flash
|
|
69
|
+
rpremote setup
|
|
70
|
+
rpremote build
|
|
71
|
+
rpremote flash
|
|
69
72
|
```
|
|
70
73
|
|
|
71
74
|
コマンドラインオプションは`config/setting.json`より優先されます。現在実装されているのはPicoRubyです。将来のMicroPythonと追加Picoボード対応に備え、`language`と`board`はインターフェースに残しています。
|
|
72
75
|
|
|
73
|
-
##
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
| `rpremote deploy PATH` | ファームウェアをビルドして書き込み、存在する場合は`PATH/lib/NAME`を`:/lib/NAME`へコピーしてから`PATH/main.rb`を実行します。ハードウェア出力は次のコマンドまで維持されます。 |
|
|
82
|
-
| `rpremote dfu app FILE` | PicoModem DFUでRubyまたは版を照合したバイトコードのアプリを更新します。 |
|
|
83
|
-
| `rpremote dfu compile FILE` | DFU用にPicoRuby版と一致する`.mrb`を生成します。 |
|
|
84
|
-
| `rpremote dfu status` | DFUのアクティブおよび起動候補スロットを表示します。 |
|
|
85
|
-
| `rpremote dfu remove` | DFU起動アプリを両スロットから削除します。実行中のアプリを停止するには別途リセットします。 |
|
|
86
|
-
| `rpremote mrbgems …` | mrbgemを検査、表示、固定、更新します。 |
|
|
87
|
-
| `rpremote flash` | 選択したUF2をBOOTSEL経由で書き込みます。 |
|
|
88
|
-
| `rpremote bootsel --reset-flash-memory` | BOOTSELへ移行し、Pico 2の外部フラッシュメモリ全体を消去します。 |
|
|
89
|
-
| `rpremote config show` | 設定ファイルとコマンドラインオプションを反映した実効設定を表示します。 |
|
|
90
|
-
| `rpremote ports` | R2P2シリアルポートを表示します。 |
|
|
91
|
-
| `rpremote run FILE` | Rubyファイルを転送して実行します。ディレクトリ指定時は`main.rb`を実行し、出力をリアルタイム表示します。タイムアウトは無出力の継続時間として扱い、Ruby例外時は非0で終了します。`--reset-on-timeout`を指定すると実行タイムアウト後にR2P2をリセットします。 |
|
|
92
|
-
| `rpremote exec CODE` | 短いRubyコードを実行します。Ruby例外時は非0で終了します。 |
|
|
93
|
-
| `rpremote monitor` / `repl` | 対話型シリアルセッションを開きます。 |
|
|
94
|
-
| `rpremote reset` | R2P2を再起動して再接続まで待ちます。 |
|
|
95
|
-
| `rpremote fs cp/push/cat/ls/rm/mkdir` | R2P2ファイルシステムを操作します。 |
|
|
96
|
-
|
|
97
|
-
コマンド一覧は`rpremote --help`で確認できます。コマンド固有の構文、既定値、影響は`rpremote <command> --help`で確認できます。
|
|
76
|
+
## 作業を選ぶ
|
|
77
|
+
|
|
78
|
+
- `setup`でプロジェクトを準備し、`config show`で実効設定を確認します。最初のファームウェア導入には`build`と`flash`を使います。
|
|
79
|
+
- プロジェクト開発では`deploy PATH`を使います。選択したファームウェアを書き込み、存在する場合は`PATH/lib/NAME`をコピーして`PATH/main.rb`を実行します。先にファームウェアを再ビルドする場合は`--build`を付けます。
|
|
80
|
+
- Rubyを一時実行する場合は`run`または`exec`、対話型シリアル接続には`monitor`または`repl`を使います。`monitor`と`repl`は`Ctrl-]`で終了します。
|
|
81
|
+
- R2P2上へファイルを保持する場合は`fs`、再起動後も実行するA/B起動アプリには`dfu`を使います。
|
|
82
|
+
|
|
83
|
+
完全なコマンド一覧は`rpremote --help`で確認します。現在の構文、既定値、処理順、影響についてはコマンドヘルプが実行可能なリファレンスです。
|
|
98
84
|
|
|
99
85
|
```sh
|
|
100
|
-
rpremote
|
|
86
|
+
rpremote deploy --help
|
|
101
87
|
rpremote dfu app --help
|
|
88
|
+
rpremote fs cp --help
|
|
102
89
|
```
|
|
103
90
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
`run`と`exec`の例外終了コードには、Ruby例外ステータスに対応したR2P2ファームウェアが必要です。[GitHubリポジトリ](https://github.com/ogom/rpremote)のPicoRubyソースから`rpremote build`で生成したUF2が対応しています。
|
|
107
|
-
|
|
108
|
-
## 操作モデル
|
|
91
|
+
### 安全上の境界
|
|
109
92
|
|
|
110
|
-
- `
|
|
111
|
-
- `
|
|
112
|
-
- `
|
|
113
|
-
- `
|
|
114
|
-
- `dfu remove`はDFUのA/B両アプリスロットを完全に空にします。RAM上ですでに動作しているアプリは`rpremote reset`を実行するまで継続するため、起動アプリのログを混ぜずに`rpremote run`を使う場合は両方のコマンドを実行します。その他の`/home`ファイルとR2P2ファームウェアは削除しません。
|
|
115
|
-
- リモートパスには`:/REMOTE/PATH`を使います。`fs cp`はローカルパスとリモートパスの間で転送します。`fs push LOCAL_DIR :/REMOTE_DIR`は`fs cp --recursive`の別名で、不足しているリモートディレクトリを作成し、ローカルディレクトリの内容を一括転送します。リモートにだけ存在するファイルは削除しません。`fs rm`は指定したリモートパスを完全に削除します。
|
|
93
|
+
- `flash`は永続的なR2P2ファームウェアを置き換えます。`bootsel --reset-flash-memory`はPico 2の外部フラッシュ全体を消去するため、実行後にR2P2を再度書き込む必要があります。
|
|
94
|
+
- `dfu remove`はDFUのA/B両アプリスロットを完全に空にします。RAM上ですでに動作しているアプリを停止するには、別途リセットします。
|
|
95
|
+
- `fs rm`は指定したリモートパスを完全に削除します。再帰転送ではボード上だけに存在するファイルを削除しません。
|
|
96
|
+
- `run`と`exec`は一時リモートファイルを削除します。Ruby例外時の非0終了には対応するR2P2ファームウェアが必要で、このリポジトリからビルドしたUF2は対応しています。
|
|
116
97
|
|
|
117
98
|
## ドキュメントとサンプル
|
|
118
99
|
|
|
119
|
-
[
|
|
100
|
+
- [作業に応じてコマンドを選ぶ](https://github.com/ogom/rpremote/blob/main/docs/command.ja.md)
|
|
101
|
+
- [プロジェクトを設定する](https://github.com/ogom/rpremote/blob/main/docs/config.ja.md)
|
|
102
|
+
- [カスタムファームウェアをビルドする](https://github.com/ogom/rpremote/blob/main/docs/firmware.ja.md)
|
|
103
|
+
- [MrbgemsとMrbgems.lockを管理する](https://github.com/ogom/rpremote/blob/main/docs/mrbgems.ja.md)
|
|
104
|
+
- [PicoModem DFUで起動アプリを更新する](https://github.com/ogom/rpremote/blob/main/docs/dfu.ja.md)
|
|
105
|
+
- [電子工作サンプルを見る](https://github.com/ogom/rpremote/tree/main/examples)
|
|
120
106
|
|
|
121
107
|
## 関連プロジェクト
|
|
122
108
|
|
data/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# rpremote
|
|
1
|
+
# rpremote
|
|
2
2
|
|
|
3
3
|
[日本語](README.ja.md)
|
|
4
4
|
|
|
@@ -26,19 +26,20 @@ Run these commands in a project directory:
|
|
|
26
26
|
```sh
|
|
27
27
|
rpremote setup
|
|
28
28
|
rpremote build
|
|
29
|
-
rpremote flash
|
|
29
|
+
rpremote flash
|
|
30
30
|
rpremote run main.rb
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
`setup` also downloads the official Raspberry Pi `nuke_universal.uf2` reset firmware into `firmware/`.
|
|
34
34
|
|
|
35
|
-
For a project that stores reusable Ruby code in `lib/NAME`, `deploy`
|
|
35
|
+
For a project that stores reusable Ruby code in `lib/NAME`, `deploy` flashes the existing firmware, copies that directory to R2P2, and then temporarily runs its entry file. Add `--build` to build the firmware first:
|
|
36
36
|
|
|
37
37
|
```sh
|
|
38
38
|
rpremote deploy path/to/project
|
|
39
|
+
rpremote deploy path/to/project --build
|
|
39
40
|
```
|
|
40
41
|
|
|
41
|
-
The defaults prepare `firmware/picoruby-
|
|
42
|
+
The defaults prepare `firmware/picoruby-latest/` and create `firmware/picoruby-latest-pico2.uf2`. Use `rpremote ports` to locate the R2P2 CDC 0 port when a board must be selected explicitly.
|
|
42
43
|
|
|
43
44
|
## Add mrbgems
|
|
44
45
|
|
|
@@ -60,64 +61,48 @@ rpremote build
|
|
|
60
61
|
|
|
61
62
|
`Mrbgems.lock` pins GitHub commits and hashes local gem contents. Existing locks are reused by `build`; `rpremote mrbgems update` deliberately resolves new commits.
|
|
62
63
|
|
|
64
|
+
Set `auto_require: false` for a gem that should remain embedded in firmware without being loaded before every `run`, `exec`, or `deploy`. Require that gem explicitly from the application when needed.
|
|
65
|
+
|
|
63
66
|
## Select a target
|
|
64
67
|
|
|
65
68
|
```sh
|
|
66
|
-
rpremote setup
|
|
67
|
-
rpremote build
|
|
68
|
-
rpremote flash
|
|
69
|
+
rpremote setup
|
|
70
|
+
rpremote build
|
|
71
|
+
rpremote flash
|
|
69
72
|
```
|
|
70
73
|
|
|
71
74
|
Command-line options override `config/setting.json`. PicoRuby is implemented today; `language` and `board` are retained for planned MicroPython and additional Pico board support.
|
|
72
75
|
|
|
73
|
-
##
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
| `rpremote deploy PATH` | Build and flash firmware, copy `PATH/lib/NAME` to `:/lib/NAME` when present, then run `PATH/main.rb` while preserving its hardware output until the next command. |
|
|
82
|
-
| `rpremote dfu app FILE` | Stage a Ruby or version-checked bytecode app through PicoModem DFU. |
|
|
83
|
-
| `rpremote dfu compile FILE` | Compile `.rb` to matching PicoRuby bytecode for DFU. |
|
|
84
|
-
| `rpremote dfu status` | Show the active and candidate DFU slots. |
|
|
85
|
-
| `rpremote dfu remove` | Remove both DFU boot applications; reset separately to stop one already running. |
|
|
86
|
-
| `rpremote mrbgems …` | Check, list, lock, or update mrbgems. |
|
|
87
|
-
| `rpremote flash` | Flash the selected UF2 through BOOTSEL. |
|
|
88
|
-
| `rpremote bootsel --reset-flash-memory` | Enter BOOTSEL and erase all Pico 2 external flash memory. |
|
|
89
|
-
| `rpremote config show` | Show the effective configuration after file and command-line options are resolved. |
|
|
90
|
-
| `rpremote ports` | List detected R2P2 serial ports. |
|
|
91
|
-
| `rpremote run FILE` | Upload and run a Ruby file, or `main.rb` when FILE is a directory, with real-time output; exit nonzero on a Ruby exception. The timeout measures idle time without output. Use `--reset-on-timeout` to reset R2P2 after a run timeout. |
|
|
92
|
-
| `rpremote exec CODE` | Run short Ruby code and exit nonzero on a Ruby exception. |
|
|
93
|
-
| `rpremote monitor` / `repl` | Open an interactive serial session. |
|
|
94
|
-
| `rpremote reset` | Reboot R2P2 and wait for reconnection. |
|
|
95
|
-
| `rpremote fs cp/push/cat/ls/rm/mkdir` | Operate on the R2P2 filesystem. |
|
|
96
|
-
|
|
97
|
-
Run `rpremote --help` for the complete command list. Run `rpremote <command> --help` for command-specific syntax, defaults, and effects.
|
|
76
|
+
## Choose a workflow
|
|
77
|
+
|
|
78
|
+
- Prepare a project with `setup`, inspect resolved settings with `config show`, then use `build` and `flash` for the first firmware installation.
|
|
79
|
+
- Use `deploy PATH` during project development. It flashes the selected firmware, copies `PATH/lib/NAME` when present, and runs `PATH/main.rb`; add `--build` when the firmware must be rebuilt first.
|
|
80
|
+
- Use `run` or `exec` for temporary Ruby execution, and `monitor` or `repl` for an interactive serial session. `monitor` and `repl` exit with `Ctrl-]`.
|
|
81
|
+
- Use `fs` commands for persistent R2P2 files, and use `dfu` commands when an A/B boot application must survive a restart.
|
|
82
|
+
|
|
83
|
+
Run `rpremote --help` for the complete command list. Command help is the executable reference for current syntax, defaults, processing order, and effects.
|
|
98
84
|
|
|
99
85
|
```sh
|
|
100
|
-
rpremote
|
|
86
|
+
rpremote deploy --help
|
|
101
87
|
rpremote dfu app --help
|
|
88
|
+
rpremote fs cp --help
|
|
102
89
|
```
|
|
103
90
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
Ruby-exception exit statuses for `run` and `exec` require R2P2 firmware with Ruby exception status support.
|
|
107
|
-
UF2 files built with `rpremote build` from the [GitHub repository](https://github.com/ogom/rpremote) include that support.
|
|
108
|
-
|
|
109
|
-
## Operation model
|
|
91
|
+
### Safety boundaries
|
|
110
92
|
|
|
111
|
-
- `
|
|
112
|
-
- `
|
|
113
|
-
- `
|
|
114
|
-
- `
|
|
115
|
-
- `dfu remove` permanently clears both DFU A/B application slots. The application already loaded in RAM continues until you run `rpremote reset`; use both commands before `rpremote run` when boot-app output would be unwanted. It does not remove other `/home` files or R2P2 firmware.
|
|
116
|
-
- Remote paths use `:/REMOTE/PATH`. `fs cp` transfers between one local and one remote path. `fs push LOCAL_DIR :/REMOTE_DIR` is an alias of `fs cp --recursive`; it creates missing remote directories and uploads the local directory contents. It does not delete remote files. `fs rm` permanently deletes the selected remote path.
|
|
93
|
+
- `flash` replaces persistent R2P2 firmware. `bootsel --reset-flash-memory` erases all Pico 2 external flash and requires R2P2 to be flashed again.
|
|
94
|
+
- `dfu remove` permanently clears both DFU A/B application slots. Reset separately to stop an application already running in RAM.
|
|
95
|
+
- `fs rm` permanently deletes the selected remote path. Recursive uploads do not delete files that exist only on the board.
|
|
96
|
+
- `run` and `exec` remove their temporary remote file. Their nonzero Ruby-exception status requires compatible R2P2 firmware; UF2 files built from this repository include that support.
|
|
117
97
|
|
|
118
98
|
## Documentation and examples
|
|
119
99
|
|
|
120
|
-
|
|
100
|
+
- [Choose commands by workflow](https://github.com/ogom/rpremote/blob/main/docs/command.md)
|
|
101
|
+
- [Configure a project](https://github.com/ogom/rpremote/blob/main/docs/config.md)
|
|
102
|
+
- [Build custom firmware](https://github.com/ogom/rpremote/blob/main/docs/firmware.md)
|
|
103
|
+
- [Manage Mrbgems and Mrbgems.lock](https://github.com/ogom/rpremote/blob/main/docs/mrbgems.md)
|
|
104
|
+
- [Update a boot application with PicoModem DFU](https://github.com/ogom/rpremote/blob/main/docs/dfu.md)
|
|
105
|
+
- [Browse electronic-craft examples](https://github.com/ogom/rpremote/tree/main/examples)
|
|
121
106
|
|
|
122
107
|
## Related projects
|
|
123
108
|
|
data/RELEASING.md
CHANGED
|
@@ -5,9 +5,10 @@ This checklist prepares and publishes one immutable gem version. Run every comma
|
|
|
5
5
|
## 1. Prepare the version
|
|
6
6
|
|
|
7
7
|
1. Update `Rpremote::VERSION` in `lib/rpremote/version.rb`.
|
|
8
|
-
2.
|
|
9
|
-
3.
|
|
10
|
-
4.
|
|
8
|
+
2. Replace `Unreleased` in the package `CHANGELOG.md` with the same version and release date, then add a new empty `Unreleased` section.
|
|
9
|
+
3. If repository-level examples, local mrbgems, or support files changed, finalize the corresponding `Unreleased` section in `../../CHANGELOG.md` with the same release version and date, then add a new empty `Unreleased` section.
|
|
10
|
+
4. Commit all intended files and verify that `git status --short` is empty.
|
|
11
|
+
5. Run the non-publishing release check:
|
|
11
12
|
|
|
12
13
|
```sh
|
|
13
14
|
bundle exec rake release:check
|
|
@@ -48,7 +49,7 @@ bundle exec rake release
|
|
|
48
49
|
After the tag exists on GitHub, create a GitHub Release and attach the exact gem that passed validation. `--verify-tag` prevents GitHub CLI from silently creating a tag at another commit:
|
|
49
50
|
|
|
50
51
|
```sh
|
|
51
|
-
gh release create v0.
|
|
52
|
+
gh release create v0.5.0 rpremote-0.5.0.gem --verify-tag --generate-notes
|
|
52
53
|
```
|
|
53
54
|
|
|
54
|
-
Replace `0.
|
|
55
|
+
Replace `0.5.0` in both places for later versions. Finally, install from RubyGems.org in a fresh environment and run `rpremote --version`.
|
|
@@ -7,6 +7,8 @@ module Rpremote
|
|
|
7
7
|
SHELL_READY_TIMEOUT = 2.0
|
|
8
8
|
RETRY_INTERVAL = 0.25
|
|
9
9
|
RESET_FLASH_FIRMWARE = File.join("firmware", "nuke_universal.uf2").freeze
|
|
10
|
+
REMOTE_SCRIPT_PATH = "/home/.rpremote-bootsel.rb"
|
|
11
|
+
REMOTE_SCRIPT = "require \"bootsel\"\nMachine.enter_bootsel\n".b.freeze
|
|
10
12
|
|
|
11
13
|
class Error < Rpremote::Error; end
|
|
12
14
|
|
|
@@ -47,7 +49,8 @@ module Rpremote
|
|
|
47
49
|
|
|
48
50
|
request_bootsel(
|
|
49
51
|
port: port, baud: baud, deadline: deadline, output: output,
|
|
50
|
-
serial: serial, device: device, shell: shell,
|
|
52
|
+
serial: serial, device: device, shell: shell, modem: services.fetch(:modem) { PicoModem },
|
|
53
|
+
sleeper: sleeper, clock: clock
|
|
51
54
|
)
|
|
52
55
|
|
|
53
56
|
remaining = deadline - clock.call
|
|
@@ -66,6 +69,7 @@ module Rpremote
|
|
|
66
69
|
serial = context.fetch(:serial)
|
|
67
70
|
device = context.fetch(:device)
|
|
68
71
|
shell = context.fetch(:shell)
|
|
72
|
+
modem = context.fetch(:modem) { PicoModem }
|
|
69
73
|
sleeper = context.fetch(:sleeper)
|
|
70
74
|
clock = context.fetch(:clock)
|
|
71
75
|
announced = false
|
|
@@ -84,11 +88,9 @@ module Rpremote
|
|
|
84
88
|
|
|
85
89
|
remote_shell = shell.new(connection, timeout: [remaining, SHELL_READY_TIMEOUT].min)
|
|
86
90
|
remote_shell.synchronize!
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
remote_shell.send_command("bootsel")
|
|
91
|
+
modem.new(connection, timeout: remaining).upload(REMOTE_SCRIPT_PATH, REMOTE_SCRIPT)
|
|
92
|
+
remote_shell.synchronize!
|
|
93
|
+
remote_shell.send_command("./#{File.basename(REMOTE_SCRIPT_PATH)}")
|
|
92
94
|
requested = true
|
|
93
95
|
end
|
|
94
96
|
break if requested
|
data/lib/rpremote/builder.rb
CHANGED
|
@@ -97,7 +97,7 @@ module Rpremote
|
|
|
97
97
|
def mrbgems_config_language(vm_name, language, version)
|
|
98
98
|
return language unless vm_name
|
|
99
99
|
|
|
100
|
-
modern = Gem::Version.new(version) >= Gem::Version.new("4.0.0")
|
|
100
|
+
modern = version == "latest" || Gem::Version.new(version) >= Gem::Version.new("4.0.0")
|
|
101
101
|
return modern ? "femtoruby" : "picoruby" if vm_name == :mrubyc
|
|
102
102
|
|
|
103
103
|
modern ? "picoruby" : "microruby"
|
|
@@ -11,7 +11,6 @@ module Rpremote
|
|
|
11
11
|
|
|
12
12
|
class Error < Rpremote::Error; end
|
|
13
13
|
|
|
14
|
-
# The command intentionally keeps the six deployment stages visible in execution order.
|
|
15
14
|
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
16
15
|
def self.run(args, defaults:, output: $stdout, error: $stderr, services: {})
|
|
17
16
|
options = parse_options(args, defaults)
|
|
@@ -21,7 +20,6 @@ module Rpremote
|
|
|
21
20
|
library, remote_library, source = project_files(project)
|
|
22
21
|
Language.validate!(options[:language])
|
|
23
22
|
|
|
24
|
-
builder = services.fetch(:builder) { Builder.new }
|
|
25
23
|
flasher = services.fetch(:flasher, Flasher)
|
|
26
24
|
serial = services.fetch(:serial, Serial)
|
|
27
25
|
device = services.fetch(:device, Device)
|
|
@@ -29,13 +27,16 @@ module Rpremote
|
|
|
29
27
|
target_options = options.slice(:language, :language_version, :board, :cache_dir, :firmware)
|
|
30
28
|
firmware_path = Target.new(**target_options).firmware_path(root: Dir.pwd)
|
|
31
29
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
30
|
+
if options[:build]
|
|
31
|
+
builder = services.fetch(:builder) { Builder.new }
|
|
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
|
+
end
|
|
39
40
|
|
|
40
41
|
flash_service = flasher.new(timeout: options[:timeout])
|
|
41
42
|
bootsel_mount = resolve_bootsel_mount(options, flash_service, output, services)
|
|
@@ -48,7 +49,7 @@ module Rpremote
|
|
|
48
49
|
port: options[:port]
|
|
49
50
|
)
|
|
50
51
|
|
|
51
|
-
connection_options = options.merge(port: result.port)
|
|
52
|
+
connection_options = options.except(:build).merge(port: result.port)
|
|
52
53
|
output.puts("deploy connect: waiting for R2P2 Shell on #{result.port}")
|
|
53
54
|
wait_for_shell(
|
|
54
55
|
port: result.port, baud: options[:baud], timeout: options[:timeout], serial: serial,
|
|
@@ -160,13 +161,15 @@ module Rpremote
|
|
|
160
161
|
mount: defaults[:mount],
|
|
161
162
|
port: defaults[:port],
|
|
162
163
|
baud: defaults.fetch(:baud, Serial::BAUD_RATE),
|
|
163
|
-
timeout: defaults.fetch(:timeout, Flasher::DEFAULT_TIMEOUT)
|
|
164
|
+
timeout: defaults.fetch(:timeout, Flasher::DEFAULT_TIMEOUT),
|
|
165
|
+
build: false
|
|
164
166
|
}
|
|
165
167
|
end
|
|
166
168
|
private_class_method :default_options
|
|
167
169
|
|
|
168
170
|
def self.option_parser(options)
|
|
169
171
|
OptionParser.new do |parser|
|
|
172
|
+
parser.on("--build") { options[:build] = true }
|
|
170
173
|
parser.on("--language LANGUAGE") { |value| options[:language] = value }
|
|
171
174
|
parser.on("--language-version VERSION") { |value| options[:language_version] = value }
|
|
172
175
|
parser.on("--board BOARD") { |value| options[:board] = value }
|
data/lib/rpremote/help.rb
CHANGED
|
@@ -10,7 +10,7 @@ module Rpremote
|
|
|
10
10
|
"[--firmware FILE] [--cache DIR] [--mrbgems FILE|--no-mrbgems]",
|
|
11
11
|
build_clean: "rpremote build clean",
|
|
12
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] " \
|
|
13
|
+
deploy: "rpremote deploy PATH [--build] [--language LANGUAGE] [--language-version VERSION] [--board BOARD] " \
|
|
14
14
|
"[--firmware FILE] [--cache DIR] [--mrbgems FILE|--no-mrbgems] [--mount DIR] " \
|
|
15
15
|
"[--port PORT] [--baud RATE] [--timeout SEC]",
|
|
16
16
|
dfu_app: "rpremote dfu app FILE [--type ruby|rite] [--port PORT] [--baud RATE] [--timeout SEC]",
|
|
@@ -73,8 +73,9 @@ module Rpremote
|
|
|
73
73
|
|
|
74
74
|
Options:
|
|
75
75
|
--force download the PicoRuby source again during setup
|
|
76
|
+
--build build the selected firmware before deploy flashes it
|
|
76
77
|
--language-version VERSION
|
|
77
|
-
use
|
|
78
|
+
use a PicoRuby tag or latest (default: latest)
|
|
78
79
|
--cache DIR use another project cache directory
|
|
79
80
|
--mrbgems FILE use an explicit Mrbgems definition during build or deploy
|
|
80
81
|
--no-mrbgems build or deploy without the automatically detected Mrbgems
|
|
@@ -125,7 +126,7 @@ module Rpremote
|
|
|
125
126
|
Usage: #{COMMAND_USAGE.fetch(:setup)}
|
|
126
127
|
|
|
127
128
|
Creates config/setting.json when it does not exist, then downloads and prepares PicoRuby source and the official Raspberry Pi nuke_universal.uf2 firmware.
|
|
128
|
-
Options: --language LANGUAGE (picoruby), --language-version VERSION (
|
|
129
|
+
Options: --language LANGUAGE (picoruby), --language-version VERSION (latest), --cache DIR (firmware), --force.
|
|
129
130
|
This changes the project configuration and source cache but does not connect to a board.
|
|
130
131
|
HELP
|
|
131
132
|
end
|
|
@@ -152,10 +153,11 @@ module Rpremote
|
|
|
152
153
|
<<~HELP
|
|
153
154
|
Usage: #{COMMAND_USAGE.fetch(:deploy)}
|
|
154
155
|
|
|
155
|
-
|
|
156
|
+
Enters BOOTSEL when needed, flashes the existing selected UF2, and waits for the R2P2 Shell to become ready.
|
|
157
|
+
With --build, builds the selected custom UF2 before entering BOOTSEL and flashing it.
|
|
156
158
|
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
159
|
The stages run in order and stop at the first failure. Flashing replaces persistent board firmware.
|
|
158
|
-
deploy requires PicoRuby 4.x firmware
|
|
160
|
+
deploy requires current PicoRuby 4.x firmware.
|
|
159
161
|
Options combine build, BOOTSEL, flash, library-copy, and run settings. The first install of firmware with automatic BOOTSEL still requires holding BOOTSEL.
|
|
160
162
|
HELP
|
|
161
163
|
end
|
|
@@ -240,7 +242,7 @@ module Rpremote
|
|
|
240
242
|
Usage: #{COMMAND_USAGE.fetch(:flash)}
|
|
241
243
|
|
|
242
244
|
Copies the selected UF2 to an RP2350 BOOTSEL volume and waits for R2P2 to reconnect. It replaces persistent board firmware.
|
|
243
|
-
Defaults select pico2, firmware/picoruby-
|
|
245
|
+
Defaults select pico2, firmware/picoruby-latest-pico2.uf2, an automatic BOOTSEL mount and CDC 0 port, and 20 seconds.
|
|
244
246
|
HELP
|
|
245
247
|
end
|
|
246
248
|
|
|
@@ -36,6 +36,7 @@ module Rpremote
|
|
|
36
36
|
unless File.directory?(source_dir) && !force
|
|
37
37
|
FileUtils.rm_rf(source_dir) if force
|
|
38
38
|
extractor.call(archive_path, cache_dir)
|
|
39
|
+
normalize_extracted_source
|
|
39
40
|
end
|
|
40
41
|
raise ExtractError, "PicoRuby source was not extracted: #{source_dir}" unless File.directory?(source_dir)
|
|
41
42
|
|
|
@@ -45,7 +46,7 @@ module Rpremote
|
|
|
45
46
|
end
|
|
46
47
|
|
|
47
48
|
def archive_url
|
|
48
|
-
"https://github.com/picoruby/picoruby/archive/refs
|
|
49
|
+
"https://github.com/picoruby/picoruby/archive/refs/#{source_ref}.zip"
|
|
49
50
|
end
|
|
50
51
|
|
|
51
52
|
def archive_path
|
|
@@ -60,6 +61,22 @@ module Rpremote
|
|
|
60
61
|
|
|
61
62
|
attr_reader :fetcher, :extractor, :preparer, :patcher
|
|
62
63
|
|
|
64
|
+
def source_ref
|
|
65
|
+
version == "latest" ? "heads/master" : "tags/#{version}"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def extracted_source_dir
|
|
69
|
+
return source_dir unless version == "latest"
|
|
70
|
+
|
|
71
|
+
File.join(cache_dir, "picoruby-master")
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def normalize_extracted_source
|
|
75
|
+
return if extracted_source_dir == source_dir || !File.directory?(extracted_source_dir)
|
|
76
|
+
|
|
77
|
+
FileUtils.mv(extracted_source_dir, source_dir)
|
|
78
|
+
end
|
|
79
|
+
|
|
63
80
|
def fetch_archive
|
|
64
81
|
temporary = "#{archive_path}.part"
|
|
65
82
|
File.binwrite(temporary, fetcher.call(archive_url))
|
|
@@ -88,7 +105,7 @@ module Rpremote
|
|
|
88
105
|
commands << ["git", "remote", "add", "origin", "https://github.com/picoruby/picoruby.git"]
|
|
89
106
|
end
|
|
90
107
|
commands.push(
|
|
91
|
-
["git", "fetch", "--depth", "1", "origin",
|
|
108
|
+
["git", "fetch", "--depth", "1", "origin", source_ref],
|
|
92
109
|
["git", "checkout", "--force", "FETCH_HEAD"],
|
|
93
110
|
["git", "submodule", "update", "--init", "--recursive", "--depth", "1"]
|
|
94
111
|
)
|
data/lib/rpremote/mrbgems.rb
CHANGED
|
@@ -14,7 +14,7 @@ module Rpremote
|
|
|
14
14
|
COMMIT_PATTERN = /\A[0-9a-f]{40,64}\z/i
|
|
15
15
|
VMS = %i[mruby mrubyc].freeze
|
|
16
16
|
|
|
17
|
-
Dependency = Data.define(:type, :source, :branch, :commit, :path, :require_name)
|
|
17
|
+
Dependency = Data.define(:type, :source, :branch, :commit, :path, :require_name, :auto_require)
|
|
18
18
|
Overlay = Data.define(:path, :fingerprint)
|
|
19
19
|
|
|
20
20
|
class Error < Rpremote::Error; end
|
|
@@ -72,7 +72,9 @@ module Rpremote
|
|
|
72
72
|
lock_data = read_lock(required: false)
|
|
73
73
|
return [] unless lock_data
|
|
74
74
|
|
|
75
|
-
lock_data.fetch("gems").filter_map
|
|
75
|
+
lock_data.fetch("gems").filter_map do |gem|
|
|
76
|
+
gem["require_name"] if gem.fetch("auto_require", true)
|
|
77
|
+
end.uniq
|
|
76
78
|
end
|
|
77
79
|
|
|
78
80
|
def prepend_requires(source)
|
|
@@ -122,14 +124,19 @@ module Rpremote
|
|
|
122
124
|
commit = dependency.commit || previous_commit(previous, dependency) || resolver.call(
|
|
123
125
|
dependency.source, dependency.branch
|
|
124
126
|
)
|
|
125
|
-
{ "type" => "github", "source" => dependency.source,
|
|
126
|
-
|
|
127
|
-
|
|
127
|
+
entry = { "type" => "github", "source" => dependency.source,
|
|
128
|
+
"branch" => dependency.branch, "commit" => validate_commit!(commit),
|
|
129
|
+
"require_name" => dependency.require_name }.compact
|
|
128
130
|
else
|
|
129
|
-
{ "type" => "path", "source" => dependency.source,
|
|
130
|
-
|
|
131
|
-
|
|
131
|
+
entry = { "type" => "path", "source" => dependency.source,
|
|
132
|
+
"sha256" => digest_directory(dependency.path),
|
|
133
|
+
"require_name" => dependency.require_name || local_require_name(dependency.path) }.compact
|
|
132
134
|
end
|
|
135
|
+
unless dependency.auto_require
|
|
136
|
+
entry.delete("require_name")
|
|
137
|
+
entry["auto_require"] = false
|
|
138
|
+
end
|
|
139
|
+
entry
|
|
133
140
|
end
|
|
134
141
|
|
|
135
142
|
def previous_commit(lock_data, dependency)
|
|
@@ -200,18 +207,24 @@ module Rpremote
|
|
|
200
207
|
|
|
201
208
|
def valid_github_lock?(entry)
|
|
202
209
|
GITHUB_PATTERN.match?(entry["source"].to_s) && !entry["branch"].to_s.empty? &&
|
|
203
|
-
COMMIT_PATTERN.match?(entry["commit"].to_s) && valid_require_name?(entry["require_name"])
|
|
210
|
+
COMMIT_PATTERN.match?(entry["commit"].to_s) && valid_require_name?(entry["require_name"]) &&
|
|
211
|
+
valid_auto_require?(entry)
|
|
204
212
|
end
|
|
205
213
|
|
|
206
214
|
def valid_path_lock?(entry)
|
|
207
215
|
entry["type"] == "path" && !entry["source"].to_s.empty? &&
|
|
208
|
-
/\A[0-9a-f]{64}\z/.match?(entry["sha256"].to_s) && valid_require_name?(entry["require_name"])
|
|
216
|
+
/\A[0-9a-f]{64}\z/.match?(entry["sha256"].to_s) && valid_require_name?(entry["require_name"]) &&
|
|
217
|
+
valid_auto_require?(entry)
|
|
209
218
|
end
|
|
210
219
|
|
|
211
220
|
def valid_require_name?(name)
|
|
212
221
|
name.nil? || (name.is_a?(String) && !name.empty? && !name.match?(/[\x00-\x1f\x7f]/))
|
|
213
222
|
end
|
|
214
223
|
|
|
224
|
+
def valid_auto_require?(entry)
|
|
225
|
+
!entry.key?("auto_require") || entry["auto_require"] == true || entry["auto_require"] == false
|
|
226
|
+
end
|
|
227
|
+
|
|
215
228
|
def local_require_name(directory)
|
|
216
229
|
source = File.read(File.join(directory, "mrbgem.rake"))
|
|
217
230
|
match = source.match(/^\s*spec\.require_name\s*=\s*["']([^"']+)["']\s*$/)
|
|
@@ -226,6 +239,8 @@ module Rpremote
|
|
|
226
239
|
|
|
227
240
|
def overlay_source(base_config, target, locked_entries)
|
|
228
241
|
lines = [
|
|
242
|
+
"# frozen_string_literal: true",
|
|
243
|
+
"",
|
|
229
244
|
"# Generated by rpremote. Do not edit.",
|
|
230
245
|
"load #{File.expand_path(base_config).inspect}",
|
|
231
246
|
"conf = MRuby.targets.fetch(#{target.inspect})"
|
|
@@ -272,17 +287,20 @@ module Rpremote
|
|
|
272
287
|
@vm_name = value
|
|
273
288
|
end
|
|
274
289
|
|
|
275
|
-
def gem(github: nil, path: nil, branch: "main", commit: nil, require: nil)
|
|
290
|
+
def gem(github: nil, path: nil, branch: "main", commit: nil, require: nil, auto_require: true)
|
|
276
291
|
sources = [github, path].compact
|
|
277
292
|
raise DefinitionError, "gem requires exactly one of github or path" unless sources.length == 1
|
|
278
293
|
unless require.nil? || (require.is_a?(String) && !require.empty? && !require.match?(/[\x00-\x1f\x7f]/))
|
|
279
294
|
raise DefinitionError, "invalid mrbgem require name: #{require.inspect}"
|
|
280
295
|
end
|
|
281
296
|
|
|
297
|
+
valid_auto_require = [true, false].include?(auto_require)
|
|
298
|
+
raise DefinitionError, "mrbgem auto_require must be true or false: #{auto_require.inspect}" unless valid_auto_require
|
|
299
|
+
|
|
282
300
|
dependency = if github
|
|
283
|
-
github_dependency(github, branch, commit, require)
|
|
301
|
+
github_dependency(github, branch, commit, require, auto_require)
|
|
284
302
|
else
|
|
285
|
-
path_dependency(path, branch, commit, require)
|
|
303
|
+
path_dependency(path, branch, commit, require, auto_require)
|
|
286
304
|
end
|
|
287
305
|
key = [dependency.type, dependency.source]
|
|
288
306
|
raise DefinitionError, "duplicate mrbgem: #{dependency.source}" if dependencies.any? do |item|
|
|
@@ -296,21 +314,23 @@ module Rpremote
|
|
|
296
314
|
|
|
297
315
|
attr_reader :directory
|
|
298
316
|
|
|
299
|
-
def github_dependency(source, branch, commit, require_name)
|
|
317
|
+
def github_dependency(source, branch, commit, require_name, auto_require)
|
|
300
318
|
raise DefinitionError, "invalid GitHub mrbgem: #{source.inspect}" unless GITHUB_PATTERN.match?(source.to_s)
|
|
301
319
|
raise DefinitionError, "GitHub mrbgem branch must not be empty" if branch.to_s.empty?
|
|
302
320
|
raise DefinitionError, "invalid Git commit: #{commit.inspect}" if commit && !COMMIT_PATTERN.match?(commit.to_s)
|
|
303
321
|
|
|
304
322
|
Dependency.new(type: :github, source: source, branch: branch,
|
|
305
|
-
commit: commit&.downcase, path: nil, require_name: require_name
|
|
323
|
+
commit: commit&.downcase, path: nil, require_name: require_name,
|
|
324
|
+
auto_require: auto_require)
|
|
306
325
|
end
|
|
307
326
|
|
|
308
|
-
def path_dependency(source, branch, commit, require_name)
|
|
327
|
+
def path_dependency(source, branch, commit, require_name, auto_require)
|
|
309
328
|
raise DefinitionError, "local mrbgem path must not be empty" if source.to_s.empty?
|
|
310
329
|
raise DefinitionError, "local mrbgem does not accept branch or commit" if branch != "main" || commit
|
|
311
330
|
|
|
312
331
|
Dependency.new(type: :path, source: source, branch: nil, commit: nil,
|
|
313
|
-
path: File.expand_path(source, directory), require_name: require_name
|
|
332
|
+
path: File.expand_path(source, directory), require_name: require_name,
|
|
333
|
+
auto_require: auto_require)
|
|
314
334
|
end
|
|
315
335
|
end
|
|
316
336
|
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
|
|
5
|
+
module Rpremote
|
|
6
|
+
class PicoRubyCompiler
|
|
7
|
+
class Error < StandardError; end
|
|
8
|
+
|
|
9
|
+
CANDIDATES = %w[
|
|
10
|
+
build/mrbc/default/bin/mrbc
|
|
11
|
+
build/host/bin/mrbc
|
|
12
|
+
].freeze
|
|
13
|
+
|
|
14
|
+
def self.ensure_legacy_path!(root)
|
|
15
|
+
root = File.expand_path(root)
|
|
16
|
+
legacy_path = File.join(root, "bin/mrbc")
|
|
17
|
+
return legacy_path if File.executable?(legacy_path)
|
|
18
|
+
|
|
19
|
+
compiler = CANDIDATES.map { |path| File.join(root, path) }.find { |path| File.executable?(path) }
|
|
20
|
+
raise Error, "PicoRuby mrbc was not built under #{root}" unless compiler
|
|
21
|
+
|
|
22
|
+
FileUtils.mkdir_p(File.dirname(legacy_path))
|
|
23
|
+
relative = File.join("..", compiler.delete_prefix("#{root}/"))
|
|
24
|
+
FileUtils.ln_sf(relative, legacy_path)
|
|
25
|
+
legacy_path
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module Rpremote
|
|
4
4
|
class PicoRubySourcePatch
|
|
5
5
|
JOB_PATH = "mrbgems/picoruby-shell/mrblib/job.rb"
|
|
6
|
-
|
|
6
|
+
PWM_PATH = "mrbgems/picoruby-pwm/ports/rp2040/pwm.c"
|
|
7
7
|
PATCH_ALIASES = { "3.4.2" => "3.4.5" }.freeze
|
|
8
8
|
|
|
9
9
|
class Error < Rpremote::Error; end
|
|
@@ -20,8 +20,6 @@ module Rpremote
|
|
|
20
20
|
patch_version = PATCH_ALIASES.fetch(version, version)
|
|
21
21
|
patches(patch_version).each do |patch, prerequisite|
|
|
22
22
|
next unless File.file?(patch) && File.file?(File.join(source, prerequisite))
|
|
23
|
-
next if prerequisite == BOOTSEL_PATH && File.read(File.join(source, prerequisite)).include?("Machine_bootsel")
|
|
24
|
-
|
|
25
23
|
next if git_apply?(source, patch, "--reverse", "--check")
|
|
26
24
|
|
|
27
25
|
raise Error, "cannot apply rpremote patch to PicoRuby #{version}" unless git_apply?(source, patch, "--check")
|
|
@@ -35,7 +33,7 @@ module Rpremote
|
|
|
35
33
|
def patches(patch_version)
|
|
36
34
|
definitions = {
|
|
37
35
|
"ruby-exception-status" => JOB_PATH,
|
|
38
|
-
"
|
|
36
|
+
"pwm-sleep-clock" => PWM_PATH
|
|
39
37
|
}
|
|
40
38
|
definitions.map do |name, prerequisite|
|
|
41
39
|
[File.expand_path("../../patches/picoruby-#{patch_version}-#{name}.patch", __dir__), prerequisite]
|
data/lib/rpremote/target.rb
CHANGED
data/lib/rpremote/terminal.rb
CHANGED
|
@@ -75,8 +75,6 @@ module Rpremote
|
|
|
75
75
|
true
|
|
76
76
|
end
|
|
77
77
|
|
|
78
|
-
# input.raw disables the terminal's usual LF-to-CRLF output conversion.
|
|
79
|
-
# Preserve device CRLF while making a bare LF start at the left margin.
|
|
80
78
|
def normalize_line_endings(data)
|
|
81
79
|
normalized = +"".b
|
|
82
80
|
data.each_byte do |byte|
|
data/lib/rpremote/version.rb
CHANGED
data/lib/rpremote.rb
CHANGED
|
@@ -21,6 +21,7 @@ require_relative "rpremote/runner"
|
|
|
21
21
|
require_relative "rpremote/resetter"
|
|
22
22
|
require_relative "rpremote/recursive_copy"
|
|
23
23
|
require_relative "rpremote/picoruby_source_patch"
|
|
24
|
+
require_relative "rpremote/picoruby_compiler"
|
|
24
25
|
require_relative "rpremote/language_source"
|
|
25
26
|
require_relative "rpremote/nuke_firmware"
|
|
26
27
|
require_relative "rpremote/mrbgems"
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
diff --git a/mrbgems/picoruby-pwm/ports/rp2040/pwm.c b/mrbgems/picoruby-pwm/ports/rp2040/pwm.c
|
|
2
|
+
--- a/mrbgems/picoruby-pwm/ports/rp2040/pwm.c
|
|
3
|
+
+++ b/mrbgems/picoruby-pwm/ports/rp2040/pwm.c
|
|
4
|
+
@@ -1,5 +1,6 @@
|
|
5
|
+
#include "pico/stdlib.h"
|
|
6
|
+
#include "hardware/pwm.h"
|
|
7
|
+
+#include "hardware/structs/clocks.h"
|
|
8
|
+
|
|
9
|
+
#include "../../include/pwm.h"
|
|
10
|
+
|
|
11
|
+
@@ -9,6 +10,11 @@
|
|
12
|
+
void
|
|
13
|
+
PWM_init(uint32_t pin)
|
|
14
|
+
{
|
|
15
|
+
+ /* The scheduler enters WFI while sleep_ms waits. Keep autonomous PWM
|
|
16
|
+
+ * output clocked between scheduler ticks after a PWM pin is initialized. */
|
|
17
|
+
+ hw_set_bits(&clocks_hw->sleep_en0,
|
|
18
|
+
+ CLOCKS_SLEEP_EN0_CLK_SYS_PWM_BITS);
|
|
19
|
+
+
|
|
20
|
+
gpio_set_function(pin, GPIO_FUNC_PWM);
|
|
21
|
+
uint slice_num = pwm_gpio_to_slice_num(pin);
|
|
22
|
+
pwm_set_clkdiv(slice_num, CLK_DIV);
|
data/sig/rpremote.rbs
CHANGED
|
@@ -188,6 +188,15 @@ module Rpremote
|
|
|
188
188
|
def apply: (String source) -> void
|
|
189
189
|
end
|
|
190
190
|
|
|
191
|
+
class PicoRubyCompiler
|
|
192
|
+
CANDIDATES: Array[String]
|
|
193
|
+
|
|
194
|
+
class Error < StandardError
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def self.ensure_legacy_path!: (String root) -> String
|
|
198
|
+
end
|
|
199
|
+
|
|
191
200
|
class Builder
|
|
192
201
|
class Error < Rpremote::Error
|
|
193
202
|
end
|
|
@@ -226,6 +235,8 @@ module Rpremote
|
|
|
226
235
|
attr_reader branch: String?
|
|
227
236
|
attr_reader commit: String?
|
|
228
237
|
attr_reader path: String?
|
|
238
|
+
attr_reader require_name: String?
|
|
239
|
+
attr_reader auto_require: bool
|
|
229
240
|
end
|
|
230
241
|
|
|
231
242
|
class Overlay < Data
|
data/tasks/firmware_build.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
require "fileutils"
|
|
5
|
+
require_relative "../lib/rpremote/picoruby_compiler"
|
|
5
6
|
|
|
6
7
|
REPOSITORY_ROOT = File.expand_path(ENV.fetch("RPREMOTE_ROOT", File.expand_path("../..", __dir__)))
|
|
7
8
|
DEFAULT_BOARD = "pico2"
|
|
@@ -14,7 +15,7 @@ def command!(*command, chdir:, env: {})
|
|
|
14
15
|
end
|
|
15
16
|
|
|
16
17
|
language = ENV.fetch("RPREMOTE_LANGUAGE", "picoruby")
|
|
17
|
-
target_version = ENV.fetch("RPREMOTE_LANGUAGE_VERSION", "
|
|
18
|
+
target_version = ENV.fetch("RPREMOTE_LANGUAGE_VERSION", "latest")
|
|
18
19
|
board = ENV.fetch("RPREMOTE_BOARD", DEFAULT_BOARD)
|
|
19
20
|
abort "Unsupported language: #{language}" unless language == "picoruby"
|
|
20
21
|
abort "Unsupported board: #{board}" unless SUPPORTED_BOARDS.include?(board)
|
|
@@ -43,7 +44,8 @@ command!("rake", "r2p2:setup", chdir: picoruby_root) unless File.directory?(pico
|
|
|
43
44
|
version_header = File.join(picoruby_root, "include/version.h")
|
|
44
45
|
version = File.read(version_header)[/#define PICORUBY_VERSION "(.+?)"/, 1]
|
|
45
46
|
abort "Could not determine PicoRuby version from #{version_header}" unless version
|
|
46
|
-
abort "PicoRuby source is #{version}, but #{target_version} was requested."
|
|
47
|
+
abort "PicoRuby source is #{version}, but #{target_version} was requested." \
|
|
48
|
+
unless target_version == "latest" || version == target_version
|
|
47
49
|
|
|
48
50
|
cache_file = File.join(output_dir, "CMakeCache.txt")
|
|
49
51
|
if File.file?(cache_file) && !File.read(cache_file).include?("CMAKE_HOME_DIRECTORY:INTERNAL=#{cmake_source_dir}")
|
|
@@ -64,6 +66,12 @@ if fingerprint && File.directory?(mruby_build_dir)
|
|
|
64
66
|
FileUtils.rm_rf(mruby_build_dir)
|
|
65
67
|
end
|
|
66
68
|
command!("rake", chdir: picoruby_root, env: build_env)
|
|
69
|
+
begin
|
|
70
|
+
compiler = Rpremote::PicoRubyCompiler.ensure_legacy_path!(picoruby_root)
|
|
71
|
+
puts "Using PicoRuby compiler: #{compiler}"
|
|
72
|
+
rescue Rpremote::PicoRubyCompiler::Error => e
|
|
73
|
+
abort e.message
|
|
74
|
+
end
|
|
67
75
|
|
|
68
76
|
cmake_definitions = [
|
|
69
77
|
"-D", "PICORUBY_ROOT=#{picoruby_root}",
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rpremote
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- ogom
|
|
@@ -46,6 +46,7 @@ files:
|
|
|
46
46
|
- lib/rpremote/mrbgems_command.rb
|
|
47
47
|
- lib/rpremote/nuke_firmware.rb
|
|
48
48
|
- lib/rpremote/picomodem.rb
|
|
49
|
+
- lib/rpremote/picoruby_compiler.rb
|
|
49
50
|
- lib/rpremote/picoruby_source_patch.rb
|
|
50
51
|
- lib/rpremote/recursive_copy.rb
|
|
51
52
|
- lib/rpremote/remote_path.rb
|
|
@@ -57,9 +58,8 @@ files:
|
|
|
57
58
|
- lib/rpremote/target.rb
|
|
58
59
|
- lib/rpremote/terminal.rb
|
|
59
60
|
- lib/rpremote/version.rb
|
|
60
|
-
- patches/picoruby-3.4.5-bootsel.patch
|
|
61
61
|
- patches/picoruby-3.4.5-ruby-exception-status.patch
|
|
62
|
-
- patches/picoruby-4.0.3-
|
|
62
|
+
- patches/picoruby-4.0.3-pwm-sleep-clock.patch
|
|
63
63
|
- patches/picoruby-4.0.3-ruby-exception-status.patch
|
|
64
64
|
- sig/rpremote.rbs
|
|
65
65
|
- tasks/firmware_build.rb
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
diff --git a/mrbgems/picoruby-machine/include/machine.h b/mrbgems/picoruby-machine/include/machine.h
|
|
2
|
-
--- a/mrbgems/picoruby-machine/include/machine.h
|
|
3
|
-
+++ b/mrbgems/picoruby-machine/include/machine.h
|
|
4
|
-
@@ -48,1 +48,2 @@
|
|
5
|
-
void Machine_reboot(void);
|
|
6
|
-
+void Machine_bootsel(void);
|
|
7
|
-
diff --git a/mrbgems/picoruby-machine/mrblib/machine.rb b/mrbgems/picoruby-machine/mrblib/machine.rb
|
|
8
|
-
--- a/mrbgems/picoruby-machine/mrblib/machine.rb
|
|
9
|
-
+++ b/mrbgems/picoruby-machine/mrblib/machine.rb
|
|
10
|
-
@@ -22,0 +23,4 @@
|
|
11
|
-
+ def self.bootsel
|
|
12
|
-
+ self._bootsel
|
|
13
|
-
+ end
|
|
14
|
-
+
|
|
15
|
-
diff --git a/mrbgems/picoruby-machine/ports/rp2040/machine.c b/mrbgems/picoruby-machine/ports/rp2040/machine.c
|
|
16
|
-
--- a/mrbgems/picoruby-machine/ports/rp2040/machine.c
|
|
17
|
-
+++ b/mrbgems/picoruby-machine/ports/rp2040/machine.c
|
|
18
|
-
@@ -12,0 +13,1 @@
|
|
19
|
-
+#include "pico/bootrom.h"
|
|
20
|
-
@@ -32,0 +34,6 @@
|
|
21
|
-
+void
|
|
22
|
-
+Machine_bootsel(void)
|
|
23
|
-
+{
|
|
24
|
-
+ rom_reset_usb_boot(0, 0);
|
|
25
|
-
+}
|
|
26
|
-
+
|
|
27
|
-
diff --git a/mrbgems/picoruby-machine/src/mruby/machine.c b/mrbgems/picoruby-machine/src/mruby/machine.c
|
|
28
|
-
--- a/mrbgems/picoruby-machine/src/mruby/machine.c
|
|
29
|
-
+++ b/mrbgems/picoruby-machine/src/mruby/machine.c
|
|
30
|
-
@@ -417,0 +418,11 @@
|
|
31
|
-
+static mrb_value
|
|
32
|
-
+mrb_s__bootsel(mrb_state *mrb, mrb_value self)
|
|
33
|
-
+{
|
|
34
|
-
+#if !defined(PICORB_PLATFORM_POSIX)
|
|
35
|
-
+ Machine_bootsel();
|
|
36
|
-
+#else
|
|
37
|
-
+ mrb_raise(mrb, E_NOTIMP_ERROR, "Machine._bootsel is not available on this platform");
|
|
38
|
-
+#endif
|
|
39
|
-
+ return mrb_nil_value();
|
|
40
|
-
+}
|
|
41
|
-
+
|
|
42
|
-
@@ -443,0 +455,1 @@
|
|
43
|
-
+ mrb_define_class_method_id(mrb, module_Machine, MRB_SYM(_bootsel), mrb_s__bootsel, MRB_ARGS_NONE());
|
|
44
|
-
diff --git a/mrbgems/picoruby-machine/src/mrubyc/machine.c b/mrbgems/picoruby-machine/src/mrubyc/machine.c
|
|
45
|
-
--- a/mrbgems/picoruby-machine/src/mrubyc/machine.c
|
|
46
|
-
+++ b/mrbgems/picoruby-machine/src/mrubyc/machine.c
|
|
47
|
-
@@ -252,0 +253,11 @@
|
|
48
|
-
+static void
|
|
49
|
-
+c_Machine__bootsel(mrbc_vm *vm, mrbc_value *v, int argc)
|
|
50
|
-
+{
|
|
51
|
-
+#if !defined(PICORB_PLATFORM_POSIX)
|
|
52
|
-
+ Machine_bootsel();
|
|
53
|
-
+#else
|
|
54
|
-
+ mrbc_raise(vm, MRBC_CLASS(RuntimeError), "Machine.bootsel is not available on this platform.");
|
|
55
|
-
+#endif
|
|
56
|
-
+ SET_NIL_RETURN();
|
|
57
|
-
+}
|
|
58
|
-
+
|
|
59
|
-
@@ -439,0 +451,1 @@
|
|
60
|
-
+ mrbc_define_method(vm, module_Machine, "_bootsel", c_Machine__bootsel);
|
|
61
|
-
diff --git a/mrbgems/picoruby-shell/shell_executables/_path.txt b/mrbgems/picoruby-shell/shell_executables/_path.txt
|
|
62
|
-
--- a/mrbgems/picoruby-shell/shell_executables/_path.txt
|
|
63
|
-
+++ b/mrbgems/picoruby-shell/shell_executables/_path.txt
|
|
64
|
-
@@ -16,0 +17,1 @@
|
|
65
|
-
+/bin/bootsel
|
|
66
|
-
diff --git a/mrbgems/picoruby-shell/shell_executables/bootsel.rb b/mrbgems/picoruby-shell/shell_executables/bootsel.rb
|
|
67
|
-
new file mode 100644
|
|
68
|
-
--- /dev/null
|
|
69
|
-
+++ b/mrbgems/picoruby-shell/shell_executables/bootsel.rb
|
|
70
|
-
@@ -0,0 +1 @@
|
|
71
|
-
+Machine.bootsel
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
diff --git a/mrbgems/picoruby-machine/include/machine.h b/mrbgems/picoruby-machine/include/machine.h
|
|
2
|
-
--- a/mrbgems/picoruby-machine/include/machine.h
|
|
3
|
-
+++ b/mrbgems/picoruby-machine/include/machine.h
|
|
4
|
-
@@ -68,1 +68,2 @@
|
|
5
|
-
void Machine_reboot(void);
|
|
6
|
-
+void Machine_bootsel(void);
|
|
7
|
-
diff --git a/mrbgems/picoruby-machine/mrblib/machine.rb b/mrbgems/picoruby-machine/mrblib/machine.rb
|
|
8
|
-
--- a/mrbgems/picoruby-machine/mrblib/machine.rb
|
|
9
|
-
+++ b/mrbgems/picoruby-machine/mrblib/machine.rb
|
|
10
|
-
@@ -22,0 +23,4 @@
|
|
11
|
-
+ def self.bootsel
|
|
12
|
-
+ self._bootsel
|
|
13
|
-
+ end
|
|
14
|
-
+
|
|
15
|
-
diff --git a/mrbgems/picoruby-machine/ports/rp2040/machine.c b/mrbgems/picoruby-machine/ports/rp2040/machine.c
|
|
16
|
-
--- a/mrbgems/picoruby-machine/ports/rp2040/machine.c
|
|
17
|
-
+++ b/mrbgems/picoruby-machine/ports/rp2040/machine.c
|
|
18
|
-
@@ -10,0 +11,1 @@
|
|
19
|
-
+#include "pico/bootrom.h"
|
|
20
|
-
@@ -42,0 +44,6 @@
|
|
21
|
-
+void
|
|
22
|
-
+Machine_bootsel(void)
|
|
23
|
-
+{
|
|
24
|
-
+ rom_reset_usb_boot(0, 0);
|
|
25
|
-
+}
|
|
26
|
-
+
|
|
27
|
-
diff --git a/mrbgems/picoruby-machine/src/mruby/machine.c b/mrbgems/picoruby-machine/src/mruby/machine.c
|
|
28
|
-
--- a/mrbgems/picoruby-machine/src/mruby/machine.c
|
|
29
|
-
+++ b/mrbgems/picoruby-machine/src/mruby/machine.c
|
|
30
|
-
@@ -496,0 +497,11 @@
|
|
31
|
-
+static mrb_value
|
|
32
|
-
+mrb_s__bootsel(mrb_state *mrb, mrb_value self)
|
|
33
|
-
+{
|
|
34
|
-
+#if !defined(PICORB_PLATFORM_POSIX)
|
|
35
|
-
+ Machine_bootsel();
|
|
36
|
-
+#else
|
|
37
|
-
+ mrb_raise(mrb, E_NOTIMP_ERROR, "Machine._bootsel is not available on this platform");
|
|
38
|
-
+#endif
|
|
39
|
-
+ return mrb_nil_value();
|
|
40
|
-
+}
|
|
41
|
-
+
|
|
42
|
-
@@ -529,0 +541,1 @@
|
|
43
|
-
+ mrb_define_class_method_id(mrb, module_Machine, MRB_SYM(_bootsel), mrb_s__bootsel, MRB_ARGS_NONE());
|
|
44
|
-
diff --git a/mrbgems/picoruby-machine/src/mrubyc/machine.c b/mrbgems/picoruby-machine/src/mrubyc/machine.c
|
|
45
|
-
--- a/mrbgems/picoruby-machine/src/mrubyc/machine.c
|
|
46
|
-
+++ b/mrbgems/picoruby-machine/src/mrubyc/machine.c
|
|
47
|
-
@@ -316,0 +317,11 @@
|
|
48
|
-
+static void
|
|
49
|
-
+c_Machine__bootsel(mrbc_vm *vm, mrbc_value *v, int argc)
|
|
50
|
-
+{
|
|
51
|
-
+#if !defined(PICORB_PLATFORM_POSIX)
|
|
52
|
-
+ Machine_bootsel();
|
|
53
|
-
+#else
|
|
54
|
-
+ mrbc_raise(vm, MRBC_CLASS(RuntimeError), "Machine.bootsel is not available on this platform.");
|
|
55
|
-
+#endif
|
|
56
|
-
+ SET_NIL_RETURN();
|
|
57
|
-
+}
|
|
58
|
-
+
|
|
59
|
-
@@ -546,0 +558,1 @@
|
|
60
|
-
+ mrbc_define_method(vm, module_Machine, "_bootsel", c_Machine__bootsel);
|
|
61
|
-
diff --git a/mrbgems/picoruby-shell/shell_executables/_path.txt b/mrbgems/picoruby-shell/shell_executables/_path.txt
|
|
62
|
-
--- a/mrbgems/picoruby-shell/shell_executables/_path.txt
|
|
63
|
-
+++ b/mrbgems/picoruby-shell/shell_executables/_path.txt
|
|
64
|
-
@@ -17,0 +18,1 @@
|
|
65
|
-
+/bin/bootsel
|
|
66
|
-
diff --git a/mrbgems/picoruby-shell/shell_executables/bootsel.rb b/mrbgems/picoruby-shell/shell_executables/bootsel.rb
|
|
67
|
-
new file mode 100644
|
|
68
|
-
--- /dev/null
|
|
69
|
-
+++ b/mrbgems/picoruby-shell/shell_executables/bootsel.rb
|
|
70
|
-
@@ -0,0 +1 @@
|
|
71
|
-
+Machine.bootsel
|