Compare commits

..

12 Commits

Author SHA1 Message Date
e4d1ba6c1c docs: update changelog and header for version v1.5.9
All checks were successful
Release Workflow / detect-provider (push) Successful in 4s
Release Workflow / github-release (push) Has been skipped
Release Workflow / gitea-release (push) Successful in 3m31s
2025-08-11 14:18:12 +02:00
88598611c5 docs: update platformio.ini for version v1.5.9 2025-08-11 14:18:12 +02:00
377f4bc146 Enhance API to support weight updates after successful spool tag updates 2025-08-11 14:17:57 +02:00
7cbd34bc91 docs: update changelog and header for version v1.5.8
All checks were successful
Release Workflow / detect-provider (push) Successful in 52s
Release Workflow / github-release (push) Has been skipped
Release Workflow / gitea-release (push) Successful in 3m19s
2025-08-10 14:39:44 +02:00
f7484f635e docs: update platformio.ini for version v1.5.8 2025-08-10 14:39:44 +02:00
90ce30215f Merge pull request #45 from janecker/nfc_write_improvements
Nfc write improvements
2025-08-10 14:38:11 +02:00
Jan Philipp Ecker
5fa93f2695 Adds a link to the spool in spoolman when reading a spool tag
Adds a link to the website that lets the user directly jump to the spool in spoolman that is currently scanned.
2025-08-08 18:14:26 +02:00
Jan Philipp Ecker
4706152022 Introduces periodic Spoolman Healthcheck
Introduces a spoolman healthcheck that is executed every 60 seconds. Also fixes a bug with the periodic wifi update.
2025-08-08 18:00:25 +02:00
Jan Philipp Ecker
5509d98969 Fixes issue that scale not calibrated message was not shown
There was no warning any more if the scale is not calibrated. This change fixes that.
2025-08-08 16:16:39 +02:00
Jan Philipp Ecker
a7c99d3f26 Improves init - NFC reading now only starts after boot is finished
NFC tags that are on the scale during startup will only be read after the boot sequence is finished.
2025-08-08 15:39:10 +02:00
Jan Philipp Ecker
89a5728cc0 Improves NFC writing workaround and removes debug output
Improved version of the NFC writing workaround. The task is no longer suspended. There is now a suspend request and a suspend state variable that is used to communicate between the writing and the reading task. The reading is stopped gracefully to prevent resets during writing.
2025-08-08 15:33:08 +02:00
Jan Philipp Ecker
b95497aec2 Further improvements on NFC writing
Fixes some issues related to tag writing. Allos writing of tags that are already on the scale when pressing the write button, but introduces a confirmation dialog before doing so. Also first test to fix reset issue when trying to write tags.
2025-08-07 21:12:01 +02:00
11 changed files with 533 additions and 710 deletions

View File

@@ -1,5 +1,35 @@
# Changelog # Changelog
## [1.5.9] - 2025-08-11
### Changed
- update platformio.ini for version v1.5.9
- Enhance API to support weight updates after successful spool tag updates
## [1.5.8] - 2025-08-10
### Added
- Adds a link to the spool in spoolman when reading a spool tag
- Fixes types and some issues in the new graphics
### Changed
- update platformio.ini for version v1.5.8
- Merge pull request #45 from janecker/nfc_write_improvements
- Introduces periodic Spoolman Healthcheck
- Improves init - NFC reading now only starts after boot is finished
- Further improvements on NFC writing
- Merge pull request #44 from janecker/graphics_rework
- Graphic rework of the NFC writing process
- Remove unused parameter of sendNfcData()
- Reworks startup graphics and timings
### Fixed
- Fixes issue that scale not calibrated message was not shown
- Improves NFC writing workaround and removes debug output
- Fixes typos in upgrade page
- Reworks graphics of tag reading and some api fixes
- Replaces usage of String with const char* in heap debug function
## [1.5.7] - 2025-07-28 ## [1.5.7] - 2025-07-28
### Changed ### Changed
- update platformio.ini for version v1.5.7 - update platformio.ini for version v1.5.7

View File

@@ -7,6 +7,7 @@ let heartbeatTimer = null;
let lastHeartbeatResponse = Date.now(); let lastHeartbeatResponse = Date.now();
const HEARTBEAT_TIMEOUT = 20000; const HEARTBEAT_TIMEOUT = 20000;
let reconnectTimer = null; let reconnectTimer = null;
let spoolDetected = false;
// WebSocket Funktionen // WebSocket Funktionen
function startHeartbeat() { function startHeartbeat() {
@@ -508,12 +509,15 @@ function updateNfcStatusIndicator(data) {
if (data.found === 0) { if (data.found === 0) {
// Kein NFC Tag gefunden // Kein NFC Tag gefunden
indicator.className = 'status-circle'; indicator.className = 'status-circle';
spoolDetected = false;
} else if (data.found === 1) { } else if (data.found === 1) {
// NFC Tag erfolgreich gelesen // NFC Tag erfolgreich gelesen
indicator.className = 'status-circle success'; indicator.className = 'status-circle success';
spoolDetected = true;
} else { } else {
// Fehler beim Lesen // Fehler beim Lesen
indicator.className = 'status-circle error'; indicator.className = 'status-circle error';
spoolDetected = true;
} }
} }
@@ -574,7 +578,7 @@ function updateNfcData(data) {
`; `;
// Spoolman ID anzeigen // Spoolman ID anzeigen
html += `<p><strong>Spoolman ID:</strong> ${data.sm_id || 'No Spoolman ID'}</p>`; html += `<p><strong>Spoolman ID:</strong> ${data.sm_id} (<a href="${spoolmanUrl}/spool/show/${data.sm_id}">Open in Spoolman</a>)</p>`;
} }
else if(data.location) else if(data.location)
{ {
@@ -618,78 +622,83 @@ function updateNfcData(data) {
} }
function writeNfcTag() { function writeNfcTag() {
const selectedText = document.getElementById("selected-filament").textContent; if(!spoolDetected || confirm("Are you sure you want to overwrite the Tag?") == true){
if (selectedText === "Please choose...") { const selectedText = document.getElementById("selected-filament").textContent;
alert('Please select a Spool first.'); if (selectedText === "Please choose...") {
return; alert('Please select a Spool first.');
} return;
}
const spoolsData = window.getSpoolData(); const spoolsData = window.getSpoolData();
const selectedSpool = spoolsData.find(spool => const selectedSpool = spoolsData.find(spool =>
`${spool.id} | ${spool.filament.name} (${spool.filament.material})` === selectedText `${spool.id} | ${spool.filament.name} (${spool.filament.material})` === selectedText
); );
if (!selectedSpool) { if (!selectedSpool) {
alert('Ausgewählte Spule konnte nicht gefunden werden.'); alert('Ausgewählte Spule konnte nicht gefunden werden.');
return; return;
} }
// Temperaturwerte korrekt extrahieren // Temperaturwerte korrekt extrahieren
let minTemp = "175"; let minTemp = "175";
let maxTemp = "275"; let maxTemp = "275";
if (Array.isArray(selectedSpool.filament.nozzle_temperature) && if (Array.isArray(selectedSpool.filament.nozzle_temperature) &&
selectedSpool.filament.nozzle_temperature.length >= 2) { selectedSpool.filament.nozzle_temperature.length >= 2) {
minTemp = String(selectedSpool.filament.nozzle_temperature[0]); minTemp = String(selectedSpool.filament.nozzle_temperature[0]);
maxTemp = String(selectedSpool.filament.nozzle_temperature[1]); maxTemp = String(selectedSpool.filament.nozzle_temperature[1]);
} }
// Erstelle das NFC-Datenpaket mit korrekten Datentypen // Erstelle das NFC-Datenpaket mit korrekten Datentypen
const nfcData = { const nfcData = {
color_hex: selectedSpool.filament.color_hex || "FFFFFF", color_hex: selectedSpool.filament.color_hex || "FFFFFF",
type: selectedSpool.filament.material, type: selectedSpool.filament.material,
min_temp: minTemp, min_temp: minTemp,
max_temp: maxTemp, max_temp: maxTemp,
brand: selectedSpool.filament.vendor.name, brand: selectedSpool.filament.vendor.name,
sm_id: String(selectedSpool.id) // Konvertiere zu String sm_id: String(selectedSpool.id) // Konvertiere zu String
}; };
if (socket?.readyState === WebSocket.OPEN) { if (socket?.readyState === WebSocket.OPEN) {
const writeButton = document.getElementById("writeNfcButton"); const writeButton = document.getElementById("writeNfcButton");
writeButton.classList.add("writing"); writeButton.classList.add("writing");
writeButton.textContent = "Writing"; writeButton.textContent = "Writing";
socket.send(JSON.stringify({ socket.send(JSON.stringify({
type: 'writeNfcTag', type: 'writeNfcTag',
tagType: 'spool', tagType: 'spool',
payload: nfcData payload: nfcData
})); }));
} else { } else {
alert('Not connected to Server. Please check connection.'); alert('Not connected to Server. Please check connection.');
}
} }
} }
function writeLocationNfcTag() { function writeLocationNfcTag() {
const selectedText = document.getElementById("locationSelect").value; if(!spoolDetected || confirm("Are you sure you want to overwrite the Tag?") == true){
if (selectedText === "Please choose...") { const selectedText = document.getElementById("locationSelect").value;
alert('Please select a location first.'); if (selectedText === "Please choose...") {
return; alert('Please select a location first.');
} return;
// Erstelle das NFC-Datenpaket mit korrekten Datentypen }
const nfcData = { // Erstelle das NFC-Datenpaket mit korrekten Datentypen
location: String(selectedText) const nfcData = {
}; location: String(selectedText)
};
if (socket?.readyState === WebSocket.OPEN) {
const writeButton = document.getElementById("writeLocationNfcButton"); if (socket?.readyState === WebSocket.OPEN) {
writeButton.classList.add("writing"); const writeButton = document.getElementById("writeLocationNfcButton");
writeButton.textContent = "Writing"; writeButton.classList.add("writing");
socket.send(JSON.stringify({ writeButton.textContent = "Writing";
type: 'writeNfcTag', socket.send(JSON.stringify({
tagType: 'location', type: 'writeNfcTag',
payload: nfcData tagType: 'location',
})); payload: nfcData
} else { }));
alert('Not connected to Server. Please check connection.'); } else {
alert('Not connected to Server. Please check connection.');
}
} }
} }

View File

@@ -9,7 +9,7 @@
; https://docs.platformio.org/page/projectconf.html ; https://docs.platformio.org/page/projectconf.html
[common] [common]
version = "1.5.7" version = "1.5.9"
to_old_version = "1.5.0" to_old_version = "1.5.0"
## ##

View File

@@ -4,8 +4,9 @@
#include "commonFS.h" #include "commonFS.h"
#include <Preferences.h> #include <Preferences.h>
#include "debug.h" #include "debug.h"
#include "scale.h"
volatile spoolmanApiStateType spoolmanApiState = API_INIT; volatile spoolmanApiStateType spoolmanApiState = API_IDLE;
//bool spoolman_connected = false; //bool spoolman_connected = false;
String spoolmanUrl = ""; String spoolmanUrl = "";
bool octoEnabled = false; bool octoEnabled = false;
@@ -13,10 +14,9 @@ bool sendOctoUpdate = false;
String octoUrl = ""; String octoUrl = "";
String octoToken = ""; String octoToken = "";
uint16_t remainingWeight = 0; uint16_t remainingWeight = 0;
uint16_t createdVendorId = 0; // Store ID of newly created vendor
uint16_t foundVendorId = 0; // Store ID of found vendor
uint16_t foundFilamentId = 0; // Store ID of found filament
bool spoolmanConnected = false; bool spoolmanConnected = false;
bool spoolmanExtraFieldsChecked = false;
TaskHandle_t* apiTask;
struct SendToApiParams { struct SendToApiParams {
SpoolmanApiRequestType requestType; SpoolmanApiRequestType requestType;
@@ -24,6 +24,10 @@ struct SendToApiParams {
String spoolsUrl; String spoolsUrl;
String updatePayload; String updatePayload;
String octoToken; String octoToken;
// Weight update parameters for sequential execution
bool triggerWeightUpdate;
String spoolIdForWeight;
uint16_t weightValue;
}; };
JsonDocument fetchSingleSpoolInfo(int spoolId) { JsonDocument fetchSingleSpoolInfo(int spoolId) {
@@ -97,15 +101,23 @@ JsonDocument fetchSingleSpoolInfo(int spoolId) {
void sendToApi(void *parameter) { void sendToApi(void *parameter) {
HEAP_DEBUG_MESSAGE("sendToApi begin"); HEAP_DEBUG_MESSAGE("sendToApi begin");
// Wait until API is IDLE
while(spoolmanApiState != API_IDLE){
Serial.println("Waiting!");
yield();
}
spoolmanApiState = API_TRANSMITTING; spoolmanApiState = API_TRANSMITTING;
SendToApiParams* params = (SendToApiParams*)parameter; SendToApiParams* params = (SendToApiParams*)parameter;
// Extrahiere die Werte // Extract values including weight update parameters
SpoolmanApiRequestType requestType = params->requestType; SpoolmanApiRequestType requestType = params->requestType;
String httpType = params->httpType; String httpType = params->httpType;
String spoolsUrl = params->spoolsUrl; String spoolsUrl = params->spoolsUrl;
String updatePayload = params->updatePayload; String updatePayload = params->updatePayload;
String octoToken = params->octoToken; String octoToken = params->octoToken;
bool triggerWeightUpdate = params->triggerWeightUpdate;
String spoolIdForWeight = params->spoolIdForWeight;
uint16_t weightValue = params->weightValue;
HTTPClient http; HTTPClient http;
http.setReuse(false); http.setReuse(false);
@@ -117,7 +129,6 @@ void sendToApi(void *parameter) {
int httpCode; int httpCode;
if (httpType == "PATCH") httpCode = http.PATCH(updatePayload); if (httpType == "PATCH") httpCode = http.PATCH(updatePayload);
else if (httpType == "POST") httpCode = http.POST(updatePayload); else if (httpType == "POST") httpCode = http.POST(updatePayload);
else if (httpType == "GET") httpCode = http.GET();
else httpCode = http.PUT(updatePayload); else httpCode = http.PUT(updatePayload);
if (httpCode == HTTP_CODE_OK) { if (httpCode == HTTP_CODE_OK) {
@@ -157,61 +168,60 @@ void sendToApi(void *parameter) {
oledShowProgressBar(5, 5, "Spool Tag", ("Done: " + String(remainingWeight) + " g remain").c_str()); oledShowProgressBar(5, 5, "Spool Tag", ("Done: " + String(remainingWeight) + " g remain").c_str());
remainingWeight = 0; remainingWeight = 0;
break; break;
case API_REQUEST_VENDOR_CREATE:
Serial.println("Vendor successfully created!");
createdVendorId = doc["id"].as<uint16_t>();
Serial.print("Created Vendor ID: ");
Serial.println(createdVendorId);
oledShowProgressBar(1, 1, "Vendor", "Created!");
break;
case API_REQUEST_VENDOR_CHECK:
if (doc.isNull() || doc.size() == 0) {
Serial.println("Vendor not found in response");
foundVendorId = 0;
} else {
foundVendorId = doc[0]["id"].as<uint16_t>();
Serial.print("Found Vendor ID: ");
Serial.println(foundVendorId);
}
break;
case API_REQUEST_FILAMENT_CHECK:
if (doc.isNull() || doc.size() == 0) {
Serial.println("Filament not found in response");
foundFilamentId = 0;
} else {
foundFilamentId = doc[0]["id"].as<uint16_t>();
Serial.print("Found Filament ID: ");
Serial.println(foundFilamentId);
}
break;
} }
} }
doc.clear(); doc.clear();
} else if (httpCode == HTTP_CODE_CREATED) {
Serial.println("Spoolman erfolgreich erstellt");
// Parse response for created resources // Execute weight update if requested and tag update was successful
String payload = http.getString(); if (triggerWeightUpdate && requestType == API_REQUEST_SPOOL_TAG_ID_UPDATE && weightValue > 10) {
JsonDocument doc; Serial.println("Executing weight update after successful tag update");
DeserializationError error = deserializeJson(doc, payload);
if (error) { // Prepare weight update request
Serial.print("Fehler beim Parsen der JSON-Antwort: "); String weightUrl = spoolmanUrl + apiUrl + "/spool/" + spoolIdForWeight + "/measure";
Serial.println(error.c_str()); JsonDocument weightDoc;
} else { weightDoc["weight"] = weightValue;
switch(requestType){
case API_REQUEST_VENDOR_CREATE: String weightPayload;
Serial.println("Vendor successfully created!"); serializeJson(weightDoc, weightPayload);
createdVendorId = doc["id"].as<uint16_t>();
Serial.print("Created Vendor ID: "); Serial.print("Weight update URL: ");
Serial.println(createdVendorId); Serial.println(weightUrl);
oledShowProgressBar(1, 1, "Vendor", "Created!"); Serial.print("Weight update payload: ");
break; Serial.println(weightPayload);
default:
// Handle other create operations if needed // Execute weight update
break; http.begin(weightUrl);
http.addHeader("Content-Type", "application/json");
int weightHttpCode = http.PUT(weightPayload);
if (weightHttpCode == HTTP_CODE_OK) {
Serial.println("Weight update successful");
String weightResponse = http.getString();
JsonDocument weightResponseDoc;
DeserializationError weightError = deserializeJson(weightResponseDoc, weightResponse);
if (!weightError) {
remainingWeight = weightResponseDoc["remaining_weight"].as<uint16_t>();
Serial.print("Updated weight: ");
Serial.println(remainingWeight);
if (!octoEnabled) {
oledShowProgressBar(1, 1, "Spool Tag", ("Done: " + String(remainingWeight) + " g remain").c_str());
remainingWeight = 0;
} else {
sendOctoUpdate = true;
}
}
weightResponseDoc.clear();
} else {
Serial.print("Weight update failed with HTTP code: ");
Serial.println(weightHttpCode);
oledShowProgressBar(1, 1, "Failure!", "Weight update");
} }
weightDoc.clear();
} }
doc.clear();
} else { } else {
switch(requestType){ switch(requestType){
case API_REQUEST_SPOOL_WEIGHT_UPDATE: case API_REQUEST_SPOOL_WEIGHT_UPDATE:
@@ -225,9 +235,6 @@ void sendToApi(void *parameter) {
case API_REQUEST_BAMBU_UPDATE: case API_REQUEST_BAMBU_UPDATE:
oledShowProgressBar(1, 1, "Failure!", "Bambu update"); oledShowProgressBar(1, 1, "Failure!", "Bambu update");
break; break;
case API_REQUEST_VENDOR_CREATE:
oledShowProgressBar(1, 1, "Failure!", "Vendor create");
break;
} }
Serial.println("Fehler beim Senden an Spoolman! HTTP Code: " + String(httpCode)); Serial.println("Fehler beim Senden an Spoolman! HTTP Code: " + String(httpCode));
@@ -263,7 +270,8 @@ bool updateSpoolTagId(String uidString, const char* payload) {
return false; return false;
} }
String spoolsUrl = spoolmanUrl + apiUrl + "/spool/" + doc["sm_id"].as<String>(); String spoolId = doc["sm_id"].as<String>();
String spoolsUrl = spoolmanUrl + apiUrl + "/spool/" + spoolId;
Serial.print("Update Spule mit URL: "); Serial.print("Update Spule mit URL: ");
Serial.println(spoolsUrl); Serial.println(spoolsUrl);
@@ -288,21 +296,25 @@ bool updateSpoolTagId(String uidString, const char* payload) {
params->spoolsUrl = spoolsUrl; params->spoolsUrl = spoolsUrl;
params->updatePayload = updatePayload; params->updatePayload = updatePayload;
// Erstelle die Task // Add weight update parameters for sequential execution
params->triggerWeightUpdate = (weight > 10);
params->spoolIdForWeight = spoolId;
params->weightValue = weight;
// Erstelle die Task mit erhöhter Stackgröße für zusätzliche HTTP-Anfrage
BaseType_t result = xTaskCreate( BaseType_t result = xTaskCreate(
sendToApi, // Task-Funktion sendToApi, // Task-Funktion
"SendToApiTask", // Task-Name "SendToApiTask", // Task-Name
6144, // Stackgröße in Bytes 8192, // Erhöhte Stackgröße für zusätzliche HTTP-Anfrage
(void*)params, // Parameter (void*)params, // Parameter
0, // Priorität 0, // Priorität
NULL // Task-Handle (nicht benötigt) apiTask // Task-Handle (nicht benötigt)
); );
updateDoc.clear(); updateDoc.clear();
// Update Spool weight // Update Spool weight now handled sequentially in sendToApi task
//TBD: how to handle this with spool and locatin tags? Also potential parallel access again // to prevent parallel API access issues
//if (weight > 10) updateSpoolWeight(doc["sm_id"].as<String>(), weight);
return true; return true;
} }
@@ -341,7 +353,7 @@ uint8_t updateSpoolWeight(String spoolId, uint16_t weight) {
6144, // Stackgröße in Bytes 6144, // Stackgröße in Bytes
(void*)params, // Parameter (void*)params, // Parameter
0, // Priorität 0, // Priorität
NULL // Task-Handle (nicht benötigt) apiTask // Task-Handle (nicht benötigt)
); );
updateDoc.clear(); updateDoc.clear();
@@ -378,17 +390,17 @@ uint8_t updateSpoolLocation(String spoolId, String location){
params->spoolsUrl = spoolsUrl; params->spoolsUrl = spoolsUrl;
params->updatePayload = updatePayload; params->updatePayload = updatePayload;
if(spoolmanApiState == API_IDLE){
// Erstelle die Task
BaseType_t result = xTaskCreate(
sendToApi, // Task-Funktion
"SendToApiTask", // Task-Name
6144, // Stackgröße in Bytes
(void*)params, // Parameter
0, // Priorität
NULL // Task-Handle (nicht benötigt)
);
if(apiTask == nullptr){
// Erstelle die Task
BaseType_t result = xTaskCreate(
sendToApi, // Task-Funktion
"SendToApiTask", // Task-Name
6144, // Stackgröße in Bytes
(void*)params, // Parameter
0, // Priorität
apiTask // Task-Handle
);
}else{ }else{
Serial.println("Not spawning new task, API still active!"); Serial.println("Not spawning new task, API still active!");
} }
@@ -433,7 +445,7 @@ bool updateSpoolOcto(int spoolId) {
6144, // Stackgröße in Bytes 6144, // Stackgröße in Bytes
(void*)params, // Parameter (void*)params, // Parameter
0, // Priorität 0, // Priorität
NULL // Task-Handle (nicht benötigt) apiTask // Task-Handle (nicht benötigt)
); );
updateDoc.clear(); updateDoc.clear();
@@ -486,438 +498,230 @@ bool updateSpoolBambuData(String payload) {
6144, // Stackgröße in Bytes 6144, // Stackgröße in Bytes
(void*)params, // Parameter (void*)params, // Parameter
0, // Priorität 0, // Priorität
NULL // Task-Handle (nicht benötigt) apiTask // Task-Handle (nicht benötigt)
); );
return true; return true;
} }
// #### Filament Fabrik
uint16_t checkVendor(String vendor) {
// Check if vendor exists using task system
foundVendorId = 0; // Reset previous value
String spoolsUrl = spoolmanUrl + apiUrl + "/vendor?name=" + vendor;
Serial.print("Check vendor with URL: ");
Serial.println(spoolsUrl);
SendToApiParams* params = new SendToApiParams();
if (params == nullptr) {
Serial.println("Fehler: Kann Speicher für Task-Parameter nicht allokieren.");
return 0;
}
params->requestType = API_REQUEST_VENDOR_CHECK;
params->httpType = "GET";
params->spoolsUrl = spoolsUrl;
params->updatePayload = ""; // Empty for GET request
// Check if API is idle before creating task
if(spoolmanApiState == API_IDLE){
// Erstelle die Task
BaseType_t result = xTaskCreate(
sendToApi, // Task-Funktion
"SendToApiTask", // Task-Name
6144, // Stackgröße in Bytes
(void*)params, // Parameter
0, // Priorität
NULL // Task-Handle (nicht benötigt)
);
} else {
Serial.println("Not spawning new task, API still active!");
delete params;
return 0;
}
// Wait for task completion
while(spoolmanApiState != API_IDLE) {
vTaskDelay(100 / portTICK_PERIOD_MS);
}
// Check if vendor was found
if (foundVendorId == 0) {
Serial.println("Vendor not found, creating new vendor...");
uint16_t vendorId = createVendor(vendor);
if (vendorId == 0) {
Serial.println("Failed to create vendor, returning 0.");
return 0; // Failed to create vendor
} else {
Serial.println("Vendor created with ID: " + String(vendorId));
checkFilament(vendorId);
return vendorId;
}
} else {
Serial.println("Vendor found: " + vendor);
Serial.print("Vendor ID: ");
Serial.println(foundVendorId);
return foundVendorId;
}
}
uint16_t createVendor(String vendor) {
// Create new vendor in Spoolman database using task system
// Note: Due to async nature, the ID will be stored in createdVendorId global variable
createdVendorId = 0; // Reset previous value
String spoolsUrl = spoolmanUrl + apiUrl + "/vendor";
Serial.print("Create vendor with URL: ");
Serial.println(spoolsUrl);
// Create JSON payload for vendor creation
JsonDocument vendorDoc;
vendorDoc["name"] = vendor;
vendorDoc["comment"] = "automatically generated";
vendorDoc["empty_spool_weight"] = 180;
vendorDoc["external_id"] = vendor;
String vendorPayload;
serializeJson(vendorDoc, vendorPayload);
Serial.print("Vendor Payload: ");
Serial.println(vendorPayload);
SendToApiParams* params = new SendToApiParams();
if (params == nullptr) {
Serial.println("Fehler: Kann Speicher für Task-Parameter nicht allokieren.");
vendorDoc.clear();
return 0;
}
params->requestType = API_REQUEST_VENDOR_CREATE;
params->httpType = "POST";
params->spoolsUrl = spoolsUrl;
params->updatePayload = vendorPayload;
// Check if API is idle before creating task
if(spoolmanApiState == API_IDLE){
// Erstelle die Task
BaseType_t result = xTaskCreate(
sendToApi, // Task-Funktion
"SendToApiTask", // Task-Name
6144, // Stackgröße in Bytes
(void*)params, // Parameter
0, // Priorität
NULL // Task-Handle (nicht benötigt)
);
} else {
Serial.println("Not spawning new task, API still active!");
delete params;
vendorDoc.clear();
return 0;
}
vendorDoc.clear();
// Wait for task completion and return the created vendor ID
// Note: createdVendorId will be set by sendToApi when response is received
while(spoolmanApiState != API_IDLE) {
vTaskDelay(100 / portTICK_PERIOD_MS);
}
return createdVendorId;
}
uint16_t checkFilament(uint16_t vendorId) {
// Check if filament exists using task system
foundFilamentId = 0; // Reset previous value
String spoolsUrl = spoolmanUrl + apiUrl + "/filament?vendor.id=" + String(vendorId);
Serial.print("Check filament with URL: ");
Serial.println(spoolsUrl);
SendToApiParams* params = new SendToApiParams();
if (params == nullptr) {
Serial.println("Fehler: Kann Speicher für Task-Parameter nicht allokieren.");
return 0;
}
params->requestType = API_REQUEST_FILAMENT_CHECK;
params->httpType = "GET";
params->spoolsUrl = spoolsUrl;
params->updatePayload = ""; // Empty for GET request
// Check if API is idle before creating task
if(spoolmanApiState == API_IDLE){
// Erstelle die Task
BaseType_t result = xTaskCreate(
sendToApi, // Task-Funktion
"SendToApiTask", // Task-Name
6144, // Stackgröße in Bytes
(void*)params, // Parameter
0, // Priorität
NULL // Task-Handle (nicht benötigt)
);
} else {
Serial.println("Not spawning new task, API still active!");
delete params;
return 0;
}
// Wait for task completion
while(spoolmanApiState != API_IDLE) {
vTaskDelay(100 / portTICK_PERIOD_MS);
}
// Check if filament was found
if (foundFilamentId == 0) {
Serial.println("Filament not found, creating new filament...");
uint16_t filamentId = createFilament();
if (filamentId == 0) {
Serial.println("Failed to create filament, returning 0.");
return 0; // Failed to create filament
} else {
Serial.println("Filament created with ID: " + String(filamentId));
checkSpool();
return filamentId;
}
} else {
Serial.println("Filament found for vendor ID: " + String(vendorId));
Serial.print("Filament ID: ");
Serial.println(foundFilamentId);
return foundFilamentId;
}
}
bool createFilament() {
// {
// "name": "PolyTerra Charcoal Black",
// "vendor_id": 0,
// "material": "PLA",
// "price": 20,
// "density": 1.24,
// "diameter": 1.75,
// "weight": 1000,
// "spool_weight": 140,
// "article_number": "PM70820",
// "comment": "automatically generated",
// "settings_extruder_temp": 210,
// "settings_bed_temp": 60,
// "color_hex": "FF0000",
// "multi_color_hexes": "FF0000,00FF00,0000FF",
// "multi_color_direction": "coaxial",
// "external_id": "polymaker_pla_polysonicblack_1000_175",
// "extra": {
// "nozzle_temperature": "string"
// }
// }
}
uint16_t checkSpool() {
}
bool createSpool() {
// Implement specific handling for Spool creation
// {
// "first_used": "2019-08-24T14:15:22Z",
// "last_used": "2019-08-24T14:15:22Z",
// "filament_id": 0,
// "price": 20,
// "initial_weight": 200,
// "spool_weight": 200,
// "remaining_weight": 800,
// "used_weight": 200,
// "location": "Shelf A",
// "lot_nr": "52342",
// "comment": "",
// "archived": false,
// "extra": {
// "nfc_id": "string"
// }
// }
}
// #### Spoolman init // #### Spoolman init
bool checkSpoolmanExtraFields() { bool checkSpoolmanExtraFields() {
HTTPClient http; // Only check extra fields if they have not been checked before
String checkUrls[] = { if(!spoolmanExtraFieldsChecked){
spoolmanUrl + apiUrl + "/field/spool", HTTPClient http;
spoolmanUrl + apiUrl + "/field/filament" String checkUrls[] = {
}; spoolmanUrl + apiUrl + "/field/spool",
spoolmanUrl + apiUrl + "/field/filament"
};
String spoolExtra[] = { String spoolExtra[] = {
"nfc_id" "nfc_id"
}; };
String filamentExtra[] = { String filamentExtra[] = {
"nozzle_temperature", "nozzle_temperature",
"price_meter", "price_meter",
"price_gramm", "price_gramm",
"bambu_setting_id", "bambu_setting_id",
"bambu_cali_id", "bambu_cali_id",
"bambu_idx", "bambu_idx",
"bambu_k", "bambu_k",
"bambu_flow_ratio", "bambu_flow_ratio",
"bambu_max_volspeed" "bambu_max_volspeed"
}; };
String spoolExtraFields[] = { String spoolExtraFields[] = {
"{\"name\": \"NFC ID\"," "{\"name\": \"NFC ID\","
"\"key\": \"nfc_id\"," "\"key\": \"nfc_id\","
"\"field_type\": \"text\"}" "\"field_type\": \"text\"}"
}; };
String filamentExtraFields[] = { String filamentExtraFields[] = {
"{\"name\": \"Nozzle Temp\"," "{\"name\": \"Nozzle Temp\","
"\"unit\": \"°C\"," "\"unit\": \"°C\","
"\"field_type\": \"integer_range\"," "\"field_type\": \"integer_range\","
"\"default_value\": \"[190,230]\"," "\"default_value\": \"[190,230]\","
"\"key\": \"nozzle_temperature\"}", "\"key\": \"nozzle_temperature\"}",
"{\"name\": \"Price/m\"," "{\"name\": \"Price/m\","
"\"unit\": \"\"," "\"unit\": \"\","
"\"field_type\": \"float\"," "\"field_type\": \"float\","
"\"key\": \"price_meter\"}", "\"key\": \"price_meter\"}",
"{\"name\": \"Price/g\"," "{\"name\": \"Price/g\","
"\"unit\": \"\"," "\"unit\": \"\","
"\"field_type\": \"float\"," "\"field_type\": \"float\","
"\"key\": \"price_gramm\"}", "\"key\": \"price_gramm\"}",
"{\"name\": \"Bambu Setting ID\"," "{\"name\": \"Bambu Setting ID\","
"\"field_type\": \"text\"," "\"field_type\": \"text\","
"\"key\": \"bambu_setting_id\"}", "\"key\": \"bambu_setting_id\"}",
"{\"name\": \"Bambu Cali ID\"," "{\"name\": \"Bambu Cali ID\","
"\"field_type\": \"text\"," "\"field_type\": \"text\","
"\"key\": \"bambu_cali_id\"}", "\"key\": \"bambu_cali_id\"}",
"{\"name\": \"Bambu Filament IDX\"," "{\"name\": \"Bambu Filament IDX\","
"\"field_type\": \"text\"," "\"field_type\": \"text\","
"\"key\": \"bambu_idx\"}", "\"key\": \"bambu_idx\"}",
"{\"name\": \"Bambu k\"," "{\"name\": \"Bambu k\","
"\"field_type\": \"float\"," "\"field_type\": \"float\","
"\"key\": \"bambu_k\"}", "\"key\": \"bambu_k\"}",
"{\"name\": \"Bambu Flow Ratio\"," "{\"name\": \"Bambu Flow Ratio\","
"\"field_type\": \"float\"," "\"field_type\": \"float\","
"\"key\": \"bambu_flow_ratio\"}", "\"key\": \"bambu_flow_ratio\"}",
"{\"name\": \"Bambu Max Vol. Speed\"," "{\"name\": \"Bambu Max Vol. Speed\","
"\"unit\": \"mm3/s\"," "\"unit\": \"mm3/s\","
"\"field_type\": \"integer\"," "\"field_type\": \"integer\","
"\"default_value\": \"12\"," "\"default_value\": \"12\","
"\"key\": \"bambu_max_volspeed\"}" "\"key\": \"bambu_max_volspeed\"}"
}; };
Serial.println("Überprüfe Extrafelder..."); Serial.println("Überprüfe Extrafelder...");
int urlLength = sizeof(checkUrls) / sizeof(checkUrls[0]); int urlLength = sizeof(checkUrls) / sizeof(checkUrls[0]);
for (uint8_t i = 0; i < urlLength; i++) { for (uint8_t i = 0; i < urlLength; i++) {
Serial.println(); Serial.println();
Serial.println("-------- Prüfe Felder für "+checkUrls[i]+" --------"); Serial.println("-------- Prüfe Felder für "+checkUrls[i]+" --------");
http.begin(checkUrls[i]); http.begin(checkUrls[i]);
int httpCode = http.GET(); int httpCode = http.GET();
if (httpCode == HTTP_CODE_OK) { if (httpCode == HTTP_CODE_OK) {
String payload = http.getString(); String payload = http.getString();
JsonDocument doc; JsonDocument doc;
DeserializationError error = deserializeJson(doc, payload); DeserializationError error = deserializeJson(doc, payload);
if (!error) { if (!error) {
String* extraFields; String* extraFields;
String* extraFieldData; String* extraFieldData;
u16_t extraLength; u16_t extraLength;
if (i == 0) { if (i == 0) {
extraFields = spoolExtra; extraFields = spoolExtra;
extraFieldData = spoolExtraFields; extraFieldData = spoolExtraFields;
extraLength = sizeof(spoolExtra) / sizeof(spoolExtra[0]); extraLength = sizeof(spoolExtra) / sizeof(spoolExtra[0]);
} else { } else {
extraFields = filamentExtra; extraFields = filamentExtra;
extraFieldData = filamentExtraFields; extraFieldData = filamentExtraFields;
extraLength = sizeof(filamentExtra) / sizeof(filamentExtra[0]); extraLength = sizeof(filamentExtra) / sizeof(filamentExtra[0]);
}
for (uint8_t s = 0; s < extraLength; s++) {
bool found = false;
for (JsonObject field : doc.as<JsonArray>()) {
if (field["key"].is<String>() && field["key"] == extraFields[s]) {
Serial.println("Feld gefunden: " + extraFields[s]);
found = true;
break;
}
} }
if (!found) {
Serial.println("Feld nicht gefunden: " + extraFields[s]);
// Extrafeld hinzufügen for (uint8_t s = 0; s < extraLength; s++) {
http.begin(checkUrls[i] + "/" + extraFields[s]); bool found = false;
http.addHeader("Content-Type", "application/json"); for (JsonObject field : doc.as<JsonArray>()) {
int httpCode = http.POST(extraFieldData[s]); if (field["key"].is<String>() && field["key"] == extraFields[s]) {
Serial.println("Feld gefunden: " + extraFields[s]);
found = true;
break;
}
}
if (!found) {
Serial.println("Feld nicht gefunden: " + extraFields[s]);
if (httpCode > 0) { // Extrafeld hinzufügen
// Antwortscode und -nachricht abrufen http.begin(checkUrls[i] + "/" + extraFields[s]);
String response = http.getString(); http.addHeader("Content-Type", "application/json");
//Serial.println("HTTP-Code: " + String(httpCode)); int httpCode = http.POST(extraFieldData[s]);
//Serial.println("Antwort: " + response);
if (httpCode != HTTP_CODE_OK) {
if (httpCode > 0) {
// Antwortscode und -nachricht abrufen
String response = http.getString();
//Serial.println("HTTP-Code: " + String(httpCode));
//Serial.println("Antwort: " + response);
if (httpCode != HTTP_CODE_OK) {
return false;
}
} else {
// Fehler beim Senden der Anfrage
Serial.println("Fehler beim Senden der Anfrage: " + String(http.errorToString(httpCode)));
return false; return false;
} }
} else { //http.end();
// Fehler beim Senden der Anfrage
Serial.println("Fehler beim Senden der Anfrage: " + String(http.errorToString(httpCode)));
return false;
} }
//http.end(); yield();
vTaskDelay(100 / portTICK_PERIOD_MS);
} }
yield();
vTaskDelay(100 / portTICK_PERIOD_MS);
} }
doc.clear();
} }
doc.clear();
} }
Serial.println("-------- ENDE Prüfe Felder --------");
Serial.println();
http.end();
spoolmanExtraFieldsChecked = true;
return true;
}else{
return true;
} }
Serial.println("-------- ENDE Prüfe Felder --------");
Serial.println();
http.end();
return true;
} }
bool checkSpoolmanInstance(const String& url) { bool checkSpoolmanInstance() {
HTTPClient http; HTTPClient http;
String healthUrl = url + apiUrl + "/health"; bool returnValue = false;
Serial.print("Überprüfe Spoolman-Instanz unter: "); // Only do the spoolman instance check if there is no active API request going on
Serial.println(healthUrl); if(spoolmanApiState == API_IDLE){
spoolmanApiState = API_TRANSMITTING;
String healthUrl = spoolmanUrl + apiUrl + "/health";
http.begin(healthUrl); Serial.print("Checking spoolman instance: ");
int httpCode = http.GET(); Serial.println(healthUrl);
if (httpCode > 0) { http.begin(healthUrl);
if (httpCode == HTTP_CODE_OK) { int httpCode = http.GET();
String payload = http.getString();
JsonDocument doc;
DeserializationError error = deserializeJson(doc, payload);
if (!error && doc["status"].is<String>()) {
const char* status = doc["status"];
http.end();
if (!checkSpoolmanExtraFields()) { if (httpCode > 0) {
Serial.println("Fehler beim Überprüfen der Extrafelder."); if (httpCode == HTTP_CODE_OK) {
String payload = http.getString();
JsonDocument doc;
DeserializationError error = deserializeJson(doc, payload);
if (!error && doc["status"].is<String>()) {
const char* status = doc["status"];
http.end();
// TBD if (!checkSpoolmanExtraFields()) {
oledShowMessage("Spoolman Error creating Extrafields"); Serial.println("Fehler beim Überprüfen der Extrafelder.");
vTaskDelay(2000 / portTICK_PERIOD_MS);
return false; // TBD
oledShowMessage("Spoolman Error creating Extrafields");
vTaskDelay(2000 / portTICK_PERIOD_MS);
return false;
}
spoolmanApiState = API_IDLE;
oledShowTopRow();
spoolmanConnected = true;
returnValue = strcmp(status, "healthy") == 0;
}else{
spoolmanConnected = false;
} }
spoolmanApiState = API_IDLE; doc.clear();
oledShowTopRow(); }else{
spoolmanConnected = true; spoolmanConnected = false;
return strcmp(status, "healthy") == 0;
} }
} else {
doc.clear(); spoolmanConnected = false;
Serial.println("Error contacting spoolman instance! HTTP Code: " + String(httpCode));
} }
} else { http.end();
Serial.println("Error contacting spoolman instance! HTTP Code: " + String(httpCode)); returnValue = false;
spoolmanApiState = API_IDLE;
}else{
// If the check is skipped, return the previous status
Serial.println("Skipping spoolman healthcheck, API is active.");
returnValue = spoolmanConnected;
} }
http.end(); Serial.println("Healthcheck completed!");
return false; return returnValue;
} }
bool saveSpoolmanUrl(const String& url, bool octoOn, const String& octo_url, const String& octoTk) { bool saveSpoolmanUrl(const String& url, bool octoOn, const String& octo_url, const String& octoTk) {
@@ -930,12 +734,13 @@ bool saveSpoolmanUrl(const String& url, bool octoOn, const String& octo_url, con
preferences.end(); preferences.end();
//TBD: This could be handled nicer in the future //TBD: This could be handled nicer in the future
spoolmanExtraFieldsChecked = false;
spoolmanUrl = url; spoolmanUrl = url;
octoEnabled = octoOn; octoEnabled = octoOn;
octoUrl = octo_url; octoUrl = octo_url;
octoToken = octoTk; octoToken = octoTk;
return true; return checkSpoolmanInstance();
} }
String loadSpoolmanUrl() { String loadSpoolmanUrl() {
@@ -955,15 +760,10 @@ String loadSpoolmanUrl() {
bool initSpoolman() { bool initSpoolman() {
oledShowProgressBar(3, 7, DISPLAY_BOOT_TEXT, "Spoolman init"); oledShowProgressBar(3, 7, DISPLAY_BOOT_TEXT, "Spoolman init");
spoolmanUrl = loadSpoolmanUrl(); spoolmanUrl = loadSpoolmanUrl();
spoolmanUrl.trim();
if (spoolmanUrl == "") {
Serial.println("Keine Spoolman-URL gefunden.");
return false;
}
bool success = checkSpoolmanInstance(spoolmanUrl); bool success = checkSpoolmanInstance();
if (!success) { if (!success) {
Serial.println("Spoolman nicht erreichbar."); Serial.println("Spoolman not available");
return false; return false;
} }

View File

@@ -17,10 +17,7 @@ typedef enum {
API_REQUEST_BAMBU_UPDATE, API_REQUEST_BAMBU_UPDATE,
API_REQUEST_SPOOL_TAG_ID_UPDATE, API_REQUEST_SPOOL_TAG_ID_UPDATE,
API_REQUEST_SPOOL_WEIGHT_UPDATE, API_REQUEST_SPOOL_WEIGHT_UPDATE,
API_REQUEST_SPOOL_LOCATION_UPDATE, API_REQUEST_SPOOL_LOCATION_UPDATE
API_REQUEST_VENDOR_CREATE,
API_REQUEST_VENDOR_CHECK,
API_REQUEST_FILAMENT_CHECK
} SpoolmanApiRequestType; } SpoolmanApiRequestType;
extern volatile spoolmanApiStateType spoolmanApiState; extern volatile spoolmanApiStateType spoolmanApiState;
@@ -30,12 +27,9 @@ extern bool octoEnabled;
extern bool sendOctoUpdate; extern bool sendOctoUpdate;
extern String octoUrl; extern String octoUrl;
extern String octoToken; extern String octoToken;
extern uint16_t createdVendorId; // ID of newly created vendor
extern uint16_t foundVendorId; // ID of found vendor
extern uint16_t foundFilamentId; // ID of found filament
extern bool spoolmanConnected; extern bool spoolmanConnected;
bool checkSpoolmanInstance(const String& url); bool checkSpoolmanInstance();
bool saveSpoolmanUrl(const String& url, bool octoOn, const String& octoWh, const String& octoTk); bool saveSpoolmanUrl(const String& url, bool octoOn, const String& octoWh, const String& octoTk);
String loadSpoolmanUrl(); // Neue Funktion zum Laden der URL String loadSpoolmanUrl(); // Neue Funktion zum Laden der URL
bool checkSpoolmanExtraFields(); // Neue Funktion zum Überprüfen der Extrafelder bool checkSpoolmanExtraFields(); // Neue Funktion zum Überprüfen der Extrafelder
@@ -46,12 +40,5 @@ uint8_t updateSpoolLocation(String spoolId, String location);
bool initSpoolman(); // Neue Funktion zum Initialisieren von Spoolman bool initSpoolman(); // Neue Funktion zum Initialisieren von Spoolman
bool updateSpoolBambuData(String payload); // Neue Funktion zum Aktualisieren der Bambu-Daten bool updateSpoolBambuData(String payload); // Neue Funktion zum Aktualisieren der Bambu-Daten
bool updateSpoolOcto(int spoolId); // Neue Funktion zum Aktualisieren der Octo-Daten bool updateSpoolOcto(int spoolId); // Neue Funktion zum Aktualisieren der Octo-Daten
uint16_t checkVendor(String vendor); // Check if vendor exists, return ID
uint16_t createVendor(String vendor); // Create vendor, return ID
uint16_t checkFilament(); // Check if filament exists, return ID
bool createFilament(); // Create filament
uint16_t checkSpool(); // Check if spool exists, return ID
bool createSpool(); // Create spool
void createFilamentFabrik(JsonDocument payload);
#endif #endif

View File

@@ -16,7 +16,6 @@ const uint8_t LOADCELL_DOUT_PIN = 16; //16;
const uint8_t LOADCELL_SCK_PIN = 17; //17; const uint8_t LOADCELL_SCK_PIN = 17; //17;
const uint8_t calVal_eepromAdress = 0; const uint8_t calVal_eepromAdress = 0;
const uint16_t SCALE_LEVEL_WEIGHT = 500; const uint16_t SCALE_LEVEL_WEIGHT = 500;
uint16_t defaultScaleCalibrationValue = 430;
// ***** HX711 // ***** HX711
// ***** TTP223 (Touch Sensor) // ***** TTP223 (Touch Sensor)

View File

@@ -3,36 +3,39 @@
#include <Arduino.h> #include <Arduino.h>
#define BAMBU_DEFAULT_AUTOSEND_TIME 60 #define BAMBU_DEFAULT_AUTOSEND_TIME 60
#define NVS_NAMESPACE_API "api"
#define NVS_KEY_SPOOLMAN_URL "spoolmanUrl"
#define NVS_KEY_OCTOPRINT_ENABLED "octoEnabled"
#define NVS_KEY_OCTOPRINT_URL "octoUrl"
#define NVS_KEY_OCTOPRINT_TOKEN "octoToken"
#define NVS_NAMESPACE_API "api" #define NVS_NAMESPACE_BAMBU "bambu"
#define NVS_KEY_SPOOLMAN_URL "spoolmanUrl" #define NVS_KEY_BAMBU_IP "bambuIp"
#define NVS_KEY_OCTOPRINT_ENABLED "octoEnabled" #define NVS_KEY_BAMBU_ACCESSCODE "bambuCode"
#define NVS_KEY_OCTOPRINT_URL "octoUrl" #define NVS_KEY_BAMBU_SERIAL "bambuSerial"
#define NVS_KEY_OCTOPRINT_TOKEN "octoToken" #define NVS_KEY_BAMBU_AUTOSEND_ENABLE "autosendEnable"
#define NVS_KEY_BAMBU_AUTOSEND_TIME "autosendTime"
#define NVS_NAMESPACE_BAMBU "bambu" #define NVS_NAMESPACE_SCALE "scale"
#define NVS_KEY_BAMBU_IP "bambuIp" #define NVS_KEY_CALIBRATION "cal_value"
#define NVS_KEY_BAMBU_ACCESSCODE "bambuCode" #define NVS_KEY_AUTOTARE "auto_tare"
#define NVS_KEY_BAMBU_SERIAL "bambuSerial" #define SCALE_DEFAULT_CALIBRATION_VALUE 430.0f;
#define NVS_KEY_BAMBU_AUTOSEND_ENABLE "autosendEnable"
#define NVS_KEY_BAMBU_AUTOSEND_TIME "autosendTime"
#define NVS_NAMESPACE_SCALE "scale" #define BAMBU_USERNAME "bblp"
#define NVS_KEY_CALIBRATION "cal_value"
#define NVS_KEY_AUTOTARE "auto_tare"
#define BAMBU_USERNAME "bblp" #define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3CU // See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin) #define SCREEN_WIDTH 128U
#define SCREEN_ADDRESS 0x3CU // See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32 #define SCREEN_HEIGHT 64U
#define SCREEN_WIDTH 128U #define SCREEN_TOP_BAR_HEIGHT 16U
#define SCREEN_HEIGHT 64U #define SCREEN_PROGRESS_BAR_HEIGHT 12U
#define SCREEN_TOP_BAR_HEIGHT 16U #define DISPLAY_BOOT_TEXT "FilaMan"
#define SCREEN_PROGRESS_BAR_HEIGHT 12U
#define DISPLAY_BOOT_TEXT "FilaMan"
#define WIFI_CHECK_INTERVAL 60000U
#define DISPLAY_UPDATE_INTERVAL 1000U
#define SPOOLMAN_HEALTHCHECK_INTERVAL 60000U
extern const uint8_t PN532_IRQ; extern const uint8_t PN532_IRQ;
extern const uint8_t PN532_RESET; extern const uint8_t PN532_RESET;

View File

@@ -97,7 +97,8 @@ int16_t lastWeight = 0;
// WIFI check variables // WIFI check variables
unsigned long lastWifiCheckTime = 0; unsigned long lastWifiCheckTime = 0;
const unsigned long wifiCheckInterval = 60000; // Überprüfe alle 60 Sekunden (60000 ms) unsigned long lastTopRowUpdateTime = 0;
unsigned long lastSpoolmanHealcheckTime = 0;
// Button debounce variables // Button debounce variables
unsigned long lastButtonPress = 0; unsigned long lastButtonPress = 0;
@@ -115,17 +116,23 @@ void loop() {
} }
// Überprüfe regelmäßig die WLAN-Verbindung // Überprüfe regelmäßig die WLAN-Verbindung
if (intervalElapsed(currentMillis, lastWifiCheckTime, wifiCheckInterval)) if (intervalElapsed(currentMillis, lastWifiCheckTime, WIFI_CHECK_INTERVAL))
{ {
checkWiFiConnection(); checkWiFiConnection();
} }
// Periodic display update // Periodic display update
if (intervalElapsed(currentMillis, lastWifiCheckTime, 1000)) if (intervalElapsed(currentMillis, lastTopRowUpdateTime, DISPLAY_UPDATE_INTERVAL))
{ {
oledShowTopRow(); oledShowTopRow();
} }
// Periodic spoolman health check
if (intervalElapsed(currentMillis, lastSpoolmanHealcheckTime, SPOOLMAN_HEALTHCHECK_INTERVAL))
{
checkSpoolmanInstance();
}
// Wenn Bambu auto set Spool aktiv // Wenn Bambu auto set Spool aktiv
if (bambuCredentials.autosend_enable && autoSetToBambuSpoolId > 0) if (bambuCredentials.autosend_enable && autoSetToBambuSpoolId > 0)
{ {
@@ -156,94 +163,93 @@ void loop() {
} }
} }
// Wenn Waage nicht Kalibriert // If scale is not calibrated, only show a warning
if (scaleCalibrated == 3) if (!scaleCalibrated)
{ {
oledShowMessage("Scale not calibrated!"); // Do not show the warning if the calibratin process is onging
vTaskDelay(5000 / portTICK_PERIOD_MS); if(!scaleCalibrationActive){
yield(); oledShowMessage("Scale not calibrated");
esp_task_wdt_reset(); vTaskDelay(1000 / portTICK_PERIOD_MS);
return;
}
// Ausgabe der Waage auf Display
if(pauseMainTask == 0)
{
if (mainTaskWasPaused || (weight != lastWeight && nfcReaderState == NFC_IDLE && (!bambuCredentials.autosend_enable || autoSetToBambuSpoolId == 0)))
{
(weight < 2) ? ((weight < -2) ? oledShowMessage("!! -0") : oledShowWeight(0)) : oledShowWeight(weight);
} }
mainTaskWasPaused = false; }else{
} // Ausgabe der Waage auf Display
else if(pauseMainTask == 0)
{
mainTaskWasPaused = true;
}
// Wenn Timer abgelaufen und nicht gerade ein RFID-Tag geschrieben wird
if (currentMillis - lastWeightReadTime >= weightReadInterval && nfcReaderState < NFC_WRITING)
{
lastWeightReadTime = currentMillis;
// Prüfen ob die Waage korrekt genullt ist
// Abweichung von 2g ignorieren
if (autoTare && (weight > 2 && weight < 7) || weight < -2)
{ {
scale_tare_counter++; if (mainTaskWasPaused || (weight != lastWeight && nfcReaderState == NFC_IDLE && (!bambuCredentials.autosend_enable || autoSetToBambuSpoolId == 0)))
{
(weight < 2) ? ((weight < -2) ? oledShowMessage("!! -0") : oledShowWeight(0)) : oledShowWeight(weight);
}
mainTaskWasPaused = false;
} }
else else
{ {
scale_tare_counter = 0; mainTaskWasPaused = true;
} }
// Prüfen ob das Gewicht gleich bleibt und dann senden
if (abs(weight - lastWeight) <= 2 && weight > 5) // Wenn Timer abgelaufen und nicht gerade ein RFID-Tag geschrieben wird
if (currentMillis - lastWeightReadTime >= weightReadInterval && nfcReaderState < NFC_WRITING)
{ {
weigthCouterToApi++; lastWeightReadTime = currentMillis;
// Prüfen ob die Waage korrekt genullt ist
// Abweichung von 2g ignorieren
if (autoTare && (weight > 2 && weight < 7) || weight < -2)
{
scale_tare_counter++;
}
else
{
scale_tare_counter = 0;
}
// Prüfen ob das Gewicht gleich bleibt und dann senden
if (abs(weight - lastWeight) <= 2 && weight > 5)
{
weigthCouterToApi++;
}
else
{
weigthCouterToApi = 0;
weightSend = 0;
}
} }
else
// reset weight counter after writing tag
// TBD: what exactly is the logic behind this?
if (currentMillis - lastWeightReadTime >= weightReadInterval && nfcReaderState != NFC_IDLE && nfcReaderState != NFC_READ_SUCCESS)
{ {
weigthCouterToApi = 0; weigthCouterToApi = 0;
weightSend = 0;
} }
}
// reset weight counter after writing tag lastWeight = weight;
// TBD: what exactly is the logic behind this?
if (currentMillis - lastWeightReadTime >= weightReadInterval && nfcReaderState != NFC_IDLE && nfcReaderState != NFC_READ_SUCCESS)
{
weigthCouterToApi = 0;
}
lastWeight = weight; // Wenn ein Tag mit SM id erkannte wurde und der Waage Counter anspricht an SM Senden
if (activeSpoolId != "" && weigthCouterToApi > 3 && weightSend == 0 && nfcReaderState == NFC_READ_SUCCESS && tagProcessed == false && spoolmanApiState == API_IDLE) {
// set the current tag as processed to prevent it beeing processed again
tagProcessed = true;
// Wenn ein Tag mit SM id erkannte wurde und der Waage Counter anspricht an SM Senden if (updateSpoolWeight(activeSpoolId, weight))
if (activeSpoolId != "" && weigthCouterToApi > 3 && weightSend == 0 && nfcReaderState == NFC_READ_SUCCESS && tagProcessed == false && spoolmanApiState == API_IDLE) { {
// set the current tag as processed to prevent it beeing processed again weightSend = 1;
tagProcessed = true;
if (updateSpoolWeight(activeSpoolId, weight))
{
weightSend = 1;
}
else
{
oledShowIcon("failed");
vTaskDelay(2000 / portTICK_PERIOD_MS);
}
} }
else
{
oledShowIcon("failed");
vTaskDelay(2000 / portTICK_PERIOD_MS);
}
}
if(sendOctoUpdate && spoolmanApiState == API_IDLE){ if(sendOctoUpdate && spoolmanApiState == API_IDLE){
autoSetToBambuSpoolId = activeSpoolId.toInt(); autoSetToBambuSpoolId = activeSpoolId.toInt();
if(octoEnabled) if(octoEnabled)
{ {
updateSpoolOcto(autoSetToBambuSpoolId); updateSpoolOcto(autoSetToBambuSpoolId);
}
sendOctoUpdate = false;
} }
sendOctoUpdate = false;
} }
esp_task_wdt_reset(); esp_task_wdt_reset();

View File

@@ -8,6 +8,7 @@
#include "esp_task_wdt.h" #include "esp_task_wdt.h"
#include "scale.h" #include "scale.h"
#include "bambu.h" #include "bambu.h"
#include "main.h"
//Adafruit_PN532 nfc(PN532_SCK, PN532_MISO, PN532_MOSI, PN532_SS); //Adafruit_PN532 nfc(PN532_SCK, PN532_MISO, PN532_MOSI, PN532_SS);
Adafruit_PN532 nfc(PN532_IRQ, PN532_RESET); Adafruit_PN532 nfc(PN532_IRQ, PN532_RESET);
@@ -20,6 +21,8 @@ String lastSpoolId = "";
String nfcJsonData = ""; String nfcJsonData = "";
bool tagProcessed = false; bool tagProcessed = false;
volatile bool pauseBambuMqttTask = false; volatile bool pauseBambuMqttTask = false;
volatile bool nfcReadingTaskSuspendRequest = false;
volatile bool nfcReadingTaskSuspendState = false;
struct NfcWriteParameterType { struct NfcWriteParameterType {
bool tagType; bool tagType;
@@ -37,11 +40,6 @@ volatile nfcReaderStateType nfcReaderState = NFC_IDLE;
// ***** PN532 // ***** PN532
// ##### Recycling Fabrik #####
bool isRecyclingFabrik(const char* brand) {
return strcmp(brand, "Recycling Fabrik") == 0;
}
// ##### Funktionen für RFID ##### // ##### Funktionen für RFID #####
void payloadToJson(uint8_t *data) { void payloadToJson(uint8_t *data) {
const char* startJson = strchr((char*)data, '{'); const char* startJson = strchr((char*)data, '{');
@@ -63,12 +61,6 @@ void payloadToJson(uint8_t *data) {
int max_temp = doc["max_temp"]; int max_temp = doc["max_temp"];
const char* brand = doc["brand"]; const char* brand = doc["brand"];
// Recycling Fabrik
if (isRecyclingFabrik(brand)) {
// TODO: Implement specific handling for Recycling Fabrik
Serial.println("Recycling Fabrik erkannt.");
}
Serial.println(); Serial.println();
Serial.println("-----------------"); Serial.println("-----------------");
Serial.println("JSON-Parsed Data:"); Serial.println("JSON-Parsed Data:");
@@ -226,7 +218,7 @@ bool decodeNdefAndReturnJson(const byte* encodedMessage) {
} }
// JSON-Dokument verarbeiten // JSON-Dokument verarbeiten
JsonDocument doc; JsonDocument doc; // Passen Sie die Größe an den JSON-Inhalt an
DeserializationError error = deserializeJson(doc, nfcJsonData); DeserializationError error = deserializeJson(doc, nfcJsonData);
if (error) if (error)
{ {
@@ -243,14 +235,12 @@ bool decodeNdefAndReturnJson(const byte* encodedMessage) {
// Sende die aktualisierten AMS-Daten an alle WebSocket-Clients // Sende die aktualisierten AMS-Daten an alle WebSocket-Clients
Serial.println("JSON-Dokument erfolgreich verarbeitet"); Serial.println("JSON-Dokument erfolgreich verarbeitet");
Serial.println(doc.as<String>()); Serial.println(doc.as<String>());
if (doc["sm_id"].is<String>() && doc["sm_id"] != "" && doc["sm_id"] != "0") if (doc["sm_id"].is<String>() && doc["sm_id"] != "")
{ {
oledShowProgressBar(2, octoEnabled?5:4, "Spool Tag", "Weighing"); oledShowProgressBar(2, octoEnabled?5:4, "Spool Tag", "Weighing");
Serial.println("SPOOL-ID gefunden: " + doc["sm_id"].as<String>()); Serial.println("SPOOL-ID gefunden: " + doc["sm_id"].as<String>());
activeSpoolId = doc["sm_id"].as<String>(); activeSpoolId = doc["sm_id"].as<String>();
lastSpoolId = activeSpoolId; lastSpoolId = activeSpoolId;
Serial.println("Api state: " + String(spoolmanApiState));
} }
else if(doc["location"].is<String>() && doc["location"] != "") else if(doc["location"].is<String>() && doc["location"] != "")
{ {
@@ -265,13 +255,6 @@ bool decodeNdefAndReturnJson(const byte* encodedMessage) {
oledShowProgressBar(1, 1, "Failure", "Scan spool first"); oledShowProgressBar(1, 1, "Failure", "Scan spool first");
} }
} }
// Recycling Fabrik
else if (isRecyclingFabrik(doc["type"].as<String>().c_str())) {
// If no sm_id is present but the brand is Recycling Fabrik then
// create a new spool, maybe brand too, in Spoolman
Serial.println("Recycling Fabrik Tag found!");
createFilamentFabrik(doc);
}
else else
{ {
Serial.println("Keine SPOOL-ID gefunden."); Serial.println("Keine SPOOL-ID gefunden.");
@@ -296,19 +279,21 @@ void writeJsonToTag(void *parameter) {
Serial.println(params->payload); Serial.println(params->payload);
nfcReaderState = NFC_WRITING; nfcReaderState = NFC_WRITING;
vTaskSuspend(RfidReaderTask);
vTaskDelay(50 / portTICK_PERIOD_MS); // First request the reading task to be suspended and than wait until it responds
nfcReadingTaskSuspendRequest = true;
while(nfcReadingTaskSuspendState == false){
vTaskDelay(100 / portTICK_PERIOD_MS);
}
//pauseBambuMqttTask = true; //pauseBambuMqttTask = true;
// aktualisieren der Website wenn sich der Status ändert // aktualisieren der Website wenn sich der Status ändert
sendNfcData(); sendNfcData();
vTaskDelay(100 / portTICK_PERIOD_MS); vTaskDelay(100 / portTICK_PERIOD_MS);
Serial.println("CP 1");
// Wait 10sec for tag // Wait 10sec for tag
uint8_t success = 0; uint8_t success = 0;
String uidString = ""; String uidString = "";
for (uint16_t i = 0; i < 20; i++) { for (uint16_t i = 0; i < 20; i++) {
Serial.println("CP 2");
uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 }; // Buffer to store the returned UID uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 }; // Buffer to store the returned UID
uint8_t uidLength; uint8_t uidLength;
// yield before potentially waiting for 400ms // yield before potentially waiting for 400ms
@@ -316,7 +301,6 @@ void writeJsonToTag(void *parameter) {
esp_task_wdt_reset(); esp_task_wdt_reset();
success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength, 400); success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength, 400);
if (success) { if (success) {
Serial.println("CP 3.1");
for (uint8_t i = 0; i < uidLength; i++) { for (uint8_t i = 0; i < uidLength; i++) {
//TBD: Rework to remove all the string operations //TBD: Rework to remove all the string operations
uidString += String(uid[i], HEX); uidString += String(uid[i], HEX);
@@ -326,8 +310,6 @@ void writeJsonToTag(void *parameter) {
} }
foundNfcTag(nullptr, success); foundNfcTag(nullptr, success);
break; break;
}else{
Serial.println("CP 3.2");
} }
yield(); yield();
@@ -390,7 +372,7 @@ void writeJsonToTag(void *parameter) {
sendWriteResult(nullptr, success); sendWriteResult(nullptr, success);
sendNfcData(); sendNfcData();
vTaskResume(RfidReaderTask); nfcReadingTaskSuspendRequest = false;
pauseBambuMqttTask = false; pauseBambuMqttTask = false;
vTaskDelete(NULL); vTaskDelete(NULL);
@@ -402,7 +384,7 @@ void startWriteJsonToTag(const bool isSpoolTag, const char* payload) {
parameters->payload = strdup(payload); parameters->payload = strdup(payload);
// Task nicht mehrfach starten // Task nicht mehrfach starten
if (nfcReaderState == NFC_IDLE) { if (nfcReaderState == NFC_IDLE || nfcReaderState == NFC_READ_ERROR || nfcReaderState == NFC_READ_SUCCESS) {
oledShowProgressBar(0, 1, "Write Tag", "Place tag now"); oledShowProgressBar(0, 1, "Write Tag", "Place tag now");
// Erstelle die Task // Erstelle die Task
xTaskCreate( xTaskCreate(
@@ -423,15 +405,16 @@ void scanRfidTask(void * parameter) {
Serial.println("RFID Task gestartet"); Serial.println("RFID Task gestartet");
for(;;) { for(;;) {
// Wenn geschrieben wird Schleife aussetzen // Wenn geschrieben wird Schleife aussetzen
if (nfcReaderState != NFC_WRITING) if (nfcReaderState != NFC_WRITING && !nfcReadingTaskSuspendRequest && !booting)
{ {
nfcReadingTaskSuspendState = false;
yield(); yield();
uint8_t success; uint8_t success;
uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 }; // Buffer to store the returned UID uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 }; // Buffer to store the returned UID
uint8_t uidLength; uint8_t uidLength;
success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength, 1000); success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength, 500);
foundNfcTag(nullptr, success); foundNfcTag(nullptr, success);
@@ -448,7 +431,7 @@ void scanRfidTask(void * parameter) {
oledShowProgressBar(0, octoEnabled?5:4, "Reading", "Detecting tag"); oledShowProgressBar(0, octoEnabled?5:4, "Reading", "Detecting tag");
vTaskDelay(500 / portTICK_PERIOD_MS); //vTaskDelay(500 / portTICK_PERIOD_MS);
if (uidLength == 7) if (uidLength == 7)
{ {
@@ -505,7 +488,7 @@ void scanRfidTask(void * parameter) {
} }
} }
if (!success && nfcReaderState != NFC_IDLE) if (!success && nfcReaderState != NFC_IDLE && !nfcReadingTaskSuspendRequest)
{ {
nfcReaderState = NFC_IDLE; nfcReaderState = NFC_IDLE;
//uidString = ""; //uidString = "";
@@ -518,6 +501,12 @@ void scanRfidTask(void * parameter) {
// aktualisieren der Website wenn sich der Status ändert // aktualisieren der Website wenn sich der Status ändert
sendNfcData(); sendNfcData();
} }
else
{
nfcReadingTaskSuspendState = true;
Serial.println("NFC Reading disabled");
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
yield(); yield();
} }
} }

View File

@@ -17,8 +17,9 @@ uint8_t weigthCouterToApi = 0;
uint8_t scale_tare_counter = 0; uint8_t scale_tare_counter = 0;
bool scaleTareRequest = false; bool scaleTareRequest = false;
uint8_t pauseMainTask = 0; uint8_t pauseMainTask = 0;
uint8_t scaleCalibrated = 1; bool scaleCalibrated;
bool autoTare = true; bool autoTare = true;
bool scaleCalibrationActive = false;
// ##### Funktionen für Waage ##### // ##### Funktionen für Waage #####
uint8_t setAutoTare(bool autoTareValue) { uint8_t setAutoTare(bool autoTareValue) {
@@ -88,7 +89,13 @@ void start_scale(bool touchSensorConnected) {
// NVS lesen // NVS lesen
Preferences preferences; Preferences preferences;
preferences.begin(NVS_NAMESPACE_SCALE, true); // true = readonly preferences.begin(NVS_NAMESPACE_SCALE, true); // true = readonly
calibrationValue = preferences.getFloat(NVS_KEY_CALIBRATION, defaultScaleCalibrationValue); if(preferences.isKey(NVS_KEY_CALIBRATION)){
calibrationValue = preferences.getFloat(NVS_KEY_CALIBRATION);
scaleCalibrated = true;
}else{
calibrationValue = SCALE_DEFAULT_CALIBRATION_VALUE;
scaleCalibrated = false;
}
// auto Tare // auto Tare
// Wenn Touch Sensor verbunden, dann autoTare auf false setzen // Wenn Touch Sensor verbunden, dann autoTare auf false setzen
@@ -103,18 +110,6 @@ void start_scale(bool touchSensorConnected) {
scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN); scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN);
if (isnan(calibrationValue) || calibrationValue < 1) {
calibrationValue = defaultScaleCalibrationValue;
scaleCalibrated = 0;
oledShowMessage("Scale not calibrated!");
for (uint16_t i = 0; i < 50000; i++) {
yield();
vTaskDelay(pdMS_TO_TICKS(1));
esp_task_wdt_reset();
}
}
oledShowProgressBar(6, 7, DISPLAY_BOOT_TEXT, "Tare scale"); oledShowProgressBar(6, 7, DISPLAY_BOOT_TEXT, "Tare scale");
for (uint16_t i = 0; i < 2000; i++) { for (uint16_t i = 0; i < 2000; i++) {
yield(); yield();
@@ -152,6 +147,8 @@ uint8_t calibrate_scale() {
uint8_t returnState = 0; uint8_t returnState = 0;
float newCalibrationValue; float newCalibrationValue;
scaleCalibrationActive = true;
vTaskSuspend(RfidReaderTask); vTaskSuspend(RfidReaderTask);
vTaskSuspend(ScaleTask); vTaskSuspend(ScaleTask);
@@ -228,6 +225,7 @@ uint8_t calibrate_scale() {
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
scaleCalibrated = true;
returnState = 1; returnState = 1;
} }
else else
@@ -262,6 +260,7 @@ uint8_t calibrate_scale() {
vTaskResume(ScaleTask); vTaskResume(ScaleTask);
pauseBambuMqttTask = false; pauseBambuMqttTask = false;
pauseMainTask = 0; pauseMainTask = 0;
scaleCalibrationActive = false;
return returnState; return returnState;
} }

View File

@@ -15,8 +15,9 @@ extern uint8_t weigthCouterToApi;
extern uint8_t scale_tare_counter; extern uint8_t scale_tare_counter;
extern uint8_t scaleTareRequest; extern uint8_t scaleTareRequest;
extern uint8_t pauseMainTask; extern uint8_t pauseMainTask;
extern uint8_t scaleCalibrated; extern bool scaleCalibrated;
extern bool autoTare; extern bool autoTare;
extern bool scaleCalibrationActive;
extern TaskHandle_t ScaleTask; extern TaskHandle_t ScaleTask;