diff --git a/docs/assets/esp32-pn532-breadboard.svg b/docs/assets/esp32-pn532-breadboard.svg new file mode 100644 index 0000000..6ebdea1 --- /dev/null +++ b/docs/assets/esp32-pn532-breadboard.svg @@ -0,0 +1,173 @@ + + Breadboard wiring diagram for XIAO ESP32-S3 and PN532 + Breadboard layout showing a XIAO ESP32-S3 wired to a PN532 NFC module in I2C mode with 3.3 volt power, ground, SDA, SCL, IRQ, and reset connections. + + + + + + + + + + + + + + + + + + + + + BREADBOARD WIRING: XIAO ESP32-S3 + PN532 + PN532 IN I2C MODE, POWERED FROM THE XIAO 3V3 PIN + + + + + + + + + + CENTER GAP + + + + - + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + USB-C + + XIAO ESP32-S3 + + 3V3 + GND + D4 / GPIO5 + D5 / GPIO6 + D2 / GPIO3 + D3 / GPIO4 + + + + + + PN532 NFC V3 + Set switches/jumpers to I2C + + + + + VCC + GND + SDA + SCL + IRQ + RSTO/RST + + + + + + + + + + + + + + + + + + + + + + + + + 3V3 -> VCC + + GND -> GND + + D4 / GPIO5 -> SDA + + D5 / GPIO6 -> SCL + + D2 / GPIO3 -> IRQ + + D3 / GPIO4 -> RST + + + + + + CHECK BEFORE POWERING + Use 3.3V, not 5V. Keep the PN532 in I2C mode. Breadboard rows are illustrative; follow the same net names if your modules sit in different rows. + + diff --git a/docs/nfc-door.md b/docs/nfc-door.md index d04afac..37fc61a 100644 --- a/docs/nfc-door.md +++ b/docs/nfc-door.md @@ -115,6 +115,10 @@ Photo overlay using the supplied board pictures: ![ESP32 to PN532 photo wiring overlay](assets/esp32-pn532-photo-overlay.svg) +Breadboard layout: + +![ESP32 to PN532 breadboard wiring diagram](assets/esp32-pn532-breadboard.svg) + Clean schematic: ![ESP32 to PN532 wiring diagram](assets/esp32-pn532-wiring.svg) diff --git a/firmware/esp32-pn532-door/README.md b/firmware/esp32-pn532-door/README.md index 587c10b..324f465 100644 --- a/firmware/esp32-pn532-door/README.md +++ b/firmware/esp32-pn532-door/README.md @@ -31,6 +31,10 @@ Photo overlay using the supplied board pictures: ![ESP32 to PN532 photo wiring overlay](../../docs/assets/esp32-pn532-photo-overlay.svg) +Breadboard layout: + +![ESP32 to PN532 breadboard wiring diagram](../../docs/assets/esp32-pn532-breadboard.svg) + Clean schematic: ![ESP32 to PN532 wiring diagram](../../docs/assets/esp32-pn532-wiring.svg) diff --git a/firmware/esp32-pn532-door/src/main.cpp b/firmware/esp32-pn532-door/src/main.cpp index 6d35e80..b928d2b 100644 --- a/firmware/esp32-pn532-door/src/main.cpp +++ b/firmware/esp32-pn532-door/src/main.cpp @@ -14,6 +14,7 @@ Adafruit_PN532 nfc(PN532_IRQ_PIN, PN532_RESET_PIN, &Wire); static unsigned long lastReadAt = 0; static String lastCredential = ""; +static bool wifiScanPrinted = false; String bytesToHex(const uint8_t *data, uint8_t length) { String out; @@ -37,15 +38,94 @@ String jsonEscape(const String &value) { return out; } +const char *wifiStatusName(wl_status_t status) { + switch (status) { + case WL_IDLE_STATUS: + return "idle"; + case WL_NO_SSID_AVAIL: + return "ssid not available"; + case WL_SCAN_COMPLETED: + return "scan completed"; + case WL_CONNECTED: + return "connected"; + case WL_CONNECT_FAILED: + return "connect failed"; + case WL_CONNECTION_LOST: + return "connection lost"; + case WL_DISCONNECTED: + return "disconnected"; + default: + return "unknown"; + } +} + +const char *encryptionName(wifi_auth_mode_t type) { + switch (type) { + case WIFI_AUTH_OPEN: + return "open"; + case WIFI_AUTH_WEP: + return "WEP"; + case WIFI_AUTH_WPA_PSK: + return "WPA"; + case WIFI_AUTH_WPA2_PSK: + return "WPA2"; + case WIFI_AUTH_WPA_WPA2_PSK: + return "WPA/WPA2"; + case WIFI_AUTH_WPA2_ENTERPRISE: + return "WPA2 enterprise"; + case WIFI_AUTH_WPA3_PSK: + return "WPA3"; + case WIFI_AUTH_WPA2_WPA3_PSK: + return "WPA2/WPA3"; + default: + return "unknown"; + } +} + +void printWifiScan() { + if (wifiScanPrinted) return; + wifiScanPrinted = true; + + Serial.println("Scanning Wi-Fi networks..."); + int count = WiFi.scanNetworks(false, true); + if (count < 0) { + Serial.printf("Wi-Fi scan failed: %d\n", count); + return; + } + + bool found = false; + for (int i = 0; i < count; i++) { + if (WiFi.SSID(i) != WIFI_SSID) continue; + found = true; + Serial.printf( + "Found %s: RSSI %d dBm, channel %d, auth %s\n", + WIFI_SSID, + WiFi.RSSI(i), + WiFi.channel(i), + encryptionName(WiFi.encryptionType(i)) + ); + } + + if (!found) { + Serial.printf("SSID %s was not found in scan.\n", WIFI_SSID); + } +} + void connectWifi() { if (WiFi.status() == WL_CONNECTED) return; - Serial.printf("Connecting to Wi-Fi %s", WIFI_SSID); + WiFi.persistent(false); WiFi.mode(WIFI_STA); + WiFi.setSleep(false); + printWifiScan(); + + Serial.printf("Connecting to Wi-Fi %s", WIFI_SSID); + WiFi.disconnect(false, false); + delay(100); WiFi.begin(WIFI_SSID, WIFI_PASSWORD); unsigned long startedAt = millis(); - while (WiFi.status() != WL_CONNECTED && millis() - startedAt < 15000) { + while (WiFi.status() != WL_CONNECTED && millis() - startedAt < 30000) { Serial.print("."); delay(350); } @@ -55,7 +135,8 @@ void connectWifi() { Serial.print("Wi-Fi connected: "); Serial.println(WiFi.localIP()); } else { - Serial.println("Wi-Fi connection failed."); + wl_status_t status = WiFi.status(); + Serial.printf("Wi-Fi connection failed: %s (%d).\n", wifiStatusName(status), status); } } diff --git a/server/server.js b/server/server.js index 840e964..1a75dfb 100644 --- a/server/server.js +++ b/server/server.js @@ -1536,7 +1536,10 @@ const handleApi = async (req, res) => { if (!privilegedConnectionAllowed(req)) return json(res, 404, { error: 'Controller access is only available from the local connection.' }) const controllerAuthed = hasControllerAuth(req) const isAdmin = (req.headers.authorization || '').startsWith('Bearer ') - if (!controllerAuthed && (!isAdmin || !requireAdmin(req, res))) return + if (!controllerAuthed) { + if (!isAdmin) return json(res, 401, { error: 'Controller token is required.' }) + if (!requireAdmin(req, res)) return + } const body = await readBody(req) const doorId = cleanText(body.doorId, 80) || 'front-door' const cardCredential = cleanText(body.cardCredential || body.uid, 160)