Teacher 3

/*
  MASTER ESP32-C6 — Light Chase Game
  MAC: 58:E6:C5:1A:7F:28

  - D6 = GPIO16 = TOP LED
  - D7 = GPIO17 = BOTTOM LED
  - GPIO 21 = Piezo speaker  (MASTER ONLY)
  - GPIO 0  = Hall sensor (LOW when magnet present)

  TIMEOUT BEHAVIOUR:
    Every 10s with no hall sensor triggered anywhere:
      → twitter on master speaker → move light
    Repeats max 2 times. After 2nd move (30s total ignored):
      → all LEDs off, descending tone, program goes idle.
    Any hall trigger resets the counter. Send "RESTART" over
    serial to re-activate.
*/

#include <esp_now.h>
#include <WiFi.h>

#define LED_TOP      16
#define LED_BOTTOM   17
#define SPEAKER_PIN  21
#define HALL_PIN     1

// Node 1: B4:3A:45:89:E9:F4
uint8_t node1Mac[] = {0xB4, 0x3A, 0x45, 0x89, 0xE9, 0xF4};
// Node 2: B4:3A:45:89:F2:B4
uint8_t node2Mac[] = {0xB4, 0x3A, 0x45, 0x89, 0xF2, 0xB4};

typedef struct {
  int  nodeID;
  char type[8];
  int  value;
} IncomingMsg;

typedef struct {
  char cmd[16];
} OutgoingCmd;

int  currentNode = -1;
int  currentPos  = -1;
int  moveCount   = 0;
bool gameActive  = true;

unsigned long lastActivityTime = 0;
const unsigned long TIMEOUT_MS = 10000;

bool masterHallTriggered = false;
bool node1HallActive     = false;
bool node2HallActive     = false;

void onReceive(const esp_now_recv_info_t *info, const uint8_t *data, int len) {
  IncomingMsg msg;
  memcpy(&msg, data, sizeof(msg));

  Serial.print("NODE:");
  Serial.print(msg.nodeID);
  Serial.print(",");
  Serial.print(msg.type);
  Serial.print(":");
  Serial.println(msg.value);

  if (strcmp(msg.type, "HALL") == 0) {
    if (msg.nodeID == 1) node1HallActive = (msg.value == 1);
    if (msg.nodeID == 2) node2HallActive = (msg.value == 1);
    if (msg.value == 1 && gameActive) {
      lastActivityTime = millis();
      moveCount = 0;
    }
  }
}

void sendToNode(int nodeID, const char* cmd) {
  OutgoingCmd msg;
  strncpy(msg.cmd, cmd, sizeof(msg.cmd));
  if      (nodeID == 1) esp_now_send(node1Mac, (uint8_t*)&msg, sizeof(msg));
  else if (nodeID == 2) esp_now_send(node2Mac, (uint8_t*)&msg, sizeof(msg));
}

void masterLedsOff() {
  digitalWrite(LED_TOP,    LOW);
  digitalWrite(LED_BOTTOM, LOW);
}

void masterSetLed(int pos, bool on) {
  if (pos == 0) digitalWrite(LED_TOP,    on ? HIGH : LOW);
  else          digitalWrite(LED_BOTTOM, on ? HIGH : LOW);
}

void allLedsOff() {
  masterLedsOff();
  sendToNode(1, "LED_TOP_OFF");
  sendToNode(1, "LED_BOT_OFF");
  sendToNode(2, "LED_TOP_OFF");
  sendToNode(2, "LED_BOT_OFF");
}

void twitter() {
  for (int chirp = 0; chirp < 5; chirp++) {
    for (int i = 0; i < 60; i++) {
      digitalWrite(SPEAKER_PIN, HIGH);
      delayMicroseconds(250);
      digitalWrite(SPEAKER_PIN, LOW);
      delayMicroseconds(250);
    }
    delay(40);
  }
}

void beepMid() {
  for (int i = 0; i < 240; i++) {
    digitalWrite(SPEAKER_PIN, HIGH);
    delayMicroseconds(625);
    digitalWrite(SPEAKER_PIN, LOW);
    delayMicroseconds(625);
  }
}

void beepShutdown() {
  int delays[] = {300, 400, 500, 700};
  for (int d = 0; d < 4; d++) {
    for (int i = 0; i < 100; i++) {
      digitalWrite(SPEAKER_PIN, HIGH);
      delayMicroseconds(delays[d]);
      digitalWrite(SPEAKER_PIN, LOW);
      delayMicroseconds(delays[d]);
    }
    delay(30);
  }
}

void moveLightTo(int node, int pos) {
  if      (currentNode == 0) masterLedsOff();
  else if (currentNode == 1) { sendToNode(1, "LED_TOP_OFF"); sendToNode(1, "LED_BOT_OFF"); }
  else if (currentNode == 2) { sendToNode(2, "LED_TOP_OFF"); sendToNode(2, "LED_BOT_OFF"); }

  currentNode = node;
  currentPos  = pos;

  if      (node == 0) masterSetLed(pos, true);
  else if (node == 1) sendToNode(1, pos == 0 ? "LED_TOP_ON" : "LED_BOT_ON");
  else if (node == 2) sendToNode(2, pos == 0 ? "LED_TOP_ON" : "LED_BOT_ON");

  beepMid();

  Serial.print("LIGHT:");
  Serial.print(node);
  Serial.print(",");
  Serial.println(pos);
}

void handleTimeout() {
  if (moveCount >= 2) {
    shutDown();
    return;
  }

  twitter();
  delay(100);

  int newNode, newPos;
  do {
    newNode = random(0, 3);
    newPos  = random(0, 2);
  } while (newNode == currentNode && newPos == currentPos);

  moveCount++;
  moveLightTo(newNode, newPos);
  lastActivityTime = millis();

  Serial.print("EVENT:TIMEOUT_MOVE:");
  Serial.println(moveCount);
}

void shutDown() {
  gameActive = false;
  allLedsOff();
  beepShutdown();
  Serial.println("EVENT:SHUTDOWN");
}

void setup() {
  Serial.begin(115200);

  pinMode(LED_TOP,     OUTPUT);
  pinMode(LED_BOTTOM,  OUTPUT);
  pinMode(SPEAKER_PIN, OUTPUT);
  pinMode(HALL_PIN,    INPUT_PULLUP);

  masterLedsOff();

  WiFi.mode(WIFI_STA);
  esp_now_init();
  esp_now_register_recv_cb(onReceive);

  esp_now_peer_info_t peer = {};
  peer.channel = 0;
  peer.encrypt = false;

  memcpy(peer.peer_addr, node1Mac, 6);
  esp_now_add_peer(&peer);

  memcpy(peer.peer_addr, node2Mac, 6);
  esp_now_add_peer(&peer);

  randomSeed(esp_random());
  int startNode = random(0, 3);
  int startPos  = random(0, 2);

  currentNode = startNode;
  currentPos  = startPos;

  if (startNode == 0) masterSetLed(startPos, true);
  else sendToNode(startNode, startPos == 0 ? "LED_TOP_ON" : "LED_BOT_ON");

  Serial.print("LIGHT:");
  Serial.print(startNode);
  Serial.print(",");
  Serial.println(startPos);

  lastActivityTime = millis();
  moveCount = 0;

  Serial.println("STATUS:MASTER_READY");
}

void loop() {
  if (!gameActive) {
    if (Serial.available()) {
      String cmd = Serial.readStringUntil('\n');
      cmd.trim();
      if (cmd == "RESTART") {
        gameActive = true;
        moveCount  = 0;
        randomSeed(esp_random());
        int n = random(0, 3);
        int p = random(0, 2);
        currentNode = -1;
        moveLightTo(n, p);
        lastActivityTime = millis();
        Serial.println("STATUS:RESTARTED");
      }
    }
    delay(50);
    return;
  }

  bool hallNow = (digitalRead(HALL_PIN) == LOW);
  if (hallNow && !masterHallTriggered) {
    masterHallTriggered = true;
    lastActivityTime = millis();
    moveCount = 0;
    Serial.println("NODE:0,HALL:1");
  } else if (!hallNow && masterHallTriggered) {
    masterHallTriggered = false;
    Serial.println("NODE:0,HALL:0");
  }

  if ((millis() - lastActivityTime) >= TIMEOUT_MS) {
    handleTimeout();
  }

  if (Serial.available()) {
    String cmd = Serial.readStringUntil('\n');
    cmd.trim();
    if (cmd.startsWith("MOVE:")) {
      String params = cmd.substring(5);
      int comma = params.indexOf(',');
      if (comma > 0) {
        int n = params.substring(0, comma).toInt();
        int p = params.substring(comma + 1).toInt();
        moveLightTo(n, p);
        lastActivityTime = millis();
        moveCount = 0;
      }
    }
    // Processing reconnected — resend current state
    else if (cmd == "STATUS") {
      Serial.println("STATUS:MASTER_READY");
      if (currentNode >= 0) {
        Serial.print("LIGHT:");
        Serial.print(currentNode);
        Serial.print(",");
        Serial.println(currentPos);
      }
    }
  }

  delay(20);
}