// added I2C test 14 MAY 2026
// no this is the latest crack 12 MAY 2026 22:50
// thisGo4It_Dvorak 11 MAY 2026
// TBD design / implement / entab the displayFSM
// LATEST BEST DVORAK later
// 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.
// go4it is a late night shot at instant gratification
//... "only" changes
// keyboard - can be a *.h one day if
// add setupCardKB to setup, do a fake in wokwi __
// add scanLoop to loop, also fake it in the wokwi __
extern char getEvent();
extern void setupCardKB(); // forgot this
extern void scanLoop();
# 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 WOKWI
void setupCardKB(){} // forgot this
void scanLoop(){}
# endif
# 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);
void logicFSM();
void logicFSM(char);
void setupDisplay();
enum display {
DISP_OFF,
DISP_INVITE,
DISP_ACK,
DISP_GOOD,
DISP_BAD,
DISP_REWARD_SARCASTIC,
DISP_REWARD_SUCCESS,
DISP_REWARD_WOW,
DISP_SERVICE = 1000
};
const char *displayTags[] = {
"Off",
"Invite",
"Ack",
"Good",
"Bad",
"Reward sarcastic",
"Reward success",
"Reward wow"
};
const unsigned long ackTime = 120;
const unsigned long goodTime = 777;
const unsigned long badTime = 1333;
const unsigned long rewardTime = 2600; // look at all coexistant states.
const unsigned long restTime = 1777;
//... nice!
unsigned long dimColor(byte r, byte g, byte b)
{
# if WOKWI
return ((unsigned long)r << 16) | ((unsigned long)g << 8) | b;
# else
r >>= 4;
g >>= 4;
b >>= 4;
return ((unsigned long)r << 16) | ((unsigned long)g << 8) | b;
# endif
}
void displayFSM()
{
displayFSM(DISP_SERVICE);
}
void displayFSM(int command)
{
static byte state = DISP_OFF;
static unsigned long timer;
static unsigned long lastStep;
if (command != DISP_SERVICE) {
if (command < DISP_SERVICE) {
dPrint("display command: ");
dPrintln(displayTags[command]);
}
switch (command) {
case DISP_OFF:
setLED(BLACK);
state = DISP_OFF;
timer = now;
return;
case DISP_INVITE:
if (state == DISP_BAD
|| state == DISP_REWARD_SARCASTIC
|| state == DISP_REWARD_SUCCESS
|| state == DISP_REWARD_WOW) return;
state = DISP_INVITE;
timer = now;
lastStep = 0;
return;
case DISP_ACK:
if (state == DISP_BAD
|| state == DISP_REWARD_SARCASTIC
|| state == DISP_REWARD_SUCCESS
|| state == DISP_REWARD_WOW) return;
setLED(YELLOW);
state = DISP_ACK;
timer = now;
return;
case DISP_GOOD:
if (state == DISP_BAD
|| state == DISP_REWARD_SARCASTIC
|| state == DISP_REWARD_SUCCESS
|| state == DISP_REWARD_WOW) return;
setLED(GREEN);
state = DISP_GOOD;
timer = now; // repeated GOOD prolongs the green stab
return;
case DISP_BAD:
setLED(RED);
state = DISP_BAD;
timer = now;
return;
case DISP_REWARD_SARCASTIC:
case DISP_REWARD_SUCCESS:
case DISP_REWARD_WOW:
state = command;
timer = now;
lastStep = 0;
return;
default:
dPrintln("Display FSM Error!");
setLED(YELLOW);
for (; ; );
}
}
// service the current display state. No delay() in here.
switch (state) {
case DISP_OFF:
break;
case DISP_INVITE:
{
const unsigned long period = 1800;
unsigned long phase = (now - timer) % period;
unsigned long half = period / 2;
byte level = phase < half
? map(phase, 0, half, 0, 160)
: map(phase - half, 0, half, 160, 0);
if (now - lastStep >= 25) {
setLED(dimColor(0, 0, level));
lastStep = now;
}
}
break;
case DISP_ACK:
if (now - timer >= ackTime) {
setLED(BLACK);
state = DISP_OFF;
}
break;
case DISP_GOOD:
if (now - timer >= goodTime) {
setLED(BLACK);
state = DISP_OFF;
}
break;
case DISP_BAD:
if (now - timer >= badTime) {
setLED(BLACK);
state = DISP_OFF;
}
break;
case DISP_REWARD_SARCASTIC:
{
unsigned long age = now - timer;
if (age < rewardTime) {
if (now - lastStep >= 180) {
static bool onOff;
setLED(onOff ? YELLOW : BLACK);
onOff = !onOff;
lastStep = now;
}
} else {
setLED(BLACK);
state = DISP_OFF;
}
}
break;
case DISP_REWARD_SUCCESS:
{
unsigned long age = now - timer;
if (age < rewardTime) {
if (now - lastStep >= 100) {
byte level = (age / 100) & 1 ? 255 : 40;
setLED(dimColor(level, level, level));
lastStep = now;
}
} else {
setLED(BLACK);
state = DISP_OFF;
}
}
break;
case DISP_REWARD_WOW:
{
unsigned long age = now - timer;
if (age < rewardTime) {
if (now - lastStep >= 120) {
byte slot = (age / 120) % 6;
switch (slot) {
case 0: setLED(RED); break;
case 1: setLED(YELLOW); break;
case 2: setLED(GREEN); break;
case 3: setLED(BLUE); break;
case 4: setLED(WHITE); break;
case 5: setLED(BLACK); break;
}
lastStep = now;
}
} else {
setLED(BLACK);
state = DISP_OFF;
}
}
break;
}
}
void setup() {
# if WOKWI
Serial.begin(115200);
Serial.println("\nReverse Alphabet Trainer\n");
# else
dBegin(115200);
dPrintln("hi Mom!");
# endif
setupCardKB();
setupDisplay();
displayFSM(DISP_OFF);
}
void loop()
{
now = millis();
logicFSM(); // crank 'em along
displayFSM();
scanLoop();
extern char getEvent();
char theChar = getEvent();
if (theChar > 0) { // chatGPT caught me calling getEvent again!
if ('a' <= theChar && theChar <= 'z') {
// dPrintln(theChar);
logicFSM(theChar);
}
//... only one change
/* fix so wokwi line ending chars don't mess us up. on the CardKB as there are no such chars
else {
displayFSM(DISP_ACK); // valid key, but currently meaningless here
}
*/
}
if (1) testIO();
}
enum logic {IDLE, AWAIT, ERROR, REST, FINI};
const char *logicTags[] = {"Idle", "Await", "Error", "Zzzz", "Fini"};
void logicFSM(char theChar)
{
static byte state = IDLE;
static bool newState = true;
static unsigned long timer;
static char expected;
static byte roundLength;
static byte rewardCommand;
# define TRANSITION(nextState) do { state = nextState; newState = true; } while (0)
if (theChar) {
dPrint("= ");
dPrintln(theChar);
}
{
static byte lastPrinted = -1;
if (lastPrinted != state) {
dPrint("logic: ");
dPrintln(logicTags[state]);
lastPrinted = state;
}
}
switch (state) {
case IDLE:
if (newState) {
displayFSM(DISP_INVITE);
roundLength = 0;
newState = false;
}
if (theChar) {
roundLength = 1;
if (theChar == 'a') {
rewardCommand = DISP_REWARD_SARCASTIC;
TRANSITION(FINI);
} else {
expected = theChar - 1;
displayFSM(DISP_GOOD);
dPrintln(" !");
TRANSITION(AWAIT);
}
}
break;
case AWAIT:
if (newState) {
newState = false;
}
if (theChar) {
if (theChar == expected) {
roundLength++;
if (theChar == 'a') {
rewardCommand = roundLength == 26
? DISP_REWARD_WOW
: DISP_REWARD_SUCCESS;
TRANSITION(FINI);
} else {
expected--;
displayFSM(DISP_GOOD);
dPrintln(" !");
}
} else {
TRANSITION(ERROR);
}
}
break;
case ERROR:
if (newState) {
displayFSM(DISP_BAD);
timer = now;
newState = false;
}
if (now - timer >= badTime) {
TRANSITION(REST);
}
break;
case REST :
if (newState) {
displayFSM(DISP_OFF);
timer = now;
newState = false;
}
if (millis() - timer >= restTime) {
TRANSITION(IDLE);
newState = true;
}
break;
case FINI:
if (newState) {
displayFSM(rewardCommand);
timer = now;
newState = false;
}
if (now - timer >= rewardTime + 333) {
TRANSITION(REST);
}
break;
default:
dPrintln("Rotten Denmark!");
for (; ; );
}
# undef TRANSITION
}
// 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();
}
//=======
// verify availability and function of the GPIO on the CardKB connector
void testIO()
{
# define kIn A5 // "clock"
# define kOut A4 // "data"
pinMode(kIn, INPUT_PULLUP);
pinMode(kOut, OUTPUT);
static bool state;
static bool lastReading;
static unsigned long timer;
if (now - timer < 25) return;
bool input = digitalRead(kIn) == LOW; // pressed?
if (lastReading != input) {
if (input) {
state = !state;
digitalWrite(kOut, state ? HIGH : LOW);
}
timer = now;
lastReading = input;
}
}
//=======
// early test for the displayFSM. This turned on the LED and it should go off "by itself"
/*
if (0)
{
static unsigned long timer;
if (now - timer > 1000) {
displayFSM(GOOD);
timer = now;
}
}
*/