MicroPythonic way to non-blocking read a character from the serial USB port

General discussions and questions abound development of code with MicroPython that is not hardware specific.
Target audience: MicroPython Users.
Post Reply
User avatar
hsmptg
Posts: 4
Joined: Tue Nov 26, 2019 5:12 pm

MicroPythonic way to non-blocking read a character from the serial USB port

Post by hsmptg » Fri Nov 29, 2019 12:24 pm

I would like to read the characters received through the serial USB connection of an ESP32-Pico-Kit board inside my main.py. I already used the sys.stdin.read(1) to read a single character but it halts the program execution while the user do not send that character. It is possible to do something similar but in an non-blocking way? Maybe MicroPython has a way to know if the character is available before I can call the read().

I already found several posts in this forum about similar issues but never saw a definite and working solution. Since some of those posts are quite old maybe the current version of MicroPython already has a solution.

Note that I am a little biased with the way these kind of things are done in C, where we have no REPL and the use of the serial port connected to the USB is more straightforward.

Thanks for any help you can give me.

User avatar
Roberthh
Posts: 3667
Joined: Sat May 09, 2015 4:13 pm
Location: Rhineland, Europe

Re: MicroPythonic way to non-blocking read a character from the serial USB port

Post by Roberthh » Fri Nov 29, 2019 1:51 pm

There is still a simple function missing, which tells, whether and how many characters are available at sys.stdin. You can however use uselect.select() with a short timeout, e.g.

Code: Select all

import uselect
list = uselect.select([sys.stdin], [], [], 0.01)
if list[0]:
    byte = sys.stdin.read(1)
else:
    byte = None
uart.read() is non-blocking. In order to use it with UART 0, you have to detach REPL from the UART.

User avatar
hsmptg
Posts: 4
Joined: Tue Nov 26, 2019 5:12 pm

Re: MicroPythonic way to non-blocking read a character from the serial USB port

Post by hsmptg » Fri Nov 29, 2019 6:54 pm

Roberthh wrote:
Fri Nov 29, 2019 1:51 pm
There is still a simple function missing, which tells, whether and how many characters are available at sys.stdin. You can however use uselect.select() with a short timeout, e.g.

Code: Select all

import uselect
import sys
list = uselect.select([sys.stdin], [], [], 0.01)
if list[0]:
    byte = sys.stdin.read(1)
else:
    byte = None
Thank you for your reply!
I tried your code but when I ran it I got a "OSError: stream operation not supported" in the line:

Code: Select all

list = uselect.select([sys.stdin], [], [], 0.01)
Any suggestion?
Roberthh wrote:
Fri Nov 29, 2019 1:51 pm
uart.read() is non-blocking. In order to use it with UART 0, you have to detach REPL from the UART.
Can you explain or give me a link where that "detach" of UART0 is explained?

Many thanks once again!

User avatar
Roberthh
Posts: 3667
Joined: Sat May 09, 2015 4:13 pm
Location: Rhineland, Europe

Re: MicroPythonic way to non-blocking read a character from the serial USB port

Post by Roberthh » Fri Nov 29, 2019 7:16 pm

Before posting, I tried that code on an ESP32 board with the actual firmware. You may have to update the firmware. I am using:
MicroPython v1.11-611-g7f24c2977-dirty on 2019-11-29; ESP32 module (spiram) with ESP32

Detaching UART 0 does not seem to work like it is for the ESP8266.

User avatar
hsmptg
Posts: 4
Joined: Tue Nov 26, 2019 5:12 pm

Re: MicroPythonic way to non-blocking read a character from the serial USB port

Post by hsmptg » Fri Nov 29, 2019 9:07 pm

Roberthh wrote:
Fri Nov 29, 2019 7:16 pm
Before posting, I tried that code on an ESP32 board with the actual firmware. You may have to update the firmware. I am using:
MicroPython v1.11-611-g7f24c2977-dirty on 2019-11-29; ESP32 module (spiram) with ESP32

Detaching UART 0 does not seem to work like it is for the ESP8266.
Well in fact I was using the default version 1.11 (esp32-idf3-20190529-v1.11.bin).
With the esp32-idf3-20191129-v1.11-611-g7f24c2977.bin it worked!!! :D
I used the GENERIC bin (not the GENERIC-SPIRAM) because my ESP32 Pico Kit board does not have SPI RAM.

Now I don't have the undesirable blocking behaviour but still have to wait during the tiny specified timeout.
But I presume this solution is the best we can get for now, right?

I wonder how this is only achievable with this last build. No one had this necessity before?!?

Thanks @Roberthh for your help!

User avatar
Roberthh
Posts: 3667
Joined: Sat May 09, 2015 4:13 pm
Location: Rhineland, Europe

Re: MicroPythonic way to non-blocking read a character from the serial USB port

Post by Roberthh » Fri Nov 29, 2019 9:19 pm

The select feature on sys.stdin is rather new. I do not exactly remember when it was introduced, but surely with the switch to treat the console I/O as a stream device. You can try how short you can set the timeout. The value I used is just an example.
Edit: I tested it and you can set it to 0.

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

uselect.select

Post by pythoncoder » Sat Nov 30, 2019 7:02 am

Note that in the docs uselect.select() is deprecated in favour of the Poll mecahnism on grounds of efficiency.
Peter Hinch
Index to my micropython libraries.

jdts
Posts: 36
Joined: Mon Dec 23, 2019 2:55 pm

Re: MicroPythonic way to non-blocking read a character from the serial USB port

Post by jdts » Tue Dec 31, 2019 4:09 am

So:

Code: Select all

import sys,uselect
spoll=uselect.poll()
spoll.register(sys.stdin,uselect.POLLIN)
def read1():
    return(sys.stdin.read(1) if spoll.poll(0) else None)

stanely
Posts: 55
Joined: Fri Jan 17, 2020 5:19 am
Location: Ohio, USA

Re: MicroPythonic way to non-blocking read a character from the serial USB port

Post by stanely » Tue Mar 16, 2021 12:30 pm

Thanks for this code. It works well but has an unusual feature I don't understand. When my code exits, the stdin port is left in a raw REPL state, instead of returning to normal REPL. Is that how it's supposed to work?

I tried different things including

Code: Select all

spoll.unregister(sys.stdin)
and

Code: Select all

sys.stdin.close()
to no avail. On exit, REPL is connected in raw mode and I have to ^B from the keyboard to exit. Is there a way to clean things up in my code and end raw REPL mode when my code exits?

Here's the simple test program...

Code: Select all

#test polling
#test polling

import sys
import uselect

# Function to read a character from USB serial or return None.
def kbhit():
    spoll=uselect.poll()        # Set up an input polling object.
    spoll.register(sys.stdin,uselect.POLLIN)    # Register polling object.

    kbch = sys.stdin.read(1) if spoll.poll(0) else None

    spoll.unregister(sys.stdin)
    return(kbch)

# MAIN ---------------------------------
print('Input characters: ', end='')
while True:
    new_ch = kbhit()
    if new_ch == None:
        continue
    elif new_ch == 'q':
        break
    else:
        print(new_ch, end='', sep='')

sys.stdin.close()
print(sys.version)
sys.exit(0)

Jibun no kage
Posts: 144
Joined: Mon Jul 25, 2022 9:45 pm

Re: MicroPythonic way to non-blocking read a character from the serial USB port

Post by Jibun no kage » Fri Aug 19, 2022 3:51 pm

Odd, the above code, I can't seem to get to work. It is blocking. Only then a input is received, is the loop cycled. I thought the purpose of the code was to be a non-blocking UART input example, no?

Post Reply