ivmarkov / rust-esp32-std-demo

Rust on ESP32 STD demo app. A demo STD binary crate for the ESP32[XX] and ESP-IDF, which connects to WiFi, Ethernet, drives a small HTTP server and draws on a LED screen.
Apache License 2.0
785 stars 105 forks source link

Question: how to read from stdin()? #59

Closed matthew-a-thomas closed 2 years ago

matthew-a-thomas commented 2 years ago

I apologize if this is not the best place to ask this beginner question. But I'm wondering how to read a line while my C3 is connected with espmonitor? Or if this isn't the best place to ask this, could you direct me to a better location?

I have a ESP32-C3-DevKitM-1. I have managed to piece together the below proof of concept project. But it panics when I try to read a line from stdin() https://github.com/matthew-a-thomas/hello_esp32c3/blob/fadceece017f99b7c934d3a80e0e169034d6c91b/src/main.rs#L34

If I replace that line with

std_in.read_line(&mut ssid)?;

...then this error appears in the output:

Error: Os { code: 11, kind: WouldBlock, message: "No more processes" }

My next thought is to attempt to use esp-idf-hal to instantiate a Serial object from the UART0 port, but I feel like I must be missing something obvious.

Thank you!!

I've attached my application binary.

Here is the output I'm seeing with the attached binary/the above linked code:

Hello, world!
<lots of info messages>
Scanning for access points...
<info messages>
Here they are:
<a few access points>
SSID?
Guru Meditation Error: Core  0 panic'ed (Illegal instruction). Exception was unhandled.

Core  0 register dump:
MEPC    : 0x4200d1fa  RA      : 0x4200017a  SP      : 0x3fc98ee0  GP      : 0x3fc8f600
TP      : 0x3fc7d460  T0      : 0x3c094408  T1      : 0x4200847c  T2      : 0x000000f0
S0/FP   : 0x00000001  S1      : 0x00000001  A0      : 0x3c0901ec  A1      : 0x00000019
A2      : 0x3fc98ee8  A3      : 0x3c090138  A4      : 0x3c090210  A5      : 0x00000001
A6      : 0x00000003  A7      : 0x00000000  S2      : 0x3c090148  S3      : 0x3c09022c
S4      : 0x3c090254  S5      : 0x3c090270  S6      : 0x00000002  S7      : 0x3c090234
S8      : 0x3fc99060  S9      : 0x3fc99020  S10     : 0x3fca59d4  S11     : 0x3fc98ff4
T3      : 0x00000030  T4      : 0xffffffbf  T5      : 0x000000f4  T6      : 0xffffffff
MSTATUS : 0x00001881  MTVEC   : 0x40380001  MCAUSE  : 0x00000002  MTVAL   : 0x00000000
MHARTID : 0x00000000

Stack memory:
3fc98ee0: 0x3c090148 0x00000001 0x00000000 0x0000000b 0x00000000 0x00000004 0x3fc96240 0x3c090210
3fc98f00: 0x3c0901e4 0x3fc92194 0x3fc9c374 0x3fc9d314 0x3fc9c374 0x3fc9d314 0x3fc9d47c 0x00000000
3fc98f20: 0x00000000 0x00000000 0x00000020 0x3fc9da58 0x3fca595c 0x00000005 0x3fca59d4 0x3fca59d4
3fc98f40: 0x00000001 0x00000000 0x00000000 0x4038d38a 0x3fc99028 0x42005b14 0x3fc9a88c 0x42031c3e
3fc98f60: 0x00000001 0x00000000 0x0000000b 0x00000001 0x3c090148 0x00000000 0x00000000 0x00000020
3fc98f80: 0x3fc9da58 0x00000030 0x00000000 0x00000002 0x00000000 0x00000000 0x00000000 0x4200977e
3fc98fa0: 0x00000000 0x3fc96254 0x00000000 0x00000000 0x00000001 0x3fc9a848 0x3c090120 0x4200b4d4
3fc98fc0: 0x00000000 0x00000000 0x00000001 0x3fc9a848 0x00000005 0x00000000 0x00000001 0x42006e1c
3fc98fe0: 0x3c092a68 0x00000000 0x3fca5a10 0x0000000a 0x0000000a 0x10880524 0x3f063562 0x00000000
3fc99000: 0x3c092a68 0x3fc99028 0x3fc9a6e0 0x00000005 0x00000000 0x3fc990c8 0x3c090120 0x420089b2
3fc99020: 0x00000000 0x00000000 0x3fca5a10 0x0000000a 0x0000000a 0x10880524 0x00063562 0x000300ac
3fc99040: 0x00000000 0x3fc9a6e0 0x00000005 0x4038ce36 0x00000000 0x00001800 0x3fc96920 0x40380b46
3fc99060: 0x00000007 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
3fc99080: 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x3fc990c8 0x3c090120 0x4200004a
3fc990a0: 0x00000000 0x00000000 0x3c090120 0x42008dca 0x00000000 0x00000000 0x00000000 0x4200003c
3fc990c0: 0x00000000 0x00000000 0x4200017c 0x4208385e 0x00000000 0x00000000 0x00000000 0x40389cdc
3fc990e0: 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0xa5a5a5a5 0xa5a5a5a5 0xa5a5a5a5
3fc99100: 0x27146414 0x01010107 0x00000001 0x00000000 0xffffffff 0x7fefffff 0x00000000 0x3fc00000
3fc99120: 0x00000000 0x40300000 0x00000000 0x3fe00000 0x00000000 0x3ff80000 0x636f4361 0x3fd287a7
3fc99140: 0x8b60c8b3 0x3fc68a28 0x509f79fb 0x3fd34413 0x00000000 0x3ff00000 0x00000000 0x40240000
3fc99160: 0x00000000 0x401c0000 0x00000000 0x40140000 0x00000000 0x43500000 0x3fc8ee00 0x00003388
3fc99180: 0xa5a5a5a5 0xa5a5a5a5 0x00000154 0x3fc98b30 0x000000e2 0x3fc9282c 0x3fc9282c 0x3fc9918c
3fc991a0: 0x3fc92824 0x00000018 0x9412a6bc 0xadf30647 0x3fc9918c 0x00000000 0x00000001 0x3fc98188
3fc991c0: 0x6e69616d 0x6574e200 0x936200aa 0x00ff71b6 0x00000000 0x3fc99180 0x00000001 0x00000000
3fc991e0: 0x3fc9a8ac 0x42031a50 0x0000000b 0x3fc96a6c 0x3fc96ad4 0x3fc96b3c 0x00000000 0x00000000
3fc99200: 0x00000001 0x00000000 0x00000000 0x00000000 0x4206f960 0x00000000 0x00000000 0x00000000
3fc99220: 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
3fc99240: 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
3fc99260: 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
3fc99280: 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
3fc992a0: 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
3fc992c0: 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x3fc90000

ELF file SHA256: 0000000000000000

Rebooting...
matthew-a-thomas commented 2 years ago

ErrorKind::WouldBlock

The operation needs to block to complete, but the blocking operation was requested to not occur.

I'm not really sure what that means in this context, though.

Also, it doesn't matter if I change std_in's assignment to this:

let std_in = std::io::stdin();
let mut std_in = std_in.lock();

When I do this then this lock() succeeds, but it still raises the error when .read_line() is called.

matthew-a-thomas commented 2 years ago

Evidently std::io::stdin() returns something configured for non-blocking IO. So to read characters from it you have to do something like this:

let mut buffer = [0u8; 100];
match std::io::stdin().read(&mut buffer) {
    Ok(num_read) => println!("{}", num_read),
    Err(error) => match error.kind() {
        std::io::ErrorKind::WouldBlock => println!("0"),
        _ => return Err(error),
    },
};

The key takeaway is you have to (awkwardly) eat the WouldBlock error and treat that case the same as if no characters were returned.

For example:

struct BlockingReader<R: std::io::Read> {
    poll: core::time::Duration,
    reader: R,
}

impl<R: Read> From<R> for BlockingReader<R> {
    fn from(reader: R) -> Self {
        Self {
            poll: core::time::Duration::from_millis(250), // Or whatever. Just don't set this so low that you get bit by the watchdog timer
            reader,
        }
    }
}

impl<R: Read> std::io::Read for BlockingReader<R> {
    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
        if buf.len() == 0 {
            return Ok(0);
        }
        loop {
            match self.reader.read(buf) {
                Ok(num_bytes) => return Ok(num_bytes),
                Err(error) => match error.kind() {
                    std::io::ErrorKind::WouldBlock => std::thread::sleep(self.poll),
                    _ => return Err(error),
                }
            }
        }
    }
}

let std_in = std::io::stdin();
let std_in = std_in.lock();
let std_in: BlockingReader<_> = std_in.into();
let mut std_in = std::io::BufReader::new(std_in);
// Now use std_in as normal

If you set the poll interval too low (and you don't type quickly enough) then the watchdog timer will bark at you:

E (7595) task_wdt: Task watchdog got triggered. The following tasks did not reset the watchdog in time:
E (7595) task_wdt:  - IDLE (CPU 0)
E (7595) task_wdt: Tasks currently running:
E (7595) task_wdt: CPU 0: main

Is there some way to configure stdin() to return something a little more idiomatic?

ivmarkov commented 2 years ago

If I remember correctly, the reason why processing stdin is in such an awkward state (and this is coming from ESP-IDF itself) is because your use case is extremely rare: the stdin/stdout are usually connected (via TTL+USB) to the host PC. This connection is only there for debugging purposes (as in looking at the output from the board while developing and testing the firmware), but this connection is not supposed to be there once your firmware is ready for deployment, as then the board is supposed to be functioning by itself, without connection to the PC. Also, the connection is supposed to be mostly a "one way street" where you can view the log on your PC, but you do not enter input.

Just recently, there was a related discussion in the Matrix channel on that topic, but I can't seem to remember the details.

But let me start from the following: why are you expecting user input from stdin in the first place, given the background details above?

matthew-a-thomas commented 2 years ago

@ivmarkov Thank you for helping me think through this, even though it's completely unrelated to rust-esp32-std-demo!

why are you expecting user input from stdin in the first place, given the background details above?

I'm not expecting to require stdin for the final form of my project. While I might expose an interactive menu/configuration over stdin/out, more likely I'll perform first time setup through an HTTP interface while running as an AP. But as I learn the ropes stdin has proven to be very useful.

I was just surprised to find this friction, and for it to manifest in this way. I suppose I wouldn't have been surprised if there were an error acquiring stdin. But since there isn't then it seems someone intended it to be available. And if it's supposed to be available I would have expected it to behave like stdin in most other places.

It's not like there's a violation of Liskov Substitution Principle or anything... the error is being communicated through the API which I've now learned allows for this very error. And it's possible to hide the awkwardness behind a layer of abstraction like I've done. So it's really not a big deal, just odd to me :)

Please feel free to close this issue if you'd like. My little workaround has been enough to keep my proof of concept moving. I appreciate your time!