Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
665a3b7
fix: only re-publish HA discovery when UID changes, legacy cleanup on…
sjordan0228 Mar 30, 2026
368de8b
fix: stale openprinttag comment in MQTT command handler
sjordan0228 Mar 30, 2026
5832e32
perf: reuse HTTP connection for Spoolman API calls (#30)
sjordan0228 Mar 30, 2026
b3f819b
feat: show extruder/bed temps on reader page for NFC+ tags (#56)
sjordan0228 Mar 30, 2026
d41ccbd
fix: HA stack overflow, legacy cleanup removal, NFC+ reader improvements
sjordan0228 Mar 30, 2026
6a37eb2
feat: link/re-assign NFC+ tags to Spoolman spools from reader page (#54)
sjordan0228 Mar 30, 2026
86547b5
fix: remove duplicate filament name line on TFT spool display
sjordan0228 Mar 30, 2026
4eb9464
spec: tag writer auto-populate from scanned tag data (#57)
sjordan0228 Mar 30, 2026
52e2d7f
Add docs/superpowers/ to gitignore — specs stay local
sjordan0228 Mar 30, 2026
40770bb
feat: add prefillFromTag() to SharedJS for writer auto-populate (#57)
sjordan0228 Mar 30, 2026
d3fb491
feat: OpenPrintTag writer pre-fills from scanned tag (#57)
sjordan0228 Mar 30, 2026
cea5ac5
feat: TigerTag writer pre-fills from scanned tag (#57)
sjordan0228 Mar 30, 2026
febe836
feat: OpenTag3D writer pre-fills from scanned tag (#57)
sjordan0228 Mar 30, 2026
9defd76
fix: code review fixes for #54 spool picker
sjordan0228 Mar 30, 2026
08d8c19
fix: CodeRabbit review fixes for v1.6.1 PR
sjordan0228 Mar 30, 2026
05e1428
Bump version to 1.6.1, changelog for NFC+ improvements + HA optimization
sjordan0228 Mar 30, 2026
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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ docs/tigertag-architecture.md
docs/deep-thoughts.md
CODE-CLEANUP.md
docs/writer-ui-plan.md
docs/superpowers/
.gstack/
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
# Changelog

## [1.6.1] - 2026-03-30

### Added

- **Link/re-assign NFC+ tags to Spoolman spools** — spool picker on reader page with search. Link unlinked tags or re-assign existing ones. Proxy endpoints avoid CORS. (#54)
- **Tag writer auto-populate from scanned tag** — place a tag on the reader, open any writer page, form fields pre-fill from the tag's data. Works cross-format (scan TigerTag, write as OpenPrintTag). (#57)
- **NFC+ reader shows temps** — extruder and bed temps from Spoolman now displayed on the reader page for NFC+ tags. (#56)

### Fixed

- **HA discovery MQTT traffic reduced ~80%** — only re-publishes when UID changes, not every scan. Legacy openprinttag_ entity cleanup removed. (#55)
- **Spoolman HTTP connection reuse** — persistent TCP connection across API calls, eliminates per-request connection overhead. (#30)
- **NFC+ reader polling** — keeps polling until Spoolman data arrives instead of stopping on first tag detection.
- **Spoolman JSON buffer** — dynamically sized to response (was fixed 16KB, failed on 25KB+ spool lists).
- **Spool picker security** — XSS escaping, hex validation on nfc_id, HTTP timeouts, safe re-assign order (set new before clearing old).
- **TFT display** — removed duplicate filament name line.
- **HA task stack** — bumped 7168 → 8192 to prevent overflow during discovery.

---

## [1.6.0] - 2026-03-30

### Added
Expand Down
2 changes: 1 addition & 1 deletion platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ board_build.partitions = partitions.csv
monitor_speed = 115200
monitor_filters = direct, printable
build_flags =
-DFIRMWARE_VERSION=\"1.6.0\"
-DFIRMWARE_VERSION=\"1.6.1\"
lib_deps =
marcoschwartz/LiquidCrystal_I2C@^1.1.4
bblanchon/ArduinoJson@^7.0.0
Expand Down
2 changes: 2 additions & 0 deletions src/ApplicationManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -676,6 +676,8 @@ void ApplicationManager::handleSpoolmanSynced(const AppMessage& msg) {
strncpy(info.color_hex, msg.payload.spoolmanSynced.color_hex, sizeof(info.color_hex) - 1);
info.remaining_weight_g = kgRemaining * 1000.0f;
info.spoolman_id = msg.payload.spoolmanSynced.spoolman_id;
info.extruder_temp = msg.payload.spoolmanSynced.extruder_temp;
info.bed_temp = msg.payload.spoolmanSynced.bed_temp;
info.valid = true;
NFCManager::getInstance().setGenericTagSpoolInfo(info);
}
Expand Down
2 changes: 2 additions & 0 deletions src/ApplicationManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ struct SpoolmanSyncedPayload {
char material_name[32]; // Carried from sync request — avoids re-reading NFC state
char manufacturer[64]; // Vendor name (populated for UID lookups)
char color_hex[8]; // "#RRGGBB" (populated for UID lookups)
int16_t extruder_temp; // Spoolman settings_extruder_temp (0 = not set)
int16_t bed_temp; // Spoolman settings_bed_temp (0 = not set)
bool is_uid_lookup; // True = result of a UID-only lookup (generic tag)
};

Expand Down
39 changes: 14 additions & 25 deletions src/HomeAssistantManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,7 @@ void HomeAssistantManager::taskLoop() {
lastMqttState_ = 0;
reconnectDelay_ = 1000; // Reset backoff
Serial.println("HomeAssistantManager: MQTT connected, publishing discovery/state");
lastDiscoveryUid_[0] = '\0'; // Reset dedupe so reconnect publishes fresh
publishDiscovery();
subscribeCommands();
publishAvailability("online");
Expand Down Expand Up @@ -398,8 +399,17 @@ void HomeAssistantManager::taskLoop() {
if (strcmp(req.topic, tagStateTopic) == 0) {
// Keep spool attributes aligned with the state payload source.
mqttClient.publish(tagAttrsTopic, req.payload, req.retained);
// Keep command topics aligned with currently-present UID.
publishDiscovery();
// Only re-publish discovery when UID changes (command topics include UID).
CurrentSpoolState state;
char currentUid[17] = {0};
if (NFCManager::getInstance().getCurrentSpoolState(state) &&
state.present && state.spool_id[0] != '\0') {
strncpy(currentUid, state.spool_id, sizeof(currentUid) - 1);
}
if (strcmp(currentUid, lastDiscoveryUid_) != 0) {
strncpy(lastDiscoveryUid_, currentUid, sizeof(lastDiscoveryUid_) - 1);
publishDiscovery();
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
}
}
}
Expand Down Expand Up @@ -510,15 +520,6 @@ void HomeAssistantManager::publishDiscovery() {
Serial.printf("HomeAssistantManager: Discovery %s -> %s (%u bytes)\n",
discoveryTopic, ok ? "OK" : "FAIL", (unsigned)len);
};
auto removeLegacyEntity = [&](const char* component, const char* objectId) {
char discoveryTopic[128];
snprintf(discoveryTopic, sizeof(discoveryTopic),
"homeassistant/%s/openprinttag_%s/%s/config",
component, deviceId_, objectId);
bool ok = mqttClient.publish(discoveryTopic, "", true);
Serial.printf("HomeAssistantManager: Remove legacy discovery %s -> %s\n",
discoveryTopic, ok ? "OK" : "FAIL");
};
auto publishNumberEntity = [&](const char* objectId, const char* name, const char* valTpl,
const char* cmdTopic, const char* cmdTpl, float minV, float maxV,
float stepV, const char* unitOfMeas, const char* icon) {
Expand Down Expand Up @@ -629,19 +630,7 @@ void HomeAssistantManager::publishDiscovery() {
}
}

// Remove stale retained discovery configs from previous read entities.
removeLegacyEntity("binary_sensor", "tag_present");
removeLegacyEntity("sensor", "spool_uid");
removeLegacyEntity("sensor", "remaining_weight");
removeLegacyEntity("sensor", "material_type");
removeLegacyEntity("sensor", "color");
removeLegacyEntity("sensor", "printer_state");
// Remove old openprinttag_-prefixed control entities (renamed to spoolsense_).
removeLegacyEntity("number", "set_remaining_weight");
removeLegacyEntity("number", "set_initial_weight");
removeLegacyEntity("number", "set_spoolman_id");
removeLegacyEntity("select", "set_material_type");
removeLegacyEntity("text", "set_manufacturer");
// Legacy openprinttag_ entity cleanup removed — no users on the old naming.

// UID is carried in command topic; payload contains only values.
char updateRemainingCmdTpl[128];
Expand Down Expand Up @@ -808,7 +797,7 @@ void HomeAssistantManager::handleCommand(const char* topic, const char* payload)
Serial.printf("HomeAssistantManager: Command received: %s payload=%s\n", topic, payload);

// Parse topic to extract command name (and optional uid suffix)
// Format: openprinttag/{id}/cmd/{command}[/uid]
// Format: spoolsense/{id}/cmd/{command}[/uid]
char cmdPrefix[64];
snprintf(cmdPrefix, sizeof(cmdPrefix), "spoolsense/%s/cmd/", deviceId_);

Expand Down
5 changes: 4 additions & 1 deletion src/HomeAssistantManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,15 @@ class HomeAssistantManager {
static constexpr uint32_t MAX_RECONNECT_DELAY = 30000;

static constexpr size_t QUEUE_SIZE = 6;
static constexpr size_t TASK_STACK_SIZE = 7168;
static constexpr size_t TASK_STACK_SIZE = 8192;
static constexpr UBaseType_t TASK_PRIORITY = 2;

// Device ID cache
char deviceId_[7] = {0}; // 6 hex chars + null
CurrentSpoolState spoolScratch_;

// Discovery dedup — only re-publish when UID changes
char lastDiscoveryUid_[17] = {0};
};

#endif // HOME_ASSISTANT_MANAGER_H
2 changes: 2 additions & 0 deletions src/NFCManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ struct GenericTagSpoolInfo {
char color_hex[8];
float remaining_weight_g;
int32_t spoolman_id;
int16_t extruder_temp;
int16_t bed_temp;
bool valid;
};

Expand Down
34 changes: 34 additions & 0 deletions src/OpenPrintTagWriterHTML.h
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,40 @@ const char OPENPRINTTAG_WRITER_HTML[] PROGMEM = R"rawliteral(
var STEP_IDS = ['step-wait', 'step-detect', 'step-format', 'step-write', 'step-verify'];

syncColorPicker('colorPicker', 'colorHex');

// Pre-fill from scanned tag if present
prefillFromTag({
material: 'material_search',
color: 'colorHex',
colorPicker: 'colorPicker',
manufacturer: 'manufacturer',
weight: 'initial_weight_g',
remaining: 'remaining_g',
density: 'density',
diameter: 'diameter_mm',
nozzle_min: 'min_print_temp',
nozzle_max: 'max_print_temp',
preheat: 'preheat_temp',
bed_min: 'min_bed_temp',
bed_max: 'max_bed_temp'
}).then(function(d) {
if (!d) return;
// Sync material_type hidden field from material name
var opts = document.getElementById('material-list').querySelectorAll('option');
var search = document.getElementById('material_search');
var hidden = document.getElementById('material_type');
for (var i = 0; i < opts.length; i++) {
if (opts[i].value.toUpperCase() === (d.material || '').toUpperCase()) {
search.value = opts[i].value;
hidden.value = opts[i].dataset.id || '0';
break;
}
}
// Trigger material DB auto-fill for any fields the tag didn't have
var materialSearchEl = document.getElementById('material_search');
if (materialSearchEl) materialSearchEl.dispatchEvent(new Event('input'));
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.

setupAdvancedToggle('advancedToggle', 'advancedBox');

// Sync material search → hidden material_type ID
Expand Down
45 changes: 45 additions & 0 deletions src/OpenTag3DWriterHTML.h
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,51 @@ const char OPENTAG3D_WRITER_HTML[] PROGMEM = R"rawliteral(
}
}
baseMaterialEl.addEventListener('input', ot3dAutoFill);

// Pre-fill from scanned tag if present
prefillFromTag({
material: 'base_material',
color: 'colorHex',
colorPicker: 'colorPicker',
manufacturer: 'manufacturer',
weight: 'target_weight_g',
density: 'density',
diameter: 'diameter_mm',
nozzle_min: 'min_print_temp_c',
nozzle_max: 'max_print_temp_c',
bed_min: 'min_bed_temp_c',
bed_max: 'max_bed_temp_c',
dry_temp: 'max_dry_temp_c',
dry_time: 'dry_time_hours'
}).then(function(d) {
if (!d) return;
// Sync modifiers dropdown if available
if (d.modifiers) {
var modEl = document.getElementById('material_modifiers');
if (modEl) modEl.value = d.modifiers;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
// Sync basic print/bed temp from nozzle_max/bed_max
var pt = document.getElementById('print_temp_c');
if (pt && d.nozzle_max && pt.dataset.autoFilled !== 'false') {
pt.value = d.nozzle_max; pt.dataset.autoFilled = 'true';
}
var bt = document.getElementById('bed_temp_c');
if (bt && d.bed_max && bt.dataset.autoFilled !== 'false') {
bt.value = d.bed_max; bt.dataset.autoFilled = 'true';
}
// Sync diameter dropdown (values in micrometers)
if (d.diameter) {
var dEl = document.getElementById('diameter_um');
if (dEl && dEl.dataset.autoFilled !== 'false') {
var um = Math.round(d.diameter * 1000);
dEl.value = um; dEl.dataset.autoFilled = 'true';
}
}
// Trigger material auto-fill for any missing fields
var matEl = document.getElementById('base_material');
if (matEl) matEl.dispatchEvent(new Event('input'));
});

if (modifiersEl) modifiersEl.addEventListener('change', ot3dAutoFill);
loadMaterialDb().then(function(db) {
var dl = document.getElementById('material-list');
Expand Down
Loading