Mi scuso a priori per il post che sarà un po' lunghetto...
Approfitto del passaggio involontario di @Claudio_FF che ha introdotto il concetto di macchina a stati finiti, per fare un po' di pubblicità (non che ci guadagni qualcosa, sia chiaro
) a questa libreria cotestatnt/YA_FSM sulla quale ho rimesso le mani in questi giorni (dopo averla abbandonata per un po' di tempo e pure buggata
).
Mi piacerebbe avere qualche feedback se possibile cosi da migliorarla e far crescere il progetto (il top sarebbe una pagina web che consente di modellare graficamente la FSM e produce il codice C++ in uscita).
Per rappresentare una macchina a stati, io trovo molto intuitivo usare come rappresentazione grafica diagrammi sequenziali GRAFCET / SFC.
Per modellare il comportamento richiesto da @lidas , ad esempio farei una cosa del genere...

Usando la libreria in questione, questo si traduce molto semplicemente in questo codice C++ che in pratica è quasi tutto "dichiarativo", c'è giusto un po' di logica nel dare la precedenza alla pressione lunga per la scrittura delle uscite (e per il blink di un secondo led).
#include <YA_FSM.h>
const byte BTN = 2;
const byte LED = 13;
const byte BLED = 12;
const byte RLED = 11;
#define SHORT_TIME 1000
#define LONG_TIME 3000
// Create new FSM
YA_FSM buttonFSM;
// State Alias
enum State {IDLE, PRESS};
// Input variable (this will trigger transition)
bool pButton = false;
// Output variables
bool led = false;
bool shortPress = false;
bool longPress = false;
bool redLed = false;
void setup() {
pinMode(BTN, INPUT_PULLUP);
pinMode(LED, OUTPUT);
pinMode(BLED, OUTPUT);
pinMode(RLED, OUTPUT);
// Configure the FSM as modelled
setupStateMachine();
}
void loop() {
// Read inputs
pButton = (digitalRead(BTN) == LOW);
// Update State Machine (true is state changed)
buttonFSM.Update();
// Set outputs
if(longPress) {
digitalWrite(LED, LOW);
}
else if (shortPress) {
digitalWrite(LED, HIGH);
}
digitalWrite(RLED, redLed);
// Check if FSM is really NON blocking.
blinkLed(BLED, 250);
}
void blinkLed(const uint8_t led, const uint32_t period) {
static uint32_t blinkTime;
if(millis() - blinkTime > period) {
blinkTime = millis();
digitalWrite(led, !digitalRead(led));
}
}
// Setup the State Machine
void setupStateMachine() {
// Follow the order of defined enumeration for the state definition (will be used as index)
// Add States => name,timeout, onEnter cb, onState cb, onLeave cb
buttonFSM.AddState("IDLE", nullptr, nullptr, nullptr);
buttonFSM.AddState("PRESS", nullptr, nullptr, nullptr); // Set MAX and MIN state duration (avoid bouncing NON blocking)
// Add actions
buttonFSM.AddAction(PRESS, YA_FSM::D, shortPress, SHORT_TIME); // Delayed action
buttonFSM.AddAction(PRESS, YA_FSM::D, longPress, LONG_TIME); // Delayed action
buttonFSM.AddAction(PRESS, YA_FSM::L, redLed, SHORT_TIME); // Limited time action
// Add transitions with related trigger input callback functions
buttonFSM.AddTransition(IDLE, PRESS, pButton);
buttonFSM.AddTransition(PRESS, IDLE, [](){ return pButton == false;} ); // simple lambda function
}
I metodi dietro le quinte usati dalla libreria non appesantiscono di molto il codice finale (anzi spesso una FSM può aiutare a snellire il tutto) perché sono basati essenzialmente sull'utilizzo dei puntatori.
Ad esempio, lo sketch proposto compilato per un Arduino Uno da questo risultato:
Questa è un'applicazione molto semplice e ovviamente le risorse occupate sono leggermente maggiori rispetto all'altro esempio nudo e crudo.
Credo però che dal punto di vista "didattico" sia molto utile per i principianti al fine di capire la logica alla base di una FSM che se ben implementata consente di realizzare algoritmi efficaci ed affidabili senza dead-lock o cose simili in modo intuitivo e facilmente scalabile.
Il vantaggio principale secondo me è che sposta l'attenzione e le energie di chi programma sulla progettazione dell'algoritmo che consente il funzionamento voluto e non sulle specifiche istruzioni da usare.
Capito il meccanismo delle macchine a stati finiti, poi è relativamente semplice estenderlo a progetti più complessi (magari suddividendo il processo in più step).
Volevo far notare che la FSM cosi definita e con l'utilizzo di questa libreria NON è bloccante (anche mentre il tasto è premuto), motivo per cui ho aggiunto il blink del secondo led come verifica.
Ho aggiunto anche un terzo led per provare l'azione Time Limited.
Se volete approfondire un po' il funzionamento dei diagrammi SFC, ho trovato ottima questa breve dispensa del Prof. Cavalieri Sequential Functional Chart
P.S.
Alo stato attuale, alla libreria manca la gestione dei rami in parallelo, che è ancora in cantiere.