-
Notifications
You must be signed in to change notification settings - Fork 1.1k
micropython/aiorepl: Add an asynchronous REPL. #524
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
5e29305 to
de26d86
Compare
This is brilliant, very nice, testing on ESP32-S3 currently.
My initial test of this was effortlessly delightful and incredibly powerful. Thanks @jimmo
Thanks @ThinkTransit & @andrewleech !
I've raised a tiny PR to the main repo (micropython/micropython#9295) which makes the REPL run by default in the context of __main__ (i.e. the same as the real REPL), rather than having to awkwardly pass globals() to aiorepl.task().
There's a couple of little issues to iron out (Ctrl-D is a bit flakey) but as soon as the PR above is merged I think this is probably good to go for version one.
@jimmo Might be a stupid question but how feasible/practical would it be to use a socket instead of sys.stdin/sys.stdout? Could this be used remotely over a network?
@jimmo Might be a stupid question but how feasible/practical would it be to use a socket instead of sys.stdin/sys.stdout? Could this be used remotely over a network?
aiorepl just uses stdin/stdout, but MicroPython supports redirecting (or duplicating) stdin/stdout to any stream you like. So for example, WebREPL and the BLE REPL will work automatically with aiorepl (just as most boards run the REPL on both the USB CDC and a UART concurrently).
If instead you mean that you want the aiorepl to be distinct to the regular REPL and have its own input/output stream, then this is also feasible to implement (we could make it so aiorepl.task() takes an optional input and output stream), but it's a bit weird because for example print() would still go out to the regular stdout.
aiorepljust uses stdin/stdout, but MicroPython supports redirecting (or duplicating) stdin/stdout to any stream you like. So for example, WebREPL and the BLE REPL will work automatically withaiorepl(just as most boards run the REPL on both the USB CDC and a UART concurrently).If instead you mean that you want the aiorepl to be distinct to the regular REPL and have its own input/output stream, then this is also feasible to implement (we could make it so
aiorepl.task()takes an optional input and output stream), but it's a bit weird because for exampleprint()would still go out to the regular stdout.
Thanks @jimmo
I was asking about point 2 however after reading your response I now understand that this doesn't really make sense. Because aiorepl is on the standard REPL then I should just use the existing mechanisms WebREPL/BLE REPL to remotely access aiorepl!
I have been using aiorepl all day now, it is a massive help for debugging/monitoring.
@jimmo do you want to update this now that core supports __dict__?
de26d86 to
32348b2
Compare
@jimmo do you want to update this now that core supports
__dict__?
Done.
I've pushed an update that improves handling of Ctrl-C and Ctrl-D (based on conversation with @peterhinch).
- Ctrl-D now terminates the async REPL (and the event loop it was running in), taking you back to the regular REPL.
- Ctrl-C now cancels the executing task (via
task.cancel()). This works really well for well-behaved async code, but will have no effect on blocking code. Hopefully nobody is executing blocking code in an async context, but perhaps at the REPL this is still useful. So I've implemented a very simple heuristic to detect whether the expression is async or not, and Ctrl-C will do a regular KeyboardInterrupt for non-async expressions.
In other words, you can Ctrl-C both time.sleep(3) and await asyncio.sleep(3) and the former will be kbd_intr, and the latter will be task.cancel().
The way Ctrl-C works also prevents mpremote (and similar tools) from working because they expect to see the regular REPL after two successive Ctrl-C's. So there's now also a heuristic to detect this and terminate the async REPL.
This provides an async REPL with the following features: - Run interactive REPL in the background. - Execute statements using await. - Simple history. Signed-off-by: Jim Mussared <jim.mussared@gmail.com>
32348b2 to
7602843
Compare
This version now lets me issue one-liners at the --> prompt and stop the "background" application with ctrl-d, returning me to the REPL.
So I got ambitious and ran this script at the --> prompt:
import uasyncio as asyncio import gc async def main(): while True: gc.collect() print(gc.mem_free()) await asyncio.sleep(2) asyncio.run(main())
This ran, with the "background" application also running, but I couldn't return to either prompt. On occasion ctrl-c briefly issued a --> but the foreground script kept running. Here is a typical session with comments added:
$ mpr
Connected to MicroPython at /dev/ttyACM0
Use Ctrl-] to exit this shell
>>> import gui.demos.aclock
Using encoder.
Analog clock demo.
Orientation: Horizontal. Reversal: False. Width: 320. Height: 240.
Start row = 0 col = 0
Starting asyncio REPL...
--> Screen.do_gc = False # Clock demo is running and responds to this
--> import test
84368
84352 # Clock demo and test script both running
84352
--> 85296 # Issued ctrl-c Clock has stopped, test script still running
85296 # Only exit was to quit mpremote
85296
So I got ambitious and ran this script at the --> prompt:
Does this mean that there's two instances of asyncio.run() running?
Is the above issue a blocker to merging? I think we should merge this as-is, as a first step.
Merged in 7602843
Does this mean that there's two instances of asyncio.run() running?
Yes, good point. I adapted my script to:
import uasyncio as asyncio import gc async def main(): while True: gc.collect() print(gc.mem_free()) await asyncio.sleep(2) asyncio.create_task(main())
Now ctrl-c still doesn't stop the script but ctrl-d does stop the aclock demo. Annotated session:
MicroPython v1.19.1 on 2022年09月25日; Raspberry Pi Pico with RP2040
Type "help()" for more information.
>>>
>>> import gui.demos.aclock
Using encoder.
Analog clock demo.
Orientation: Horizontal. Reversal: False. Width: 320. Height: 240.
Start row = 0 col = 0
Starting asyncio REPL...
--> Free RAM 85712
Screen.do_gc = False # Turn off Free RAM reports from aclock.py
--> import test
--> 85296 # test.py running
85296
85296
85296
# ctrl-c issued
--> 85264
85264
85264
85264
85264
85264
# ctrl-c issued
--> 85264
# ctrl-d issued
>>>
I also tried commenting out the create_task statement, importing test, and issuing create_task at the --> prompt, but the outcome was the same: I couldn't interrupt the script other than the nuclear ctrl-d.
[EDIT]
I can start and stop the script by issuing create_task and task.cancel at the --> prompt. With this particular script it's untidy as the script is doing output while I'm typing but it works. Better is to do
--> from test import main --> await asyncio.wait_for(main(), 20)
which works beautifully. ctrl-c would be a convenience, notably for scripts that don't work as designed.
I love this enhancement - it is excellent!
This provides an async REPL with the following features:
See documentation and example here https://github.com/jimmo/micropython-lib/tree/aiorepl/micropython/aiorepl