prremote 0.2.1 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +13 -13
- data/VERSION +1 -1
- data/lib/prremote/esp_flasher.rb +80 -15
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ddaba1909af7ae567dda45d75d78c240347062d0e00a5dab966190b22f0b28eb
|
|
4
|
+
data.tar.gz: e9f933d93ca77aa8324f2502958150370afe8f61b902c4420d06cfeb3edfcabd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d885014b3034e5bcbb02203f12c994544a147f5d2439af7fd2162f39591104ce220f697732e6a62a455374508f62fa8685eb2a2f4d9e67f8593d83b9ec4676db
|
|
7
|
+
data.tar.gz: dc99149a4481721d2347f73345684ff1dd780a9ce96e4f861376a3077e0cce5be11a3be6598e4728cf1659bab1403e9678248ab7ffbce2c17091db9e08fd2695
|
data/README.md
CHANGED
|
@@ -18,9 +18,9 @@ Inspired by [mpremote](https://docs.micropython.org/en/latest/reference/mpremote
|
|
|
18
18
|
|
|
19
19
|
- Ruby 3.4 or later
|
|
20
20
|
- Supported boards:
|
|
21
|
-
- Raspberry Pi Pico W / Pico
|
|
22
|
-
- ESP32 (classic) — e.g. M5GO / M5Stack Core gen1, generic dev boards
|
|
23
|
-
- ESP32-C6 (RISC-V) — e.g. Seeed Studio XIAO ESP32C6
|
|
21
|
+
- [Raspberry Pi Pico W](https://www.raspberrypi.com/products/raspberry-pi-pico-w/) / [Pico](https://www.raspberrypi.com/products/raspberry-pi-pico/)
|
|
22
|
+
- ESP32 (classic) — e.g. [M5GO / M5Stack Core gen1](https://docs.m5stack.com/en/core/m5go), generic [ESP32](https://www.espressif.com/en/products/socs/esp32) dev boards
|
|
23
|
+
- ESP32-C6 (RISC-V) — e.g. [Seeed Studio XIAO ESP32C6](https://wiki.seeedstudio.com/xiao_esp32c6_getting_started/)
|
|
24
24
|
- `mrbc` (mruby 4.x) for `run`, `deploy`, and `eval`
|
|
25
25
|
- macOS: `brew install mruby`
|
|
26
26
|
- Linux: build from source — [github.com/mruby/mruby/releases](https://github.com/mruby/mruby/releases)
|
|
@@ -74,7 +74,11 @@ The firmware is downloaded from GitHub Releases on first use and cached in `~/.p
|
|
|
74
74
|
|
|
75
75
|
Pico boards: put the device into BOOTSEL mode (hold BOOTSEL, connect USB, release) when prompted.
|
|
76
76
|
|
|
77
|
-
ESP32
|
|
77
|
+
ESP32 (classic): no button dance and no extra tools needed — the firmware is written over the serial port by prremote's pure-Ruby implementation of the Espressif bootloader protocol (the chip is reset into its boot ROM automatically, and the write is verified with an on-chip MD5).
|
|
78
|
+
|
|
79
|
+
ESP32-C6: flashing is delegated to [esptool](https://docs.espressif.com/projects/esptool/en/latest/), because the C6 boot ROM rejects the direct write the classic ESP32 accepts. Install it first (`brew install esptool`, or `pip3 install esptool`), and put the board into bootloader mode when prompted — on the XIAO ESP32C6, hold **BOOT**, press **RST**, then release both.
|
|
80
|
+
|
|
81
|
+
Reflashing the runtime does not erase a deployed script on either chip.
|
|
78
82
|
|
|
79
83
|
---
|
|
80
84
|
|
|
@@ -128,7 +132,7 @@ Evaluate a Ruby one-liner on the device.
|
|
|
128
132
|
|
|
129
133
|
```bash
|
|
130
134
|
prremote eval "puts 1 + 1"
|
|
131
|
-
prremote eval "
|
|
135
|
+
prremote eval "GPIO.led.write 1"
|
|
132
136
|
```
|
|
133
137
|
|
|
134
138
|
---
|
|
@@ -172,8 +176,8 @@ Show the gem version, mrbc version, and the connected device's runtime version.
|
|
|
172
176
|
|
|
173
177
|
```bash
|
|
174
178
|
prremote version
|
|
175
|
-
# prremote: 0.
|
|
176
|
-
# runtime: 0.
|
|
179
|
+
# prremote: 0.3.1
|
|
180
|
+
# runtime: 0.3.1 (/dev/tty.usbmodem101)
|
|
177
181
|
# mrbc: mruby 4.0.0 (2026-04-20) (/opt/homebrew/bin/mrbc)
|
|
178
182
|
```
|
|
179
183
|
|
|
@@ -218,7 +222,7 @@ prremote flashes a minimal C firmware (built on mruby/c) onto the Pico W. The fi
|
|
|
218
222
|
- `DPLY` + `.mrb` bytecode → save to flash and confirm with `DEPLOYED` (`deploy`)
|
|
219
223
|
3. Waits for the next command
|
|
220
224
|
|
|
221
|
-
Scripts saved via `deploy` are stored in flash and run automatically on every boot. GPIO / ADC / PWM / I2C / SPI bindings are available on all boards; WiFi
|
|
225
|
+
Scripts saved via `deploy` are stored in flash and run automatically on every boot. GPIO / ADC / PWM / I2C / SPI bindings are available on all boards; a `WiFi` module on boards with a radio (Pico W and ESP32); an `LCD` class (ILI9342C) on ESP32 / M5Stack.
|
|
222
226
|
|
|
223
227
|
---
|
|
224
228
|
|
|
@@ -284,8 +288,4 @@ bundle exec rake test
|
|
|
284
288
|
|
|
285
289
|
- [mruby/c](https://github.com/mrubyc/mrubyc) — Lightweight mruby implementation used in the runtime
|
|
286
290
|
- [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
|
|
287
|
-
- [mpremote](https://docs.micropython.org/en/latest/reference/mpremote.html) — MicroPython equivalent (inspiration)
|
|
288
|
-
|
|
289
|
-
https://wiki.seeedstudio.com/ja/xiao_esp32c6_getting_started/
|
|
290
|
-
|
|
291
|
-
brew install esptool
|
|
291
|
+
- [mpremote](https://docs.micropython.org/en/latest/reference/mpremote.html) — MicroPython equivalent (inspiration)
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.3.1
|
data/lib/prremote/esp_flasher.rb
CHANGED
|
@@ -20,6 +20,7 @@ module Prremote
|
|
|
20
20
|
FLASH_DATA = 0x03
|
|
21
21
|
FLASH_END = 0x04
|
|
22
22
|
SYNC = 0x08
|
|
23
|
+
READ_REG = 0x0A
|
|
23
24
|
SPI_SET_PARAMS = 0x0B
|
|
24
25
|
SPI_ATTACH = 0x0D
|
|
25
26
|
CHANGE_BAUD = 0x0F
|
|
@@ -32,6 +33,16 @@ module Prremote
|
|
|
32
33
|
# Boards with built-in USB Serial/JTAG — flashed via esptool subprocess.
|
|
33
34
|
USB_JTAG_SERIAL_BOARDS = %w[esp32c6].freeze
|
|
34
35
|
|
|
36
|
+
# Classic ESP32 ROM only auto-attaches flash on modules with pads wired
|
|
37
|
+
# to the default HSPI pins. SiP packages with in-package flash (e.g.
|
|
38
|
+
# ESP32-PICO-D4, used on M5StickC/PLUS) route flash through pins burned
|
|
39
|
+
# into eFuse instead, and FLASH_BEGIN silently hangs forever (no error
|
|
40
|
+
# response) if SPI_ATTACH(0) is sent on those. Mirrors esptool's
|
|
41
|
+
# attach_flash()/get_chip_spi_pads() (cmds.py / targets/esp32.py).
|
|
42
|
+
EFUSE_RD_REG_BASE = 0x3FF5A000
|
|
43
|
+
EFUSE_BLK0_RDATA3_REG_OFFS = EFUSE_RD_REG_BASE + 0x00C
|
|
44
|
+
EFUSE_BLK0_RDATA5_REG_OFFS = EFUSE_RD_REG_BASE + 0x014
|
|
45
|
+
|
|
35
46
|
# ROM status-byte count: classic ESP32 appends 4 bytes, RISC-V chips 2.
|
|
36
47
|
STATUS_BYTES_BY_BOARD = Hash.new(4).freeze
|
|
37
48
|
|
|
@@ -47,14 +58,13 @@ module Prremote
|
|
|
47
58
|
TIOCM_RTS = 0x0004
|
|
48
59
|
DARWIN = RbConfig::CONFIG['host_os'] =~ /darwin/ ? true : false
|
|
49
60
|
TIOCMGET = DARWIN ? 0x4004746A : 0x5415
|
|
50
|
-
|
|
51
|
-
TIOCMBIC = DARWIN ? 0x8004746F : 0x5417
|
|
61
|
+
TIOCMSET = DARWIN ? 0x8004746D : 0x5418
|
|
52
62
|
|
|
53
63
|
class Error < RuntimeError; end
|
|
54
64
|
|
|
55
65
|
# Entry point. Routes USB-JTAG/Serial boards through esptool; handles
|
|
56
66
|
# classic ESP32 with the pure-Ruby protocol implementation.
|
|
57
|
-
def self.flash(port:, image_path:, baud:
|
|
67
|
+
def self.flash(port:, image_path:, baud: ROM_BAUD, board: nil, verbose: false)
|
|
58
68
|
if USB_JTAG_SERIAL_BOARDS.include?(board)
|
|
59
69
|
return flash_via_esptool(port: port, image_path: image_path, board: board,
|
|
60
70
|
verbose: verbose)
|
|
@@ -104,21 +114,42 @@ module Prremote
|
|
|
104
114
|
MSG
|
|
105
115
|
end
|
|
106
116
|
|
|
117
|
+
ctx = { esptool: esptool, port: port, image_path: image_path, board: board, verbose: verbose }
|
|
118
|
+
|
|
119
|
+
# USB-Serial/JTAG chips (e.g. XIAO ESP32C6) can be dropped into the
|
|
120
|
+
# download ROM over USB, so esptool's usb-reset flashes hands-free — no
|
|
121
|
+
# BOOT/RST button dance. --after hard-reset then reboots straight into the
|
|
122
|
+
# freshly flashed firmware. Verified on a physical XIAO ESP32C6.
|
|
123
|
+
return if esptool_write(ctx, before: 'usb-reset', after: 'hard-reset', connect_attempts: 7)
|
|
124
|
+
|
|
125
|
+
# Fallback for hosts/boards where usb-reset doesn't take: enter the
|
|
126
|
+
# bootloader by hand and let esptool retry forever. --before no-reset never
|
|
127
|
+
# resets the chip, so each attempt just re-sends SYNC until the manual
|
|
128
|
+
# BOOT/RST lands the board in download mode — no timeout to race against.
|
|
107
129
|
warn ''
|
|
108
|
-
warn '
|
|
130
|
+
warn "Couldn't reset #{board} automatically. Put it in bootloader mode by hand:"
|
|
109
131
|
warn ' XIAO ESP32C6: hold BOOT, press RST, release both.'
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
'--before', 'no-reset', '--after', 'no-reset',
|
|
114
|
-
'write-flash', '0x0', image_path]
|
|
115
|
-
warn "[flash] #{cmd.join(' ')}" if verbose
|
|
116
|
-
system(*cmd) or raise Error, 'esptool exited with an error'
|
|
132
|
+
warn 'No rush — esptool keeps retrying the SYNC handshake until it connects.'
|
|
133
|
+
esptool_write(ctx, before: 'no-reset', after: 'no-reset', connect_attempts: 0) or
|
|
134
|
+
raise Error, 'esptool exited with an error'
|
|
117
135
|
|
|
118
136
|
warn ''
|
|
119
137
|
warn 'Flash complete. Press RST to start the firmware.'
|
|
120
138
|
end
|
|
121
139
|
|
|
140
|
+
# Runs one `esptool write-flash 0x0 <image>` pass, returning esptool's
|
|
141
|
+
# success boolean. connect_attempts 0 = retry the connect forever.
|
|
142
|
+
# `ctx` carries the fixed invocation context (esptool/port/image_path/board/verbose).
|
|
143
|
+
def self.esptool_write(ctx, before:, after:, connect_attempts:)
|
|
144
|
+
cmd = [*ctx[:esptool],
|
|
145
|
+
'--chip', ctx[:board], '--port', ctx[:port],
|
|
146
|
+
'--connect-attempts', connect_attempts.to_s,
|
|
147
|
+
'--before', before, '--after', after,
|
|
148
|
+
'write-flash', '0x0', ctx[:image_path]]
|
|
149
|
+
warn "[flash] #{cmd.join(' ')}" if ctx[:verbose]
|
|
150
|
+
system(*cmd)
|
|
151
|
+
end
|
|
152
|
+
|
|
122
153
|
def self.find_esptool
|
|
123
154
|
dirs = ENV.fetch('PATH', '').split(File::PATH_SEPARATOR)
|
|
124
155
|
%w[esptool esptool.py].each do |exe|
|
|
@@ -193,7 +224,8 @@ module Prremote
|
|
|
193
224
|
end
|
|
194
225
|
|
|
195
226
|
def write_flash(image, offset: 0)
|
|
196
|
-
|
|
227
|
+
hspi_arg = SPI_ATTACH_LEGACY_BOARDS.include?(@board) ? efuse_spi_attach_arg : 0
|
|
228
|
+
spi_attach_payload = SPI_ATTACH_LEGACY_BOARDS.include?(@board) ? [hspi_arg, 0].pack('V2') : [hspi_arg].pack('V')
|
|
197
229
|
command(SPI_ATTACH, spi_attach_payload)
|
|
198
230
|
# id, total_size, block_size, sector_size, page_size, status_mask
|
|
199
231
|
command(SPI_SET_PARAMS, [0, 4 * 1024 * 1024, 64 * 1024, 4096, 256, 0xFFFF].pack('V6'))
|
|
@@ -226,6 +258,30 @@ module Prremote
|
|
|
226
258
|
raise Error, "MD5 mismatch after flashing (device #{device_md5}, local #{local_md5})"
|
|
227
259
|
end
|
|
228
260
|
|
|
261
|
+
# SPI_ATTACH(0) only works for modules with flash on the default HSPI
|
|
262
|
+
# pins. In-package flash (ESP32-PICO-D4/V3, e.g. M5StickC/PLUS) needs its
|
|
263
|
+
# actual pin numbers, burned into eFuse, packed into SPI_ATTACH's arg.
|
|
264
|
+
# Returns 0 (the "use default pins" value) when eFuse has nothing burned.
|
|
265
|
+
def efuse_spi_attach_arg
|
|
266
|
+
rdata5 = read_reg(EFUSE_BLK0_RDATA5_REG_OFFS)
|
|
267
|
+
clk = rdata5 & 0x1F
|
|
268
|
+
q = (rdata5 >> 5) & 0x1F
|
|
269
|
+
d = (rdata5 >> 10) & 0x1F
|
|
270
|
+
cs = (rdata5 >> 15) & 0x1F
|
|
271
|
+
hd = (read_reg(EFUSE_BLK0_RDATA3_REG_OFFS) >> 4) & 0x1F
|
|
272
|
+
|
|
273
|
+
return 0 if [clk, q, d, hd, cs].all?(&:zero?)
|
|
274
|
+
|
|
275
|
+
vlog format('in-package flash detected (CLK:%<clk>d Q:%<q>d D:%<d>d HD:%<hd>d CS:%<cs>d)',
|
|
276
|
+
clk: clk, q: q, d: d, hd: hd, cs: cs)
|
|
277
|
+
(hd << 24) | (cs << 18) | (d << 12) | (q << 6) | clk
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def read_reg(addr)
|
|
281
|
+
value, = command(READ_REG, [addr].pack('V'))
|
|
282
|
+
value
|
|
283
|
+
end
|
|
284
|
+
|
|
229
285
|
# ── request/response plumbing ──────────────────────────────────────────
|
|
230
286
|
|
|
231
287
|
def command(op, payload, checksum: 0, timeout: 3)
|
|
@@ -314,12 +370,21 @@ module Prremote
|
|
|
314
370
|
loop { break if read_frame(deadline).nil? }
|
|
315
371
|
end
|
|
316
372
|
|
|
373
|
+
# Sets DTR and RTS together via a single TIOCMSET, matching esptool's
|
|
374
|
+
# UnixTightReset. Two separate TIOCMBIS/TIOCMBIC calls (the classic-reset
|
|
375
|
+
# approach) send DTR and RTS as separate USB control-line-state requests;
|
|
376
|
+
# some USB-serial adapters (seen with CP2104 on M5StickC PLUS) glitch the
|
|
377
|
+
# boot ROM's auto-reset during that gap.
|
|
317
378
|
def set_lines(dtr:, rts:)
|
|
318
379
|
return if @fd.nil?
|
|
319
380
|
|
|
320
|
-
io
|
|
321
|
-
|
|
322
|
-
io.ioctl(
|
|
381
|
+
io = IO.for_fd(@fd, autoclose: false)
|
|
382
|
+
buf = [0].pack('L')
|
|
383
|
+
io.ioctl(TIOCMGET, buf)
|
|
384
|
+
status = buf.unpack1('L')
|
|
385
|
+
status = dtr ? (status | TIOCM_DTR) : (status & ~TIOCM_DTR)
|
|
386
|
+
status = rts ? (status | TIOCM_RTS) : (status & ~TIOCM_RTS)
|
|
387
|
+
io.ioctl(TIOCMSET, [status].pack('L'))
|
|
323
388
|
rescue StandardError
|
|
324
389
|
nil
|
|
325
390
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: prremote
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- ITO Yosei
|
|
@@ -139,7 +139,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
139
139
|
- !ruby/object:Gem::Version
|
|
140
140
|
version: '0'
|
|
141
141
|
requirements: []
|
|
142
|
-
rubygems_version: 4.0.
|
|
142
|
+
rubygems_version: 4.0.19
|
|
143
143
|
specification_version: 4
|
|
144
144
|
summary: CLI tool for deploying and running mruby/c scripts on a Raspberry Pi Pico
|
|
145
145
|
W over USB serial
|