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
13 changes: 13 additions & 0 deletions src/SharedJS.h
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,7 @@ function prefillFromTag(fieldMap) {

var _spoolmanPickerSpools = [];
var _spoolmanPickerFieldMap = {};
var _selectedSpoolId = -1;

function renderSpoolmanPicker(containerId, fieldMap) {
var container = document.getElementById(containerId);
Expand Down Expand Up @@ -434,6 +435,7 @@ function filterSpoolmanPicker(spools, query, fieldMap) {
}

function selectSpoolmanSpool(spoolId) {
_selectedSpoolId = spoolId;
var spool = _spoolmanPickerSpools.find(function(s) { return s.id === spoolId; });
if (spool) fillFromSpoolman(spool, _spoolmanPickerFieldMap);
}
Expand Down Expand Up @@ -556,6 +558,17 @@ async function sharedWriteFlow(config) {
backBtn.classList.add('hidden');
anotherBtn.classList.add('hidden');

if (_selectedSpoolId > 0) {
try {
await api('/api/spoolman/pending-link', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({spool_id: _selectedSpoolId})
});
} catch(e) {}
_selectedSpoolId = -1;
}

try {
var presentStatus = await waitForTag(8000);

Expand Down
34 changes: 34 additions & 0 deletions src/SpoolmanManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1256,6 +1256,14 @@ bool SpoolmanManager::lookupSpoolByUid(const char* uid, SpoolDetails& outDetails
return ok;
}

void SpoolmanManager::setPendingLink(int32_t spoolId) {
// Store timestamp before ID so that any reader seeing a valid ID is
// guaranteed the timestamp is already set (happens-before).
pendingLinkSetAt_.store(millis());
pendingLinkSpoolId_.store(spoolId);
Serial.printf("SpoolmanManager: Pending link set for spool %d\n", spoolId);
}

float SpoolmanManager::deductFromSpoolman(const char* uid, float grams) {
if (!isConfigured()) return 0.0f;
if (xSemaphoreTake(httpMutex_, HTTP_MUTEX_TIMEOUT) != pdTRUE) {
Expand Down Expand Up @@ -1329,6 +1337,32 @@ bool SpoolmanManager::syncSpool(const SpoolmanSyncRequest& req, int& resolvedSpo
resolvedSpoolmanId = -1;
bool success = false;

// If the writer pre-registered a spool to link, consume it and patch nfc_id before syncing.
// This prevents auto-sync from creating a duplicate when a pre-selected spool exists.
// Read timestamp before exchange: setPendingLink stores time before ID, so a valid ID
// guarantees the timestamp is already set (no race window on the age check).
uint32_t linkSetAt = pendingLinkSetAt_.load();
int32_t linkSpoolId = pendingLinkSpoolId_.exchange(-1);
if (linkSpoolId > 0) {
uint32_t age = millis() - linkSetAt;
if (age < PENDING_LINK_TIMEOUT_MS) {
char patchBody[64];
snprintf(patchBody, sizeof(patchBody), "{\"extra\":{\"nfc_id\":\"\\\"%s\\\"\"}}", req.spool_id);
char patchPath[48];
snprintf(patchPath, sizeof(patchPath), "/api/v1/spool/%d", linkSpoolId);
String patchResp;
int patchCode = httpPatch(patchPath, patchBody, patchResp);
if (patchCode == 200) {
storeCachedSpoolmanId(req.spool_id, linkSpoolId);
Serial.printf("SpoolmanManager: Linked nfc_id=%s to spool %d via pending link\n", req.spool_id, linkSpoolId);
} else {
Serial.printf("SpoolmanManager: Pending link PATCH failed (HTTP %d) for spool %d\n", patchCode, linkSpoolId);
}
} else {
Serial.printf("SpoolmanManager: Pending link for spool %d expired (%ums old), discarding\n", linkSpoolId, age);
}
}

// Prefer a known-good ID for this spool UID over potentially stale tag data.
int32_t preferredSpoolmanId = req.spoolman_id;
int32_t cachedSpoolmanId = lookupCachedSpoolmanId(req.spool_id);
Expand Down
8 changes: 8 additions & 0 deletions src/SpoolmanManager.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#ifndef SPOOLMAN_MANAGER_H
#define SPOOLMAN_MANAGER_H

#include <atomic>
#include <cstdint>
#include <freertos/FreeRTOS.h>
#include <freertos/queue.h>
Expand Down Expand Up @@ -53,6 +54,9 @@ class SpoolmanManager {
bool isConfigured() const;
bool getSpoolDetails(int32_t spoolmanId, SpoolDetails& outDetails);
void invalidateCachedSpoolmanId(const char* spoolId);
// Pre-emptively link the next detected tag to an existing spool instead of auto-creating.
// Set before write flow starts; consumed on first tag sync within PENDING_LINK_TIMEOUT_MS.
void setPendingLink(int32_t spoolId);

// Deduct weight directly in Spoolman for non-writable tags.
// Returns grams deducted, or 0 on failure (caller should retry later).
Expand Down Expand Up @@ -99,6 +103,10 @@ class SpoolmanManager {
static constexpr UBaseType_t TASK_PRIORITY = 1;
static constexpr TickType_t HTTP_MUTEX_TIMEOUT = pdMS_TO_TICKS(10000);
static constexpr uint32_t SYNC_CACHE_TTL_MS = 2 * 60 * 60 * 1000; // 2 hours
static constexpr uint32_t PENDING_LINK_TIMEOUT_MS = 120000; // 2 minutes

std::atomic<int32_t> pendingLinkSpoolId_{-1};
std::atomic<uint32_t> pendingLinkSetAt_{0};
};

#endif // SPOOLMAN_MANAGER_H
23 changes: 22 additions & 1 deletion src/WebServerManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,8 @@ bool WebServerManager::begin(bool apMode, uint16_t port) {
_server.on("/api/write-openspool", HTTP_POST, [this]() { handleApiWriteOpenSpool(); });
_server.on("/api/register-uid", HTTP_POST, [this]() { handleApiRegisterUid(); });
_server.on("/api/spoolman/spools", HTTP_GET, [this]() { handleApiSpoolmanSpools(); });
_server.on("/api/spoolman/link", HTTP_POST, [this]() { handleApiSpoolmanLink(); });
_server.on("/api/spoolman/link", HTTP_POST, [this]() { handleApiSpoolmanLink(); });
_server.on("/api/spoolman/pending-link", HTTP_POST, [this]() { handleApiSpoolmanPendingLink(); });
_server.on("/api/spoolman/find-vendor", HTTP_GET, [this]() { handleApiSpoolmanFindVendor(); });
_server.on("/api/spoolman/find-filament", HTTP_GET, [this]() { handleApiSpoolmanFindFilament(); });
_server.on("/api/spoolman/save-enrichment", HTTP_POST, [this]() { handleApiSpoolmanSaveEnrichment(); });
Expand Down Expand Up @@ -615,6 +616,26 @@ void WebServerManager::handleApiSpoolmanLink() {
_server.send(200, "application/json", "{\"success\":true}");
}

void WebServerManager::handleApiSpoolmanPendingLink() {
_server.sendHeader("Access-Control-Allow-Origin", "*");

StaticJsonDocument<128> doc;
if (deserializeJson(doc, _server.arg("plain"))) {
sendError(400, "Invalid JSON");
return;
}

int spoolId = doc["spool_id"] | -1;
if (spoolId <= 0) {
sendError(400, "spool_id required");
return;
}

SpoolmanManager::getInstance().setPendingLink(spoolId);
Serial.printf("WebServerManager: Pending link set for spool %d\n", spoolId);
_server.send(200, "application/json", "{\"success\":true}");
}

void WebServerManager::handleApiDiagnostics() {
_server.sendHeader("Access-Control-Allow-Origin", "*");

Expand Down
1 change: 1 addition & 0 deletions src/WebServerManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ class WebServerManager {
void handleApiRegisterUid();
void handleApiSpoolmanSpools();
void handleApiSpoolmanLink();
void handleApiSpoolmanPendingLink();
void handleApiSpoolmanFindVendor();
void handleApiSpoolmanFindFilament();
void handleApiSpoolmanSaveEnrichment();
Expand Down
Loading