-
Notifications
You must be signed in to change notification settings - Fork 6
Add troubleshooting page with connectivity diagnostics #9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 — 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 + ' — ' + 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 — ' + 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)' + | ||
| ' — 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'); | ||
| }); | ||
| } | ||
|
|
||
| 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"; | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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