Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

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

Merged
jimmo merged 1 commit into micropython:master from jimmo:aiorepl
Sep 27, 2022

Conversation

@jimmo
Copy link
Member

@jimmo jimmo commented Sep 6, 2022

This provides an async REPL with the following features:

  • Run interactive REPL in the background.
  • Execute statements using await.
  • Simple history.

See documentation and example here https://github.com/jimmo/micropython-lib/tree/aiorepl/micropython/aiorepl

andrewleech and beyonlo reacted with hooray emoji andrewleech, ThinkTransit, peterhinch, and beyonlo reacted with heart emoji andrewleech and beyonlo reacted with eyes emoji
@jimmo jimmo force-pushed the aiorepl branch 2 times, most recently from 5e29305 to de26d86 Compare September 8, 2022 04:25
Copy link
Contributor

This is brilliant, very nice, testing on ESP32-S3 currently.

Copy link
Contributor

My initial test of this was effortlessly delightful and incredibly powerful. Thanks @jimmo

Copy link
Member Author

jimmo commented Sep 13, 2022

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.

Copy link
Contributor

@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?

Copy link
Member Author

jimmo commented Sep 13, 2022

@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?

@ThinkTransit

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.

Copy link
Contributor

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.

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.

Copy link
Member

@jimmo do you want to update this now that core supports __dict__?

Copy link
Member Author

jimmo commented Sep 26, 2022

@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>
Copy link
Contributor

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

Copy link
Member Author

jimmo commented Sep 26, 2022

So I got ambitious and ran this script at the --> prompt:

Does this mean that there's two instances of asyncio.run() running?

Copy link
Member

Is the above issue a blocker to merging? I think we should merge this as-is, as a first step.

@jimmo jimmo merged commit 7602843 into micropython:master Sep 27, 2022
Copy link
Member Author

jimmo commented Sep 27, 2022

Merged in 7602843

Copy link
Contributor

peterhinch commented Sep 27, 2022
edited
Loading

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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

No reviews

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

AltStyle によって変換されたページ (->オリジナル) /