MitchBradley / cforth

Mitch Bradley's CForth implementation
Other
155 stars 41 forks source link

How does the keyboard input loop work in cforth? #132

Closed jonsmirl closed 8 months ago

jonsmirl commented 8 months ago

With alignment fixed I have cforth running in interactive mode now on ESP32S3

CForth built 2023-03-18 17:31 from 1a6805a
Type a key within 2 seconds to interact
ok 
ok 5 6 * .
1e 
ok decimal
ok 5 6 * .
30 
ok 

What I need is a safe way to run both interactive mode and event driven words at the same time. I can queue the events up in a FREERTOS queue. So then I went looking in the cforth code for a place to say --- check the queue and if there is event in queue run that word (I can make the events be words). The event words are well behaved, run very quickly and don't leave anything on the stack. But then if there is no event I want to keep polling the keyboard and interacting. I am not finding an obvious location to insert this logic, can you please point at the right spot in the code?

My goal is to see something like this where pushing the button triggers a word which prints the message. I'm trying to add an ability to debug the running system. Using the messaging queue keeps everything serialized, forth can take a message off from the queue and run it when it chooses too instead of at interrupt time.

CForth built 2023-03-18 17:31 from 1a6805a
Type a key within 2 seconds to interact
ok 
ok 5 6 * .
1e 
ok decimal
BUTTON-A PUSHED
ok 5 6 * .
30 
ok 

Longer term I will have to get rid of this keyboard polling and replace it with interrupt driven code since the CPU can't go to sleep while polling. The keyboard interrupts will also generate messages for the queue.

jonsmirl commented 8 months ago

I think this is the right location?

/*$p sys-accept */ case SYS_ACCEPT:
    scr = pop;
    ascr = (u_char *)pop;
    scr = caccept((char *)ascr, scr, up);
    ESP_LOGI(TAG, "SYS_ACCEPT %s", ascr);
    if (scr == -2) {
        // Save interpreter state, return, and expect reentry
        // to inner_interpreter() upon a later callback
        scr = 2;  goto out;
    }
    push(scr);
    next;

So I need to modify that to check for messages in the queue, and then change keyboard IO to use the queue.

MitchBradley commented 8 months ago

Implement key() in terms of key_avail() and do the event polling there.

MitchBradley commented 8 months ago

For many embedded targets, key() is implemented on top of getkey(), which in turn goes through kbhit(). which is the right place for event checks.

MitchBradley commented 8 months ago

But, as I said before, you would get more precise answers if we could see your current code.

jonsmirl commented 8 months ago

Here's a current snapshot of the component. I have changed very little of the cforth code, mainly just copied it around to different places. https://github.com/jonsmirl/forth_component

MitchBradley commented 8 months ago

If components/forth/consio.c is indeed what is being used by your build, key_avail() is the ideal location for handling other events.

jonsmirl commented 8 months ago

The key thing the component can't do is rebuild forth, makebi, makecalls in the regen directory. Interactive console does work in that component.

RIght now it is using consio.c. ESP console can come from many sources -- UART, USB Serial, even websocket. All of that is hidden behind the ESP IDF libnoise() implementation which is interrupt driven.

So I need to do three things. 1) I can change caccept() internally to use a FREERTOS message queue, that will hide the change from forth.c. A message queue will let me insert my event driven trigger words.

2) Use the ESP IDF libnoise() implementation. It is interrupt driven and it works on all possible ESP console implementations including UART, USB serial and websockets. This will be an indirect implementation via the message queue. A task will just loop reading lines, and then insert those lines into the message queue.

3) Figure out if the ESP console implementation supports a way to implement key_avail() so that it is isolated from the choice of ESP console hardware.

MitchBradley commented 8 months ago

If you switch it out at the caccept level, events will be blocked if you do anything that calls key() - like the Forth debugger.

Look at the linenoise source code. It doesn't do anything special to access all of those input sources. It just calls read on stdin. So all of the magic happens inside read(stdin, ...). Look at raw_poll() in interface.c . Guess what - it calls read(stdin, ...).

This is the point where I am starting to get annoyed and thinking that maybe I should stop giving advice if you don't want to take it.

jonsmirl commented 8 months ago

I haven't written anything yet. You are much more experienced with this code so some of my ideas might be bad ones.

Back when I first started this I think I made a mistake by having the hardware events directly run forth words via the API. That seems like the obvious solution but it has a lot of side effects. So a better solution might be to entirely get rid of that and implement a new architecture where the hardware events put messages onto a queue, and then forth code pulls the messages off and processes them. app.fth would load a forth program which blocks in a loop waiting for messages, then process them. You'd Ctrl-C to get to the console and event processing would simply stop, there is no requirement for the forth level event processing to run no matter what. The critical events are handled in C code and can't be altered. Now I don't have to mess with the console code at all.

MitchBradley commented 8 months ago

You are on you own at this point