Skip to content

feat: PN532 NFC reader support#48

Merged
sjordan0228 merged 3 commits into
devfrom
feature/pn532-support
Mar 28, 2026
Merged

feat: PN532 NFC reader support#48
sjordan0228 merged 3 commits into
devfrom
feature/pn532-support

Conversation

@sjordan0228
Copy link
Copy Markdown
Contributor

@sjordan0228 sjordan0228 commented Mar 28, 2026

Summary

  • Add Adafruit PN532 as a second NFC reader option alongside PN5180
  • NVS key nfc_reader selects which reader initializes at boot (one-binary model)
  • Both readers implement NFCConnectionI — zero changes to NFCManager or tag classification
  • Fix two abstraction leaks in NFCManager (static_cast to concrete type replaced with virtual methods)
  • PN532 is ISO14443A only — supports GenericUidTag, TigerTag, BambuTag (UID), OpenPrintTag on NTAG

Changes

  • New: HardwareNFCConnectionPN532.h/.cpp — full NFCConnectionI implementation
  • New: lib/openprinttag/openprinttag_adafruit_pn532.h — HAL adapter for Adafruit library
  • NFCConnectionI.h — added getReaderInfo() and logDiagnostics() virtual methods
  • NFCManager.cpp — removed static_cast<HardwareNFCConnection*> leaks
  • ConfigurationManagernfc_reader NVS key + accessor
  • ConfigHTML.h — NFC reader dropdown in Hardware section
  • WebServerManager — reader-agnostic /api/status + config API
  • main.cpp — reader selection based on NVS config
  • platformio.ini — Adafruit PN532@1.3.4 (pinned exact)
  • BoardPins.h — PN532 pin aliases (both boards)

Review fixes applied

  • Firmware version byte order (was showing IC byte as major version)
  • UID verification in reactivateTag() (prevent cross-tag corruption on swap)
  • GCode digit validation in sendAssignSpool() (injection prevention)
  • Memory cleanup on begin() failure paths
  • NFC reader config value validation in web POST
  • Reduced sendAssignSpool HTTP timeouts (1s/2s stopgap for loop blocking)

Test plan

  • pio run -e esp32s3zero compiles clean
  • pio run -e esp32dev compiles clean
  • 37/37 native tests pass
  • Flash with nfc_reader=pn5180, verify existing tag read/write (regression)
  • Flash with nfc_reader=pn532, test NTAG215 read/write (hardware test)
  • Verify /config page shows NFC reader dropdown
  • Verify /api/status shows nfc.reader string

Summary by CodeRabbit

  • New Features

    • Selectable NFC reader (PN5180 or PN532) in UI and runtime
    • PN532 support added (Adafruit-backed) and optional 3x4 keypad support
  • Bug Fixes

    • Reject invalid characters for spool tool assignments
    • Reduced network/HTTP timeouts for faster failure handling
  • Improvements

    • Improved NFC diagnostics/reporting and more robust tag reactivation/read-write
    • Config and troubleshooting UIs reflect generic NFC reader info
  • Chores

    • Added dependency and updated ignore rules

Add Adafruit PN532 as a second NFC reader option alongside PN5180.
NVS key `nfc_reader` selects which reader initializes at boot (one-binary model).
Both readers implement NFCConnectionI — zero changes to NFCManager or tag classification.

- HardwareNFCConnectionPN532: full NFCConnectionI impl (ISO14443A only)
- openprinttag_adafruit_pn532.h: HAL adapter for Adafruit library
- Fix abstraction leaks: getReaderInfo() + logDiagnostics() virtual methods
  replace static_cast<HardwareNFCConnection*> in NFCManager
- NVS config + web UI dropdown + installer prompt
- Review fixes: firmware version parsing, UID verification in reactivateTag,
  GCode digit validation, memory cleanup on init failure, config validation
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 28, 2026

Warning

Rate limit exceeded

@sjordan0228 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 6 minutes and 8 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 6 minutes and 8 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5a9cd2f1-8d99-4db3-bf99-da53b088f12b

📥 Commits

Reviewing files that changed from the base of the PR and between d632455 and 56557a0.

📒 Files selected for processing (1)
  • CHANGELOG.md
📝 Walkthrough

Walkthrough

Adds PN532 NFC reader support (Adafruit PN532 HAL and HardwareNFCConnectionPN532), makes NFC diagnostics reader‑agnostic, adds runtime NFC reader selection and config/UI, introduces keypad GPIO definitions and ENABLE_KEYPAD flag, tightens ApplicationManager HTTP timeouts and toolNumber validation, and adds Adafruit PN532 dependency.

Changes

Cohort / File(s) Summary
NFC PN532 Driver & HAL
src/HardwareNFCConnectionPN532.h, src/HardwareNFCConnectionPN532.cpp, lib/openprinttag/openprinttag_adafruit_pn532.h
New Adafruit PN532 SPI-backed NFC connection implementation with tag detection, read/write (with retries/reactivation), firmware reporting, reset logic, and an openprinttag HAL adapter exposing read/write page callbacks.
NFC Interface & Manager
src/NFCConnectionI.h, src/NFCManager.h, src/NFCManager.cpp, src/HardwareNFCConnection.h, src/HardwareNFCConnection.cpp
Introduced generic getReaderInfo() and logDiagnostics() on NFC interface with defaults; replaced PN5180-specific firmware API with getNfcReaderInfo() and updated diagnostics dispatch.
Configuration & Board Pins
src/ConfigurationManager.h, src/ConfigurationManager.cpp, include/BoardPins.h, include/UserConfig.example.h
Added persisted nfc_reader config (default "pn5180"), new accessor, PN532 SPI pin aliases, 3x4 keypad GPIO definitions for ESP32-S3 and ESP32-WROOM-32, and ENABLE_KEYPAD example flag.
Web UI / API
src/ConfigHTML.h, src/WebServerManager.cpp, src/TroubleshootingHTML.h
Added NFC reader selector to config UI; web API now accepts/returns nfc_reader and reports reader string in diagnostics; troubleshooting UI shows generic "NFC Reader" with dynamic reader info.
Application Integration & Misc
src/main.cpp, src/ApplicationManager.cpp, platformio.ini, .gitignore
Runtime selection/installation of PN532 connection before NFC init; input validation for toolNumber and reduced HTTP timeouts; added Adafruit PN532 lib dependency and .gstack/ to .gitignore.

Sequence Diagram

sequenceDiagram
    participant App as Application
    participant NFC as HardwareNFCConnectionPN532
    participant HAL as openprinttag HAL
    participant Device as Adafruit_PN532
    participant SPI as SPI Bus

    App->>NFC: detectTag()
    activate NFC
    NFC->>Device: readPassiveTargetID(timeout)
    Device->>SPI: SPI exchange (ISO14443A)
    SPI-->>Device: ATQA, SAK, UID
    Device-->>NFC: UID, length
    NFC->>NFC: store UID/ATQA/SAK
    deactivate NFC
    NFC-->>App: UID present

    App->>NFC: readISO14443Pages(start,pageCount,buf)
    activate NFC
    NFC->>HAL: getHal()
    loop per page
      App->>HAL: read_page(page) (via callback)
      HAL->>Device: mifareultralight_ReadPage()
      Device->>SPI: SPI read
      alt success
        HAL-->>App: OPT_OK + data
      else failure
        NFC->>NFC: reactivateTag()
        NFC->>Device: readPassiveTargetID()
        NFC->>HAL: retry read_page()
      end
    end
    deactivate NFC
    NFC-->>App: bytes read

    App->>NFC: writeISO14443Pages(start,pageCount,data)
    activate NFC
    loop per page
      NFC->>HAL: write_page(page,data)
      HAL->>Device: mifareultralight_WritePage()
      Device->>SPI: SPI write
      alt success
        HAL-->>NFC: OPT_OK
      else retry up to 3 times (reactivateTag between attempts)
        NFC->>Device: readPassiveTargetID()
      end
      NFC->>NFC: vTaskDelay()
    end
    deactivate NFC
    NFC-->>App: write result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related issues

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.05% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: PN532 NFC reader support' clearly and concisely describes the main change—adding Adafruit PN532 as a second NFC reader option alongside PN5180.
Description check ✅ Passed The PR description follows the template with a comprehensive Summary, detailed Changes list, and a Test plan. All major components are addressed: new files, modifications to existing abstractions, configuration changes, and testing status.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/pn532-support

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/TroubleshootingHTML.h (1)

252-252: ⚠️ Potential issue | 🟡 Minor

Error handler still references hardcoded "PN5180".

The catch block on line 252 still uses 'NFC Reader (PN5180)' in the names object, which is inconsistent with the reader-agnostic approach. This should be updated to just 'NFC Reader' to match line 234.

Proposed fix
-        const names = {wifi:'WiFi', mqtt:'MQTT Broker', spoolman:'Spoolman', nfc:'NFC Reader (PN5180)', heap:'Memory'};
+        const names = {wifi:'WiFi', mqtt:'MQTT Broker', spoolman:'Spoolman', nfc:'NFC Reader', heap:'Memory'};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/TroubleshootingHTML.h` at line 252, Update the hardcoded NFC reader label
in the error handler: change the entry in the names object that currently uses
'NFC Reader (PN5180)' to the generic 'NFC Reader' so it matches the
reader-agnostic label used elsewhere (e.g., the other occurrence at line ~234);
edit the const names = {wifi:'WiFi', mqtt:'MQTT Broker', spoolman:'Spoolman',
nfc:'NFC Reader (PN5180)', heap:'Memory'} to use nfc:'NFC Reader' instead.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@include/BoardPins.h`:
- Around line 65-72: Document that PIN_KEYPAD_ROW2 and PIN_KEYPAD_ROW3 (GPIO 16
and 17) can conflict with PSRAM on some ESP32-WROOM/WROVER modules and add a
short note next to the keypad pin definitions explaining this; instruct users to
avoid 16/17 on PSRAM-equipped boards and suggest alternative GPIOs (for example
use non-PSRAM pins such as GPIOs 2, 4, 12, 13, 14, 25-27, etc.), and optionally
recommend providing a comment or preprocessor/README hint to select alternate
PIN_KEYPAD_ROW* values when PSRAM/ESP32-WROVER is present.

In `@lib/openprinttag/openprinttag_adafruit_pn532.h`:
- Around line 14-16: The extern "C" linkage block around the header is
unnecessary and misleading because the header only declares static inline
functions that use C++ constructs (casts to Adafruit_PN532*) and therefore have
no external linkage; either remove the extern "C" { ... } / `#endif` pair (the
block beginning at the top of the file and the matching closing at lines near
74-76) or keep it but add a clear comment above it stating it is only for
documentation and does not affect the static inline functions; update references
to the static inline helpers (the inline functions that cast to Adafruit_PN532*)
accordingly so there’s no confusion about C vs C++ linkage.

In `@platformio.ini`:
- Line 27: The dependency "adafruit/Adafruit PN532@1.3.4" is pinned to an exact
version while other deps use semver-compatible ranges; update the entry (the
adafruit/Adafruit PN532 dependency line) to use a caret-prefixed range, e.g.,
change "@1.3.4" to "@^1.3.4", so it matches the project's versioning convention
and allows non-breaking updates.

In `@src/ApplicationManager.cpp`:
- Line 1000: The mutex and HTTP timeouts in ApplicationManager.cpp are much
shorter than the reference constant SpoolmanManager::HTTP_MUTEX_TIMEOUT; update
the call that uses g_httpMutex and xSemaphoreTake(pdMS_TO_TICKS(3000)) to use
SpoolmanManager::HTTP_MUTEX_TIMEOUT (or another shared constant) and similarly
align the HTTP connect/operation timeouts (1000ms/2000ms) to the same shared
timeout or document why shorter values are required for this code path (also
apply the same change where similar calls occur around the xSemaphoreTake/HTTP
setup later in the file).

In `@src/HardwareNFCConnectionPN532.cpp`:
- Around line 25-29: The allocation check after "pn532_ = new
Adafruit_PN532(PIN_PN532_SS, &SPI);" is unreliable across platforms because
plain new may throw instead of returning nullptr; to make the null-check
meaningful and portable, allocate with the nothrow variant (use std::nothrow)
when creating the Adafruit_PN532 instance and keep the existing if (!pn532_)
{...} error path, and ensure you include the <new> header; alternatively, if you
prefer throwing semantics, remove the nullptr check and wrap the allocation in a
try/catch for std::bad_alloc around the Adafruit_PN532 construction in
HardwareNFCConnectionPN532.cpp.
- Around line 64-86: Both reset() and hardwareReset() call pn532_->SAMConfig()
without checking its result; update both to verify SAMConfig's return value and
handle failures: for hardwareReset(), after pn532_->SAMConfig() check the bool
return and return false (and log an error) when it fails; for reset() (currently
void) either change its signature to return bool and return false on SAMConfig
failure (and true on success) or keep void but at minimum log an error and set
the object's initialized/error state so callers know initialization failed;
reference HardwareNFCConnectionPN532::reset,
HardwareNFCConnectionPN532::hardwareReset and the pn532_->SAMConfig() call when
making the change and update any callers if you change reset()'s return type.
- Around line 8-11: The code directly references the Adafruit PN532 internal
array pn532_packetbuffer[] which is fragile; update the file header to document
the minimum tested Adafruit PN532 library version (e.g., "Requires Adafruit
PN532 >= 1.3.4") and replace blind accesses with runtime checks: before reading
ATQA/SAK validate that pn532_packetbuffer exists and has the expected
length/indices (check buffer size >= 12 and that expected response offsets are
within bounds) and add assertions or guarded error-returns if the checks fail;
alternatively, where possible switch to public API calls instead of reading
pn532_packetbuffer directly (search for pn532_packetbuffer usages in
HardwareNFCConnectionPN532.cpp and add the described guards or API
replacements).

In `@src/WebServerManager.cpp`:
- Around line 529-531: The single-statement if that normalizes nfcVal should be
made explicit with braces for clarity and consistency: in the block where nfcVal
is set from doc (the code using nfcVal, strcmp(nfcVal, "pn532") and assigning
update.nfc_reader), wrap the body of the if (strcmp(nfcVal, "pn532") != 0)
statement in { } so the fallback assignment nfcVal = "pn5180"; is enclosed in
braces.

---

Outside diff comments:
In `@src/TroubleshootingHTML.h`:
- Line 252: Update the hardcoded NFC reader label in the error handler: change
the entry in the names object that currently uses 'NFC Reader (PN5180)' to the
generic 'NFC Reader' so it matches the reader-agnostic label used elsewhere
(e.g., the other occurrence at line ~234); edit the const names = {wifi:'WiFi',
mqtt:'MQTT Broker', spoolman:'Spoolman', nfc:'NFC Reader (PN5180)',
heap:'Memory'} to use nfc:'NFC Reader' instead.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 90837597-529f-4031-b99d-d421a9e070ca

📥 Commits

Reviewing files that changed from the base of the PR and between d9389ef and 57dbea9.

📒 Files selected for processing (19)
  • .gitignore
  • include/BoardPins.h
  • include/UserConfig.example.h
  • lib/openprinttag/openprinttag_adafruit_pn532.h
  • platformio.ini
  • src/ApplicationManager.cpp
  • src/ConfigHTML.h
  • src/ConfigurationManager.cpp
  • src/ConfigurationManager.h
  • src/HardwareNFCConnection.cpp
  • src/HardwareNFCConnection.h
  • src/HardwareNFCConnectionPN532.cpp
  • src/HardwareNFCConnectionPN532.h
  • src/NFCConnectionI.h
  • src/NFCManager.cpp
  • src/NFCManager.h
  • src/TroubleshootingHTML.h
  • src/WebServerManager.cpp
  • src/main.cpp

Comment thread include/BoardPins.h
Comment on lines +65 to +72
// 3x4 Matrix Keypad (ENABLE_KEYPAD)
#define PIN_KEYPAD_ROW1 15
#define PIN_KEYPAD_ROW2 16
#define PIN_KEYPAD_ROW3 17
#define PIN_KEYPAD_ROW4 18
#define PIN_KEYPAD_COL1 19
#define PIN_KEYPAD_COL2 21
#define PIN_KEYPAD_COL3 5
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Document potential GPIO conflicts on PSRAM-equipped ESP32-WROOM modules.

GPIO 16 and 17 (PIN_KEYPAD_ROW2, PIN_KEYPAD_ROW3) are used for PSRAM interface on ESP32-WROVER and some ESP32-WROOM-32D modules. Users with PSRAM-equipped boards may encounter conflicts.

Consider adding a note similar to the S3 section:

   // 3x4 Matrix Keypad (ENABLE_KEYPAD)
+  // NOTE: GPIO 16/17 conflict with PSRAM on ESP32-WROVER modules.
+  //       For PSRAM builds, remap ROW2/ROW3 to different pins.
   `#define` PIN_KEYPAD_ROW1  15
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// 3x4 Matrix Keypad (ENABLE_KEYPAD)
#define PIN_KEYPAD_ROW1 15
#define PIN_KEYPAD_ROW2 16
#define PIN_KEYPAD_ROW3 17
#define PIN_KEYPAD_ROW4 18
#define PIN_KEYPAD_COL1 19
#define PIN_KEYPAD_COL2 21
#define PIN_KEYPAD_COL3 5
// 3x4 Matrix Keypad (ENABLE_KEYPAD)
// NOTE: GPIO 16/17 conflict with PSRAM on ESP32-Wrover modules.
// For PSRAM builds, remap ROW2/ROW3 to different pins.
`#define` PIN_KEYPAD_ROW1 15
`#define` PIN_KEYPAD_ROW2 16
`#define` PIN_KEYPAD_ROW3 17
`#define` PIN_KEYPAD_ROW4 18
`#define` PIN_KEYPAD_COL1 19
`#define` PIN_KEYPAD_COL2 21
`#define` PIN_KEYPAD_COL3 5
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@include/BoardPins.h` around lines 65 - 72, Document that PIN_KEYPAD_ROW2 and
PIN_KEYPAD_ROW3 (GPIO 16 and 17) can conflict with PSRAM on some
ESP32-WROOM/WROVER modules and add a short note next to the keypad pin
definitions explaining this; instruct users to avoid 16/17 on PSRAM-equipped
boards and suggest alternative GPIOs (for example use non-PSRAM pins such as
GPIOs 2, 4, 12, 13, 14, 25-27, etc.), and optionally recommend providing a
comment or preprocessor/README hint to select alternate PIN_KEYPAD_ROW* values
when PSRAM/ESP32-WROVER is present.

Comment on lines +14 to +16
#ifdef __cplusplus
extern "C" {
#endif
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

extern "C" linkage is unnecessary for static inline functions.

These are static inline functions that won't have external linkage. The extern "C" block has no effect on them and could be misleading since the function bodies use C++ constructs (Adafruit_PN532* cast). Consider removing the extern "C" guards or, if kept for documentation purposes, add a comment clarifying their purpose.

Also applies to: 74-76

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/openprinttag/openprinttag_adafruit_pn532.h` around lines 14 - 16, The
extern "C" linkage block around the header is unnecessary and misleading because
the header only declares static inline functions that use C++ constructs (casts
to Adafruit_PN532*) and therefore have no external linkage; either remove the
extern "C" { ... } / `#endif` pair (the block beginning at the top of the file and
the matching closing at lines near 74-76) or keep it but add a clear comment
above it stating it is only for documentation and does not affect the static
inline functions; update references to the static inline helpers (the inline
functions that cast to Adafruit_PN532*) accordingly so there’s no confusion
about C vs C++ linkage.

Comment thread platformio.ini
knolleary/PubSubClient@^2.8
codewitch-honey-crisis/htcw_json@^0.2.5
adafruit/Adafruit NeoPixel
adafruit/Adafruit PN532@1.3.4
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

🧩 Analysis chain

🌐 Web query:

What is the latest version of Adafruit PN532 Arduino library?

💡 Result:

The latest version of the Adafruit PN532 Arduino library is 1.3.4, released on 2025-02-18.

Citations:


🏁 Script executed:

# Read the platformio.ini file to see all dependencies and their version pinning styles
cat platformio.ini

Repository: SpoolSense/spoolsense_scanner

Length of output: 1187


Version pinning is inconsistent with other dependencies.

Most dependencies use ^ prefix for semver-compatible updates (e.g., ^1.1.4, ^7.0.0), but Adafruit PN532@1.3.4 is pinned to an exact version. While 1.3.4 is the current latest version, consider using @^1.3.4 for consistency with the rest of the dependency list.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@platformio.ini` at line 27, The dependency "adafruit/Adafruit PN532@1.3.4" is
pinned to an exact version while other deps use semver-compatible ranges; update
the entry (the adafruit/Adafruit PN532 dependency line) to use a caret-prefixed
range, e.g., change "@1.3.4" to "@^1.3.4", so it matches the project's
versioning convention and allows non-breaking updates.


extern SemaphoreHandle_t g_httpMutex;
if (g_httpMutex && xSemaphoreTake(g_httpMutex, pdMS_TO_TICKS(5000)) != pdTRUE) {
if (g_httpMutex && xSemaphoreTake(g_httpMutex, pdMS_TO_TICKS(3000)) != pdTRUE) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

HTTP timeout values are significantly shorter than other HTTP operations.

The mutex timeout (3000ms) and HTTP timeouts (1000ms connect, 2000ms operation) are much shorter than SpoolmanManager::HTTP_MUTEX_TIMEOUT which is 10000ms. This inconsistency could cause premature failures if the network or Moonraker is slow. Consider aligning these values or documenting why shorter timeouts are appropriate for this specific use case.

Proposed timeout alignment
-    if (g_httpMutex && xSemaphoreTake(g_httpMutex, pdMS_TO_TICKS(3000)) != pdTRUE) {
+    if (g_httpMutex && xSemaphoreTake(g_httpMutex, pdMS_TO_TICKS(5000)) != pdTRUE) {
-    http.setConnectTimeout(1000);
-    http.setTimeout(2000);
+    http.setConnectTimeout(3000);
+    http.setTimeout(5000);

Also applies to: 1017-1018

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ApplicationManager.cpp` at line 1000, The mutex and HTTP timeouts in
ApplicationManager.cpp are much shorter than the reference constant
SpoolmanManager::HTTP_MUTEX_TIMEOUT; update the call that uses g_httpMutex and
xSemaphoreTake(pdMS_TO_TICKS(3000)) to use SpoolmanManager::HTTP_MUTEX_TIMEOUT
(or another shared constant) and similarly align the HTTP connect/operation
timeouts (1000ms/2000ms) to the same shared timeout or document why shorter
values are required for this code path (also apply the same change where similar
calls occur around the xSemaphoreTake/HTTP setup later in the file).

Comment on lines +8 to +11
// Adafruit_PN532 uses a file-scope global packet buffer. After readPassiveTargetID,
// the InListPassiveTarget response populates it with ATQA (bytes 9-10) and SAK (byte 11).
extern byte pn532_packetbuffer[];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Adafruit PN532 library pn532_packetbuffer API stability version 1.3.4

💡 Result:

The Adafruit PN532 Arduino library (https://github.com/adafruit/Adafruit-PN532) version 1.3.4, released on 2025-02-18, uses an internal pn532_packetbuffer array in its implementation for handling PN532 command/response packets, as evidenced by historical code references and usage in functions like ntag2xx_ReadPage. This buffer is not exposed as a public API class or method (no PN532_PacketBuffer class or public accessor found in Adafruit_PN532.h). The library API consists of high-level functions like SAMConfig, getFirmwareVersion, readPassiveTargetID, etc. No breaking changes to packet handling are noted in recent releases (1.3.x series focuses on minor updates like CI and version bumps); it remains stable and backward-compatible with prior versions (e.g., 1.2.x). For official documentation, refer to in-library examples and Doxygen-generated docs.

Citations:


Accessing non-public library internals creates maintenance risk.

The pn532_packetbuffer[] is an internal implementation detail of the Adafruit PN532 library, not part of the public API. Relying on it directly couples this code to the library's internal structure, which could break if the buffer layout changes, is renamed, or made static.

Document the minimum library version required (e.g., 1.3.4+) and consider adding assertions to validate buffer indices at runtime.

🧰 Tools
🪛 Clang (14.0.6)

[warning] 10-10: variable 'pn532_packetbuffer' is non-const and globally accessible, consider making it const

(cppcoreguidelines-avoid-non-const-global-variables)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/HardwareNFCConnectionPN532.cpp` around lines 8 - 11, The code directly
references the Adafruit PN532 internal array pn532_packetbuffer[] which is
fragile; update the file header to document the minimum tested Adafruit PN532
library version (e.g., "Requires Adafruit PN532 >= 1.3.4") and replace blind
accesses with runtime checks: before reading ATQA/SAK validate that
pn532_packetbuffer exists and has the expected length/indices (check buffer size
>= 12 and that expected response offsets are within bounds) and add assertions
or guarded error-returns if the checks fail; alternatively, where possible
switch to public API calls instead of reading pn532_packetbuffer directly
(search for pn532_packetbuffer usages in HardwareNFCConnectionPN532.cpp and add
the described guards or API replacements).

Comment on lines +25 to +29
pn532_ = new Adafruit_PN532(PIN_PN532_SS, &SPI);
if (!pn532_) {
Serial.println("PN532: Failed to allocate");
return false;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Null check after new may be redundant.

In standard C++, new throws std::bad_alloc on allocation failure rather than returning nullptr. However, some embedded toolchains (including ESP32 Arduino) may define new with nothrow semantics. This defensive check is acceptable for portability but may never trigger depending on the platform configuration.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/HardwareNFCConnectionPN532.cpp` around lines 25 - 29, The allocation
check after "pn532_ = new Adafruit_PN532(PIN_PN532_SS, &SPI);" is unreliable
across platforms because plain new may throw instead of returning nullptr; to
make the null-check meaningful and portable, allocate with the nothrow variant
(use std::nothrow) when creating the Adafruit_PN532 instance and keep the
existing if (!pn532_) {...} error path, and ensure you include the <new> header;
alternatively, if you prefer throwing semantics, remove the nullptr check and
wrap the allocation in a try/catch for std::bad_alloc around the Adafruit_PN532
construction in HardwareNFCConnectionPN532.cpp.

Comment thread src/HardwareNFCConnectionPN532.cpp
Comment thread src/WebServerManager.cpp
Comment on lines +529 to +531
const char* nfcVal = doc["nfc_reader"] | "pn5180";
if (strcmp(nfcVal, "pn532") != 0) nfcVal = "pn5180"; // only allow known values
strncpy(update.nfc_reader, nfcVal, sizeof(update.nfc_reader) - 1);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Input validation is effective but consider adding braces for clarity.

The validation logic correctly normalizes unknown values to "pn5180", preventing invalid reader configurations. Per static analysis, consider wrapping the single-statement if body in braces for consistency:

-    if (strcmp(nfcVal, "pn532") != 0) nfcVal = "pn5180";  // only allow known values
+    if (strcmp(nfcVal, "pn532") != 0) {
+        nfcVal = "pn5180";  // only allow known values
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const char* nfcVal = doc["nfc_reader"] | "pn5180";
if (strcmp(nfcVal, "pn532") != 0) nfcVal = "pn5180"; // only allow known values
strncpy(update.nfc_reader, nfcVal, sizeof(update.nfc_reader) - 1);
const char* nfcVal = doc["nfc_reader"] | "pn5180";
if (strcmp(nfcVal, "pn532") != 0) {
nfcVal = "pn5180"; // only allow known values
}
strncpy(update.nfc_reader, nfcVal, sizeof(update.nfc_reader) - 1);
🧰 Tools
🪛 Clang (14.0.6)

[warning] 529-529: variable 'nfcVal' is not initialized

(cppcoreguidelines-init-variables)


[warning] 530-530: statement should be inside braces

(readability-braces-around-statements)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/WebServerManager.cpp` around lines 529 - 531, The single-statement if
that normalizes nfcVal should be made explicit with braces for clarity and
consistency: in the block where nfcVal is set from doc (the code using nfcVal,
strcmp(nfcVal, "pn532") and assigning update.nfc_reader), wrap the body of the
if (strcmp(nfcVal, "pn532") != 0) statement in { } so the fallback assignment
nfcVal = "pn5180"; is enclosed in braces.

CodeRabbit finding — SAMConfig() failures were silently ignored in
reset() and hardwareReset(). Now logs on soft reset failure and
returns false on hard reset failure.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
src/HardwareNFCConnectionPN532.cpp (1)

64-69: ⚠️ Potential issue | 🟠 Major

Drop ready_ on reset failures.

Lines 64-69 and 73-87 detect re-init failures, but they never clear ready_, and hardwareReset() still returns true when pn532_ is null. That leaves the rest of the stack free to keep treating the reader as healthy after a failed recovery. Set ready_ = false before re-init starts, return false when there is no instance to reset, and only restore ready_ after the firmware probe and SAMConfig() both succeed.

Suggested fix
 void HardwareNFCConnectionPN532::reset() {
-    if (pn532_) {
-        pn532_->begin();
-        if (!pn532_->SAMConfig()) {
-            Serial.println("PN532: SAMConfig failed during reset");
-        }
+    ready_ = false;
+    if (!pn532_) {
+        return;
     }
+
+    pn532_->begin();
+    if (!pn532_->SAMConfig()) {
+        Serial.println("PN532: SAMConfig failed during reset");
+        return;
+    }
+
+    ready_ = true;
 }
 
 bool HardwareNFCConnectionPN532::hardwareReset() {
+    ready_ = false;
+    if (!pn532_) {
+        return false;
+    }
+
     // Toggle RST pin for hardware reset
     pinMode(PIN_PN532_RST, OUTPUT);
     digitalWrite(PIN_PN532_RST, LOW);
@@
-    if (pn532_) {
-        pn532_->begin();
-        uint32_t ver = pn532_->getFirmwareVersion();
-        if (!ver) return false;
-        if (!pn532_->SAMConfig()) return false;
-    }
-    return true;
+    pn532_->begin();
+    uint32_t ver = pn532_->getFirmwareVersion();
+    if (!ver) {
+        return false;
+    }
+    if (!pn532_->SAMConfig()) {
+        return false;
+    }
+
+    ready_ = true;
+    return true;
 }

Also applies to: 73-87

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/HardwareNFCConnectionPN532.cpp` around lines 64 - 69, In
HardwareNFCConnectionPN532::reset(), clear the ready_ flag immediately before
attempting re-init (set ready_ = false), and if pn532_ is null return false to
indicate failure; then perform pn532_->begin(), probe firmware version and call
pn532_->SAMConfig(), and only restore ready_ = true after both the firmware
probe and SAMConfig() succeed; apply the same ready_/return-false logic to the
similar re-init path in the later block (lines ~73-87) and ensure
hardwareReset() observes the updated return value when pn532_ is null.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/HardwareNFCConnectionPN532.cpp`:
- Around line 196-199: getReaderInfo currently always writes a firmware string
using fwMajor_/fwMinor_, which can produce a bogus "PN532 v0.0" after init
failure; update HardwareNFCConnectionPN532::getReaderInfo to check the same
ready_ flag used elsewhere (e.g., where uninitialized is handled in the 202–206
block) and, when !ready_, write a clear uninitialized/disabled message (or an
empty/explicitly prefixed status) into buf instead of the firmware version so
diagnostics match the readiness check.
- Around line 151-156: The current multi-page read logic in
HardwareNFCConnectionPN532.cpp can return a partial bytesRead on mid-read
errors; change it so any failed pn532_->mifareultralight_ReadPage or failed
reactivateTag leads to returning 0 (per NFCConnectionI.h). Specifically, in the
block that calls pn532_->mifareultralight_ReadPage(page, pageBuf) and then
reactivateTag(), replace returning bytesRead on failure with returning 0 so
callers never see truncated data; ensure all early-failure paths in the read
routine (including the case where reactivateTag() itself fails) return 0 instead
of the current bytesRead.

---

Duplicate comments:
In `@src/HardwareNFCConnectionPN532.cpp`:
- Around line 64-69: In HardwareNFCConnectionPN532::reset(), clear the ready_
flag immediately before attempting re-init (set ready_ = false), and if pn532_
is null return false to indicate failure; then perform pn532_->begin(), probe
firmware version and call pn532_->SAMConfig(), and only restore ready_ = true
after both the firmware probe and SAMConfig() succeed; apply the same
ready_/return-false logic to the similar re-init path in the later block (lines
~73-87) and ensure hardwareReset() observes the updated return value when pn532_
is null.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: b4d419af-a8c4-4c0f-8792-558dd74239cf

📥 Commits

Reviewing files that changed from the base of the PR and between 57dbea9 and d632455.

📒 Files selected for processing (1)
  • src/HardwareNFCConnectionPN532.cpp

Comment on lines +151 to +156
// Try read, reactivate tag on failure and retry once
if (!pn532_->mifareultralight_ReadPage(page, pageBuf)) {
if (!reactivateTag()) return bytesRead;
if (!pn532_->mifareultralight_ReadPage(page, pageBuf)) {
return bytesRead;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Return 0 on any failed multi-page read.

src/NFCConnectionI.h documents this API as returning 0 on failure, but Lines 153-156 return a partial byte count after a mid-read error. That can surface truncated NTAG/OpenPrintTag data as a successful read to callers that only check for non-zero.

Suggested fix
         if (!pn532_->mifareultralight_ReadPage(page, pageBuf)) {
-            if (!reactivateTag()) return bytesRead;
+            if (!reactivateTag()) return 0;
             if (!pn532_->mifareultralight_ReadPage(page, pageBuf)) {
-                return bytesRead;
+                return 0;
             }
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Try read, reactivate tag on failure and retry once
if (!pn532_->mifareultralight_ReadPage(page, pageBuf)) {
if (!reactivateTag()) return bytesRead;
if (!pn532_->mifareultralight_ReadPage(page, pageBuf)) {
return bytesRead;
}
// Try read, reactivate tag on failure and retry once
if (!pn532_->mifareultralight_ReadPage(page, pageBuf)) {
if (!reactivateTag()) return 0;
if (!pn532_->mifareultralight_ReadPage(page, pageBuf)) {
return 0;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/HardwareNFCConnectionPN532.cpp` around lines 151 - 156, The current
multi-page read logic in HardwareNFCConnectionPN532.cpp can return a partial
bytesRead on mid-read errors; change it so any failed
pn532_->mifareultralight_ReadPage or failed reactivateTag leads to returning 0
(per NFCConnectionI.h). Specifically, in the block that calls
pn532_->mifareultralight_ReadPage(page, pageBuf) and then reactivateTag(),
replace returning bytesRead on failure with returning 0 so callers never see
truncated data; ensure all early-failure paths in the read routine (including
the case where reactivateTag() itself fails) return 0 instead of the current
bytesRead.

Comment on lines +196 to +199
void HardwareNFCConnectionPN532::getReaderInfo(char* buf, size_t len) const {
if (buf && len > 0) {
snprintf(buf, len, "PN532 v%d.%d", fwMajor_, fwMinor_);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Keep getReaderInfo() consistent with the readiness check.

Lines 196-199 always emit a firmware version, while Lines 202-206 already treat !ready_ as uninitialized. That can leave the status/diagnostics path reporting a bogus PN532 v0.0-style string after init failure.

Suggested fix
 void HardwareNFCConnectionPN532::getReaderInfo(char* buf, size_t len) const {
     if (buf && len > 0) {
+        if (!ready_) {
+            snprintf(buf, len, "PN532 (not initialized)");
+            return;
+        }
         snprintf(buf, len, "PN532 v%d.%d", fwMajor_, fwMinor_);
     }
 }

Also applies to: 202-206

🧰 Tools
🪛 Clang (14.0.6)

[warning] 196-196: method 'getReaderInfo' can be made static

(readability-convert-member-functions-to-static)

🪛 Cppcheck (2.20.0)

[style] 196-196: The function 'getReaderInfo' is never used.

(unusedFunction)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/HardwareNFCConnectionPN532.cpp` around lines 196 - 199, getReaderInfo
currently always writes a firmware string using fwMajor_/fwMinor_, which can
produce a bogus "PN532 v0.0" after init failure; update
HardwareNFCConnectionPN532::getReaderInfo to check the same ready_ flag used
elsewhere (e.g., where uninitialized is handled in the 202–206 block) and, when
!ready_, write a clear uninitialized/disabled message (or an empty/explicitly
prefixed status) into buf instead of the firmware version so diagnostics match
the readiness check.

@sjordan0228 sjordan0228 merged commit bde26e7 into dev Mar 28, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant