Skip to content

Commit a933f69

Browse files
committed
WIP: mpremote: Support bytecode raw paste for 'mpremote run module.mpy'
- Adds a new raw paste command 'B' for 'raw paste bytecode' ('A' is 'paste source') - Adds an escaping mechanism (Ctrl-F <char + 8>) during raw paste so bytes less than 8 can be sent without triggering Ctrl-C or Ctrl-D handlers. The two-byte escape sequence still counts as one byte in the paste window. - Adds relevant support to mpremote.py
1 parent 5bb2a85 commit a933f69

File tree

3 files changed

+56
-14
lines changed

3 files changed

+56
-14
lines changed

shared/runtime/pyexec.c

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
#include "py/gc.h"
3636
#include "py/frozenmod.h"
3737
#include "py/mphal.h"
38+
#include "py/persistentcode.h" // TODO guard for this?
3839
#if MICROPY_HW_ENABLE_USB
3940
#include "irq.h"
4041
#include "usb.h"
@@ -58,6 +59,8 @@ STATIC bool repl_display_debugging_info = 0;
5859
#define EXEC_FLAG_SOURCE_IS_FILENAME (1 << 5)
5960
#define EXEC_FLAG_SOURCE_IS_READER (1 << 6)
6061

62+
#define NUM_ESCAPED 8 // This value has to match the value in tools/pyboard.py
63+
6164
// parses, compiles and executes the code in the lexer
6265
// frees the lexer before returning
6366
// EXEC_FLAG_PRINT_EOF prints 2 EOF chars: 1 after normal output, 1 after exception output
@@ -82,12 +85,20 @@ STATIC int parse_compile_execute(const void *source, mp_parse_input_kind_t input
8285
mp_obj_t module_fun;
8386
#if MICROPY_MODULE_FROZEN_MPY
8487
if (exec_flags & EXEC_FLAG_SOURCE_IS_RAW_CODE) {
85-
// source is a raw_code object, create the function
86-
const mp_frozen_module_t *frozen = source;
8788
mp_module_context_t *ctx = m_new_obj(mp_module_context_t);
8889
ctx->module.globals = mp_globals_get();
89-
ctx->constants = frozen->constants;
90-
module_fun = mp_make_function_from_raw_code(frozen->rc, ctx, NULL);
90+
if ((exec_flags & EXEC_FLAG_SOURCE_IS_READER) == 0) {
91+
// source is a raw_code object, create the module function from it
92+
const mp_frozen_module_t *frozen = source;
93+
ctx->constants = frozen->constants;
94+
module_fun = mp_make_function_from_raw_code(frozen->rc, ctx, NULL);
95+
} else {
96+
#if 1 // TODO conditions for compilation
97+
// source is a reader that will give us raw code (mpy file equivalent)
98+
mp_compiled_module_t cm = mp_raw_code_load((mp_reader_t *)source, ctx);
99+
module_fun = mp_make_function_from_raw_code(cm.rc, ctx, NULL);
100+
#endif
101+
}
91102
} else
92103
#endif
93104
{
@@ -220,6 +231,10 @@ STATIC mp_uint_t mp_reader_stdin_readbyte(void *data) {
220231
} else {
221232
return MP_READER_EOF;
222233
}
234+
} else if (c == CHAR_CTRL_F) {
235+
// escape sequence, next character is escaped by adding NUM_ESCAPED to it
236+
int e = mp_hal_stdin_rx_chr();
237+
c = e - NUM_ESCAPED;
223238
}
224239

225240
if (--reader->window_remain == 0) {
@@ -261,7 +276,7 @@ STATIC void mp_reader_new_stdin(mp_reader_t *reader, mp_reader_stdin_t *reader_s
261276
}
262277

263278
STATIC int do_reader_stdin(int c) {
264-
if (c != 'A') {
279+
if (c != 'A' && c != 'B') {
265280
// Unsupported command.
266281
mp_hal_stdout_tx_strn("R\x00", 2);
267282
return 0;
@@ -270,10 +285,15 @@ STATIC int do_reader_stdin(int c) {
270285
// Indicate reception of command.
271286
mp_hal_stdout_tx_strn("R\x01", 2);
272287

288+
// Entering raw paste mode
289+
// c == 'A' input is source, c == 'B' input is bytecode
273290
mp_reader_t reader;
274291
mp_reader_stdin_t reader_stdin;
275292
mp_reader_new_stdin(&reader, &reader_stdin, MICROPY_REPL_STDIN_BUFFER_MAX);
276293
int exec_flags = EXEC_FLAG_PRINT_EOF | EXEC_FLAG_SOURCE_IS_READER;
294+
if (c == 'B') {
295+
exec_flags |= EXEC_FLAG_SOURCE_IS_RAW_CODE;
296+
}
277297
return parse_compile_execute(&reader, MP_PARSE_FILE_INPUT, exec_flags);
278298
}
279299

tools/mpremote/mpremote/main.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -352,8 +352,10 @@ def do_repl_main_loop(pyb, console_in, console_out_write, *, code_to_inject, fil
352352
pyb.enter_raw_repl(soft_reset=False)
353353
with open(file_to_inject, "rb") as f:
354354
pyfile = f.read()
355+
356+
is_bytecode = pyfile[0] == ord("M") and file_to_inject.endswith(".mpy")
355357
try:
356-
pyb.exec_raw_no_follow(pyfile)
358+
pyb.exec_raw_no_follow(pyfile, is_bytecode)
357359
except pyboard.PyboardError as er:
358360
console_out_write(b"Error:\r\n")
359361
console_out_write(er)
@@ -430,10 +432,10 @@ def console_out_write(b):
430432
capture_file.close()
431433

432434

433-
def execbuffer(pyb, buf, follow):
435+
def execbuffer(pyb, buf, follow, is_bytecode=False):
434436
ret_val = 0
435437
try:
436-
pyb.exec_raw_no_follow(buf)
438+
pyb.exec_raw_no_follow(buf, is_bytecode)
437439
if follow:
438440
ret, ret_err = pyb.follow(timeout=None, data_consumer=pyboard.stdout_write_bytes)
439441
if ret_err:
@@ -546,6 +548,7 @@ def main():
546548
elif cmd == "umount":
547549
pyb.umount_local()
548550
elif cmd in ("exec", "eval", "run"):
551+
is_bytecode = False
549552
follow = True
550553
if args[0] == "--no-follow":
551554
args.pop(0)
@@ -554,15 +557,16 @@ def main():
554557
buf = args.pop(0)
555558
elif cmd == "eval":
556559
buf = "print(" + args.pop(0) + ")"
557-
else:
560+
else: # run
558561
filename = args.pop(0)
559562
try:
560563
with open(filename, "rb") as f:
561564
buf = f.read()
565+
is_bytecode = buf[0] == ord("M") and filename.endswith(".mpy")
562566
except OSError:
563567
print(f"{_PROG}: could not read file '{filename}'")
564568
return 1
565-
ret = execbuffer(pyb, buf, follow)
569+
ret = execbuffer(pyb, buf, follow, is_bytecode)
566570
if ret:
567571
return ret
568572
elif cmd == "fs":

tools/pyboard.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
import sys
7171
import time
7272
import os
73+
import re
7374
import ast
7475

7576
try:
@@ -399,10 +400,22 @@ def raw_paste_write(self, command_bytes):
399400
raise PyboardError("unexpected read during raw paste: {}".format(data))
400401
# Send out as much data as possible that fits within the allowed window.
401402
b = command_bytes[i : min(i + window_remain, len(command_bytes))]
402-
self.serial.write(b)
403+
403404
window_remain -= len(b)
404405
i += len(b)
405406

407+
# escape any characters that need to be escaped. Note this doesn't
408+
# count towards the window size, as unescaping happens before filling
409+
# the window buffer in the device
410+
NUM_ESCAPED = 8 # this values has to match value in pyexec.c
411+
b = re.sub(
412+
rb"[" + bytes(range(NUM_ESCAPED)) + rb"]",
413+
lambda c: bytes((0x06, c.group()[0] + NUM_ESCAPED)),
414+
b,
415+
)
416+
417+
self.serial.write(b)
418+
406419
# Indicate end of data.
407420
self.serial.write(b"\x04")
408421

@@ -411,7 +424,7 @@ def raw_paste_write(self, command_bytes):
411424
if not data.endswith(b"\x04"):
412425
raise PyboardError("could not complete raw paste: {}".format(data))
413426

414-
def exec_raw_no_follow(self, command):
427+
def exec_raw_no_follow(self, command, is_bytecode=False):
415428
if isinstance(command, bytes):
416429
command_bytes = command
417430
else:
@@ -424,20 +437,25 @@ def exec_raw_no_follow(self, command):
424437

425438
if self.use_raw_paste:
426439
# Try to enter raw-paste mode.
427-
self.serial.write(b"\x05A\x01")
440+
raw_paste_cmd = b"\x05A\x01" if not is_bytecode else b"\x05B\x01"
441+
self.serial.write(raw_paste_cmd)
428442
data = self.serial.read(2)
429443
if data == b"R\x00":
430444
# Device understood raw-paste command but doesn't support it.
431445
pass
432446
elif data == b"R\x01":
433447
# Device supports raw-paste mode, write out the command using this mode.
434448
return self.raw_paste_write(command_bytes)
435-
else:
449+
elif not is_bytecode:
436450
# Device doesn't support raw-paste, fall back to normal raw REPL.
437451
data = self.read_until(1, b"w REPL; CTRL-B to exit\r\n>")
438452
if not data.endswith(b"w REPL; CTRL-B to exit\r\n>"):
439453
print(data)
440454
raise PyboardError("could not enter raw repl")
455+
else:
456+
raise RuntimeError(
457+
"Sending bytecode without raw paste is not yet implemented"
458+
) # TODO
441459
# Don't try to use raw-paste mode again for this connection.
442460
self.use_raw_paste = False
443461

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy