feat: implement auto send feature for Bambu spool management and update related configurations

This commit is contained in:
Manuel Weiser 2025-02-24 19:14:51 +01:00
parent 1ade007473
commit d70b187bf9
9 changed files with 124 additions and 98 deletions

View File

@ -74,8 +74,9 @@
const ip = document.getElementById('bambuIp').value; const ip = document.getElementById('bambuIp').value;
const serial = document.getElementById('bambuSerial').value; const serial = document.getElementById('bambuSerial').value;
const code = document.getElementById('bambuCode').value; const code = document.getElementById('bambuCode').value;
const autoSend = document.getElementById('autoSend').checked;
fetch(`/api/bambu?bambu_ip=${encodeURIComponent(ip)}&bambu_serialnr=${encodeURIComponent(serial)}&bambu_accesscode=${encodeURIComponent(code)}`) fetch(`/api/bambu?bambu_ip=${encodeURIComponent(ip)}&bambu_serialnr=${encodeURIComponent(serial)}&bambu_accesscode=${encodeURIComponent(code)}&autoSend=${autoSend}`)
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
if (data.healthy) { if (data.healthy) {
@ -121,6 +122,12 @@
<label for="bambuCode">Access Code:</label> <label for="bambuCode">Access Code:</label>
<input type="text" id="bambuCode" placeholder="Access Code vom Drucker" value="{{bambuCode}}"> <input type="text" id="bambuCode" placeholder="Access Code vom Drucker" value="{{bambuCode}}">
</div> </div>
<div class="input-group">
If activated, FilaMan will automatically update the next filled tray with the last scanned and weighed spool.
<label for="autoSend">Auto Send to Bambu:</label>
<input type="checkbox" id="autoSend">
</div>
<button onclick="saveBambuCredentials()">Save Bambu Credentials</button> <button onclick="saveBambuCredentials()">Save Bambu Credentials</button>
<p id="bambuStatusMessage"></p> <p id="bambuStatusMessage"></p>
</div> </div>

View File

@ -37,9 +37,9 @@ struct SendToApiParams {
} }
*/ */
JsonDocument fetchSpoolsForWebsite() { JsonDocument fetchSingleSpoolInfo(int spoolId) {
HTTPClient http; HTTPClient http;
String spoolsUrl = spoolmanUrl + apiUrl + "/spool"; String spoolsUrl = spoolmanUrl + apiUrl + "/spool/" + spoolId;
Serial.print("Rufe Spool-Daten von: "); Serial.print("Rufe Spool-Daten von: ");
Serial.println(spoolsUrl); Serial.println(spoolsUrl);
@ -56,84 +56,45 @@ JsonDocument fetchSpoolsForWebsite() {
Serial.print("Fehler beim Parsen der JSON-Antwort: "); Serial.print("Fehler beim Parsen der JSON-Antwort: ");
Serial.println(error.c_str()); Serial.println(error.c_str());
} else { } else {
JsonArray spools = doc.as<JsonArray>(); String filamentType = doc["filament"]["material"].as<String>();
JsonArray filteredSpools = filteredDoc.to<JsonArray>(); String filamentBrand = doc["filament"]["vendor"]["name"].as<String>();
for (JsonObject spool : spools) { int nozzle_temp_min = 0;
JsonObject filteredSpool = filteredSpools.add<JsonObject>(); int nozzle_temp_max = 0;
filteredSpool["extra"]["nfc_id"] = spool["extra"]["nfc_id"]; if (doc["filament"]["extra"]["nozzle_temperature"].is<String>()) {
String tempString = doc["filament"]["extra"]["nozzle_temperature"].as<String>();
tempString.replace("[", "");
tempString.replace("]", "");
int commaIndex = tempString.indexOf(',');
JsonObject filament = filteredSpool["filament"].to<JsonObject>(); if (commaIndex != -1) {
filament["sm_id"] = spool["id"]; nozzle_temp_min = tempString.substring(0, commaIndex).toInt();
filament["id"] = spool["filament"]["id"]; nozzle_temp_max = tempString.substring(commaIndex + 1).toInt();
filament["name"] = spool["filament"]["name"];
filament["material"] = spool["filament"]["material"];
filament["color_hex"] = spool["filament"]["color_hex"];
filament["nozzle_temperature"] = spool["filament"]["extra"]["nozzle_temperature"]; // [190,230]
filament["price_meter"] = spool["filament"]["extra"]["price_meter"];
filament["price_gramm"] = spool["filament"]["extra"]["price_gramm"];
JsonObject vendor = filament["vendor"].to<JsonObject>();
vendor["id"] = spool["filament"]["vendor"]["id"];
vendor["name"] = spool["filament"]["vendor"]["name"];
} }
} }
} else {
Serial.print("Fehler beim Abrufen der Spool-Daten. HTTP-Code: ");
Serial.println(httpCode);
}
http.end(); String filamentColor = doc["filament"]["color_hex"].as<String>();
return filteredDoc; filamentColor.toUpperCase();
}
JsonDocument fetchAllSpoolsInfo() { String tray_info_idx = doc["filament"]["extra"]["bambu_idx"].as<String>();
HTTPClient http; tray_info_idx.replace("\"", "");
String spoolsUrl = spoolmanUrl + apiUrl + "/spool";
Serial.print("Rufe Spool-Daten von: "); String cali_idx = doc["filament"]["extra"]["bambu_cali_id"].as<String>(); // "\"153\""
Serial.println(spoolsUrl); cali_idx.replace("\"", "");
http.begin(spoolsUrl); String bambu_setting_id = doc["filament"]["extra"]["bambu_setting_id"].as<String>(); // "\"PFUSf40e9953b40d3d\""
int httpCode = http.GET(); bambu_setting_id.replace("\"", "");
JsonDocument filteredDoc; doc.clear();
if (httpCode == HTTP_CODE_OK) {
String payload = http.getString();
JsonDocument doc;
DeserializationError error = deserializeJson(doc, payload);
if (error) {
Serial.print("Fehler beim Parsen der JSON-Antwort: ");
Serial.println(error.c_str());
} else {
JsonArray spools = doc.as<JsonArray>();
JsonArray filteredSpools = filteredDoc.to<JsonArray>();
for (JsonObject spool : spools) { filteredDoc["color"] = filamentColor;
JsonObject filteredSpool = filteredSpools.add<JsonObject>(); filteredDoc["type"] = filamentType;
filteredSpool["price"] = spool["price"]; filteredDoc["nozzle_temp_min"] = nozzle_temp_min;
filteredSpool["remaining_weight"] = spool["remaining_weight"]; filteredDoc["nozzle_temp_max"] = nozzle_temp_max;
filteredSpool["used_weight"] = spool["used_weight"]; filteredDoc["brand"] = filamentBrand;
filteredSpool["extra"]["nfc_id"] = spool["extra"]["nfc_id"]; filteredDoc["tray_info_idx"] = tray_info_idx;
filteredDoc["cali_idx"] = cali_idx;
JsonObject filament = filteredSpool["filament"].to<JsonObject>(); filteredDoc["bambu_setting_id"] = bambu_setting_id;
filament["id"] = spool["filament"]["id"];
filament["name"] = spool["filament"]["name"];
filament["material"] = spool["filament"]["material"];
filament["density"] = spool["filament"]["density"];
filament["diameter"] = spool["filament"]["diameter"];
filament["spool_weight"] = spool["filament"]["spool_weight"];
filament["color_hex"] = spool["filament"]["color_hex"];
JsonObject vendor = filament["vendor"].to<JsonObject>();
vendor["id"] = spool["filament"]["vendor"]["id"];
vendor["name"] = spool["filament"]["vendor"]["name"];
JsonObject extra = filament["extra"].to<JsonObject>();
extra["nozzle_temperature"] = spool["filament"]["extra"]["nozzle_temperature"];
extra["price_gramm"] = spool["filament"]["extra"]["price_gramm"];
extra["price_meter"] = spool["filament"]["extra"]["price_meter"];
}
} }
} else { } else {
Serial.print("Fehler beim Abrufen der Spool-Daten. HTTP-Code: "); Serial.print("Fehler beim Abrufen der Spool-Daten. HTTP-Code: ");

View File

@ -14,8 +14,7 @@ bool checkSpoolmanInstance(const String& url);
bool saveSpoolmanUrl(const String& url); bool saveSpoolmanUrl(const String& url);
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
JsonDocument fetchSpoolsForWebsite(); // API-Funktion für die Webseite JsonDocument fetchSingleSpoolInfo(int spoolId); // API-Funktion für die Webseite
JsonDocument fetchAllSpoolsInfo();
void sendAmsData(AsyncWebSocketClient *client); // Neue Funktion zum Senden von AMS-Daten void sendAmsData(AsyncWebSocketClient *client); // Neue Funktion zum Senden von AMS-Daten
bool updateSpoolTagId(String uidString, const char* payload); // Neue Funktion zum Aktualisieren eines Spools 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 updateSpoolWeight(String spoolId, uint16_t weight); // Neue Funktion zum Aktualisieren des Gewichts

View File

@ -24,13 +24,15 @@ const char* bambu_ip = nullptr;
const char* bambu_accesscode = nullptr; const char* bambu_accesscode = nullptr;
const char* bambu_serialnr = nullptr; const char* bambu_serialnr = nullptr;
bool bambu_connected = false; bool bambu_connected = false;
bool autoSendToBambu = false;
int autoSetToBambuSpoolId = 0;
// Globale Variablen für AMS-Daten // Globale Variablen für AMS-Daten
int ams_count = 0; int ams_count = 0;
String amsJsonData; // Speichert das fertige JSON für WebSocket-Clients String amsJsonData; // Speichert das fertige JSON für WebSocket-Clients
AMSData ams_data[MAX_AMS]; // Definition des Arrays AMSData ams_data[MAX_AMS]; // Definition des Arrays;
bool saveBambuCredentials(const String& ip, const String& serialnr, const String& accesscode) { bool saveBambuCredentials(const String& ip, const String& serialnr, const String& accesscode, bool autoSend) {
if (BambuMqttTask) { if (BambuMqttTask) {
vTaskDelete(BambuMqttTask); vTaskDelete(BambuMqttTask);
} }
@ -39,6 +41,7 @@ bool saveBambuCredentials(const String& ip, const String& serialnr, const String
doc["bambu_ip"] = ip; doc["bambu_ip"] = ip;
doc["bambu_accesscode"] = accesscode; doc["bambu_accesscode"] = accesscode;
doc["bambu_serialnr"] = serialnr; doc["bambu_serialnr"] = serialnr;
doc["autoSendToBambu"] = autoSend;
if (!saveJsonValue("/bambu_credentials.json", doc)) { if (!saveJsonValue("/bambu_credentials.json", doc)) {
Serial.println("Fehler beim Speichern der Bambu-Credentials."); Serial.println("Fehler beim Speichern der Bambu-Credentials.");
@ -49,6 +52,7 @@ bool saveBambuCredentials(const String& ip, const String& serialnr, const String
bambu_ip = ip.c_str(); bambu_ip = ip.c_str();
bambu_accesscode = accesscode.c_str(); bambu_accesscode = accesscode.c_str();
bambu_serialnr = serialnr.c_str(); bambu_serialnr = serialnr.c_str();
autoSendToBambu = autoSend;
vTaskDelay(100 / portTICK_PERIOD_MS); vTaskDelay(100 / portTICK_PERIOD_MS);
if (!setupMqtt()) return false; if (!setupMqtt()) return false;
@ -63,6 +67,7 @@ bool loadBambuCredentials() {
String ip = doc["bambu_ip"].as<String>(); String ip = doc["bambu_ip"].as<String>();
String code = doc["bambu_accesscode"].as<String>(); String code = doc["bambu_accesscode"].as<String>();
String serial = doc["bambu_serialnr"].as<String>(); String serial = doc["bambu_serialnr"].as<String>();
autoSendToBambu = doc["autoSendToBambu"].as<bool>();
ip.trim(); ip.trim();
code.trim(); code.trim();
@ -256,6 +261,26 @@ bool setBambuSpool(String payload) {
return true; return true;
} }
void autoSetSpool(int spoolId, uint8_t trayId) {
// wenn neue spule erkannt und autoSetToBambu > 0
JsonDocument spoolInfo = fetchSingleSpoolInfo(spoolId);
if (!spoolInfo.isNull())
{
// AMS und TRAY id ergänzen
spoolInfo["amsId"] = 0;
spoolInfo["trayId"] = trayId;
Serial.println("Auto set spool");
Serial.println(spoolInfo.as<String>());
setBambuSpool(spoolInfo.as<String>());
}
// id wieder zurücksetzen damit abgeschlossen
autoSetToBambuSpoolId = 0;
}
// init // init
void mqtt_callback(char* topic, byte* payload, unsigned int length) { void mqtt_callback(char* topic, byte* payload, unsigned int length) {
String message; String message;
@ -267,16 +292,27 @@ void mqtt_callback(char* topic, byte* payload, unsigned int length) {
// JSON-Dokument parsen // JSON-Dokument parsen
JsonDocument doc; JsonDocument doc;
DeserializationError error = deserializeJson(doc, message); DeserializationError error = deserializeJson(doc, message);
if (error) { if (error)
{
Serial.print("Fehler beim Parsen des JSON: "); Serial.print("Fehler beim Parsen des JSON: ");
Serial.println(error.c_str()); Serial.println(error.c_str());
return; return;
} }
// Wenn bambu auto set spool aktiv und eine spule erkannt und mqtt meldung das neue spule im ams
if (autoSendToBambu && autoSetToBambuSpoolId > 0 &&
doc["print"]["command"].as<String>() == "push_status" && doc["print"]["ams"]["tray_pre"].as<uint8_t>()
&& !doc["print"]["ams"]["ams"].as<JsonArray>())
{
autoSetSpool(autoSetToBambuSpoolId, doc["print"]["ams"]["tray_pre"].as<uint8_t>());
}
// Prüfen, ob "print->upgrade_state" und "print.ams.ams" existieren // Prüfen, ob "print->upgrade_state" und "print.ams.ams" existieren
if (doc["print"]["upgrade_state"].is<JsonObject>()) { if (doc["print"]["upgrade_state"].is<JsonObject>())
{
// Prüfen ob AMS-Daten vorhanden sind // Prüfen ob AMS-Daten vorhanden sind
if (!doc["print"]["ams"].is<JsonObject>() || !doc["print"]["ams"]["ams"].is<JsonArray>()) { if (!doc["print"]["ams"].is<JsonObject>() || !doc["print"]["ams"]["ams"].is<JsonArray>())
{
return; return;
} }
@ -470,7 +506,7 @@ void mqtt_callback(char* topic, byte* payload, unsigned int length) {
void reconnect() { void reconnect() {
// Loop until we're reconnected // Loop until we're reconnected
while (!client.connected()) { while (!client.connected()) {
Serial.print("Attempting MQTT connection..."); Serial.println("Attempting MQTT connection...");
bambu_connected = false; bambu_connected = false;
oledShowTopRow(); oledShowTopRow();

View File

@ -28,9 +28,11 @@ extern bool bambu_connected;
extern int ams_count; extern int ams_count;
extern AMSData ams_data[MAX_AMS]; extern AMSData ams_data[MAX_AMS];
extern bool autoSendToBambu;
extern int autoSetToBambuSpoolId;
bool loadBambuCredentials(); bool loadBambuCredentials();
bool saveBambuCredentials(const String& bambu_ip, const String& bambu_serialnr, const String& bambu_accesscode); bool saveBambuCredentials(const String& bambu_ip, const String& bambu_serialnr, const String& bambu_accesscode, const bool autoSend);
bool setupMqtt(); bool setupMqtt();
void mqtt_loop(void * parameter); void mqtt_loop(void * parameter);
bool setBambuSpool(String payload); bool setBambuSpool(String payload);

View File

@ -40,6 +40,10 @@ const uint8_t webserverPort = 80;
const char* apiUrl = "/api/v1"; const char* apiUrl = "/api/v1";
// ***** API // ***** API
// ***** Bambu Auto Set Spool
uint8_t autoSetBambuAmsCounter = 60;
// ***** Bambu Auto Set Spool
// ***** Task Prios // ***** Task Prios
uint8_t rfidTaskCore = 1; uint8_t rfidTaskCore = 1;
uint8_t rfidTaskPrio = 1; uint8_t rfidTaskPrio = 1;

View File

@ -23,6 +23,8 @@ extern const uint8_t OLED_DATA_END;
extern const char* apiUrl; extern const char* apiUrl;
extern const uint8_t webserverPort; extern const uint8_t webserverPort;
extern uint8_t autoSetBambuAmsCounter;
extern const unsigned char wifi_on[]; extern const unsigned char wifi_on[];
extern const unsigned char wifi_off[]; extern const unsigned char wifi_off[];
extern const unsigned char cloud_on[]; extern const unsigned char cloud_on[];

View File

@ -90,6 +90,10 @@ void setup() {
unsigned long lastWeightReadTime = 0; unsigned long lastWeightReadTime = 0;
const unsigned long weightReadInterval = 1000; // 1 second const unsigned long weightReadInterval = 1000; // 1 second
unsigned long lastAutoSetBambuAmsTime = 0;
const unsigned long autoSetBambuAmsInterval = 1000; // 1 second
uint8_t autoAmsCounter = 0;
unsigned long lastAmsSendTime = 0; unsigned long lastAmsSendTime = 0;
const unsigned long amsSendInterval = 60000; // 1 minute const unsigned long amsSendInterval = 60000; // 1 minute
@ -108,6 +112,22 @@ void loop() {
sendAmsData(nullptr); sendAmsData(nullptr);
} }
// Wenn Bambu auto set Spool aktiv
if (autoSendToBambu && autoSetToBambuSpoolId > 0 && currentMillis - lastAutoSetBambuAmsTime >= autoSetBambuAmsInterval)
{
lastAutoSetBambuAmsTime = currentMillis;
oledShowMessage("Auto Set " + String(autoSetBambuAmsCounter - autoAmsCounter) + "s");
autoAmsCounter++;
if (autoAmsCounter >= autoSetBambuAmsCounter)
{
autoSetToBambuSpoolId = 0;
autoAmsCounter = 0;
oledShowWeight(weight);
}
}
// Wenn Waage nicht Kalibriert // Wenn Waage nicht Kalibriert
if (scaleCalibrated == 3) if (scaleCalibrated == 3)
{ {
@ -120,7 +140,7 @@ void loop() {
} }
// Ausgabe der Waage auf Display // Ausgabe der Waage auf Display
if (pauseMainTask == 0 && weight != lastWeight && hasReadRfidTag == 0) if (pauseMainTask == 0 && weight != lastWeight && hasReadRfidTag == 0 && (!autoSendToBambu || autoSetToBambuSpoolId == 0))
{ {
(weight < 2) ? ((weight < -2) ? oledShowMessage("!! -0") : oledShowWeight(0)) : oledShowWeight(weight); (weight < 2) ? ((weight < -2) ? oledShowMessage("!! -0") : oledShowWeight(0)) : oledShowWeight(weight);
} }
@ -169,6 +189,7 @@ void loop() {
oledShowIcon("success"); oledShowIcon("success");
vTaskDelay(2000 / portTICK_PERIOD_MS); vTaskDelay(2000 / portTICK_PERIOD_MS);
weightSend = 1; weightSend = 1;
autoSetToBambuSpoolId = spoolId.toInt();
} }
else else
{ {

View File

@ -351,17 +351,6 @@ void setupWebserver(AsyncWebServer &server) {
Serial.println("RFID-Seite gesendet"); Serial.println("RFID-Seite gesendet");
}); });
/*
// Neue API-Route für das Abrufen der Spool-Daten
server.on("/api/spools", HTTP_GET, [](AsyncWebServerRequest *request){
Serial.println("API-Aufruf: /api/spools");
JsonDocument spoolsData = fetchSpoolsForWebsite();
String response;
serializeJson(spoolsData, response);
request->send(200, "application/json", response);
});
*/
server.on("/api/url", HTTP_GET, [](AsyncWebServerRequest *request){ server.on("/api/url", HTTP_GET, [](AsyncWebServerRequest *request){
Serial.println("API-Aufruf: /api/url"); Serial.println("API-Aufruf: /api/url");
String jsonResponse = "{\"spoolman_url\": \"" + String(spoolmanUrl) + "\"}"; String jsonResponse = "{\"spoolman_url\": \"" + String(spoolmanUrl) + "\"}";
@ -389,6 +378,7 @@ void setupWebserver(AsyncWebServer &server) {
String bambuIp = doc["bambu_ip"].as<String>(); String bambuIp = doc["bambu_ip"].as<String>();
String bambuSerial = doc["bambu_serialnr"].as<String>(); String bambuSerial = doc["bambu_serialnr"].as<String>();
String bambuCode = doc["bambu_accesscode"].as<String>(); String bambuCode = doc["bambu_accesscode"].as<String>();
autoSendToBambu = doc["autoSendToBambu"].as<bool>();
bambuIp.trim(); bambuIp.trim();
bambuSerial.trim(); bambuSerial.trim();
bambuCode.trim(); bambuCode.trim();
@ -396,12 +386,14 @@ void setupWebserver(AsyncWebServer &server) {
html.replace("{{bambuIp}}", bambuIp ? bambuIp : ""); html.replace("{{bambuIp}}", bambuIp ? bambuIp : "");
html.replace("{{bambuSerial}}", bambuSerial ? bambuSerial : ""); html.replace("{{bambuSerial}}", bambuSerial ? bambuSerial : "");
html.replace("{{bambuCode}}", bambuCode ? bambuCode : ""); html.replace("{{bambuCode}}", bambuCode ? bambuCode : "");
html.replace("{{autoSendToBambu}}", autoSendToBambu ? "checked" : "");
} }
else else
{ {
html.replace("{{bambuIp}}", ""); html.replace("{{bambuIp}}", "");
html.replace("{{bambuSerial}}", ""); html.replace("{{bambuSerial}}", "");
html.replace("{{bambuCode}}", ""); html.replace("{{bambuCode}}", "");
html.replace("{{autoSendToBambu}}", "");
} }
request->send(200, "text/html", html); request->send(200, "text/html", html);
@ -433,6 +425,8 @@ void setupWebserver(AsyncWebServer &server) {
String bambu_ip = request->getParam("bambu_ip")->value(); String bambu_ip = request->getParam("bambu_ip")->value();
String bambu_serialnr = request->getParam("bambu_serialnr")->value(); String bambu_serialnr = request->getParam("bambu_serialnr")->value();
String bambu_accesscode = request->getParam("bambu_accesscode")->value(); String bambu_accesscode = request->getParam("bambu_accesscode")->value();
bool autoSend = (request->getParam("autoSend")->value() == "true") ? true : false;
Serial.println(autoSend);
bambu_ip.trim(); bambu_ip.trim();
bambu_serialnr.trim(); bambu_serialnr.trim();
bambu_accesscode.trim(); bambu_accesscode.trim();
@ -442,7 +436,7 @@ void setupWebserver(AsyncWebServer &server) {
return; return;
} }
bool success = saveBambuCredentials(bambu_ip, bambu_serialnr, bambu_accesscode); bool success = saveBambuCredentials(bambu_ip, bambu_serialnr, bambu_accesscode, autoSend);
request->send(200, "application/json", "{\"healthy\": " + String(success ? "true" : "false") + "}"); request->send(200, "application/json", "{\"healthy\": " + String(success ? "true" : "false") + "}");
}); });