Untitled
unknown
c_cpp
2 months ago
30 kB
6
Indexable
#include <SPI.h>
#include <MFRC522.h>
#include <Wire.h>
#include <RadioLib.h>
#include <LiquidCrystal_I2C.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <stdio.h>
// ============================================================
// DEBUG CONTROL
// ============================================================
static const bool DEBUG_ENABLE = true;
#define DBG_PRINT(...) do { if (DEBUG_ENABLE) Serial.print(__VA_ARGS__); } while (0)
#define DBG_PRINTLN(...) do { if (DEBUG_ENABLE) Serial.println(__VA_ARGS__); } while (0)
#define DBG_PRINTF(...) do { if (DEBUG_ENABLE) Serial.printf(__VA_ARGS__); } while (0)
// ============================================================
// HARDWARE PINS
// ============================================================
#define LED_YELLOW_P 2 // Yellow LED on pin 2
#define LED_GREEN_P 17 // Green LED on pin 17
#define LED_RED_P 15 // Red LED on pin 15
#define RC522_SS_P 4 // RC522 SS pin
#define RC522_RST_P 16 // RC522 RST pin
#define SCK_P 14 // SPI SCK pin
#define MISO_P 35 // SPI MISO pin
#define MOSI_P 13 // SPI MOSI pin
#define SENSOR_P 21 // PIR sensor pin
#define LCD_SDA_P 32 // LCD SDA pin
#define LCD_SCL_P 33 // LCD SCL pin
#define LORA_CS_P 5 // LoRa CS pin
#define LORA_DIO1_P 25 // LoRa DIO1 pin
#define LORA_RST_P 27 // LoRa RST pin
#define LORA_BUSY_P 26 // LoRa BUSY pin
// ============================================================
// TIMING / RADIO SETTINGS
// ============================================================
static const float LORA_FREQ_MHZ = 915.0f; // CHANGE: set to 915 MHz for US; change to 433.0f for EU
static const int LORA_SF = 9; // CHANGE: can be 7-12; higher is more robust but slower
static const float LORA_BW_KHZ = 62.5f; // CHANGE: set to 62.5 kHz for US; change to 125.0 kHz for EU
static const int LORA_CR = 5; // CHANGE: can be 5-8; higher is more robust but slower
static const int LORA_TX_DBM = 2; // CHANGE: can be 2-17; higher is stronger but draws more power
static const uint32_t PIR_STABLE_MS = 200; // Time that PIR must be continuously HIGH to trigger start
static const uint32_t PIR_REARM_MS = 1200; // Minimum time after a PIR trigger before another can be registered (prevents multiple triggers from one crossing)
static const uint32_t START_LOW_VERIFY_MS = 800; // Time that PIR must be continuously LOW to arm the start (prevents false arming from brief LOWs)
static const uint32_t START_WAIT_TIMEOUT_MS = 15000; // Time to wait for the racer to trigger the start PIR before cancelling and returning to RFID_WAIT
static const uint32_t RUN_TIMEOUT_MS = 600000; // Maximum run time (10 minutes) before automatically cancelling and returning to RFID_WAIT
static const uint32_t START_ACK_RESEND_MS = 800; // Time after sending CMD START to resend if no ACK START received
static const uint32_t START_ACK_TIMEOUT_MS = 3500; // Time to wait for ACK START before cancelling run (should be longer than START_ACK_RESEND_MS to allow for at least one resend)
static const uint32_t READY_FLASH_MS = 250; // Time interval for flashing LEDs when waiting for start arm
static const uint32_t RUN_PING_MS = 10000; // Time interval to send PING packets during an active run to verify the link is still alive
static const uint32_t RUN_LINK_WARN_MS = 30000; // Time with no ACK PING received before showing a warning about possible link issues (but without cancelling the run, since it may just be a one-way issue or a delayed packet)
// ============================================================
// BUFFER SIZES
// ============================================================
#define UID_BUF_SZ 24 // Enough for 10 bytes in hex plus colons (e.g. "FF:FF:FF:FF:FF:FF:FF:FF:FF:FF") and null terminator
#define NAME_BUF_SZ 32 // Enough for the longest expected racer name plus null terminator
#define TIME_BUF_SZ 24 // Enough for formatted time strings like "HH:MM:SS:MMM" plus null terminator
#define PACKET_BUF_SZ 160 // Enough for the longest expected packet (e.g. RESULT with all fields) plus null terminator
#define LCD_LINE_BUF_SZ 17 // 16 characters for LCD line plus null terminator
// ============================================================
// OBJECTS
// ============================================================
MFRC522 rfid(RC522_SS_P, RC522_RST_P); // Create MFRC522 instance for RFID reader
LiquidCrystal_I2C lcd(0x27, 16, 2); // Create LCD instance (I2C address 0x27, 16 columns, 2 rows)
SX1262 radio = new Module(LORA_CS_P, LORA_DIO1_P, LORA_RST_P, LORA_BUSY_P); // Create SX1262 instance for LoRa radio
// ============================================================
// RACER DATABASE
// ============================================================
// Racer Data
struct Racer {
const char* uid;
const char* name;
};
// RFID saved UIDs and corresponding racer names; CHANGE as needed for your racers
static const Racer library[] = {
{"A3:36:3A:21", "NJ Bascug"},
{"D3:62:E2:20", "WenXing Tan"},
{"2D:25:BC:01", "Jacob Matthews"},
{"A7:88:4A:01", "Ian Booty"},
{"3D:02:3E:03", "Rob Baillie"},
{"76:EA:1E:2F", "Lucas Fermer"},
{"9C:0D:76:33", "Kip"}
};
// Number of racers in the library; calculated from the size of the array
static const int RACER_COUNT = sizeof(library) / sizeof(library[0]);
// ============================================================
// STATE
// ============================================================
enum MODE {
RFID_WAIT,
START_ARM_WAIT,
RUN_ACTIVE
};
static MODE mode = RFID_WAIT;
static bool timerRunning = false;
static uint32_t currentRunId = 0;
static uint32_t nextRunId = 1;
static uint32_t tStartMs = 0;
static uint32_t startStampMs = 0;
static uint32_t stateEnterMs = 0;
static uint32_t startCmdFirstSentMs = 0;
static uint32_t startCmdLastSentMs = 0;
static uint32_t lastPingSentMs = 0;
static uint32_t lastPingAckMs = 0;
static bool waitingForStartAck = false;
static bool startAckReceived = false;
static bool runLinkWarned = false;
static bool startPirArmed = false;
static uint32_t startPirLowSinceMs = 0;
static bool pirWasHigh = false;
static uint32_t pirHighStartMs = 0;
static uint32_t lastPirTriggerMs = 0;
static bool readyFlashState = false;
static uint32_t readyFlashLastToggleMs = 0;
static char activeRacerUid[UID_BUF_SZ] = { 0 };
static char activeRacerName[NAME_BUF_SZ] = { 0 };
static char lcdLine1Cache[LCD_LINE_BUF_SZ] = { 0 };
static char lcdLine2Cache[LCD_LINE_BUF_SZ] = { 0 };
// ============================================================
// FORWARD DECLARATIONS
// ============================================================
static void setupRadio();
static void setupDisplay();
static void setupPins();
static void handleRfidWait();
static void handleStartArmWait();
static void handleRunActive();
static void resetStartAckState();
static void resetStartPirState();
static void resetReadyFlashState();
static void resetRunLinkState();
static void resetActiveRacer();
static void resetForIdle();
static bool parseAckStart(const char* rx, uint32_t& runIdOut);
static bool parseAckPing(const char* rx, uint32_t& runIdOut);
static bool parseCmdStop(const char* rx, uint32_t& runIdOut);
static bool rfidScanDetected();
static void finishRfidTransaction();
static void getScannedUidString(char* out, size_t outSize);
static bool lookupRacerName(const char* uid, char* outName, size_t outNameSize);
static void sendPacket(const char* s);
static void sendCancelBurst(uint32_t runId);
static void sendRacerBurst(uint32_t runId, const char* racerName);
static void sendResultBurst(const char* name, const char* uid, uint32_t runId, uint32_t startMs, uint32_t elapsedMs);
static void sendResetBurst();
static void ledsWaitForRfid();
static void ledsReady();
static void ledsTiming();
static void ledsWaitForStartArm();
static void setLcdLines(const char* line1, const char* line2);
static void showScanPrompt();
static void showRunnerWaiting();
static void showStartArmed();
static void showRunning();
static void showCancelled();
static void showTimeout(const char* top, const char* bottom);
static void showElapsedTime(uint32_t elapsedMs);
static void formatElapsedTime(uint32_t elapsedMs, char* out, size_t outSize);
static bool detectStartMovement();
static void cancelCurrentRun(const char* reason, bool sendRadioCancel);
static void completeRunFromStop(uint32_t stopRunId);
static void printUID();
static void buildCmdStart(uint32_t runId, char* out, size_t outSize);
static void buildCmdCancel(uint32_t runId, char* out, size_t outSize);
static void buildRacerPacket(uint32_t runId, const char* racerName, char* out, size_t outSize);
static void buildTimePacket(uint32_t runId, uint32_t elapsedMs, char* out, size_t outSize);
static void buildPingPacket(uint32_t runId, char* out, size_t outSize);
static void buildResultPacket(const char* name, const char* uid, uint32_t runId, uint32_t startMs, uint32_t elapsedMs, char* out, size_t outSize);
// ============================================================
// SETUP
// ============================================================
void setup() {
Serial.begin(115200);
delay(200);
setupPins();
setupDisplay();
setupRadio();
rfid.PCD_Init();
delay(50);
rfid.PCD_DumpVersionToSerial();
// CHANGE: force Unit B back to idle whenever Unit A boots
sendResetBurst();
resetForIdle();
showScanPrompt();
}
// ============================================================
// LOOP
// ============================================================
void loop() {
switch (mode) {
case RFID_WAIT:
handleRfidWait();
break;
case START_ARM_WAIT:
handleStartArmWait();
break;
case RUN_ACTIVE:
handleRunActive();
break;
}
}
// ============================================================
// SETUP HELPERS
// ============================================================
// setupPins
// Sets up the pin modes for the PIR sensor and the three LEDs.
// Inputs: none
// Outputs: none
static void setupPins() {
pinMode(SENSOR_P, INPUT);
pinMode(LED_YELLOW_P, OUTPUT);
pinMode(LED_GREEN_P, OUTPUT);
pinMode(LED_RED_P, OUTPUT);
}
// setupDisplay
// Initializes the LCD display so it can show messages.
// Inputs: none
// Outputs: none
static void setupDisplay() {
Wire.begin(LCD_SDA_P, LCD_SCL_P);
lcd.init();
lcd.backlight();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Booting...");
}
// setupRadio
// Sets up the LoRa radio for wireless communication. If it fails, shows an error on the LCD and stops the program.
// Inputs: none
// Outputs: none
static void setupRadio() {
SPI.begin(SCK_P, MISO_P, MOSI_P);
int state = radio.begin(LORA_FREQ_MHZ);
if (state != RADIOLIB_ERR_NONE) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("BOOT FAIL:");
lcd.setCursor(0, 1);
lcd.print(state);
DBG_PRINT("LoRa boot failed: ");
DBG_PRINTLN(state);
while (true) {
delay(1000);
}
}
radio.setSpreadingFactor(LORA_SF);
radio.setBandwidth(LORA_BW_KHZ);
radio.setCodingRate(LORA_CR);
radio.setOutputPower(LORA_TX_DBM);
radio.startReceive();
}
// ============================================================
// STATE HANDLERS
// ============================================================
// handleRfidWait
// This function runs when the system is waiting for someone to scan their RFID card.
// It checks for a scan, looks up the racer's name, and prepares for the next step.
// Inputs: none
// Outputs: none
static void handleRfidWait() {
ledsWaitForRfid();
if (!rfidScanDetected()) return;
char scannedUid[UID_BUF_SZ];
char matchedName[NAME_BUF_SZ];
getScannedUidString(scannedUid, sizeof(scannedUid));
bool found = lookupRacerName(scannedUid, matchedName, sizeof(matchedName));
finishRfidTransaction();
if (!found) {
DBG_PRINT("RFID not found in database: ");
DBG_PRINTLN(scannedUid);
setLcdLines("Card Not Found", "Scan Again");
delay(1500);
showScanPrompt();
return;
}
// CHANGE: new scan means old state anywhere should be cleared on Unit B
sendResetBurst();
resetForIdle();
strncpy(activeRacerUid, scannedUid, sizeof(activeRacerUid) - 1);
activeRacerUid[sizeof(activeRacerUid) - 1] = '\0';
strncpy(activeRacerName, matchedName, sizeof(activeRacerName) - 1);
activeRacerName[sizeof(activeRacerName) - 1] = '\0';
currentRunId = nextRunId++;
DBG_PRINT("RFID UID: ");
DBG_PRINTLN(activeRacerUid);
DBG_PRINT("Matched Racer: ");
DBG_PRINTLN(activeRacerName);
DBG_PRINT("Prepared run ID ");
DBG_PRINTLN(currentRunId);
mode = START_ARM_WAIT;
stateEnterMs = millis();
showRunnerWaiting();
}
static void handleStartArmWait() {
if (rfidScanDetected()) {
finishRfidTransaction();
cancelCurrentRun("RFID re-scan before start", true);
return;
}
if (!startPirArmed) {
ledsWaitForStartArm();
}
else {
ledsReady();
}
if ((millis() - stateEnterMs) >= START_WAIT_TIMEOUT_MS) {
DBG_PRINTLN("START TIMEOUT -> returning to RFID_WAIT");
sendResetBurst();
resetForIdle();
showTimeout("Start Timeout", "Scan RFID Again");
delay(1000);
showScanPrompt();
return;
}
if (!detectStartMovement()) return;
uint32_t now = millis();
timerRunning = true;
tStartMs = now;
startStampMs = now;
waitingForStartAck = true;
startAckReceived = false;
startCmdFirstSentMs = now;
startCmdLastSentMs = now;
char pkt[PACKET_BUF_SZ];
buildCmdStart(currentRunId, pkt, sizeof(pkt));
DBG_PRINT("Start PIR triggered -> sending ");
DBG_PRINTLN(pkt);
sendPacket(pkt);
ledsTiming();
setLcdLines("Timer Started", "Wait ACK...");
mode = RUN_ACTIVE;
stateEnterMs = millis();
}
static void handleRunActive() {
if (rfidScanDetected()) {
finishRfidTransaction();
cancelCurrentRun("RFID re-scan during active run", true);
return;
}
String rxStr;
int r = radio.readData(rxStr);
if (r == RADIOLIB_ERR_NONE && rxStr.length() > 0) {
const char* rx = rxStr.c_str();
uint32_t rxRunId = 0;
if (parseAckStart(rx, rxRunId)) {
if (rxRunId == currentRunId) {
waitingForStartAck = false;
startAckReceived = true;
lastPingSentMs = millis();
lastPingAckMs = millis();
runLinkWarned = false;
DBG_PRINT("ACK START received for run ");
DBG_PRINTLN(rxRunId);
DBG_PRINT("Sending racer name to Unit B: ");
DBG_PRINTLN(activeRacerName);
sendRacerBurst(currentRunId, activeRacerName);
showRunning();
}
}
else if (parseAckPing(rx, rxRunId)) {
if (rxRunId == currentRunId) {
lastPingAckMs = millis();
if (runLinkWarned) {
DBG_PRINTLN("RUN LINK RESTORED");
runLinkWarned = false;
}
DBG_PRINT("ACK PING received for run ");
DBG_PRINTLN(rxRunId);
}
}
else if (parseCmdStop(rx, rxRunId)) {
if (timerRunning) {
completeRunFromStop(rxRunId);
return;
}
else {
DBG_PRINTLN("Got STOP but timer not running.");
}
}
else {
DBG_PRINT("RX: ");
DBG_PRINTLN(rx);
}
}
if (waitingForStartAck) {
uint32_t now = millis();
if ((now - startCmdLastSentMs) >= START_ACK_RESEND_MS) {
char pkt[PACKET_BUF_SZ];
buildCmdStart(currentRunId, pkt, sizeof(pkt));
DBG_PRINT("No ACK yet -> re-sending ");
DBG_PRINTLN(pkt);
sendPacket(pkt);
startCmdLastSentMs = now;
}
if ((now - startCmdFirstSentMs) >= START_ACK_TIMEOUT_MS) {
DBG_PRINTLN("START ACK TIMEOUT -> cancelling run");
cancelCurrentRun("ACK timeout", true);
return;
}
}
if (timerRunning && !waitingForStartAck) {
uint32_t now = millis();
if ((now - lastPingSentMs) >= RUN_PING_MS) {
char pkt[PACKET_BUF_SZ];
buildPingPacket(currentRunId, pkt, sizeof(pkt));
DBG_PRINT("Sending ");
DBG_PRINTLN(pkt);
sendPacket(pkt);
lastPingSentMs = now;
}
if ((now - lastPingAckMs) >= RUN_LINK_WARN_MS && !runLinkWarned) {
DBG_PRINTLN("WARNING: no ACK PING recently, but race continues");
runLinkWarned = true;
}
}
if ((millis() - stateEnterMs) >= RUN_TIMEOUT_MS) {
DBG_PRINTLN("RUN TIMEOUT -> returning to RFID_WAIT");
sendResetBurst();
resetForIdle();
showTimeout("Run Timeout", "Resetting...");
delay(1000);
showScanPrompt();
}
}
// ============================================================
// RESET HELPERS
// ============================================================
static void resetStartAckState() {
waitingForStartAck = false;
startAckReceived = false;
startCmdFirstSentMs = 0;
startCmdLastSentMs = 0;
}
static void resetStartPirState() {
startPirArmed = false;
startPirLowSinceMs = 0;
pirWasHigh = false;
pirHighStartMs = 0;
lastPirTriggerMs = 0;
}
static void resetReadyFlashState() {
readyFlashState = false;
readyFlashLastToggleMs = millis();
}
static void resetRunLinkState() {
lastPingSentMs = 0;
lastPingAckMs = 0;
runLinkWarned = false;
}
static void resetActiveRacer() {
activeRacerUid[0] = '\0';
activeRacerName[0] = '\0';
}
static void resetForIdle() {
timerRunning = false;
tStartMs = 0;
startStampMs = 0;
currentRunId = 0;
mode = RFID_WAIT;
stateEnterMs = millis();
resetStartAckState();
resetStartPirState();
resetReadyFlashState();
resetRunLinkState();
resetActiveRacer();
}
// ============================================================
// PACKET HELPERS
// ============================================================
static void buildCmdStart(uint32_t runId, char* out, size_t outSize) {
snprintf(out, outSize, "CMD START %lu", (unsigned long)runId);
}
static void buildCmdCancel(uint32_t runId, char* out, size_t outSize) {
snprintf(out, outSize, "CMD CANCEL %lu", (unsigned long)runId);
}
static void buildRacerPacket(uint32_t runId, const char* racerName, char* out, size_t outSize) {
snprintf(out, outSize, "RACER %lu %s", (unsigned long)runId, racerName);
}
static void buildTimePacket(uint32_t runId, uint32_t elapsedMs, char* out, size_t outSize) {
snprintf(out, outSize, "TIME %lu %lu", (unsigned long)runId, (unsigned long)elapsedMs);
}
static void buildPingPacket(uint32_t runId, char* out, size_t outSize) {
snprintf(out, outSize, "PING %lu", (unsigned long)runId);
}
static void buildResultPacket(const char* name, const char* uid, uint32_t runId, uint32_t startMs, uint32_t elapsedMs, char* out, size_t outSize) {
char timeBuf[TIME_BUF_SZ];
formatElapsedTime(elapsedMs, timeBuf, sizeof(timeBuf));
snprintf(out, outSize,
"RESULT|%s|%s|%lu|%lu|%lu|%s",
name,
uid,
(unsigned long)runId,
(unsigned long)startMs,
(unsigned long)elapsedMs,
timeBuf);
}
// ============================================================
// PARSER HELPERS
// ============================================================
static bool parseAckStart(const char* rx, uint32_t& runIdOut) {
if (strncmp(rx, "ACK START ", 10) != 0) return false;
runIdOut = strtoul(rx + 10, nullptr, 10);
return true;
}
static bool parseAckPing(const char* rx, uint32_t& runIdOut) {
if (strncmp(rx, "ACK PING ", 9) != 0) return false;
runIdOut = strtoul(rx + 9, nullptr, 10);
return true;
}
static bool parseCmdStop(const char* rx, uint32_t& runIdOut) {
if (strncmp(rx, "CMD STOP ", 9) != 0) return false;
runIdOut = strtoul(rx + 9, nullptr, 10);
return true;
}
// ============================================================
// RFID HELPERS
// ============================================================
static bool rfidScanDetected() {
if (rfid.PICC_IsNewCardPresent() && rfid.PICC_ReadCardSerial()) {
printUID();
return true;
}
return false;
}
static void finishRfidTransaction() {
rfid.PICC_HaltA();
rfid.PCD_StopCrypto1();
}
static void getScannedUidString(char* out, size_t outSize) {
out[0] = '\0';
size_t pos = 0;
for (byte i = 0; i < rfid.uid.size && pos + 3 < outSize; i++) {
int written = snprintf(out + pos, outSize - pos,
(i < rfid.uid.size - 1) ? "%02X:" : "%02X",
rfid.uid.uidByte[i]);
if (written < 0) break;
pos += (size_t)written;
}
}
static bool lookupRacerName(const char* uid, char* outName, size_t outNameSize) {
for (int i = 0; i < RACER_COUNT; i++) {
if (strcasecmp(library[i].uid, uid) == 0) {
strncpy(outName, library[i].name, outNameSize - 1);
outName[outNameSize - 1] = '\0';
return true;
}
}
outName[0] = '\0';
return false;
}
// ============================================================
// RADIO HELPERS
// ============================================================
static void sendPacket(const char* s) {
int txState = radio.transmit(s);
if (txState != RADIOLIB_ERR_NONE) {
DBG_PRINT("TX FAIL: ");
DBG_PRINTLN(txState);
}
else {
DBG_PRINT("TX: ");
DBG_PRINTLN(s);
}
delay(30);
radio.startReceive();
}
static void sendCancelBurst(uint32_t runId) {
if (runId == 0) return;
char pkt[PACKET_BUF_SZ];
buildCmdCancel(runId, pkt, sizeof(pkt));
sendPacket(pkt);
delay(120);
sendPacket(pkt);
}
static void sendRacerBurst(uint32_t runId, const char* racerName) {
if (runId == 0 || racerName[0] == '\0') return;
char pkt[PACKET_BUF_SZ];
buildRacerPacket(runId, racerName, pkt, sizeof(pkt));
sendPacket(pkt);
delay(120);
sendPacket(pkt);
}
static void sendResultBurst(const char* name, const char* uid, uint32_t runId, uint32_t startMs, uint32_t elapsedMs) {
char pkt[PACKET_BUF_SZ];
buildResultPacket(name, uid, runId, startMs, elapsedMs, pkt, sizeof(pkt));
sendPacket(pkt);
delay(120);
sendPacket(pkt);
}
// CHANGE: reset/sync packet for Unit B
static void sendResetBurst() {
sendPacket("CMD RESET");
delay(120);
sendPacket("CMD RESET");
}
// ============================================================
// UI HELPERS
// ============================================================
static void ledsWaitForRfid() {
digitalWrite(LED_YELLOW_P, HIGH);
digitalWrite(LED_GREEN_P, LOW);
digitalWrite(LED_RED_P, LOW);
}
static void ledsReady() {
digitalWrite(LED_YELLOW_P, LOW);
digitalWrite(LED_GREEN_P, HIGH);
digitalWrite(LED_RED_P, LOW);
}
static void ledsTiming() {
digitalWrite(LED_YELLOW_P, LOW);
digitalWrite(LED_GREEN_P, LOW);
digitalWrite(LED_RED_P, HIGH);
}
static void ledsWaitForStartArm() {
uint32_t now = millis();
if ((now - readyFlashLastToggleMs) >= READY_FLASH_MS) {
readyFlashLastToggleMs = now;
readyFlashState = !readyFlashState;
}
digitalWrite(LED_YELLOW_P, readyFlashState ? HIGH : LOW);
digitalWrite(LED_GREEN_P, readyFlashState ? HIGH : LOW);
digitalWrite(LED_RED_P, LOW);
}
static void setLcdLines(const char* line1, const char* line2) {
char l1[LCD_LINE_BUF_SZ];
char l2[LCD_LINE_BUF_SZ];
snprintf(l1, sizeof(l1), "%-16.16s", line1 ? line1 : "");
snprintf(l2, sizeof(l2), "%-16.16s", line2 ? line2 : "");
if (strncmp(l1, lcdLine1Cache, 16) == 0 && strncmp(l2, lcdLine2Cache, 16) == 0) {
return;
}
strncpy(lcdLine1Cache, l1, sizeof(lcdLine1Cache));
lcdLine1Cache[sizeof(lcdLine1Cache) - 1] = '\0';
strncpy(lcdLine2Cache, l2, sizeof(lcdLine2Cache));
lcdLine2Cache[sizeof(lcdLine2Cache) - 1] = '\0';
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(l1);
lcd.setCursor(0, 1);
lcd.print(l2);
}
static void showScanPrompt() {
setLcdLines("Start Unit Ready", "Scan RFID");
}
static void showRunnerWaiting() {
setLcdLines(activeRacerName, "Wait PIR LOW...");
}
static void showStartArmed() {
setLcdLines(activeRacerName, "Cross to begin");
}
static void showRunning() {
setLcdLines(activeRacerName, "Running...");
}
static void showCancelled() {
setLcdLines("Run Cancelled", "Scan RFID");
}
static void showTimeout(const char* top, const char* bottom) {
setLcdLines(top, bottom);
}
static void showElapsedTime(uint32_t elapsedMs) {
char timeBuf[TIME_BUF_SZ];
formatElapsedTime(elapsedMs, timeBuf, sizeof(timeBuf));
setLcdLines(activeRacerName, timeBuf);
}
static void formatElapsedTime(uint32_t elapsedMs, char* out, size_t outSize) {
uint32_t ms = elapsedMs % 1000;
uint32_t totalSeconds = elapsedMs / 1000;
uint32_t seconds = totalSeconds % 60;
uint32_t totalMinutes = totalSeconds / 60;
uint32_t minutes = totalMinutes % 60;
uint32_t hours = totalMinutes / 60;
if (hours > 0) {
snprintf(out, outSize, "%02lu:%02lu:%02lu:%03lu",
(unsigned long)hours,
(unsigned long)minutes,
(unsigned long)seconds,
(unsigned long)ms);
}
else {
snprintf(out, outSize, "%02lu:%02lu:%03lu",
(unsigned long)minutes,
(unsigned long)seconds,
(unsigned long)ms);
}
}
// ============================================================
// RUN HELPERS
// ============================================================
static bool detectStartMovement() {
uint32_t now = millis();
bool pir = digitalRead(SENSOR_P);
if (!startPirArmed) {
if (!pir) {
if (startPirLowSinceMs == 0) startPirLowSinceMs = now;
if ((now - startPirLowSinceMs) >= START_LOW_VERIFY_MS) {
startPirArmed = true;
pirWasHigh = false;
pirHighStartMs = 0;
lastPirTriggerMs = 0;
DBG_PRINTLN("Start PIR verified LOW -> sensor armed");
showStartArmed();
}
}
else {
startPirLowSinceMs = 0;
}
return false;
}
if ((now - lastPirTriggerMs) < PIR_REARM_MS) return false;
if (pir && !pirWasHigh) {
pirWasHigh = true;
pirHighStartMs = now;
}
if (!pir) {
pirWasHigh = false;
return false;
}
if (pirWasHigh && (now - pirHighStartMs) >= PIR_STABLE_MS) {
lastPirTriggerMs = now;
pirWasHigh = false;
return true;
}
return false;
}
static void cancelCurrentRun(const char* reason, bool sendRadioCancel) {
DBG_PRINT("CANCEL: ");
DBG_PRINTLN(reason);
if (sendRadioCancel && currentRunId != 0) {
char pkt[PACKET_BUF_SZ];
buildCmdCancel(currentRunId, pkt, sizeof(pkt));
DBG_PRINT("Sending ");
DBG_PRINTLN(pkt);
sendCancelBurst(currentRunId);
}
// CHANGE: always hard-reset Unit B too
sendResetBurst();
resetForIdle();
ledsWaitForRfid();
showCancelled();
delay(1000);
showScanPrompt();
}
static void completeRunFromStop(uint32_t stopRunId) {
uint32_t tStopMs = millis();
uint32_t elapsed = tStopMs - tStartMs;
timerRunning = false;
if (stopRunId != currentRunId) {
DBG_PRINT("WARNING: STOP run ID ");
DBG_PRINT(stopRunId);
DBG_PRINT(" did not match current run ID ");
DBG_PRINT(currentRunId);
DBG_PRINTLN(" -> accepting STOP anyway");
}
char formattedTime[TIME_BUF_SZ];
formatElapsedTime(elapsed, formattedTime, sizeof(formattedTime));
DBG_PRINT("TIMER STOP (A) Run ");
DBG_PRINT(currentRunId);
DBG_PRINT(" Racer ");
DBG_PRINT(activeRacerName);
DBG_PRINT(" StartMs ");
DBG_PRINT(startStampMs);
DBG_PRINT(" Elapsed ");
DBG_PRINTLN(formattedTime);
char resultLine[PACKET_BUF_SZ];
buildResultPacket(activeRacerName, activeRacerUid, currentRunId, startStampMs, elapsed, resultLine, sizeof(resultLine));
DBG_PRINTLN(resultLine);
sendResultBurst(activeRacerName, activeRacerUid, currentRunId, startStampMs, elapsed);
showElapsedTime(elapsed);
char timePkt[PACKET_BUF_SZ];
buildTimePacket(currentRunId, elapsed, timePkt, sizeof(timePkt));
sendPacket(timePkt);
resetForIdle();
}
// ============================================================
// DEBUG
// ============================================================
static void printUID() {
DBG_PRINT("UID: ");
for (byte i = 0; i < rfid.uid.size; i++) {
if (rfid.uid.uidByte[i] < 0x10) {
DBG_PRINT('0');
}
DBG_PRINT(rfid.uid.uidByte[i], HEX);
if (i < rfid.uid.size - 1) {
DBG_PRINT(':');
}
}
DBG_PRINTLN("");
}Editor is loading...
Leave a Comment