#include <Arduino.h>
#define DEBUG_MODE 1 // 1 = mensajes de debug activos (Wokwi) | 0 = protocolo limpio (NetLogo)
#if DEBUG_MODE
#define DEBUG_PRINT(x) Serial.print(x)
#define DEBUG_PRINTLN(x) Serial.println(x)
#else
#define DEBUG_PRINT(x)
#define DEBUG_PRINTLN(x)
#endif
enum class AgentState {
BOOT,
WAITING_SETUP,
RUNNING,
FINISH
};
AgentState agentState;
volatile bool resetPending = false; // se pone en true desde el ISR, se consume en loop()
// Variables del modelo del agente (equivalente a self.n_celdas / self.model /
// self.performance en agente_base.py). Basura hasta que llega el Setup real.
int nCeldas = 0;
int modelo = 0;
int performance = 0;
// Pines físicos
const int LED_PIN = 2; // LED de estado (heredado de main.cpp de referencia)
const int RESET_BUTTON_PIN = 4; // Botón de reset lógico, con pull-up interno (a GND)
String rxBuffer = "";
// Lectura no bloqueante por línea, equivalente en propósito al rx_buffer
// persistente ya usado del lado de Python (ver README, sección 5.1).
// Retorna true solo cuando se completó una línea (encontró '\n').
bool leerLineaSerial(String &lineaCompleta) {
while (Serial.available() > 0) {
char c = Serial.read();
if (c == '\n') {
lineaCompleta = rxBuffer;
rxBuffer = "";
return true;
} else if (c != '\r') {
rxBuffer += c;
}
}
return false;
}
void resetButtonInterruptHandler() {
// Válido desde cualquier estado (incluido RUNNING, confirmado):
// fuerza el regreso a WAITING_SETUP sin tocar Serial ni los pines.
agentState = AgentState::WAITING_SETUP;
resetPending = true; // el ISR solo marca la bandera; el mensaje se imprime en loop()
}
void handleBootState() {
DEBUG_PRINTLN("[DEBUG] BOOT -> WAITING_SETUP");
agentState = AgentState::WAITING_SETUP;
}
void handleWaitingSetupState() {
String linea;
if (leerLineaSerial(linea)) {
int comaPos = linea.indexOf(',');
if (comaPos > 0) {
nCeldas = linea.substring(0, comaPos).toInt();
modelo = linea.substring(comaPos + 1).toInt();
performance = 0;
agentState = AgentState::RUNNING;
DEBUG_PRINTLN("[DEBUG] WAITING_SETUP -> RUNNING");
DEBUG_PRINT("[DEBUG] nCeldas="); DEBUG_PRINTLN(nCeldas);
DEBUG_PRINT("[DEBUG] modelo="); DEBUG_PRINTLN(modelo);
}
}
}
void handleRunningState() {
String linea;
if (!leerLineaSerial(linea)) {
return; // nada nuevo en el buffer, no bloquea
}
if (linea == "EXIT") {
DEBUG_PRINTLN("[DEBUG] RUNNING -> FINISH (EXIT recibido)");
agentState = AgentState::FINISH;
return;
}
int percepto = linea.toInt();
if (modelo != percepto) {
DEBUG_PRINT("[DEBUG] desincronizacion: modelo="); DEBUG_PRINT(modelo);
DEBUG_PRINT(" percepto="); DEBUG_PRINTLN(percepto);
modelo = percepto;
}
int prediccion = (modelo + 1) % nCeldas;
if (prediccion == 0) {
performance++;
DEBUG_PRINT("[DEBUG] vuelta completa, performance="); DEBUG_PRINTLN(performance);
}
modelo = prediccion;
DEBUG_PRINT("[DEBUG] percepto="); DEBUG_PRINT(percepto);
DEBUG_PRINT(" -> modelo="); DEBUG_PRINTLN(modelo);
Serial.println('A');
}
void handleFinishState() {
// Estado terminal real: no hace nada, no lee Serial, no transiciona
// por sí solo. La única salida es el botón de reset (interrupción).
}
void setup() {
pinMode(LED_PIN, OUTPUT);
pinMode(RESET_BUTTON_PIN, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(RESET_BUTTON_PIN), resetButtonInterruptHandler, FALLING);
Serial.begin(9600);
agentState = AgentState::BOOT;
}
void loop() {
if (resetPending) {
DEBUG_PRINTLN("[DEBUG] reset por boton -> WAITING_SETUP");
resetPending = false;
}
switch (agentState) {
case AgentState::BOOT:
handleBootState();
break;
case AgentState::WAITING_SETUP:
handleWaitingSetupState();
break;
case AgentState::RUNNING:
handleRunningState();
break;
case AgentState::FINISH:
handleFinishState();
break;
}
}