Circuitpython: 'getch()'

Created on 5 Sep 2017  Â·  31Comments  Â·  Source: adafruit/circuitpython

OK its odd but hear me out :)

python used to allow raw REPL keygrabs (e.g. 'press any key to continue') but deprecated it in 3 for just input() which is blocking and requires a return key

it would be useful for interactive-at-the repl scripts to have a non-input() way of getting raw characters. i mean, you can send individual chars with print("x", end='') so why not have the reverse :D

also, using input() wonks with the REPL history: if you use input() it puts that entry into the history which i think is a bit odd:
image

circuitpython api enhancement

Most helpful comment

member hybotics/geekguy mentioned the same use case that I have for which this feature is highly desirable. At my company we use Raspberry Pi boards to host our internal test and production machines and sometimes have Arduino boards hanging on them over USB. CircuitPython boards are preferred compared to Arduino because we can stay with one language and also the instrument is self-documenting because it hold's its own code and readme files. The hardware is relatively elegant because it only requires that a USB cable be run to each remote device or sensor.

A problem is that we don't have a great way to send serial data instructions to the boards.

as a work-around is there a way to query the input buffer so that I can know if sys.stdin.read(1) would pass through or block?

All 31 comments

Try sys.stdin.read(1). I'm not sure if CircuitPython supports it but CPython should. We can add it if its closer to what you want.

Reference: https://docs.python.org/3/library/sys.html#sys.stdin

not really - its basically input() but it only gives you a char at a time. i wanted something where it was timeout-not-blocking AND you didnt have to hit return

We'll have to add an new API for this. Its not well covered in CPython. More background is here: https://stackoverflow.com/questions/2408560/python-nonblocking-console-input

Shouldn't read() be non-blocking, returning an empty string if there is nothing in the buffer? I think it works that way for the sockets and the serial — returning at most n characters.

Just tried this: sys.stdin.read() in CPython 3.5 will read until EOF (ctrl-D); it's blocking. Reading a single character, sys.stdin.read(1) hangs until one character is typed (and it buffers by line, so it waits for a newline as well) or EOF is encountered.

Yes, but that's not consistent with other stream-like interfaces.

I think we want to make the behavior consistent with CPython. Do you mean other stream-like interfaces in Python or in general? It's a file-like interface, where the reads might be really slow, because the device (the human) is slow to supply characters. I tried to find some asyncio examples for stdin, but I didn't see anything right away.

Looking at the documentation at https://docs.python.org/3/library/io.html I can see there is a whole zoo of different behaviors, depending on what the object on which you call read() is and how it was created, and also what options were used. Sometimes it will block, sometimes it will return 0 bytes, sometimes None, and sometimes it will raise an exception.

It seems that there is a way to switch the stdio into a non-blocking mode, at least on Linux, using non-standard terminal libraries: https://stackoverflow.com/questions/21791621/python-taking-input-from-sys-stdin-non-blocking

there's nothing in 'standard' python that works on all OS's - windows uses a DLL and mscvt or whatnot - we have to do something custom :)

IMHO the canonical python way to handle non-blocking io is with select: https://docs.python.org/3/library/select.html Like Radomir mentions though it quickly gets into platform specific optimizations for desktop though (poll, epoll, kpoll, etc.), but at the end of the day they're all the same idea of polling for availability of data from an arbitrary file descriptor with a select-like API. Adding a select API and ability to use it to poll for new chars from stdin, etc. would be handy, and it would be a natural progression to add polling for other descriptors like sockets (same way python likes to handle sockets on the desktop). With a light wrapper on top it could become a general purpose async IO engine too (i.e. your main loop is just a big poll of awaiting descriptors with select).

It seems to me that being able to select or await on a DigitaIO object (or something that wraps it) would handle most of the cases for us. SPI and I2C are clocked by master, so nothing interesting happens with them without the master initiating it. AnalogIO could be selectable/awaitable also, but would require specifying a threshold. Selecting on a timer would pretty much be equivalent to doing select with a timeout, so I'm not that sure this is useful.

Another way to achieve this is to provide a second "raw" serial connection to the computer in addition to the input. This would allow for binary data transmission in addition to text. It could work just like a UART connection.

We'd want dynamic USB descriptors for this (mentioned in #190) so that its only on as needed.

good idea! that's probably the 'right' way to do it, but yeah would have to wait until you can select the USB interfaces cauz there'd be juggling between webusb, cdc, HID.

I have a situation where I need to have a Raspberry Pi 3 interact with a board running Circuit Python. I need to use Firmata between the two for two-way communication. I would need to transfer both ASCII and binary data. Being able to do this is highly desirable for some applications, like robotics.

PS I am geekguy on the Adafruit forums.

member hybotics/geekguy mentioned the same use case that I have for which this feature is highly desirable. At my company we use Raspberry Pi boards to host our internal test and production machines and sometimes have Arduino boards hanging on them over USB. CircuitPython boards are preferred compared to Arduino because we can stay with one language and also the instrument is self-documenting because it hold's its own code and readme files. The hardware is relatively elegant because it only requires that a USB cable be run to each remote device or sensor.

A problem is that we don't have a great way to send serial data instructions to the boards.

as a work-around is there a way to query the input buffer so that I can know if sys.stdin.read(1) would pass through or block?

Hello folks!

I was directed to this thread by Scott (@tannewt ) over at the Adafruit forums, in reply to my query about "Arduino-like 'serial' with Circuit Python".

For what I'm trying to achieve, being able to send and receive serial data in this way through the USB connection would be really great.

Thanks all!

-Casey

Ditto to @protothesis comments above... and I was similarly directed by @tannewt from the Arduino Discord... My goal would be to have something similar to the Arduino CmdMessenger. Right now the best I can come up with is using two boards: 1 for communication via the Serial Console, and 1 for Device Control with a UART between them. If anybody has already been down that road, I'd love to hear about it...

https://playground.arduino.cc/Code/CmdMessenger

an intermediate solution for now is to use the hardware UART + a usb/uart cable. elegant? not as much but it does work now :)
https://learn.adafruit.com/circuit-playground-express-serial-communications
(code works for any/all circuitpy boards)

I created a workaround for this and am sharing it via this pull request
https://github.com/adafruit/circuitpython/pull/1193

Not sure it's the right solution, but it does solve our issues.

Has anything changed with respect to the original request, i.e. a non-blocking input() or sys.stdin.read() function? Or is there another way for bi-directional communication via USB?

@bludin Is supervisor.runtime.serial_bytes_available (doc) useful to you? It returns the number of bytes that are queued to read.

@dhalbert yes, that is very useful. It returns a boolean, though, not the number of bytes. So a non-blocking read func has to look like

def non_blocking_read():
    i = ""
    while supervisor.runtime.serial_bytes_available:
        i += sys.stdin.read(1)
    return i

or is there a better way?

Yes, that's the best way for now. Sorry, it would be nicer if serial_bytes_available returned a count.

Related to 2nd USB-serial channel wanted,
endpoint shortage and dynamic or static descriptor:

Perhaps we could have static descriptor with shared
endpoints for usb-serial and something-else, for example microphone.

Of course usb-serial and microphone cannot work
at the same time using the same endpoints.

but I was thinking - if in the OS the microphone driver is blacklisted,
then usb-serial may normally load and work.

but I was thinking - if in the OS the microphone driver is blacklisted,
then usb-serial may normally load and work.

We want to provide run-time choice of the USB devices that CircuitPython presents. That is #1015. Blacklisting might work on Linux, but we want an easier mechanism and one that is OS-independent.

The secondary serial channel idea mentioned in this thread now is covered in issue #3853.

kk - we'd still need a way to read individual characters from the secondary USB-serial connection

kk - we'd still need a way to read individual characters from the secondary USB-serial connection

Yeah, Python is still bad at that.

I think we should have the second CDC available as a UART compatible (aka stream). It'd be similar to how we have objects for MIDI stuff.

yes! think about interfacing with processing, unity, cnc etc. just need bidirectional byte stream (e.g. like pyserial which is the cpy equiv)

Was this page helpful?
0 / 5 - 0 ratings

Related issues

tdicola picture tdicola  Â·  4Comments

rrace001 picture rrace001  Â·  6Comments

ladyada picture ladyada  Â·  5Comments

ericwertz picture ericwertz  Â·  7Comments

jscholes picture jscholes  Â·  3Comments