Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/ConfigHTML.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ const char CONFIG_HTML[] PROGMEM = R"rawliteral(
<a href="/writer/tigertag">TigerTag</a>
<a href="/writer/opentag3d">OpenTag3D</a>
<a href="/update">Update</a>
<a href="/troubleshooting">Troubleshooting</a>
<a href="/config">Config</a>
</nav>

Expand Down
12 changes: 9 additions & 3 deletions src/HardwareNFCConnection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,15 @@ bool HardwareNFCConnection::begin() {

// Read firmware version
uint8_t firmwareVersion[2];
nfc_->readEEprom(PN5180_FIRMWARE_VERSION, firmwareVersion, 2);
Serial.printf("HardwareNFCConnection: PN5180 firmware: %d.%d\n",
firmwareVersion[1], firmwareVersion[0]);
if (nfc_->readEEprom(PN5180_FIRMWARE_VERSION, firmwareVersion, 2)) {
fw_[0] = firmwareVersion[0];
fw_[1] = firmwareVersion[1];
pn5180Ready_ = true;
Serial.printf("HardwareNFCConnection: PN5180 firmware: %d.%d\n",
firmwareVersion[1], firmwareVersion[0]);
} else {
Serial.println("HardwareNFCConnection: Failed to read PN5180 firmware version");
}

// Setup RF for ISO15693
if (!nfc_->setupRF()) {
Expand Down
7 changes: 7 additions & 0 deletions src/HardwareNFCConnection.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ class HardwareNFCConnection : public NFCConnectionI {
uint16_t getLastATQA() const override { return lastATQA_; }
// Diagnostics: log RF_STATUS, IRQ_STATUS, SYSTEM_STATUS registers
void logDiagnostics();
// Returns PN5180 firmware version bytes (set during begin()). fw[0]=minor, fw[1]=major.
void getPN5180FirmwareVersion(uint8_t fw[2]) const { fw[0] = fw_[0]; fw[1] = fw_[1]; }
bool isPN5180Ready() const { return pn5180Ready_; }

private:
PN5180ISO15693* nfc_ = nullptr;
Expand All @@ -43,6 +46,10 @@ class HardwareNFCConnection : public NFCConnectionI {
uint8_t lastSAK_ = 0;
uint16_t lastATQA_ = 0;

// PN5180 firmware version (populated during begin())
uint8_t fw_[2] = {0, 0};
bool pn5180Ready_ = false;

// Static HAL callbacks
static opt_error_t halReadPage(void* ctx, uint8_t page, uint8_t* buffer);
static opt_error_t halWritePage(void* ctx, uint8_t page, const uint8_t* data);
Expand Down
1 change: 1 addition & 0 deletions src/LandingHTML.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const char LANDING_HTML[] PROGMEM = R"rawliteral(
<a href="/writer/tigertag">TigerTag</a>
<a href="/writer/opentag3d">OpenTag3D</a>
<a href="/update">Update</a>
<a href="/troubleshooting">Troubleshooting</a>
<a href="/config">Config</a>
</nav>

Expand Down
12 changes: 12 additions & 0 deletions src/NFCManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,18 @@ bool NFCManager::getLastOpenTag3DData(opentag3d_t& out) {
return valid;
}

bool NFCManager::getPN5180FirmwareVersion(uint8_t fw[2]) const {
#ifndef NATIVE_TEST
if (!connection_) return false;
auto* hw = static_cast<HardwareNFCConnection*>(connection_);
if (!hw->isPN5180Ready()) return false;
hw->getPN5180FirmwareVersion(fw);
return true;
#else
return false;
#endif
}

void NFCManager::pauseScanTask() {
#ifndef NATIVE_TEST
if (scanTaskHandle) {
Expand Down
2 changes: 2 additions & 0 deletions src/NFCManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ class NFCManager {
bool getCurrentSpoolState(CurrentSpoolState& out);
bool getLastTigerTagData(TigerTagData& out);
bool getLastOpenTag3DData(opentag3d_t& out);
// Returns PN5180 firmware version. fw[0]=minor, fw[1]=major. Returns false if not available.
bool getPN5180FirmwareVersion(uint8_t fw[2]) const;
void pauseScanTask();
void resumeScanTask();

Expand Down
3 changes: 2 additions & 1 deletion src/TagWriterHTML.h → src/OpenPrintTagWriterHTML.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
// POST /api/write-tag — write all fields to tag (JSON body)
// POST /api/format-tag — format a blank tag (optional JSON body: {"uid":"..."})

const char TAG_WRITER_HTML[] PROGMEM = R"rawliteral(
const char OPENPRINTTAG_WRITER_HTML[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html lang="en">
<head>
Expand All @@ -26,6 +26,7 @@ const char TAG_WRITER_HTML[] PROGMEM = R"rawliteral(
<a href="/writer/tigertag">TigerTag</a>
<a href="/writer/opentag3d">OpenTag3D</a>
<a href="/update">Update</a>
<a href="/troubleshooting">Troubleshooting</a>
<a href="/config">Config</a>
</nav>

Expand Down
1 change: 1 addition & 0 deletions src/OpenTag3DWriterHTML.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const char OPENTAG3D_WRITER_HTML[] PROGMEM = R"rawliteral(
<a href="/writer/tigertag">TigerTag</a>
<a href="/writer/opentag3d">OpenTag3D</a>
<a href="/update">Update</a>
<a href="/troubleshooting">Troubleshooting</a>
<a href="/config">Config</a>
</nav>

Expand Down
1 change: 1 addition & 0 deletions src/ReaderHTML.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const char READER_HTML[] PROGMEM = R"rawliteral(
<a href="/writer/tigertag">TigerTag</a>
<a href="/writer/opentag3d">OpenTag3D</a>
<a href="/update">Update</a>
<a href="/troubleshooting">Troubleshooting</a>
<a href="/config">Config</a>
</nav>

Expand Down
1 change: 1 addition & 0 deletions src/TigerTagWriterHTML.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const char TIGERTAG_WRITER_HTML[] PROGMEM = R"rawliteral(
<a href="/writer/tigertag">TigerTag</a>
<a href="/writer/opentag3d">OpenTag3D</a>
<a href="/update">Update</a>
<a href="/troubleshooting">Troubleshooting</a>
<a href="/config">Config</a>
</nav>

Expand Down
272 changes: 272 additions & 0 deletions src/TroubleshootingHTML.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
#pragma once

// Troubleshooting page served at GET /troubleshooting
// Runs connectivity and hardware checks, displays scanner device ID prominently.

const char TROUBLESHOOTING_HTML[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Troubleshooting &mdash; SpoolSense</title>
<link rel="stylesheet" href="/css/shared.css" />
<style>
.device-id-box {
background: rgba(99,102,241,.12);
border: 1px solid rgba(99,102,241,.35);
border-radius: 14px;
padding: 18px 22px;
margin-bottom: 24px;
text-align: center;
}
.device-id-label { font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: .08em; margin-bottom: 6px; }
.device-id-value {
font-size: 28px; font-weight: 800; letter-spacing: .12em;
font-family: monospace; color: #a5b4fc;
}
.device-id-hint { font-size: 12px; color: var(--muted); margin-top: 6px; }
.check-list { display: flex; flex-direction: column; gap: 10px; }
.check-item {
display: flex; align-items: center; gap: 14px;
background: rgba(255,255,255,.03);
border: 1px solid var(--border);
border-radius: 12px;
padding: 14px 16px;
}
.check-icon { font-size: 20px; flex-shrink: 0; width: 24px; text-align: center; }
.check-body { flex: 1; min-width: 0; }
.check-name { font-size: 14px; font-weight: 700; color: #e5e7eb; }
.check-detail { font-size: 12px; color: var(--muted); margin-top: 2px; }
.check-item.pass { border-color: rgba(74,222,128,.3); }
.check-item.fail { border-color: rgba(248,113,113,.3); }
.check-item.warn { border-color: rgba(251,191,36,.3); }
.check-item.loading { opacity: .6; }
.run-btn {
width: 100%; margin-top: 20px;
padding: 13px; font-size: 15px; font-weight: 700;
background: var(--accent); color: #fff;
border: none; border-radius: 12px; cursor: pointer;
}
.run-btn:hover { opacity: .88; }
.copy-btn {
font-size: 11px; padding: 4px 10px;
background: rgba(99,102,241,.2); color: #a5b4fc;
border: 1px solid rgba(99,102,241,.4); border-radius: 6px;
cursor: pointer; margin-top: 8px;
}
.copy-btn:hover { background: rgba(99,102,241,.35); }
</style>
</head>
<body>
<div class="wrap">
<nav>
<span class="nav-brand">SpoolSense</span>
<a href="/">Home</a>
<a href="/reader">Reader</a>
<a href="/writer/openprinttag">OpenPrintTag</a>
<a href="/writer/tigertag">TigerTag</a>
<a href="/writer/opentag3d">OpenTag3D</a>
<a href="/config">Config</a>
<a href="/update">Update</a>
<a href="/troubleshooting" class="active">Troubleshooting</a>
</nav>

<h2 style="margin-bottom:6px">Troubleshooting</h2>
<p style="color:var(--muted);font-size:14px;margin-bottom:20px">
Verify your scanner setup. Copy your Device ID for middleware configuration.
</p>

<div class="device-id-box">
<div class="device-id-label">Scanner Device ID</div>
<div class="device-id-value" id="deviceId">—</div>
<div class="device-id-hint">Use this ID in your SpoolSense middleware config</div>
<br>
<button class="copy-btn" onclick="copyDeviceId()">Copy ID</button>
</div>

<div class="check-list" id="checkList">
<div class="check-item loading" id="chk-wifi">
<div class="check-icon">⟳</div>
<div class="check-body">
<div class="check-name">WiFi</div>
<div class="check-detail">Checking...</div>
</div>
</div>
<div class="check-item loading" id="chk-mqtt">
<div class="check-icon">⟳</div>
<div class="check-body">
<div class="check-name">MQTT Broker</div>
<div class="check-detail">Checking...</div>
</div>
</div>
<div class="check-item loading" id="chk-spoolman">
<div class="check-icon">⟳</div>
<div class="check-body">
<div class="check-name">Spoolman</div>
<div class="check-detail">Checking...</div>
</div>
</div>
<div class="check-item loading" id="chk-nfc">
<div class="check-icon">⟳</div>
<div class="check-body">
<div class="check-name">NFC Reader (PN5180)</div>
<div class="check-detail">Checking...</div>
</div>
</div>
<div class="check-item loading" id="chk-heap">
<div class="check-icon">⟳</div>
<div class="check-body">
<div class="check-name">Memory</div>
<div class="check-detail">Checking...</div>
</div>
</div>
</div>

<button class="run-btn" id="runBtn" onclick="runChecks()">Run Checks</button>
</div>

<script>
function setCheck(id, status, name, detail) {
const el = document.getElementById('chk-' + id);
el.className = 'check-item ' + status;
const icons = { pass: '✓', fail: '✗', warn: '⚠' };
el.innerHTML =
'<div class="check-icon">' + (icons[status] || '⟳') + '</div>' +
'<div class="check-body">' +
'<div class="check-name">' + name + '</div>' +
'<div class="check-detail">' + detail + '</div>' +
'</div>';
}

function signalLabel(rssi) {
if (rssi >= -50) return 'Excellent';
if (rssi >= -65) return 'Good';
if (rssi >= -75) return 'Fair';
return 'Weak';
}

function formatBytes(b) {
return (b / 1024).toFixed(1) + ' KB';
}

function copyDeviceId() {
const id = document.getElementById('deviceId').textContent;
if (id && id !== '—') {
navigator.clipboard.writeText(id).then(() => {
const btn = document.querySelector('.copy-btn');
btn.textContent = 'Copied!';
setTimeout(() => btn.textContent = 'Copy ID', 1500);
});
}
}

async function runChecks() {
const btn = document.getElementById('runBtn');
btn.disabled = true;
btn.textContent = 'Running...';

// Reset all to loading
['wifi','mqtt','spoolman','nfc','heap'].forEach(id => {
const el = document.getElementById('chk-' + id);
el.className = 'check-item loading';
el.querySelector('.check-detail').textContent = 'Checking...';
el.querySelector('.check-icon').textContent = '⟳';
});

try {
const r = await fetch('/api/diagnostics');
const d = await r.json();

// Device ID
if (d.device_id) {
document.getElementById('deviceId').textContent = d.device_id;
}

// WiFi
if (d.wifi) {
const w = d.wifi;
if (w.connected) {
const label = signalLabel(w.rssi_dbm);
const status = w.rssi_dbm >= -75 ? 'pass' : 'warn';
setCheck('wifi', status, 'WiFi',
w.ssid + ' &mdash; ' + label + ' (' + w.rssi_dbm + ' dBm)');
} else {
setCheck('wifi', 'fail', 'WiFi', 'Not connected. Check SSID and password in Config.');
}
}

// MQTT
if (d.mqtt) {
const m = d.mqtt;
if (m.connected) {
setCheck('mqtt', 'pass', 'MQTT Broker', 'Connected to ' + m.broker);
} else if (!m.enabled) {
setCheck('mqtt', 'warn', 'MQTT Broker', 'Disabled in config');
} else {
setCheck('mqtt', 'fail', 'MQTT Broker',
'Cannot reach ' + m.broker + '. Check broker address and port in Config.');
}
}

// Spoolman
if (d.spoolman) {
const s = d.spoolman;
if (!s.enabled) {
setCheck('spoolman', 'warn', 'Spoolman', 'Disabled in config');
} else if (s.reachable) {
setCheck('spoolman', 'pass', 'Spoolman',
'Connected &mdash; ' + s.url + (s.version ? ' (v' + s.version + ')' : ''));
} else {
setCheck('spoolman', 'fail', 'Spoolman',
'Cannot reach ' + s.url + '. Check URL in Config.');
}
}

// NFC
if (d.nfc) {
const n = d.nfc;
if (n.ok) {
setCheck('nfc', 'pass', 'NFC Reader (PN5180)',
'Firmware v' + n.fw_major + '.' + n.fw_minor);
} else {
setCheck('nfc', 'fail', 'NFC Reader (PN5180)',
'Not responding. Check SPI wiring.');
}
}

// Heap
if (d.memory) {
const mem = d.memory;
const status = mem.free_bytes > 50000 ? 'pass' : 'warn';
const usedPct = mem.total_bytes > 0 ? Math.round(mem.used_bytes / mem.total_bytes * 100) : 0;
setCheck('heap', status, 'Memory',
'Free: ' + formatBytes(mem.free_bytes) +
' / Total: ' + formatBytes(mem.total_bytes) +
' (' + usedPct + '% used)' +
' &mdash; Uptime: ' + formatUptime(mem.uptime_s));
}

} catch(e) {
const names = {wifi:'WiFi', mqtt:'MQTT Broker', spoolman:'Spoolman', nfc:'NFC Reader (PN5180)', heap:'Memory'};
['wifi','mqtt','spoolman','nfc','heap'].forEach(id => {
setCheck(id, 'fail', names[id] || id, 'Error fetching diagnostics');
});
}
Comment on lines +250 to +255
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

Minor: Error handler shows raw IDs instead of proper names.

When the diagnostics fetch fails, the error message shows the raw ID (e.g., "wifi") instead of the proper display name (e.g., "WiFi"). This is a minor cosmetic issue.

       } catch(e) {
-        ['wifi','mqtt','spoolman','nfc','heap'].forEach(id => {
-          setCheck(id, 'fail', id, 'Error fetching diagnostics');
+        const names = {wifi:'WiFi', mqtt:'MQTT Broker', spoolman:'Spoolman', nfc:'NFC Reader (PN5180)', heap:'Memory'};
+        ['wifi','mqtt','spoolman','nfc','heap'].forEach(id => {
+          setCheck(id, 'fail', names[id], 'Error fetching diagnostics');
         });
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/TroubleshootingHTML.h` around lines 250 - 254, The catch block is passing
raw service IDs ('wifi','mqtt','spoolman','nfc','heap') into setCheck, which
shows raw IDs instead of user-facing names; change the catch to map each id to
its display name (e.g. via an existing helper or a new lookup like
getDisplayName(id) or a DISPLAY_NAME map) and pass that display name as the
third argument to setCheck (keep setCheck(id, 'fail', <displayName>, 'Error
fetching diagnostics')). Ensure you reference the same ids and the setCheck
function when implementing the lookup.


btn.disabled = false;
btn.textContent = 'Run Checks Again';
}

function formatUptime(s) {
if (s < 60) return s + 's';
if (s < 3600) return Math.floor(s/60) + 'm ' + (s%60) + 's';
return Math.floor(s/3600) + 'h ' + Math.floor((s%3600)/60) + 'm';
}

// Auto-run on page load
window.addEventListener('load', runChecks);
</script>
</body>
</html>
)rawliteral";
Loading