updated card reader to multiple cards added on nfc

This commit is contained in:
ske087
2026-06-13 10:31:40 +03:00
parent e5fd3645d1
commit 494f91e001
2 changed files with 486 additions and 173 deletions
@@ -1,14 +1,14 @@
/**
* ESP32-C6 Home Assistant Integration
* Arduino IDE Project
*
* v18
* Board: ESP32C6 Dev Module
* Flash Size: 4MB
* USB CDC On Boot: Enabled (REQUIRED for serial output!)
*
* Provides REST API for Home Assistant integration
*/
// version 2.2.1 Persistent NFC config via NVS
// version 2.3.0 C5-parity: secrets.h WiFi/IP, relay toggle, NFC probe, improved NFC polling, WiFi LED
#include <WiFi.h>
#include <WebServer.h>
#include <WiFiClient.h>
@@ -42,18 +42,17 @@ bool nfc_initialized = false;
String nfc_last_uid = "";
unsigned long nfc_last_poll_ms = 0;
int nfc_miss_count = 0; // consecutive polls with no card detected
unsigned long nfc_absent_since = 0; // millis() when card first went absent (GRANTED→absent)
// NFC Access Control
char nfc_auth_uid[32] = ""; // authorized card UID; empty = any card triggers
#define NFC_MAX_CARDS 10 // maximum authorized cards
char nfc_auth_uids[NFC_MAX_CARDS][32] = {}; // authorized UIDs (empty string = unused slot)
int nfc_auth_count = 0; // number of active cards
int nfc_relay_num = 1; // relay to open on match (1-4)
unsigned long nfc_pulse_ms = 5000; // absence timeout: relay closes after this many ms of no card
unsigned long nfc_pulse_ms = 0; // relay release delay after card removed (ms); 0 = off after ~1 s debounce
char nfc_access_state[8] = "idle"; // "idle" | "granted" | "denied"
bool nfc_enabled = false; // access control module on/off; off by default
// ────────────────────────────────────────────────────────────────────────────
// WiFi credentials
const char* ssid = "BUON GUSTO PARTER";
const char* password = "arleta13";
// Web server on port 80
WebServer server(80);
@@ -102,6 +101,7 @@ void handleStatus();
void handleRelayOn();
void handleRelayOff();
void handleRelayStatus();
void handleRelayToggle();
void handleInputStatus();
void handleRegister();
void handleLEDOn();
@@ -111,6 +111,7 @@ void handleNFCStatus();
void handleNFCConfigGet();
void handleNFCConfigSet();
void handleNFCEnable();
void handleNFCProbe();
void handleDebug();
void postNFCEvent(const String& uid);
void postInputEvent(int input_num, bool state);
@@ -123,14 +124,20 @@ void scanWiFiNetworks();
// ─────────────────────────────────────────────────────────────────────────────
void setup() {
// Initialize USB CDC serial
// ── USB CDC serial init ───────────────────────────────────────────────────
// IMPORTANT: In Arduino IDE set Tools → USB CDC On Boot → Enabled
// Without that, Serial maps to UART and you'll see nothing on the USB port.
Serial.begin(115200);
delay(2000); // Give time for USB CDC to initialize
// Wait for serial port to be ready (up to 5 seconds)
for (int i = 0; i < 10 && !Serial; i++) {
delay(500);
// After a flash-and-reset cycle the USB CDC port re-enumerates. We wait up
// to 5 s for the host to open the port, then continue regardless so the
// board doesn't hang when running headless (no monitor attached).
{
unsigned long _t = millis();
while (!Serial && millis() - _t < 5000) { delay(100); }
}
// Brief additional settle time for the CDC driver on the host side
delay(200);
Serial.println("\n\n=================================");
Serial.println("ESP32-C6 Home Assistant Device");
@@ -149,27 +156,34 @@ void setup() {
pinMode(DIN2_PIN, INPUT_PULLUP);
pinMode(DIN3_PIN, INPUT_PULLUP);
pinMode(DIN4_PIN, INPUT_PULLUP);
// Set all outputs to LOW
digitalWrite(LED_PIN, LOW);
// Set all outputs to initial safe state
digitalWrite(LED_PIN, HIGH); // HIGH = LED OFF (active LOW) — will turn ON when WiFi connects
digitalWrite(RELAY_1_PIN, LOW);
digitalWrite(RELAY_2_PIN, LOW);
digitalWrite(RELAY_3_PIN, LOW);
digitalWrite(RELAY_4_PIN, LOW);
Serial.println("GPIO initialized");
Serial.println("GPIO initialized - LED OFF (will turn ON when WiFi connects)");
// ── Load persistent NFC config from NVS ──────────────────────────────────
prefs.begin("nfc_cfg", true); // read-only namespace
String saved_uid = prefs.getString("auth_uid", "");
if (saved_uid.length() > 0 && saved_uid.length() < sizeof(nfc_auth_uid)) {
saved_uid.toCharArray(nfc_auth_uid, sizeof(nfc_auth_uid));
}
nfc_relay_num = prefs.getInt("relay_num", 1);
nfc_pulse_ms = prefs.getULong("pulse_ms", 5000);
nfc_pulse_ms = prefs.getULong("pulse_ms", 0);
nfc_enabled = prefs.getBool("enabled", false);
nfc_auth_count = 0;
for (int i = 0; i < NFC_MAX_CARDS; i++) {
String key = "uid" + String(i);
String saved = prefs.getString(key.c_str(), "");
if (saved.length() > 0 && saved.length() < 32) {
saved.toCharArray(nfc_auth_uids[i], 32);
nfc_auth_count++;
} else {
nfc_auth_uids[i][0] = '\0';
}
}
prefs.end();
Serial.printf("NFC config loaded: auth='%s' relay=%d pulse=%lu ms\n",
nfc_auth_uid, nfc_relay_num, nfc_pulse_ms);
Serial.printf("NFC config loaded: %d authorized card(s), relay=%d, pulse=%lu ms\n",
nfc_auth_count, nfc_relay_num, nfc_pulse_ms);
// ─────────────────────────────────────────────────────────────────────────
// ── Load persistent HA registration from NVS ─────────────────────────────
@@ -197,16 +211,36 @@ void setup() {
WiFi.mode(WIFI_STA);
WiFi.setAutoReconnect(true);
// Static IP
IPAddress staticIP(192, 168, 0, 240);
IPAddress gateway(192, 168, 0, 1);
IPAddress subnet(255, 255, 255, 0);
WiFi.config(staticIP, gateway, subnet);
// Parse static IP configuration from secrets.h
// Note: WiFi.config() must be called right before WiFi.begin() on ESP32-C6;
// calling it before disconnect(true)/mode() causes it to be silently cleared.
IPAddress _staticIP, _gateway, _subnet, _dns1, _dns2;
bool _have_static = false;
if (USE_STATIC_IP) {
if (_staticIP.fromString(STATIC_IP_ADDR) &&
_gateway.fromString(STATIC_GATEWAY) &&
_subnet.fromString(STATIC_SUBNET) &&
_dns1.fromString(STATIC_DNS1) &&
_dns2.fromString(STATIC_DNS2)) {
_have_static = true;
Serial.printf("Static IP will be applied: %s\n", STATIC_IP_ADDR);
} else {
Serial.println("⚠ Invalid static IP configuration — falling back to DHCP");
}
} else {
Serial.println("Using DHCP for IP configuration");
}
bool wifi_ok = false;
for (int pass = 1; pass <= 3 && !wifi_ok; pass++) {
Serial.printf("Connecting to WiFi: %s (attempt %d/3)\n", ssid, pass);
WiFi.begin(ssid, password);
Serial.printf("Connecting to WiFi: %s (attempt %d/3)\n", WIFI_SSID, pass);
// Apply static IP immediately before begin() so it is never cleared by a
// preceding disconnect(true) call.
if (_have_static) {
WiFi.config(_staticIP, _gateway, _subnet, _dns1, _dns2);
Serial.printf("Static IP configured: %s\n", STATIC_IP_ADDR);
}
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
for (int t = 0; t < 40 && WiFi.status() != WL_CONNECTED; t++) {
delay(500);
Serial.print(".");
@@ -218,20 +252,23 @@ void setup() {
WiFi.disconnect(true);
delay(1000);
WiFi.mode(WIFI_STA);
WiFi.config(staticIP, gateway, subnet);
}
}
if (wifi_ok) {
Serial.println("\n\u2713 WiFi connected!");
Serial.println("\n WiFi connected!");
Serial.print(" IP : "); Serial.println(WiFi.localIP());
Serial.print(" RSSI : "); Serial.print(WiFi.RSSI()); Serial.println(" dBm");
Serial.print(" MAC : "); Serial.println(WiFi.macAddress());
digitalWrite(LED_PIN, LOW); // LED steady ON = WiFi connected (active LOW)
led_state = true;
} else {
Serial.println("\n\u2717 WiFi connection failed after 3 attempts.");
Serial.println("\n WiFi connection failed after 3 attempts.");
Serial.print(" Status code: "); Serial.println(WiFi.status());
Serial.println(" Check: correct SSID/password, 2.4 GHz band, board in range.");
Serial.println(" HTTP server will start anyway \u2014 accessible once WiFi reconnects.");
Serial.println(" Check: correct WIFI_SSID/WIFI_PASSWORD, 2.4 GHz band, board in range.");
Serial.println(" HTTP server will start anyway accessible once WiFi reconnects.");
digitalWrite(LED_PIN, HIGH); // LED OFF = no WiFi
led_state = false;
}
// ── NTP time sync ────────────────────────────────────────────────────────────
@@ -262,6 +299,7 @@ void setup() {
// Relay endpoints
server.on("/relay/on", HTTP_POST, handleRelayOn);
server.on("/relay/off", HTTP_POST, handleRelayOff);
server.on("/relay/toggle", HTTP_POST, handleRelayToggle);
server.on("/relay/status", HTTP_GET, handleRelayStatus);
// Input endpoints
server.on("/input/status", HTTP_GET, handleInputStatus);
@@ -275,6 +313,7 @@ void setup() {
server.on("/nfc/config", HTTP_GET, handleNFCConfigGet);
server.on("/nfc/config", HTTP_POST, handleNFCConfigSet);
server.on("/nfc/enable", HTTP_POST, handleNFCEnable);
server.on("/nfc/probe", HTTP_POST, handleNFCProbe);
server.on("/debug", HTTP_GET, handleDebug);
// ── Collect HMAC auth headers for verifyAPIRequest() ─────────────────────────
@@ -293,57 +332,60 @@ void setup() {
// ── NFC (PN532 HSU) initialisation — multi-baud auto-detect ─────────────
// The PN532 default HSU baud is 115200, but some modules ship at 9600.
// We try both, plus RX/TX swapped, so the board will find the module
// regardless of those two variables.
// Only the correct pin pair (NFC_RX_PIN / NFC_TX_PIN) is tried — the board
// has a fixed UEXT1 wiring so pin-swap probing is unnecessary and causes
// hangs when no module is connected (the PN532 library uses its own blocking
// read loop that does not respect HardwareSerial::setTimeout()).
Serial.println("--- NFC (PN532 HSU) Initialization ---");
// Baud rates to try, in order
const long NFC_BAUDS[] = {115200, 9600, 57600, 38400};
const int NFC_NBAUDS = 4;
// Pin pairs to try: {RX, TX}. Second pair = swapped.
const int NFC_PINS[2][2] = {{NFC_RX_PIN, NFC_TX_PIN},
{NFC_TX_PIN, NFC_RX_PIN}};
uint32_t versiondata = 0;
long found_baud = 0;
int found_rx = NFC_RX_PIN;
int found_tx = NFC_TX_PIN;
for (int pi = 0; pi < 2 && !versiondata; pi++) {
int rx = NFC_PINS[pi][0];
int tx = NFC_PINS[pi][1];
for (int bi = 0; bi < NFC_NBAUDS && !versiondata; bi++) {
long baud = NFC_BAUDS[bi];
Serial.printf(" Trying baud=%-7ld RX=GPIO%d TX=GPIO%d ...\n", baud, rx, tx);
nfcSerial.begin(baud, SERIAL_8N1, rx, tx);
// Serve HTTP and feed watchdog during probe delay instead of blocking
{ unsigned long _t = millis(); while (millis() - _t < 500) { server.handleClient(); yield(); } }
nfc.begin(); // PN532_HSU::begin() is a no-op — pins already set
Serial.printf(" Trying baud=%-7ld RX=GPIO%d TX=GPIO%d ...\n", baud, NFC_RX_PIN, NFC_TX_PIN);
nfcSerial.begin(baud, SERIAL_8N1, NFC_RX_PIN, NFC_TX_PIN);
nfcSerial.setTimeout(800);
// Drain any garbage from a floating / unconnected RX line
while (nfcSerial.available()) nfcSerial.read();
// Let the bus settle; keep HTTP alive during wait
{ unsigned long _t = millis(); while (millis() - _t < 300) { server.handleClient(); yield(); } }
nfc.begin();
versiondata = nfc.getFirmwareVersion();
if (!versiondata) {
unsigned long _t = millis(); while (millis() - _t < 200) { server.handleClient(); yield(); }
} else { found_baud = baud; found_rx = rx; found_tx = tx; }
while (nfcSerial.available()) nfcSerial.read();
unsigned long _t = millis(); while (millis() - _t < 100) { server.handleClient(); yield(); }
} else {
found_baud = baud;
}
}
if (!versiondata) {
Serial.println("\u2717 PN532 not detected with any baud/pin combination.");
Serial.println(" NFC capabilities are NOT available \u2014 all other features remain active.");
Serial.println(" (Connect a PN532 via UEXT1 and reboot to enable NFC.)");
Serial.println(" Hardware checklist:");
Serial.println(" 1. DIP/solder-jumpers on PN532 board: BOTH = 0 (HSU mode)");
Serial.println(" Some boards label them SEL0/SEL1 or I0/I1 — both must be LOW.");
Serial.println(" 2. Power: UEXT1 pin 1 = 3V3, pin 2 = GND. Measure with multimeter.");
Serial.println(" 3. Wiring: UEXT1 pin 3 (GPIO4) PN532 RXD");
Serial.println(" UEXT1 pin 4 (GPIO5) PN532 TXD");
Serial.println(" 3. Wiring: UEXT1 pin 3 (GPIO4) \u2194 PN532 RXD");
Serial.println(" UEXT1 pin 4 (GPIO5) \u2194 PN532 TXD");
Serial.println(" 4. Some PN532 breakouts need a 100 ohm series resistor on TX line.");
nfcSerial.end(); // release UART — don't hold pins in an indeterminate state
nfc_initialized = false;
} else {
Serial.printf("\u2713 PN532 found! baud=%ld RX=GPIO%d TX=GPIO%d\n",
found_baud, found_rx, found_tx);
found_baud, NFC_RX_PIN, NFC_TX_PIN);
Serial.print(" Chip: PN5");
Serial.println((versiondata >> 24) & 0xFF, HEX);
Serial.printf(" Firmware: %d.%d\n",
(versiondata >> 16) & 0xFF, (versiondata >> 8) & 0xFF);
// Re-init serial with confirmed settings
nfcSerial.begin(found_baud, SERIAL_8N1, found_rx, found_tx);
nfcSerial.begin(found_baud, SERIAL_8N1, NFC_RX_PIN, NFC_TX_PIN);
nfc.SAMConfig();
nfc_initialized = true;
Serial.println("\u2713 NFC ready \u2014 waiting for ISO14443A / Mifare cards");
@@ -366,23 +408,64 @@ void loop() {
checkInputChanges();
}
// ── NFC: two-phase polling ────────────────────────────────────────────────
// IDLE : fast poll every NFC_POLL_MS (500 ms), 50 ms RF timeout
// GRANTED/DENIED: slow presence-check every nfc_pulse_ms, 500 ms RF timeout
// 2 consecutive misses required to confirm card is gone
// ── WiFi LED status: steady ON = connected, OFF = disconnected ───────────
static bool last_wifi_state = false;
bool wifi_now = (WiFi.status() == WL_CONNECTED);
if (wifi_now != last_wifi_state) {
last_wifi_state = wifi_now;
if (wifi_now) {
digitalWrite(LED_PIN, LOW); // LED ON — WiFi reconnected (active LOW)
led_state = true;
Serial.printf("\u2713 WiFi reconnected: %s\n", WiFi.localIP().toString().c_str());
} else {
digitalWrite(LED_PIN, HIGH); // LED OFF — WiFi lost
led_state = false;
Serial.println("\u26a0 WiFi connection lost");
}
}
// ─────────────────────────────────────────────────────────────────────────
// ── NFC polling ──────────────────────────────────────────────────────────
// State machine with a "card present" flag:
//
// IDLE → poll every 1 s with 100 ms RF timeout (quick new-card scan)
// card found + authorized → relay ON, state = GRANTED
// card found + denied → state = DENIED
//
// GRANTED → poll every 1 s with 500 ms RF timeout (card needs time to
// power back up after inRelease on the previous poll)
// card still present → reset absence timer, keep relay ON
// card absent → start absence timer
// absent > limit → relay OFF, state = IDLE
// limit = nfc_pulse_ms if > 0, otherwise default 5 000 ms
//
// DENIED → poll every 1 s; card gone → state = IDLE
{
bool is_active_state = (strcmp(nfc_access_state, "granted") == 0 ||
strcmp(nfc_access_state, "denied") == 0);
unsigned long nfc_interval = is_active_state ? nfc_pulse_ms : (unsigned long)NFC_POLL_MS;
if (nfc_enabled && nfc_initialized && millis() - nfc_last_poll_ms >= nfc_interval) {
const unsigned long NFC_POLL_INTERVAL = 1000UL;
bool is_granted = (strcmp(nfc_access_state, "granted") == 0);
bool is_denied = (strcmp(nfc_access_state, "denied") == 0);
// Absent limit: configurable via nfc_pulse_ms (web UI), default 5 s
unsigned long absent_limit = (nfc_pulse_ms > 0) ? nfc_pulse_ms : 5000UL;
if (nfc_enabled && nfc_initialized && millis() - nfc_last_poll_ms >= NFC_POLL_INTERVAL) {
nfc_last_poll_ms = millis();
uint16_t rf_timeout = is_active_state ? 500 : 50;
// GRANTED: use a longer RF timeout — after inRelease the card returns to
// its IDLE state and needs 300-500 ms to be powered up and respond.
// Using 100 ms here is why presence checks were failing every poll.
uint16_t rf_timeout = is_granted ? 500 : 100;
uint8_t uid[7] = {0};
uint8_t uidLen = 0;
while (nfcSerial.available()) nfcSerial.read(); // flush stale UART bytes
bool found = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLen, rf_timeout);
if (found && uidLen > 0) {
nfc.inRelease(1); // deselect card returns to ISO14443A IDLE state for next poll
nfc_miss_count = 0;
nfc.inRelease(1); // deselect card returns to ISO14443A IDLE for next poll
// Build UID string
String uid_str = "";
for (uint8_t i = 0; i < uidLen; i++) {
if (uid[i] < 0x10) uid_str += "0";
@@ -390,26 +473,28 @@ void loop() {
if (i < uidLen - 1) uid_str += ":";
}
uid_str.toUpperCase();
if (strcmp(nfc_access_state, "granted") == 0) {
// Presence check passed — card still on reader
Serial.printf("NFC: card present UID=%s\n", uid_str.c_str());
} else if (strcmp(nfc_access_state, "denied") == 0 && uid_str == nfc_last_uid) {
// Same card that was already denied is still on reader — do nothing
Serial.printf("NFC: denied card still present UID=%s\n", uid_str.c_str());
} else {
// New card — authenticate
nfc_last_uid = uid_str;
Serial.printf("NFC: card UID=%s\n", uid_str.c_str());
postNFCEvent(uid_str);
// Require an explicit authorized UID — empty = no card is authorized yet
if (strlen(nfc_auth_uid) == 0) {
strcpy(nfc_access_state, "denied");
Serial.printf("NFC: ACCESS DENIED — no authorized UID configured. Set one in the web UI.\n");
} else if (uid_str == String(nfc_auth_uid)) {
// Card is physically present — reset absence timer regardless of state
nfc_absent_since = 0;
bool is_authorized = false;
if (nfc_auth_count > 0) {
for (int i = 0; i < NFC_MAX_CARDS; i++) {
if (strlen(nfc_auth_uids[i]) > 0 && uid_str == String(nfc_auth_uids[i])) {
is_authorized = true;
break;
}
}
}
if (is_authorized) {
if (!is_granted) {
// First detection of this authorized card: open relay
strcpy(nfc_access_state, "granted");
int gpin = nfcRelayPin(nfc_relay_num);
if (gpin >= 0) {
digitalWrite(gpin, HIGH);
nfc_last_uid = uid_str;
int pin = nfcRelayPin(nfc_relay_num);
if (pin >= 0) {
digitalWrite(pin, HIGH);
switch (nfc_relay_num) {
case 1: relay1_state = true; break;
case 2: relay2_state = true; break;
@@ -417,19 +502,34 @@ void loop() {
case 4: relay4_state = true; break;
}
}
Serial.printf("NFC: ACCESS GRANTED relay=%d (presence-check every %lums)\n",
nfc_relay_num, nfc_pulse_ms);
Serial.printf("NFC: ACCESS GRANTED UID=%s — relay %d ON\n",
uid_str.c_str(), nfc_relay_num);
postNFCEvent(uid_str);
}
// Already GRANTED and card still present — absence timer already
// reset above; relay stays ON, no further action needed.
} else {
// Card present but not in the authorized list
if (!is_denied || uid_str != nfc_last_uid) {
strcpy(nfc_access_state, "denied");
nfc_last_uid = uid_str;
Serial.printf("NFC: ACCESS DENIED UID=%s\n", uid_str.c_str());
postNFCEvent(uid_str);
}
}
} else {
// No card this poll
if (strcmp(nfc_access_state, "granted") == 0) {
nfc_miss_count++;
if (nfc_miss_count >= 2) { // 2 consecutive presence-check failures = card gone
nfc_miss_count = 0;
// No card detected this poll
if (is_granted) {
// Start the absence timer on first missed poll
if (nfc_absent_since == 0) {
nfc_absent_since = millis();
Serial.printf("NFC: card not detected — relay OFF if absent > %lu ms\n",
absent_limit);
}
if (millis() - nfc_absent_since >= absent_limit) {
// Card has been absent long enough — close relay
int pin = nfcRelayPin(nfc_relay_num);
if (pin >= 0) {
digitalWrite(pin, LOW);
@@ -440,27 +540,23 @@ void loop() {
case 4: relay4_state = false; break;
}
}
strcpy(nfc_access_state, "idle");
nfc_absent_since = 0;
nfc_last_uid = "";
Serial.printf("NFC: card removed — relay %d closed\n", nfc_relay_num);
strcpy(nfc_access_state, "idle");
Serial.printf("NFC: card absent %lu ms — relay %d OFF\n",
absent_limit, nfc_relay_num);
}
} else {
Serial.printf("NFC: presence miss %d/2 — retrying\n", nfc_miss_count);
}
} else if (strcmp(nfc_access_state, "denied") == 0) {
nfc_miss_count++;
if (nfc_miss_count >= 2) {
Serial.printf("NFC: denied card removed\n");
strcpy(nfc_access_state, "idle");
// DENIED or IDLE — card gone, just return to idle
nfc_absent_since = 0;
nfc_last_uid = "";
nfc_miss_count = 0;
}
}
// idle: keep fast-polling, no action needed
strcpy(nfc_access_state, "idle");
}
}
}
delay(10);
}
// ──────────────────────────────────────────────────────────────────────────
yield();
}
// ============================================
@@ -617,6 +713,8 @@ void handleRoot() {
html += "table.settings tr:last-child{border-bottom:none}";
html += "table.settings .val{font-weight:700;color:var(--text)}";
html += "table.settings .mono{font-family:monospace;color:#1b5e20}";
html += ".uid-tag{display:inline-flex;align-items:center;gap:4px;background:var(--card2,#e8f5e9);color:#1b5e20;border-radius:4px;padding:3px 8px;margin:2px 4px 2px 0;font-family:monospace;font-size:13px}";
html += ".uid-tag-del{cursor:default}";
html += ".api-list{font-size:12px;color:var(--text2);line-height:2}";
html += "</style>";
// ── JavaScript ─────────────────────────────────────────────────────────
@@ -647,7 +745,7 @@ void handleRoot() {
html += "b.className='relay-btn '+(on?'relay-on':'relay-off');";
html += "b.textContent='Relay '+i+': '+(on?'ON':'OFF');}";
html += "var nled=document.getElementById('nfc-led');";
html += "if(nled)nled.className='led '+(d.nfc_card_present?'led-on':'led-off');";
html += "if(nled)nled.className='led '+(d.nfc_last_uid?'led-on':'led-off');";
html += "var nuid=document.getElementById('nfc-uid');";
html += "if(nuid)nuid.textContent=d.nfc_last_uid||'No card inserted';";
html += "var ncb=document.getElementById('nfc-copy-btn');if(ncb)ncb.disabled=!d.nfc_card_present;";
@@ -656,12 +754,13 @@ void handleRoot() {
html += "nac.className='nfc-state nfc-'+as;";
html += "nac.textContent=as==='granted'?'ACCESS GRANTED':as==='denied'?'ACCESS DENIED':'Waiting for card';}";
html += "var ef=document.getElementById('nfc-auth-field');";
html += "if(ef&&document.activeElement!==ef)ef.value=d.nfc_auth_uid||'';";
html += "if(ef&&document.activeElement!==ef)ef.value=d.nfc_last_uid||'';";
html += "var ad=document.getElementById('nfc-auth-display');";
html += "if(ad){if(d.nfc_auth_uid){ad.style.color='#1b5e20';ad.textContent=d.nfc_auth_uid;}";
html += "else{ad.style.color='var(--wh-err-c)';ad.textContent='None \u2014 no card authorized yet';}}";
html += "if(ad){var uids=d.nfc_auth_uids||[];";
html += "if(uids.length>0){ad.style.color='#1b5e20';ad.innerHTML=uids.map(function(u,i){return '<span class=\\'uid-tag\\'>'+u+'</span>';}).join('');}";
html += "else{ad.style.color='var(--wh-err-c)';ad.innerHTML='None \u2014 no card authorized yet';}}";
html += "var rs=document.getElementById('nfc-relay-sel');if(rs&&document.activeElement!==rs)rs.value=d.nfc_relay_num||1;";
html += "var pf=document.getElementById('nfc-pulse-field');if(pf&&document.activeElement!==pf)pf.value=d.nfc_pulse_ms||3000;";
html += "var pf=document.getElementById('nfc-pulse-field');if(pf&&document.activeElement!==pf)pf.value=d.nfc_pulse_ms!=null?d.nfc_pulse_ms:0;";
html += "var nmb=document.getElementById('nfc-module-btn');";
html += "if(nmb){var ne=!!d.nfc_enabled;nmb.dataset.enabled=ne?'1':'0';";
html += "nmb.className='btn '+(ne?'nfc-module-on':'nfc-module-off');";
@@ -681,18 +780,33 @@ void handleRoot() {
html += "if(ns){ns.textContent=ne?'Active':'Disabled';ns.style.color=ne?'#4CAF50':'#f44336';}}";
html += "else{alert('Error: '+d.error);}}).catch(function(e){alert('Network error');});}";
// NFC helpers
html += "function clearNFCAuth(){if(!confirm('Remove the authorized card?'))return;";
html += "fetch('/nfc/config?auth_uid=&relay='+document.getElementById('nfc-relay-sel').value+'&pulse_ms='+document.getElementById('nfc-pulse-field').value,{method:'POST'})";
html += "function getCurrentUIDs(){";
html += "var tags=document.querySelectorAll('#nfc-uid-list .uid-tag-del');";
html += "return Array.from(tags).map(function(t){return t.dataset.uid;}).filter(function(u){return u&&u.length>0;});}";
html += "function renderUIDList(uids){";
html += "var list=document.getElementById('nfc-uid-list');if(!list)return;";
html += "list.innerHTML='';";
html += "uids.forEach(function(u){";
html += "var s=document.createElement('span');s.className='uid-tag uid-tag-del';s.dataset.uid=u;";
html += "s.innerHTML=u+' <button onclick=\\'removeCard(\\\"'+u+'\\\")\\' style=\\'background:none;border:none;cursor:pointer;color:inherit;font-size:14px;padding:0 2px;\\'>&#10005;</button>';";
html += "list.appendChild(s);});";
html += "if(uids.length===0)list.innerHTML='<span style=\\'color:var(--wh-err-c)\\'>None \u2014 no card authorized yet</span>';}";
html += "function removeCard(uid){";
html += "var uids=getCurrentUIDs().filter(function(u){return u!==uid;});";
html += "fetch('/nfc/config?auth_uids='+encodeURIComponent(uids.join(','))+'&relay='+document.getElementById('nfc-relay-sel').value+'&pulse_ms='+document.getElementById('nfc-pulse-field').value,{method:'POST'})";
html += ".then(function(r){return r.json();})";
html += ".then(function(d){if(d.status==='ok'){alert('Authorized card cleared.');}else{alert('Error: '+d.error);}})";
html += ".then(function(d){if(d.status==='ok'){renderUIDList(d.auth_uids||[]);}else{alert('Error: '+d.error);}})";
html += ".catch(function(e){alert('Network error');});}";
html += "function saveNFCConfig(){";
html += "var uid=document.getElementById('nfc-auth-field').value.trim().toUpperCase();";
html += "var newuid=document.getElementById('nfc-auth-field').value.trim().toUpperCase();";
html += "var uids=getCurrentUIDs();";
html += "if(newuid.length>0){if(uids.indexOf(newuid)===-1){if(uids.length>=10){alert('Maximum 10 authorized cards reached. Remove one first.');return;}uids.push(newuid);}";
html += "document.getElementById('nfc-auth-field').value='';}";
html += "var relay=document.getElementById('nfc-relay-sel').value;";
html += "var pulse=document.getElementById('nfc-pulse-field').value;";
html += "fetch('/nfc/config?auth_uid='+encodeURIComponent(uid)+'&relay='+relay+'&pulse_ms='+pulse,{method:'POST'})";
html += "fetch('/nfc/config?auth_uids='+encodeURIComponent(uids.join(','))+'&relay='+relay+'&pulse_ms='+pulse,{method:'POST'})";
html += ".then(function(r){return r.json();})";
html += ".then(function(d){if(d.status==='ok'){alert('Saved!\\nUID: '+(d.auth_uid||'(none)')+'\\nRelay: '+d.relay_num+'\\nTimeout: '+d.pulse_ms+'ms');}else{alert('Error: '+d.error);}})";
html += ".then(function(d){if(d.status==='ok'){renderUIDList(d.auth_uids||[]);alert('Saved! '+d.auth_uids.length+' card(s) authorized.');}else{alert('Error: '+d.error);}})";
html += ".catch(function(e){alert('Network error');});}";
html += "function copyUID(){var u=document.getElementById('nfc-uid').textContent;";
html += "if(u&&u!=='No card inserted'){var f=document.getElementById('nfc-auth-field');f.value=u;";
@@ -776,37 +890,44 @@ void handleRoot() {
// Current settings table
html += "<h3>Current Settings</h3>";
html += "<table class='settings'>";
html += "<tr><td style='width:150px'>Authorized card</td>";
if (strlen(nfc_auth_uid) > 0) {
html += "<td class='val mono' id='nfc-auth-display'>" + String(nfc_auth_uid) + "</td>";
html += "<td style='text-align:right'><button class='btn btn-off' style='font-size:12px;padding:5px 10px' onclick='clearNFCAuth()'>&#10005; Remove</button></td>";
html += "<tr><td style='width:150px;vertical-align:top;padding-top:6px'>Authorized cards</td>";
html += "<td colspan='2' id='nfc-uid-list' style='padding:4px 0'>";
if (nfc_auth_count == 0) {
html += "<span style='color:var(--wh-err-c)'>None &mdash; no card authorized yet</span>";
} else {
html += "<td colspan='2' style='color:var(--wh-err-c)' id='nfc-auth-display'>None &mdash; no card authorized yet</td>";
for (int i = 0; i < NFC_MAX_CARDS; i++) {
if (strlen(nfc_auth_uids[i]) > 0) {
html += "<span class='uid-tag uid-tag-del' data-uid='" + String(nfc_auth_uids[i]) + "'>";
html += String(nfc_auth_uids[i]);
html += " <button onclick='removeCard(\"" + String(nfc_auth_uids[i]) + "\")' style='background:none;border:none;cursor:pointer;color:inherit;font-size:14px;padding:0 2px;'>&#10005;</button>";
html += "</span>";
}
html += "</tr>";
}
}
html += "</td></tr>";
html += "<tr><td>Trigger relay</td><td colspan='2' class='val'>Relay " + String(nfc_relay_num) + "</td></tr>";
html += "<tr><td>Absent timeout</td><td colspan='2' class='val'>" + String(nfc_pulse_ms) + " ms</td></tr>";
html += "<tr><td>Absent timeout</td><td colspan='2' class='val'>" + String(nfc_pulse_ms) + " ms (0 = 5 s default)</td></tr>";
html += "</table>";
// Edit settings form
html += "<h3>Edit Settings</h3>";
html += "<div class='nfc-edit'>";
html += "<div><div class='field-label'>Authorized UID</div>";
html += "<input type='text' id='nfc-auth-field' placeholder='e.g. 04:AB:CD:EF' value='" + String(nfc_auth_uid) + "' style='font-family:monospace'></div>";
html += "<div><div class='field-label'>Add authorized UID <small style='color:var(--text3)'>(max 10 cards)</small></div>";
html += "<input type='text' id='nfc-auth-field' placeholder='e.g. 04:AB:CD:EF or use &quot;Use as authorized&quot; button' style='font-family:monospace;width:100%'></div>";
html += "<div><div class='field-label'>Trigger relay</div>";
html += "<select id='nfc-relay-sel'>";
for (int r = 1; r <= 4; r++) {
html += "<option value='" + String(r) + "'" + String(nfc_relay_num == r ? " selected" : "") + ">Relay " + String(r) + "</option>";
}
html += "</select></div>";
html += "<div><div class='field-label'>Absent timeout (ms)</div>";
html += "<input type='number' id='nfc-pulse-field' min='100' max='60000' value='" + String(nfc_pulse_ms) + "'></div>";
html += "<button class='btn btn-on nfc-save-btn' onclick='saveNFCConfig()' style='padding:9px 18px'>Save</button>";
html += "<div><div class='field-label'>Absent timeout (ms, 0 = 5 s default)</div>";
html += "<input type='number' id='nfc-pulse-field' min='0' max='60000' value='" + String(nfc_pulse_ms) + "'></div>";
html += "<button class='btn btn-on nfc-save-btn' onclick='saveNFCConfig()' style='padding:9px 18px'>Add card &amp; Save</button>";
html += "</div>";
if (!nfc_initialized) {
html += "<p style='color:var(--wh-err-c);font-size:13px;margin-top:10px'>&#10007; PN532 not detected &mdash; check UEXT1 wiring (TX=GPIO4, RX=GPIO5)</p>";
}
if (nfc_initialized && strlen(nfc_auth_uid) == 0) {
html += "<p style='color:#e65100;font-size:13px;margin-top:10px'>&#9888; No authorized UID &mdash; present a card, click &ldquo;Use as authorized&rdquo; then Save.</p>";
if (nfc_initialized && nfc_auth_count == 0) {
html += "<p style='color:#e65100;font-size:13px;margin-top:10px'>&#9888; No authorized UID &mdash; present a card, click &ldquo;Use as authorized&rdquo; then &ldquo;Add card &amp; Save&rdquo;.</p>";
}
html += "</div>"; // close NFC card
@@ -821,7 +942,7 @@ void handleRoot() {
// API reference
html += "<div class='card'><h2>API Endpoints</h2><div class='api-list'>";
html += "GET /api/status &nbsp;&bull;&nbsp; POST /relay/on?relay=1-4 &nbsp;&bull;&nbsp; POST /relay/off?relay=1-4<br>";
html += "GET /api/status &nbsp;&bull;&nbsp; POST /relay/on?relay=1-4 &nbsp;&bull;&nbsp; POST /relay/off?relay=1-4 &nbsp;&bull;&nbsp; POST /relay/toggle?relay=1-4<br>";
html += "GET /input/status?input=1-4 &nbsp;&bull;&nbsp; POST /led/on &nbsp;&bull;&nbsp; POST /led/off<br>";
html += "GET /nfc/status &nbsp;&bull;&nbsp; GET /nfc/config &nbsp;&bull;&nbsp; POST /nfc/config?auth_uid=&amp;relay=&amp;pulse_ms=<br>";
html += "POST /nfc/enable?state=0|1 &nbsp;&bull;&nbsp; POST /register?callback_url=...";
@@ -937,12 +1058,16 @@ void handleStatus() {
json += "\"relay3\":" + String(relay3_state ? "true" : "false") + ",";
json += "\"relay4\":" + String(relay4_state ? "true" : "false") + ",";
json += "\"led\":" + String(led_state ? "true" : "false") + ",";
bool nfc_present = nfc_initialized && (strcmp(nfc_access_state, "granted") == 0);
bool nfc_present = nfc_initialized && nfc_last_uid.length() > 0;
json += "\"nfc_initialized\":" + String(nfc_initialized ? "true" : "false") + ",";
json += "\"nfc_card_present\":" + String(nfc_present ? "true" : "false") + ",";
json += "\"nfc_last_uid\":\"" + nfc_last_uid + "\",";
json += "\"nfc_access_state\":\"" + String(nfc_access_state) + "\",";
json += "\"nfc_auth_uid\":\"" + String(nfc_auth_uid) + "\",";
json += "\"nfc_auth_uid\":\"" + (nfc_auth_count > 0 ? String(nfc_auth_uids[0]) : "") + "\",";
// Full list for multi-card support
json += "\"nfc_auth_uids\":[";
{ bool f=true; for(int i=0;i<NFC_MAX_CARDS;i++){if(strlen(nfc_auth_uids[i])>0){if(!f)json+=",";json+="\""+String(nfc_auth_uids[i])+"\"";f=false;}} }
json += "],";
json += "\"nfc_relay_num\":" + String(nfc_relay_num) + ",";
json += "\"nfc_pulse_ms\":" + String(nfc_pulse_ms) + ",";
json += "\"nfc_enabled\":" + String(nfc_enabled ? "true" : "false");
@@ -1012,6 +1137,36 @@ void handleRelayOff() {
Serial.printf("Relay %d OFF\n", relay_num);
}
void handleRelayToggle() {
if (!verifyAPIRequest()) return;
if (!server.hasArg("relay")) {
server.send(400, "application/json", "{\"error\":\"Missing relay parameter\"}");
return;
}
int relay_num = server.arg("relay").toInt();
int pin = -1;
bool *state_ptr = nullptr;
switch (relay_num) {
case 1: pin = RELAY_1_PIN; state_ptr = &relay1_state; break;
case 2: pin = RELAY_2_PIN; state_ptr = &relay2_state; break;
case 3: pin = RELAY_3_PIN; state_ptr = &relay3_state; break;
case 4: pin = RELAY_4_PIN; state_ptr = &relay4_state; break;
default:
server.send(400, "application/json", "{\"error\":\"Invalid relay number (1-4)\"}");
return;
}
// Flip current state (active HIGH: HIGH = ON, LOW = OFF)
bool new_state = !(*state_ptr);
digitalWrite(pin, new_state ? HIGH : LOW);
*state_ptr = new_state;
String json = "{\"status\":\"ok\",\"state\":" + String(new_state ? "true" : "false") + "}";
server.send(200, "application/json", json);
Serial.printf("Relay %d TOGGLE → %s\n", relay_num, new_state ? "ON" : "OFF");
}
void handleRelayStatus() {
if (!verifyAPIRequest()) return;
if (!server.hasArg("relay")) {
@@ -1247,6 +1402,84 @@ void handleNFCEnable() {
String("{\"status\":\"ok\",\"nfc_enabled\":") + (nfc_enabled ? "true" : "false") + "}");
}
void handleNFCProbe() {
// Manual NFC detection at runtime with timeout protection
if (!requireAuth()) return;
server.setContentLength(CONTENT_LENGTH_UNKNOWN);
server.send(200, "text/plain");
server.sendContent("Starting NFC probe (with 300ms timeout per attempt)...\n\n");
const long NFC_BAUDS[] = {115200, 9600, 57600, 38400};
const int NFC_NBAUDS = 4;
const int NFC_PINS[2][2] = {{NFC_RX_PIN, NFC_TX_PIN},
{NFC_TX_PIN, NFC_RX_PIN}};
uint32_t versiondata = 0;
long found_baud = 0;
int found_rx = NFC_RX_PIN;
int found_tx = NFC_TX_PIN;
for (int pi = 0; pi < 2 && !versiondata; pi++) {
int rx = NFC_PINS[pi][0];
int tx = NFC_PINS[pi][1];
for (int bi = 0; bi < NFC_NBAUDS && !versiondata; bi++) {
long baud = NFC_BAUDS[bi];
char msg[128];
snprintf(msg, sizeof(msg), " Trying baud=%-7ld RX=GPIO%d TX=GPIO%d ... ", baud, rx, tx);
server.sendContent(msg);
nfcSerial.begin(baud, SERIAL_8N1, rx, tx);
delay(100);
while (nfcSerial.available()) nfcSerial.read();
nfc.begin();
unsigned long probe_start = millis();
versiondata = nfc.getFirmwareVersion();
unsigned long probe_time = millis() - probe_start;
if (versiondata) {
found_baud = baud; found_rx = rx; found_tx = tx;
snprintf(msg, sizeof(msg), "✓ FOUND in %lu ms\n", probe_time);
server.sendContent(msg);
} else {
server.sendContent("\n");
}
server.handleClient();
yield();
}
}
server.sendContent("\n");
if (!versiondata) {
server.sendContent("✗ PN532 not detected with any baud/pin combination.\n\n");
server.sendContent("Hardware checklist:\n");
server.sendContent("1. DIP/solder-jumpers on PN532: BOTH = 0 for HSU mode\n");
server.sendContent(" (Some boards label them SEL0/SEL1 or I0/I1 — both must be LOW)\n");
server.sendContent("2. Power: UEXT1 pin 1 = 3.3V, pin 2 = GND (measure with multimeter)\n");
server.sendContent("3. Wiring: UEXT1 pin 3 (GPIO4) ↔ PN532 RXD\n");
server.sendContent(" UEXT1 pin 4 (GPIO5) ↔ PN532 TXD\n");
server.sendContent("4. Some PN532 boards need 100Ω series resistor on TX line\n");
server.sendContent("5. Power off the module, power on again, then run probe\n");
nfc_initialized = false;
} else {
char msg[256];
snprintf(msg, sizeof(msg),
"✓ PN532 found! baud=%ld RX=GPIO%d TX=GPIO%d\n"
" Chip: PN5%02X\n"
" Firmware: %d.%d\n\n",
found_baud, found_rx, found_tx,
(versiondata >> 24) & 0xFF,
(versiondata >> 16) & 0xFF, (versiondata >> 8) & 0xFF);
server.sendContent(msg);
nfcSerial.begin(found_baud, SERIAL_8N1, found_rx, found_tx);
nfc.SAMConfig();
nfc_initialized = true;
server.sendContent("✓ NFC initialized successfully — ready for card detection\n");
}
server.sendContent(""); // Finish response
}
void handleNFCStatus() {
if (!verifyAPIRequest()) return;
bool present = nfc_initialized && nfc_enabled && (strcmp(nfc_access_state, "granted") == 0);
@@ -1256,7 +1489,10 @@ void handleNFCStatus() {
json += "\"card_present\":" + String(present ? "true" : "false") + ",";
json += "\"last_uid\":\"" + nfc_last_uid + "\",";
json += "\"access_state\":\"" + String(nfc_access_state) + "\",";
json += "\"auth_uid\":\"" + String(nfc_auth_uid) + "\",";
json += "\"auth_uid\":\"" + String(nfc_auth_count > 0 ? nfc_auth_uids[0] : "") + "\",";
json += "\"auth_uids\":[";
{ bool f=true; for(int i=0;i<NFC_MAX_CARDS;i++){if(strlen(nfc_auth_uids[i])>0){if(!f)json+=",";json+="\""+String(nfc_auth_uids[i])+"\"";f=false;}} }
json += "],";
json += "\"relay_num\":" + String(nfc_relay_num) + ",";
json += "\"pulse_ms\":" + String(nfc_pulse_ms);
json += "}";
@@ -1298,7 +1534,16 @@ int nfcRelayPin(int rnum) {
void handleNFCConfigGet() {
if (!verifyAPIRequest()) return;
String json = "{";
json += "\"auth_uid\":\"" + String(nfc_auth_uid) + "\",";
json += "\"auth_uids\":[";
bool first = true;
for (int i = 0; i < NFC_MAX_CARDS; i++) {
if (strlen(nfc_auth_uids[i]) > 0) {
if (!first) json += ",";
json += "\"" + String(nfc_auth_uids[i]) + "\"";
first = false;
}
}
json += "],";
json += "\"relay_num\":" + String(nfc_relay_num) + ",";
json += "\"pulse_ms\":" + String(nfc_pulse_ms) + ",";
json += "\"nfc_enabled\":" + String(nfc_enabled ? "true" : "false");
@@ -1308,17 +1553,34 @@ void handleNFCConfigGet() {
void handleNFCConfigSet() {
if (!verifyAPIRequest()) return;
if (server.hasArg("auth_uid")) {
String u = server.arg("auth_uid");
u.trim();
u.toUpperCase();
if (u.length() < sizeof(nfc_auth_uid)) {
u.toCharArray(nfc_auth_uid, sizeof(nfc_auth_uid));
} else {
server.send(400, "application/json", "{\"error\":\"auth_uid too long (max 31 chars)\"}");
return;
// auth_uids: comma-separated list of UIDs, e.g. "04:AB:CD:EF,04:11:22:33"
// Sending auth_uids= (empty) clears all authorized cards.
if (server.hasArg("auth_uids")) {
String raw = server.arg("auth_uids");
raw.trim();
raw.toUpperCase();
// Clear all slots first
for (int i = 0; i < NFC_MAX_CARDS; i++) nfc_auth_uids[i][0] = '\0';
nfc_auth_count = 0;
if (raw.length() > 0) {
int start = 0;
int slot = 0;
while (slot < NFC_MAX_CARDS) {
int comma = raw.indexOf(',', start);
String uid = (comma == -1) ? raw.substring(start) : raw.substring(start, comma);
uid.trim();
if (uid.length() > 0 && uid.length() < 32) {
uid.toCharArray(nfc_auth_uids[slot], 32);
slot++;
nfc_auth_count++;
}
if (comma == -1) break;
start = comma + 1;
}
}
}
if (server.hasArg("relay")) {
int r = server.arg("relay").toInt();
if (r >= 1 && r <= 4) {
@@ -1330,26 +1592,58 @@ void handleNFCConfigSet() {
}
if (server.hasArg("pulse_ms")) {
long p = server.arg("pulse_ms").toInt();
if (p >= 100 && p <= 60000) {
if (p >= 0 && p <= 60000) {
nfc_pulse_ms = (unsigned long)p;
} else {
server.send(400, "application/json", "{\"error\":\"pulse_ms range: 100-60000\"}");
server.send(400, "application/json", "{\"error\":\"pulse_ms range: 0-60000\"}");
return;
}
}
Serial.printf("NFC config: auth='%s' relay=%d pulse=%lu ms\n",
nfc_auth_uid, nfc_relay_num, nfc_pulse_ms);
// ── Persist to NVS so settings survive power cycles ──────────────────────
prefs.begin("nfc_cfg", false); // read-write namespace
prefs.putString("auth_uid", nfc_auth_uid);
Serial.printf("NFC config: %d card(s) authorized, relay=%d, pulse=%lu ms\n",
nfc_auth_count, nfc_relay_num, nfc_pulse_ms);
// If relay was ON from a previous grant, turn it off before resetting state
if (strcmp(nfc_access_state, "granted") == 0) {
int pin = nfcRelayPin(nfc_relay_num);
if (pin >= 0) {
digitalWrite(pin, LOW);
switch (nfc_relay_num) {
case 1: relay1_state = false; break;
case 2: relay2_state = false; break;
case 3: relay3_state = false; break;
case 4: relay4_state = false; break;
}
}
}
strcpy(nfc_access_state, "idle");
nfc_last_uid = "";
nfc_miss_count = 0;
nfc_absent_since = 0;
// ── Persist to NVS ────────────────────────────────────────────────────────
prefs.begin("nfc_cfg", false);
prefs.putInt("relay_num", nfc_relay_num);
prefs.putULong("pulse_ms", nfc_pulse_ms);
for (int i = 0; i < NFC_MAX_CARDS; i++) {
String key = "uid" + String(i);
prefs.putString(key.c_str(), nfc_auth_uids[i]);
}
prefs.end();
// ─────────────────────────────────────────────────────────────────────────
String json = "{\"status\":\"ok\","
"\"auth_uid\":\"" + String(nfc_auth_uid) + "\","
"\"relay_num\":" + String(nfc_relay_num) + ","
"\"pulse_ms\":" + String(nfc_pulse_ms) + "}";
// Build response with current uid list
String json = "{\"status\":\"ok\",\"auth_uids\":[";
bool first = true;
for (int i = 0; i < NFC_MAX_CARDS; i++) {
if (strlen(nfc_auth_uids[i]) > 0) {
if (!first) json += ",";
json += "\"" + String(nfc_auth_uids[i]) + "\"";
first = false;
}
}
json += "],\"relay_num\":" + String(nfc_relay_num) +
",\"pulse_ms\":" + String(nfc_pulse_ms) + "}";
server.send(200, "application/json", json);
}
@@ -1382,7 +1676,12 @@ void handleDebug() {
out += "NFC last UID:" + String(nfc_last_uid.length() > 0 ? nfc_last_uid : "(none)") + "\n";
out += "NFC state: " + String(nfc_access_state) + "\n";
out += "NFC relay: " + String(nfc_relay_num) + "\n";
out += "NFC auth: " + String(strlen(nfc_auth_uid) > 0 ? nfc_auth_uid : "(any)") + "\n";
out += "NFC auth: " + String(nfc_auth_count) + " card(s)\n";
for (int i = 0; i < NFC_MAX_CARDS; i++) {
if (strlen(nfc_auth_uids[i]) > 0) {
out += " [" + String(i) + "] " + String(nfc_auth_uids[i]) + "\n";
}
}
out += "\n";
out += "Relay states: R1=" + String(relay1_state) + " R2=" + String(relay2_state)
+ " R3=" + String(relay3_state) + " R4=" + String(relay4_state) + "\n";
@@ -10,6 +10,20 @@
// WEB_USER / WEB_PASSWORD — credentials for the browser control panel.
// ─────────────────────────────────────────────────────────────────────────────
// ─ WiFi Credentials ───────────────────────────────────────────────────────
#define WIFI_SSID "your_wifi_network"
#define WIFI_PASSWORD "your_wifi_password"
// ─ Static IP Configuration ────────────────────────────────────────────────
// Set USE_STATIC_IP to true for a fixed IP; false = use DHCP
#define USE_STATIC_IP true
#define STATIC_IP_ADDR "192.168.0.240" // C6-EVB board IP
#define STATIC_GATEWAY "192.168.0.1" // Local network gateway
#define STATIC_SUBNET "255.255.255.0" // Subnet mask
#define STATIC_DNS1 "8.8.8.8" // Google DNS primary
#define STATIC_DNS2 "8.8.4.4" // Google DNS secondary
// ─ Web Server Credentials ─────────────────────────────────────────────────
#define API_SECRET "REPLACE_WITH_OUTPUT_OF_python3_-c_import_secrets_token_hex_32"
#define WEB_USER "your_username"
#define WEB_PASSWORD "your_password"