Fix NFC controller auth and document breadboard wiring

This commit is contained in:
Dorian
2026-05-20 14:42:41 -05:00
parent 0068c56bf3
commit 496cf8e9d6
5 changed files with 269 additions and 4 deletions

View File

@@ -0,0 +1,173 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1600" height="1040" viewBox="0 0 1600 1040" role="img" aria-labelledby="title desc">
<title id="title">Breadboard wiring diagram for XIAO ESP32-S3 and PN532</title>
<desc id="desc">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.</desc>
<defs>
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
<feDropShadow dx="0" dy="16" stdDeviation="18" flood-color="#000000" flood-opacity="0.45"/>
</filter>
<linearGradient id="breadboard" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#f6f1e7"/>
<stop offset="1" stop-color="#dfd5c6"/>
</linearGradient>
<linearGradient id="xiaoBoard" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#173f55"/>
<stop offset="1" stop-color="#071d28"/>
</linearGradient>
<linearGradient id="pnBoard" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#0b765f"/>
<stop offset="1" stop-color="#064236"/>
</linearGradient>
<style>
.bg { fill: #070707; }
.title { fill: #fafafa; font: 800 36px Inter, Arial, sans-serif; letter-spacing: 0.06em; }
.subtitle { fill: #b8b8b8; font: 600 17px Inter, Arial, sans-serif; letter-spacing: 0.07em; }
.boardLabel { fill: #fafafa; font: 800 20px Inter, Arial, sans-serif; letter-spacing: 0.08em; }
.small { fill: #d4d4d4; font: 600 14px Inter, Arial, sans-serif; }
.tiny { fill: #5b5148; font: 700 12px Inter, Arial, sans-serif; }
.pinText { fill: #fafafa; font: 800 14px Inter, Arial, sans-serif; letter-spacing: 0.04em; }
.hole { fill: #6b625a; opacity: 0.78; }
.railHole { fill: #685f58; opacity: 0.72; }
.railText { font: 800 16px Inter, Arial, sans-serif; }
.xiao { fill: url(#xiaoBoard); stroke: #7dd3fc; stroke-opacity: 0.4; stroke-width: 3; }
.pn532 { fill: url(#pnBoard); stroke: #67e8c9; stroke-opacity: 0.4; stroke-width: 3; }
.chip { fill: #171717; stroke: #4b5563; stroke-width: 2; }
.pinPad { fill: #171717; stroke: #f4f4f5; stroke-opacity: 0.35; stroke-width: 2; }
.wire { fill: none; stroke-width: 8; stroke-linecap: round; stroke-linejoin: round; }
.wireHalo { fill: none; stroke: #ffffff; stroke-width: 15; stroke-linecap: round; stroke-linejoin: round; opacity: 0.2; }
.tag { fill: #111111; stroke-width: 2; }
.tagText { fill: #fafafa; font: 800 14px Inter, Arial, sans-serif; letter-spacing: 0.07em; }
.noteBox { fill: #111111; stroke: #f2ad24; stroke-width: 2; }
.noteTitle { fill: #f2ad24; font: 800 17px Inter, Arial, sans-serif; letter-spacing: 0.08em; }
.note { fill: #d4d4d4; font: 600 15px Inter, Arial, sans-serif; }
</style>
</defs>
<rect class="bg" width="1600" height="1040"/>
<text class="title" x="70" y="70">BREADBOARD WIRING: XIAO ESP32-S3 + PN532</text>
<text class="subtitle" x="72" y="104">PN532 IN I2C MODE, POWERED FROM THE XIAO 3V3 PIN</text>
<!-- Breadboard body -->
<g filter="url(#shadow)">
<rect x="90" y="155" width="980" height="735" rx="28" fill="url(#breadboard)" stroke="#c9bba8" stroke-width="4"/>
<rect x="132" y="197" width="896" height="78" rx="12" fill="#eee6db" stroke="#cfc2b4" stroke-width="2"/>
<rect x="132" y="770" width="896" height="78" rx="12" fill="#eee6db" stroke="#cfc2b4" stroke-width="2"/>
<rect x="132" y="336" width="896" height="90" rx="8" fill="#e6dbcd"/>
<rect x="132" y="468" width="896" height="90" rx="8" fill="#e6dbcd"/>
<rect x="132" y="426" width="896" height="42" fill="#cfc2b4" opacity="0.9"/>
<text class="tiny" x="540" y="453">CENTER GAP</text>
<text class="railText" x="152" y="222" fill="#d92d20">+</text>
<text class="railText" x="152" y="258" fill="#1d4ed8">-</text>
<text class="railText" x="152" y="795" fill="#d92d20">+</text>
<text class="railText" x="152" y="831" fill="#1d4ed8">-</text>
<!-- Power rail holes -->
<g>
<circle class="railHole" cx="202" cy="217" r="5"/><circle class="railHole" cx="238" cy="217" r="5"/><circle class="railHole" cx="274" cy="217" r="5"/><circle class="railHole" cx="310" cy="217" r="5"/><circle class="railHole" cx="346" cy="217" r="5"/><circle class="railHole" cx="382" cy="217" r="5"/><circle class="railHole" cx="418" cy="217" r="5"/><circle class="railHole" cx="454" cy="217" r="5"/><circle class="railHole" cx="490" cy="217" r="5"/><circle class="railHole" cx="526" cy="217" r="5"/><circle class="railHole" cx="562" cy="217" r="5"/><circle class="railHole" cx="598" cy="217" r="5"/><circle class="railHole" cx="634" cy="217" r="5"/><circle class="railHole" cx="670" cy="217" r="5"/><circle class="railHole" cx="706" cy="217" r="5"/><circle class="railHole" cx="742" cy="217" r="5"/><circle class="railHole" cx="778" cy="217" r="5"/><circle class="railHole" cx="814" cy="217" r="5"/><circle class="railHole" cx="850" cy="217" r="5"/><circle class="railHole" cx="886" cy="217" r="5"/><circle class="railHole" cx="922" cy="217" r="5"/><circle class="railHole" cx="958" cy="217" r="5"/>
<circle class="railHole" cx="202" cy="253" r="5"/><circle class="railHole" cx="238" cy="253" r="5"/><circle class="railHole" cx="274" cy="253" r="5"/><circle class="railHole" cx="310" cy="253" r="5"/><circle class="railHole" cx="346" cy="253" r="5"/><circle class="railHole" cx="382" cy="253" r="5"/><circle class="railHole" cx="418" cy="253" r="5"/><circle class="railHole" cx="454" cy="253" r="5"/><circle class="railHole" cx="490" cy="253" r="5"/><circle class="railHole" cx="526" cy="253" r="5"/><circle class="railHole" cx="562" cy="253" r="5"/><circle class="railHole" cx="598" cy="253" r="5"/><circle class="railHole" cx="634" cy="253" r="5"/><circle class="railHole" cx="670" cy="253" r="5"/><circle class="railHole" cx="706" cy="253" r="5"/><circle class="railHole" cx="742" cy="253" r="5"/><circle class="railHole" cx="778" cy="253" r="5"/><circle class="railHole" cx="814" cy="253" r="5"/><circle class="railHole" cx="850" cy="253" r="5"/><circle class="railHole" cx="886" cy="253" r="5"/><circle class="railHole" cx="922" cy="253" r="5"/><circle class="railHole" cx="958" cy="253" r="5"/>
<circle class="railHole" cx="202" cy="791" r="5"/><circle class="railHole" cx="238" cy="791" r="5"/><circle class="railHole" cx="274" cy="791" r="5"/><circle class="railHole" cx="310" cy="791" r="5"/><circle class="railHole" cx="346" cy="791" r="5"/><circle class="railHole" cx="382" cy="791" r="5"/><circle class="railHole" cx="418" cy="791" r="5"/><circle class="railHole" cx="454" cy="791" r="5"/><circle class="railHole" cx="490" cy="791" r="5"/><circle class="railHole" cx="526" cy="791" r="5"/><circle class="railHole" cx="562" cy="791" r="5"/><circle class="railHole" cx="598" cy="791" r="5"/><circle class="railHole" cx="634" cy="791" r="5"/><circle class="railHole" cx="670" cy="791" r="5"/><circle class="railHole" cx="706" cy="791" r="5"/><circle class="railHole" cx="742" cy="791" r="5"/><circle class="railHole" cx="778" cy="791" r="5"/><circle class="railHole" cx="814" cy="791" r="5"/><circle class="railHole" cx="850" cy="791" r="5"/><circle class="railHole" cx="886" cy="791" r="5"/><circle class="railHole" cx="922" cy="791" r="5"/><circle class="railHole" cx="958" cy="791" r="5"/>
<circle class="railHole" cx="202" cy="827" r="5"/><circle class="railHole" cx="238" cy="827" r="5"/><circle class="railHole" cx="274" cy="827" r="5"/><circle class="railHole" cx="310" cy="827" r="5"/><circle class="railHole" cx="346" cy="827" r="5"/><circle class="railHole" cx="382" cy="827" r="5"/><circle class="railHole" cx="418" cy="827" r="5"/><circle class="railHole" cx="454" cy="827" r="5"/><circle class="railHole" cx="490" cy="827" r="5"/><circle class="railHole" cx="526" cy="827" r="5"/><circle class="railHole" cx="562" cy="827" r="5"/><circle class="railHole" cx="598" cy="827" r="5"/><circle class="railHole" cx="634" cy="827" r="5"/><circle class="railHole" cx="670" cy="827" r="5"/><circle class="railHole" cx="706" cy="827" r="5"/><circle class="railHole" cx="742" cy="827" r="5"/><circle class="railHole" cx="778" cy="827" r="5"/><circle class="railHole" cx="814" cy="827" r="5"/><circle class="railHole" cx="850" cy="827" r="5"/><circle class="railHole" cx="886" cy="827" r="5"/><circle class="railHole" cx="922" cy="827" r="5"/><circle class="railHole" cx="958" cy="827" r="5"/>
</g>
<!-- Breadboard terminal holes -->
<g>
<circle class="hole" cx="202" cy="356" r="5"/><circle class="hole" cx="202" cy="378" r="5"/><circle class="hole" cx="202" cy="400" r="5"/><circle class="hole" cx="202" cy="488" r="5"/><circle class="hole" cx="202" cy="510" r="5"/><circle class="hole" cx="202" cy="532" r="5"/>
<circle class="hole" cx="238" cy="356" r="5"/><circle class="hole" cx="238" cy="378" r="5"/><circle class="hole" cx="238" cy="400" r="5"/><circle class="hole" cx="238" cy="488" r="5"/><circle class="hole" cx="238" cy="510" r="5"/><circle class="hole" cx="238" cy="532" r="5"/>
<circle class="hole" cx="274" cy="356" r="5"/><circle class="hole" cx="274" cy="378" r="5"/><circle class="hole" cx="274" cy="400" r="5"/><circle class="hole" cx="274" cy="488" r="5"/><circle class="hole" cx="274" cy="510" r="5"/><circle class="hole" cx="274" cy="532" r="5"/>
<circle class="hole" cx="310" cy="356" r="5"/><circle class="hole" cx="310" cy="378" r="5"/><circle class="hole" cx="310" cy="400" r="5"/><circle class="hole" cx="310" cy="488" r="5"/><circle class="hole" cx="310" cy="510" r="5"/><circle class="hole" cx="310" cy="532" r="5"/>
<circle class="hole" cx="346" cy="356" r="5"/><circle class="hole" cx="346" cy="378" r="5"/><circle class="hole" cx="346" cy="400" r="5"/><circle class="hole" cx="346" cy="488" r="5"/><circle class="hole" cx="346" cy="510" r="5"/><circle class="hole" cx="346" cy="532" r="5"/>
<circle class="hole" cx="382" cy="356" r="5"/><circle class="hole" cx="382" cy="378" r="5"/><circle class="hole" cx="382" cy="400" r="5"/><circle class="hole" cx="382" cy="488" r="5"/><circle class="hole" cx="382" cy="510" r="5"/><circle class="hole" cx="382" cy="532" r="5"/>
<circle class="hole" cx="418" cy="356" r="5"/><circle class="hole" cx="418" cy="378" r="5"/><circle class="hole" cx="418" cy="400" r="5"/><circle class="hole" cx="418" cy="488" r="5"/><circle class="hole" cx="418" cy="510" r="5"/><circle class="hole" cx="418" cy="532" r="5"/>
<circle class="hole" cx="454" cy="356" r="5"/><circle class="hole" cx="454" cy="378" r="5"/><circle class="hole" cx="454" cy="400" r="5"/><circle class="hole" cx="454" cy="488" r="5"/><circle class="hole" cx="454" cy="510" r="5"/><circle class="hole" cx="454" cy="532" r="5"/>
<circle class="hole" cx="490" cy="356" r="5"/><circle class="hole" cx="490" cy="378" r="5"/><circle class="hole" cx="490" cy="400" r="5"/><circle class="hole" cx="490" cy="488" r="5"/><circle class="hole" cx="490" cy="510" r="5"/><circle class="hole" cx="490" cy="532" r="5"/>
<circle class="hole" cx="526" cy="356" r="5"/><circle class="hole" cx="526" cy="378" r="5"/><circle class="hole" cx="526" cy="400" r="5"/><circle class="hole" cx="526" cy="488" r="5"/><circle class="hole" cx="526" cy="510" r="5"/><circle class="hole" cx="526" cy="532" r="5"/>
<circle class="hole" cx="562" cy="356" r="5"/><circle class="hole" cx="562" cy="378" r="5"/><circle class="hole" cx="562" cy="400" r="5"/><circle class="hole" cx="562" cy="488" r="5"/><circle class="hole" cx="562" cy="510" r="5"/><circle class="hole" cx="562" cy="532" r="5"/>
<circle class="hole" cx="598" cy="356" r="5"/><circle class="hole" cx="598" cy="378" r="5"/><circle class="hole" cx="598" cy="400" r="5"/><circle class="hole" cx="598" cy="488" r="5"/><circle class="hole" cx="598" cy="510" r="5"/><circle class="hole" cx="598" cy="532" r="5"/>
<circle class="hole" cx="634" cy="356" r="5"/><circle class="hole" cx="634" cy="378" r="5"/><circle class="hole" cx="634" cy="400" r="5"/><circle class="hole" cx="634" cy="488" r="5"/><circle class="hole" cx="634" cy="510" r="5"/><circle class="hole" cx="634" cy="532" r="5"/>
<circle class="hole" cx="670" cy="356" r="5"/><circle class="hole" cx="670" cy="378" r="5"/><circle class="hole" cx="670" cy="400" r="5"/><circle class="hole" cx="670" cy="488" r="5"/><circle class="hole" cx="670" cy="510" r="5"/><circle class="hole" cx="670" cy="532" r="5"/>
<circle class="hole" cx="706" cy="356" r="5"/><circle class="hole" cx="706" cy="378" r="5"/><circle class="hole" cx="706" cy="400" r="5"/><circle class="hole" cx="706" cy="488" r="5"/><circle class="hole" cx="706" cy="510" r="5"/><circle class="hole" cx="706" cy="532" r="5"/>
<circle class="hole" cx="742" cy="356" r="5"/><circle class="hole" cx="742" cy="378" r="5"/><circle class="hole" cx="742" cy="400" r="5"/><circle class="hole" cx="742" cy="488" r="5"/><circle class="hole" cx="742" cy="510" r="5"/><circle class="hole" cx="742" cy="532" r="5"/>
<circle class="hole" cx="778" cy="356" r="5"/><circle class="hole" cx="778" cy="378" r="5"/><circle class="hole" cx="778" cy="400" r="5"/><circle class="hole" cx="778" cy="488" r="5"/><circle class="hole" cx="778" cy="510" r="5"/><circle class="hole" cx="778" cy="532" r="5"/>
<circle class="hole" cx="814" cy="356" r="5"/><circle class="hole" cx="814" cy="378" r="5"/><circle class="hole" cx="814" cy="400" r="5"/><circle class="hole" cx="814" cy="488" r="5"/><circle class="hole" cx="814" cy="510" r="5"/><circle class="hole" cx="814" cy="532" r="5"/>
<circle class="hole" cx="850" cy="356" r="5"/><circle class="hole" cx="850" cy="378" r="5"/><circle class="hole" cx="850" cy="400" r="5"/><circle class="hole" cx="850" cy="488" r="5"/><circle class="hole" cx="850" cy="510" r="5"/><circle class="hole" cx="850" cy="532" r="5"/>
<circle class="hole" cx="886" cy="356" r="5"/><circle class="hole" cx="886" cy="378" r="5"/><circle class="hole" cx="886" cy="400" r="5"/><circle class="hole" cx="886" cy="488" r="5"/><circle class="hole" cx="886" cy="510" r="5"/><circle class="hole" cx="886" cy="532" r="5"/>
<circle class="hole" cx="922" cy="356" r="5"/><circle class="hole" cx="922" cy="378" r="5"/><circle class="hole" cx="922" cy="400" r="5"/><circle class="hole" cx="922" cy="488" r="5"/><circle class="hole" cx="922" cy="510" r="5"/><circle class="hole" cx="922" cy="532" r="5"/>
<circle class="hole" cx="958" cy="356" r="5"/><circle class="hole" cx="958" cy="378" r="5"/><circle class="hole" cx="958" cy="400" r="5"/><circle class="hole" cx="958" cy="488" r="5"/><circle class="hole" cx="958" cy="510" r="5"/><circle class="hole" cx="958" cy="532" r="5"/>
</g>
</g>
<!-- XIAO module straddling breadboard center gap -->
<g filter="url(#shadow)">
<rect class="xiao" x="318" y="300" width="238" height="288" rx="26"/>
<rect x="383" y="322" width="108" height="42" rx="8" fill="#d8d8d8" stroke="#6b7280" stroke-width="2"/>
<text class="small" x="409" y="349" fill="#111111">USB-C</text>
<rect class="chip" x="384" y="408" width="106" height="78" rx="10"/>
<text class="boardLabel" x="377" y="529">XIAO ESP32-S3</text>
<circle class="pinPad" cx="334" cy="340" r="11"/><text class="pinText" x="350" y="345">3V3</text>
<circle class="pinPad" cx="334" cy="382" r="11"/><text class="pinText" x="350" y="387">GND</text>
<circle class="pinPad" cx="334" cy="424" r="11"/><text class="pinText" x="350" y="429">D4 / GPIO5</text>
<circle class="pinPad" cx="334" cy="466" r="11"/><text class="pinText" x="350" y="471">D5 / GPIO6</text>
<circle class="pinPad" cx="334" cy="508" r="11"/><text class="pinText" x="350" y="513">D2 / GPIO3</text>
<circle class="pinPad" cx="334" cy="550" r="11"/><text class="pinText" x="350" y="555">D3 / GPIO4</text>
</g>
<!-- PN532 module to the side, pins facing the breadboard -->
<g filter="url(#shadow)">
<rect class="pn532" x="1156" y="206" width="344" height="500" rx="28"/>
<text class="boardLabel" x="1248" y="258">PN532 NFC V3</text>
<text class="small" x="1234" y="286">Set switches/jumpers to I2C</text>
<rect x="1242" y="330" width="174" height="174" rx="22" fill="none" stroke="#d1fae5" stroke-opacity="0.22" stroke-width="9"/>
<rect x="1269" y="357" width="120" height="120" rx="18" fill="none" stroke="#d1fae5" stroke-opacity="0.22" stroke-width="7"/>
<rect class="chip" x="1270" y="548" width="116" height="58" rx="10"/>
<text class="pinText" x="1194" y="347" text-anchor="end">VCC</text><circle class="pinPad" cx="1166" cy="342" r="11"/>
<text class="pinText" x="1194" y="389" text-anchor="end">GND</text><circle class="pinPad" cx="1166" cy="384" r="11"/>
<text class="pinText" x="1194" y="431" text-anchor="end">SDA</text><circle class="pinPad" cx="1166" cy="426" r="11"/>
<text class="pinText" x="1194" y="473" text-anchor="end">SCL</text><circle class="pinPad" cx="1166" cy="468" r="11"/>
<text class="pinText" x="1194" y="515" text-anchor="end">IRQ</text><circle class="pinPad" cx="1166" cy="510" r="11"/>
<text class="pinText" x="1194" y="557" text-anchor="end">RSTO/RST</text><circle class="pinPad" cx="1166" cy="552" r="11"/>
</g>
<!-- Wires from XIAO through breadboard rows to PN532 -->
<path class="wireHalo" d="M334 340 C430 224 690 207 958 217 C1070 222 1122 292 1166 342"/>
<path class="wire" stroke="#e23a3a" d="M334 340 C430 224 690 207 958 217 C1070 222 1122 292 1166 342"/>
<path class="wireHalo" d="M334 382 C430 264 712 246 958 253 C1065 258 1120 336 1166 384"/>
<path class="wire" stroke="#202020" d="M334 382 C430 264 712 246 958 253 C1065 258 1120 336 1166 384"/>
<path class="wireHalo" d="M334 424 C500 365 790 368 958 356 C1060 350 1120 397 1166 426"/>
<path class="wire" stroke="#3f8cff" d="M334 424 C500 365 790 368 958 356 C1060 350 1120 397 1166 426"/>
<path class="wireHalo" d="M334 466 C502 420 790 394 958 378 C1065 370 1122 438 1166 468"/>
<path class="wire" stroke="#ffd23c" d="M334 466 C502 420 790 394 958 378 C1065 370 1122 438 1166 468"/>
<path class="wireHalo" d="M334 508 C520 510 782 492 958 488 C1060 486 1125 496 1166 510"/>
<path class="wire" stroke="#b96cff" d="M334 508 C520 510 782 492 958 488 C1060 486 1125 496 1166 510"/>
<path class="wireHalo" d="M334 550 C522 572 786 534 958 532 C1060 530 1125 548 1166 552"/>
<path class="wire" stroke="#34d399" d="M334 550 C522 572 786 534 958 532 C1060 530 1125 548 1166 552"/>
<!-- Wire labels -->
<g>
<rect class="tag" x="624" y="170" width="142" height="34" rx="8" stroke="#e23a3a"/>
<text class="tagText" x="642" y="193">3V3 -> VCC</text>
<rect class="tag" x="624" y="280" width="144" height="34" rx="8" stroke="#202020"/>
<text class="tagText" x="642" y="303">GND -> GND</text>
<rect class="tag" x="610" y="606" width="174" height="34" rx="8" stroke="#3f8cff"/>
<text class="tagText" x="628" y="629">D4 / GPIO5 -> SDA</text>
<rect class="tag" x="802" y="606" width="174" height="34" rx="8" stroke="#ffd23c"/>
<text class="tagText" x="820" y="629">D5 / GPIO6 -> SCL</text>
<rect class="tag" x="610" y="650" width="154" height="34" rx="8" stroke="#b96cff"/>
<text class="tagText" x="628" y="673">D2 / GPIO3 -> IRQ</text>
<rect class="tag" x="802" y="650" width="186" height="34" rx="8" stroke="#34d399"/>
<text class="tagText" x="820" y="673">D3 / GPIO4 -> RST</text>
</g>
<!-- Callouts -->
<g>
<rect class="noteBox" x="90" y="922" width="1410" height="76" rx="16"/>
<text class="noteTitle" x="122" y="953">CHECK BEFORE POWERING</text>
<text class="note" x="122" y="980">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.</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -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)

View File

@@ -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)

View File

@@ -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);
}
}

View File

@@ -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)