// reverse alphabet trainer processes and, for now, output
// this is intended to becone the *.ino for the CardKB
// we allow switching away from any serial capability or any dependence there on.
# define DEBUG 1 // set to 0 when the CardKB ATMEGA8A is the target
# define WOKWI 1 // the CardKB is hjella bright, so we need two color tables
# if DEBUG
# define dBegin(baud) Serial.begin(baud)
# define dPrint(x) Serial.print(x)
# define dPrintln(x) Serial.println(x)
# else
# define dBegin(baud) do {} while (0)
# define dPrint(x) do {} while (0)
# define dPrintln(x) do {} while (0)
# endif
// reverse alphabet trainer processes and, for now, output
extern void setLED(unsigned long);
// travel with displayFSM if we put display in a tab
# if WOKWI
# define BLACK 0x0
# define RED 0xff0000
# define GREEN 0x00ff00
# define BLUE 0x0000ff
# define YELLOW 0xffff00
# define WHITE 0xffffff
# else
# define BLACK 0x0
# define RED 0x100000
# define GREEN 0x001000
# define BLUE 0x000010
# define YELLOW 0x101000
# define WHITE 0x101010
# endif
unsigned long now;
//void displayFSM();
//void displayFSM(int);
enum display {OFF, GOOD, BAD, SUCCESS, WOW, SERVICE = 1000};
const char *displayTags[] = {"Off (black)", "Good (green)", "Bad (red)", "Success (white)", "Wow! (blue)"};
const int blinkTime = 777; // usual time of a blip on the pixel. Too long here!
void displayFSM()
{
displayFSM(SERVICE);
}
void displayFSM(int command)
{
static byte state = OFF;
static unsigned long timer;
{
static byte lastPrinted = -1;
if (lastPrinted != state) {
dPrint("display: ");
dPrintln(displayTags[state]);
lastPrinted = state;
}
}
if (command == SERVICE) {
if (state != OFF && now - timer > 777) {
dPrintln(" and off");
setLED(BLACK);
state = OFF;
}
return;
}
// this could be compact, but here we have flexibilty - a displayFSM state might handle an animation, e.g.
if (command != SERVICE) { // we know it is not, I like to leave this in case the code above changes
switch (command) {
case OFF :
dPrintln(" LED off");
setLED(BLACK);
break;
case GOOD :
dPrintln(" LED GREEN");
setLED(GREEN);
break;
case BAD :
dPrintln(" LED RED");
setLED(RED);
break;
case SUCCESS :
dPrintln(" LED WHITE");
setLED(WHITE);
break;
case WOW :
dPrintln(" LED BLUE");
setLED(BLUE);
break;
default :
dPrintln("Display FSM Error!\n");
setLED(YELLOW);
for (; ; );
}
// this isn't quite enough but works for what the switch statement does now
if (command != OFF && state == OFF) {
timer = now;
state = command;
}
}
}
void setup() {
# if WOKWI
Serial.begin(115200);
Serial.println("\nReverse Alphabet Trainer\n");
# else
dBegin(115200);
dPrintln("hi Mom!");
# endif
setupDisplay(); // this might be a display init, or a displayFSM cut-out command INIT?
// hello, I am alive!
for (byte ii = 0; ii < 3; ii++) {
setLED(RED); delay(111);
setLED(BLACK); delay(222);
}
}
void loop()
{
now = millis();
logicFSM(); // crank 'em along
displayFSM();
extern char getEvent();
char theChar = getEvent();
if (theChar > 0) { // chatGPT caught me calling getEvent again!
if ('a' <= theChar && theChar <= 'z') {
// dPrintln(theChar);
logicFSM(theChar);
}
// else command/control/configure keys handled here
}
delay(20); // sue me. this is a work in progress
}
enum logic {IDLE, AWAIT, ERROR, FINI};
const char *logicTags[] = {"Idle", "Await", "Error", "Fini"};
void logicFSM(char theChar)
{
static byte state = IDLE;
static unsigned long timer; // general use timer
static char expected;
if (theChar) {
dPrint("= ");
dPrintln(theChar);
}
{
static byte lastPrinted = -1;
if (lastPrinted != state) {
dPrint("logic: ");
dPrintln(logicTags[state]);
lastPrinted = state;
}
}
switch (state) {
// let IDLE fully consume the first character, here's where we might say "off to the races"
// IDLE could also use displayFSM to "invite" the player to enter the first char
case IDLE:
if (theChar) {
if (theChar == 'a') {
state = FINI;
} else {
expected = theChar - 1;
// acknowledge two ways
displayFSM(GOOD);
dPrintln(" !");
state = AWAIT;
}
}
else {
static bool onOff;
if (now - timer > 333) {
setLED(onOff ? YELLOW : BLACK);
onOff = !onOff;
timer = now;
}
}
break;
// Then AWAIT becomes:
case AWAIT:
if (theChar) {
if (theChar == expected) {
if (theChar == 'a') {
state = FINI;
} else {
expected--;
// acknowledge two ways
displayFSM(GOOD);
dPrintln(" !");
}
} else {
state = ERROR;
timer = now;
}
}
break;
// harsh punishment for now - this will be sticky, and require an acknowledgement or
// the passage of a longer period of illumination
case ERROR :
setLED(RED);
if (now - timer > 1777) {
setLED(BLACK);
state = IDLE;
}
break;
case FINI :
displayFSM(SUCCESS);
dPrintln("ding ding!");
state = IDLE;
break;
default :
dPrintln("Rotten Denmark!");
for (; ; );
}
}
// service or just call logicFSM(0)
void logicFSM()
{
logicFSM(0);
}
//=======
// may move with displayFSM to a tab.
# include <Adafruit_NeoPixel.h>
# define PIN 13 // the pin
# define NPIXELS 1 // number of LEDs on strip
Adafruit_NeoPixel pixel(NPIXELS, PIN, NEO_GRB + NEO_KHZ800);
void setupDisplay()
{
pixel.begin();
}
void setLED(unsigned long theColor)
{
pixel.setPixelColor(0, theColor);
pixel.show();
}
//=======
/*
// test the displayFSM. This turnd on the LED and it should go off "by itself"
if (0)
{
static unsigned long timer;
if (now - timer > 1000) {
displayFSM(GOOD);
timer = now;
}
}
*/