diff --git a/html/rfid.html b/html/rfid.html
index a481c04..cfffff8 100644
--- a/html/rfid.html
+++ b/html/rfid.html
@@ -139,6 +139,18 @@
+
+
+
Spoolman Locations
+
+
+
+
+
+
+
diff --git a/html/rfid.js b/html/rfid.js
index 3761f51..6bdcea2 100644
--- a/html/rfid.js
+++ b/html/rfid.js
@@ -626,11 +626,11 @@ function writeNfcTag() {
// Erstelle das NFC-Datenpaket mit korrekten Datentypen
const nfcData = {
- color_hex: selectedSpool.filament.color_hex || "FFFFFF",
- type: selectedSpool.filament.material,
- min_temp: minTemp,
- max_temp: maxTemp,
- brand: selectedSpool.filament.vendor.name,
+ //color_hex: selectedSpool.filament.color_hex || "FFFFFF",
+ //type: selectedSpool.filament.material,
+ //min_temp: minTemp,
+ //max_temp: maxTemp,
+ //brand: selectedSpool.filament.vendor.name,
sm_id: String(selectedSpool.id) // Konvertiere zu String
};
@@ -647,16 +647,56 @@ function writeNfcTag() {
}
}
+function writeLocationNfcTag() {
+ const selectedText = document.getElementById("locationSelect").value;
+ if (selectedText === "Please choose...") {
+ alert('Please select a location first.');
+ return;
+ }
+ // Erstelle das NFC-Datenpaket mit korrekten Datentypen
+ const nfcData = {
+ location: String(selectedText)
+ };
+
+ if (socket?.readyState === WebSocket.OPEN) {
+ const writeButton = document.getElementById("writeLocationNfcButton");
+ writeButton.classList.add("writing");
+ writeButton.textContent = "Writing";
+ socket.send(JSON.stringify({
+ type: 'writeNfcTag',
+ payload: nfcData
+ }));
+ } else {
+ alert('Not connected to Server. Please check connection.');
+ }
+}
+
function handleWriteNfcTagResponse(success) {
const writeButton = document.getElementById("writeNfcButton");
- writeButton.classList.remove("writing");
- writeButton.classList.add(success ? "success" : "error");
- writeButton.textContent = success ? "Write success" : "Write failed";
+ const writeLocationButton = document.getElementById("writeLocationNfcButton");
+ if(writeButton.classList.contains("writing")){
+ writeButton.classList.remove("writing");
+ writeButton.classList.add(success ? "success" : "error");
+ writeButton.textContent = success ? "Write success" : "Write failed";
- setTimeout(() => {
- writeButton.classList.remove("success", "error");
- writeButton.textContent = "Write Tag";
- }, 5000);
+ setTimeout(() => {
+ writeButton.classList.remove("success", "error");
+ writeButton.textContent = "Write Tag";
+ }, 5000);
+ }
+
+ if(writeLocationButton.classList.contains("writing")){
+ writeLocationButton.classList.remove("writing");
+ writeLocationButton.classList.add(success ? "success" : "error");
+ writeLocationButton.textContent = success ? "Write success" : "Write failed";
+
+ setTimeout(() => {
+ writeLocationButton.classList.remove("success", "error");
+ writeLocationButton.textContent = "Write Location Tag";
+ }, 5000);
+ }
+
+
}
function showNotification(message, isSuccess) {
diff --git a/html/spoolman.js b/html/spoolman.js
index 8193984..aba63f0 100644
--- a/html/spoolman.js
+++ b/html/spoolman.js
@@ -1,6 +1,7 @@
// Globale Variablen
let spoolmanUrl = '';
let spoolsData = [];
+let locationData = [];
// Hilfsfunktionen für Datenmanipulation
function processSpoolData(data) {
@@ -133,6 +134,26 @@ function populateVendorDropdown(data, selectedSmId = null) {
}
}
+// Dropdown-Funktionen
+function populateLocationDropdown(data) {
+ const locationSelect = document.getElementById("locationSelect");
+ if (!locationSelect) {
+ console.error('locationSelect Element nicht gefunden');
+ return;
+ }
+
+ locationSelect.innerHTML = '';
+ // Dropdown mit gefilterten Herstellern befüllen - alphabetisch sortiert
+ Object.entries(data)
+ .sort(([, nameA], [, nameB]) => nameA.localeCompare(nameB)) // Sort vendors alphabetically by name
+ .forEach(([id, name]) => {
+ const option = document.createElement("option");
+ option.value = name;
+ option.textContent = name;
+ locationSelect.appendChild(option);
+ });
+}
+
function updateFilamentDropdown(selectedSmId = null) {
const vendorId = document.getElementById("vendorSelect").value;
const dropdownContentInner = document.getElementById("filament-dropdown-content");
@@ -208,6 +229,13 @@ function updateFilamentDropdown(selectedSmId = null) {
}
}
+function updateLocationSelect(){
+ const writeLocationNfcButton = document.getElementById('writeLocationNfcButton');
+ if(writeLocationNfcButton){
+ writeLocationNfcButton.classList.remove("hidden");
+ }
+}
+
function selectFilament(spool) {
const selectedColor = document.getElementById("selected-color");
const selectedText = document.getElementById("selected-filament");
@@ -261,10 +289,18 @@ async function initSpoolman() {
const fetchedData = await fetchSpoolData();
spoolsData = processSpoolData(fetchedData);
-
+
document.dispatchEvent(new CustomEvent('spoolDataLoaded', {
detail: spoolsData
}));
+
+ locationData = await fetchLocationData();
+
+ document.dispatchEvent(new CustomEvent('locationDataLoaded', {
+ detail: locationData
+ }));
+
+
} catch (error) {
console.error('Fehler beim Initialisieren von Spoolman:', error);
document.dispatchEvent(new CustomEvent('spoolmanError', {
@@ -292,6 +328,25 @@ async function fetchSpoolData() {
}
}
+async function fetchLocationData() {
+ try {
+ if (!spoolmanUrl) {
+ throw new Error('Spoolman URL ist nicht initialisiert');
+ }
+
+ const response = await fetch(`${spoolmanUrl}/api/v1/location`);
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ const data = await response.json();
+ return data;
+ } catch (error) {
+ console.error('Fehler beim Abrufen der Location-Daten:', error);
+ return [];
+ }
+}
+
// Event Listener
document.addEventListener('DOMContentLoaded', () => {
initSpoolman();
@@ -300,6 +355,11 @@ document.addEventListener('DOMContentLoaded', () => {
if (vendorSelect) {
vendorSelect.addEventListener('change', () => updateFilamentDropdown());
}
+
+ const locationSelect = document.getElementById('locationSelect');
+ if (locationSelect) {
+ locationSelect.addEventListener('change', () => updateLocationSelect());
+ }
const onlyWithoutSmId = document.getElementById('onlyWithoutSmId');
if (onlyWithoutSmId) {
@@ -312,6 +372,10 @@ document.addEventListener('DOMContentLoaded', () => {
document.addEventListener('spoolDataLoaded', (event) => {
populateVendorDropdown(event.detail);
});
+
+ document.addEventListener('locationDataLoaded', (event) => {
+ populateLocationDropdown(event.detail);
+ });
window.onclick = function(event) {
if (!event.target.closest('.custom-dropdown')) {
@@ -342,6 +406,7 @@ window.getSpoolData = () => spoolsData;
window.setSpoolData = (data) => { spoolsData = data; };
window.reloadSpoolData = initSpoolman;
window.populateVendorDropdown = populateVendorDropdown;
+window.populateLocationDropdown = populateLocationDropdown;
window.updateFilamentDropdown = updateFilamentDropdown;
window.toggleFilamentDropdown = () => {
const content = document.getElementById("filament-dropdown-content");
diff --git a/html/style.css b/html/style.css
index e0f37aa..d51700c 100644
--- a/html/style.css
+++ b/html/style.css
@@ -971,31 +971,35 @@ input[type="submit"]:disabled,
}
/* Schreib-Button */
-#writeNfcButton {
+#writeNfcButton, #writeLocationNfcButton {
background-color: #007bff;
color: white;
transition: background-color 0.3s, color 0.3s;
width: 160px;
}
-#writeNfcButton.writing {
+#writeNfcButton.writing, #writeLocationNfcButton.writing {
background-color: #ffc107;
color: black;
width: 160px;
}
-#writeNfcButton.success {
+#writeNfcButton.success, #writeLocationNfcButton.success {
background-color: #28a745;
color: white;
width: 160px;
}
-#writeNfcButton.error {
+#writeNfcButton.error, #writeLocationNfcButton.error {
background-color: #dc3545;
color: white;
width: 160px;
}
+#writeLocationNfcButton{
+ width: 250px;
+}
+
@keyframes dots {
0% { content: ""; }
33% { content: "."; }
@@ -1003,7 +1007,7 @@ input[type="submit"]:disabled,
100% { content: "..."; }
}
-#writeNfcButton.writing::after {
+#writeNfcButton.writing::after, #writeLocationNfcButton.writing::after {
content: "...";
animation: dots 1s steps(3, end) infinite;
}
diff --git a/src/api.cpp b/src/api.cpp
index a8b3e2d..a100239 100644
--- a/src/api.cpp
+++ b/src/api.cpp
@@ -11,6 +11,7 @@ String octoUrl = "";
String octoToken = "";
struct SendToApiParams {
+ SpoolmanApiRequestType requestType;
String httpType;
String spoolsUrl;
String updatePayload;
@@ -90,6 +91,7 @@ void sendToApi(void *parameter) {
SendToApiParams* params = (SendToApiParams*)parameter;
// Extrahiere die Werte
+ SpoolmanApiRequestType requestType = params->requestType;
String httpType = params->httpType;
String spoolsUrl = params->spoolsUrl;
String updatePayload = params->updatePayload;
@@ -118,12 +120,15 @@ void sendToApi(void *parameter) {
Serial.print("Fehler beim Parsen der JSON-Antwort: ");
Serial.println(error.c_str());
} else {
- if (httpType == "PUT") {
+ if (requestType == API_REQUEST_SPOOL_WEIGHT_UPDATE) {
uint16_t remaining_weight = doc["remaining_weight"].as();
Serial.print("Aktuelles Gewicht: ");
Serial.println(remaining_weight);
oledShowMessage("Remaining: " + String(remaining_weight) + "g");
}
+ else if ( requestType == API_REQUEST_SPOOL_LOCATION_UPDATE) {
+ oledShowMessage("Location updated!");
+ }
vTaskDelay(3000 / portTICK_PERIOD_MS);
doc.clear();
@@ -178,6 +183,7 @@ bool updateSpoolTagId(String uidString, const char* payload) {
Serial.println("Fehler: Kann Speicher für Task-Parameter nicht allokieren.");
return false;
}
+ params->requestType = API_REQUEST_SPOOL_TAG_ID_UPDATE;
params->httpType = "PATCH";
params->spoolsUrl = spoolsUrl;
params->updatePayload = updatePayload;
@@ -219,6 +225,7 @@ uint8_t updateSpoolWeight(String spoolId, uint16_t weight) {
Serial.println("Fehler: Kann Speicher für Task-Parameter nicht allokieren.");
return 0;
}
+ params->requestType = API_REQUEST_SPOOL_WEIGHT_UPDATE;
params->httpType = "PUT";
params->spoolsUrl = spoolsUrl;
params->updatePayload = updatePayload;
@@ -238,6 +245,45 @@ uint8_t updateSpoolWeight(String spoolId, uint16_t weight) {
return 1;
}
+uint8_t updateSpoolLocation(String spoolId, String location){
+ String spoolsUrl = spoolmanUrl + apiUrl + "/spool/" + spoolId;
+ Serial.print("Update Spule mit URL: ");
+ Serial.println(spoolsUrl);
+
+ // Update Payload erstellen
+ JsonDocument updateDoc;
+ updateDoc["location"] = location;
+
+ String updatePayload;
+ serializeJson(updateDoc, updatePayload);
+ Serial.print("Update Payload: ");
+ Serial.println(updatePayload);
+
+ SendToApiParams* params = new SendToApiParams();
+ if (params == nullptr) {
+ Serial.println("Fehler: Kann Speicher für Task-Parameter nicht allokieren.");
+ return 0;
+ }
+ params->requestType = API_REQUEST_SPOOL_LOCATION_UPDATE;
+ params->httpType = "PATCH";
+ params->spoolsUrl = spoolsUrl;
+ params->updatePayload = updatePayload;
+
+ // 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)
+ );
+
+ updateDoc.clear();
+
+ return 1;
+}
+
bool updateSpoolOcto(int spoolId) {
String spoolsUrl = octoUrl + "/plugin/Spoolman/selectSpool";
Serial.print("Update Spule in Octoprint mit URL: ");
@@ -257,6 +303,7 @@ bool updateSpoolOcto(int spoolId) {
Serial.println("Fehler: Kann Speicher für Task-Parameter nicht allokieren.");
return false;
}
+ params->requestType = API_REQUEST_OCTO_SPOOL_UPDATE;
params->httpType = "POST";
params->spoolsUrl = spoolsUrl;
params->updatePayload = updatePayload;
@@ -306,6 +353,7 @@ bool updateSpoolBambuData(String payload) {
Serial.println("Fehler: Kann Speicher für Task-Parameter nicht allokieren.");
return false;
}
+ params->requestType = API_REQUEST_BAMBU_UPDATE;
params->httpType = "PATCH";
params->spoolsUrl = spoolsUrl;
params->updatePayload = updatePayload;
@@ -510,6 +558,8 @@ bool checkSpoolmanInstance(const String& url) {
return strcmp(status, "healthy") == 0;
}
}
+ } else {
+ Serial.println("Error contacting spoolman instance! HTTP Code: " + String(httpCode));
}
http.end();
return false;
diff --git a/src/api.h b/src/api.h
index 88853c8..5311dfc 100644
--- a/src/api.h
+++ b/src/api.h
@@ -12,6 +12,14 @@ typedef enum {
API_TRANSMITTING
} spoolmanApiStateType;
+typedef enum {
+ API_REQUEST_OCTO_SPOOL_UPDATE,
+ API_REQUEST_BAMBU_UPDATE,
+ API_REQUEST_SPOOL_TAG_ID_UPDATE,
+ API_REQUEST_SPOOL_WEIGHT_UPDATE,
+ API_REQUEST_SPOOL_LOCATION_UPDATE
+} SpoolmanApiRequestType;
+
extern volatile spoolmanApiStateType spoolmanApiState;
extern bool spoolman_connected;
extern String spoolmanUrl;
@@ -26,6 +34,7 @@ bool checkSpoolmanExtraFields(); // Neue Funktion zum Überprüfen der Extrafeld
JsonDocument fetchSingleSpoolInfo(int spoolId); // API-Funktion für die Webseite
bool updateSpoolTagId(String uidString, const char* payload); // Neue Funktion zum Aktualisieren eines Spools
uint8_t updateSpoolWeight(String spoolId, uint16_t weight); // Neue Funktion zum Aktualisieren des Gewichts
+uint8_t updateSpoolLocation(String spoolId, String location);
bool initSpoolman(); // Neue Funktion zum Initialisieren von Spoolman
bool updateSpoolBambuData(String payload); // Neue Funktion zum Aktualisieren der Bambu-Daten
bool updateSpoolOcto(int spoolId); // Neue Funktion zum Aktualisieren der Octo-Daten
diff --git a/src/nfc.cpp b/src/nfc.cpp
index 855405f..ea10267 100644
--- a/src/nfc.cpp
+++ b/src/nfc.cpp
@@ -218,11 +218,25 @@ bool decodeNdefAndReturnJson(const byte* encodedMessage) {
// Sende die aktualisierten AMS-Daten an alle WebSocket-Clients
Serial.println("JSON-Dokument erfolgreich verarbeitet");
Serial.println(doc.as());
- if (doc["sm_id"] != "")
+ if (doc.containsKey("sm_id") && doc["sm_id"] != "")
{
Serial.println("SPOOL-ID gefunden: " + doc["sm_id"].as());
spoolId = doc["sm_id"].as();
- }
+ }
+ else if(doc.containsKey("location") && doc["location"] != "")
+ {
+ Serial.println("Location Tag found!");
+ String location = doc["location"].as();
+ if(spoolId != ""){
+ updateSpoolLocation(spoolId, location);
+ }
+ else
+ {
+ Serial.println("Location update tag scanned without scanning spool before!");
+ oledShowMessage("No spool scanned before!");
+ }
+
+ }
else
{
Serial.println("Keine SPOOL-ID gefunden.");
diff --git a/src/scale.cpp b/src/scale.cpp
index 5808829..83edeeb 100644
--- a/src/scale.cpp
+++ b/src/scale.cpp
@@ -74,7 +74,11 @@ void scale_loop(void * parameter) {
scaleTareRequest = false;
}
- weight = round(scale.get_units());
+ // Only update weight if median changed more than 1
+ int16_t newWeight = round(scale.get_units());
+ if(abs(weight-newWeight) > 1){
+ weight = newWeight;
+ }
}
vTaskDelay(pdMS_TO_TICKS(100));