How does the keyboard input loop work in cforth?
Closed this issue · 10 comments
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.
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.
Implement key() in terms of key_avail() and do the event polling there.
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.
But, as I said before, you would get more precise answers if we could see your current code.
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
If components/forth/consio.c is indeed what is being used by your build, key_avail() is the ideal location for handling other events.
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.
-
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.
-
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.
-
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.
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.
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.
You are on you own at this point