From 1c01bfb2da2c9811a214c988a6d0ed2ac3879f30 Mon Sep 17 00:00:00 2001 From: tugsi Date: Sun, 23 Mar 2025 18:09:25 +0100 Subject: [PATCH] - X1C MQTT Fix - Spoolman Settings Fix - Spoolman Out Fix - Spoolman In Fix --- html/rfid.js | 242 +++++++++++++++++++++++++++++++------------------- src/bambu.cpp | 218 +++++++++++++++++++++++++++++++++------------ 2 files changed, 311 insertions(+), 149 deletions(-) diff --git a/html/rfid.js b/html/rfid.js index d0d2f07..12b3d82 100644 --- a/html/rfid.js +++ b/html/rfid.js @@ -53,7 +53,13 @@ function initWebSocket() { // Wenn eine existierende Verbindung besteht, diese erst schließen if (socket) { - socket.close(); + try { + socket.onclose = null; // Remove onclose handler before closing + socket.onerror = null; // Remove error handler + socket.close(); + } catch (e) { + console.error('Error closing existing socket:', e); + } socket = null; } @@ -61,110 +67,80 @@ function initWebSocket() { socket = new WebSocket('ws://' + window.location.host + '/ws'); socket.onopen = function() { + console.log('WebSocket connection established'); isConnected = true; updateConnectionStatus(); startHeartbeat(); // Starte Heartbeat nach erfolgreicher Verbindung }; - socket.onclose = function() { + socket.onclose = function(event) { + console.log('WebSocket connection closed:', event.code, event.reason); isConnected = false; updateConnectionStatus(); - if (heartbeatTimer) clearInterval(heartbeatTimer); + if (heartbeatTimer) { + clearInterval(heartbeatTimer); + heartbeatTimer = null; + } // Nur neue Verbindung versuchen, wenn kein Timer läuft if (!reconnectTimer) { reconnectTimer = setTimeout(() => { + console.log('Attempting to reconnect...'); initWebSocket(); }, RECONNECT_INTERVAL); } }; socket.onerror = function(error) { + console.error('WebSocket error occurred:', error); isConnected = false; updateConnectionStatus(); - if (heartbeatTimer) clearInterval(heartbeatTimer); - - // Bei Fehler Verbindung schließen und neu aufbauen - if (socket) { - socket.close(); - socket = null; + if (heartbeatTimer) { + clearInterval(heartbeatTimer); + heartbeatTimer = null; } }; socket.onmessage = function(event) { - lastHeartbeatResponse = Date.now(); // Aktualisiere Zeitstempel bei jeder Server-Antwort - - const data = JSON.parse(event.data); - if (data.type === 'amsData') { - displayAmsData(data.payload); - } else if (data.type === 'nfcTag') { - updateNfcStatusIndicator(data.payload); - } else if (data.type === 'nfcData') { - updateNfcData(data.payload); - } else if (data.type === 'writeNfcTag') { - handleWriteNfcTagResponse(data.success); - } else if (data.type === 'heartbeat') { - // Optional: Spezifische Behandlung von Heartbeat-Antworten - // Update status dots - const bambuDot = document.getElementById('bambuDot'); - const spoolmanDot = document.getElementById('spoolmanDot'); - const ramStatus = document.getElementById('ramStatus'); + try { + lastHeartbeatResponse = Date.now(); + const data = JSON.parse(event.data); - if (bambuDot) { - bambuDot.className = 'status-dot ' + (data.bambu_connected ? 'online' : 'offline'); - // Add click handler only when offline - if (!data.bambu_connected) { - bambuDot.style.cursor = 'pointer'; - bambuDot.onclick = function() { - if (socket && socket.readyState === WebSocket.OPEN) { - socket.send(JSON.stringify({ - type: 'reconnect', - payload: 'bambu' - })); - } - }; - } else { - bambuDot.style.cursor = 'default'; - bambuDot.onclick = null; - } - } - if (spoolmanDot) { - spoolmanDot.className = 'status-dot ' + (data.spoolman_connected ? 'online' : 'offline'); - // Add click handler only when offline - if (!data.spoolman_connected) { - spoolmanDot.style.cursor = 'pointer'; - spoolmanDot.onclick = function() { - if (socket && socket.readyState === WebSocket.OPEN) { - socket.send(JSON.stringify({ - type: 'reconnect', - payload: 'spoolman' - })); - } - }; - } else { - spoolmanDot.style.cursor = 'default'; - spoolmanDot.onclick = null; - } - } - if (ramStatus) { - ramStatus.textContent = `${data.freeHeap}k`; - } - } - else if (data.type === 'setSpoolmanSettings') { - if (data.payload == 'success') { - showNotification(`Spoolman Settings set successfully`, true); - } else { - showNotification(`Error setting Spoolman Settings`, false); + // Handle different message types + switch(data.type) { + case 'amsData': + displayAmsData(data.payload); + break; + case 'nfcTag': + updateNfcStatusIndicator(data.payload); + break; + case 'nfcData': + updateNfcData(data.payload); + break; + case 'writeNfcTag': + handleWriteNfcTagResponse(data.success); + break; + case 'heartbeat': + handleHeartbeatResponse(data); + break; + case 'setSpoolmanSettings': + handleSpoolmanSettingsResponse(data); + break; + default: + console.warn('Unknown message type:', data.type); } + } catch (error) { + console.error('Error processing WebSocket message:', error); } }; } catch (error) { + console.error('Error initializing WebSocket:', error); isConnected = false; updateConnectionStatus(); - // Nur neue Verbindung versuchen, wenn kein Timer läuft if (!reconnectTimer) { reconnectTimer = setTimeout(() => { + console.log('Attempting to reconnect after error...'); initWebSocket(); }, RECONNECT_INTERVAL); } @@ -389,6 +365,7 @@ function updateSpoolButtons(show) { }); } +// Verbesserte Funktion zum Behandeln von Spoolman Settings function handleSpoolmanSettings(tray_info_idx, setting_id, cali_idx, nozzle_temp_min, nozzle_temp_max) { // Hole das ausgewählte Filament const selectedText = document.getElementById("selected-filament").textContent; @@ -419,6 +396,7 @@ function handleSpoolmanSettings(tray_info_idx, setting_id, cali_idx, nozzle_temp } } +// Verbesserte Funktion zum Behandeln von Spool Out function handleSpoolOut() { // Erstelle Payload const payload = { @@ -443,17 +421,21 @@ function handleSpoolOut() { } } -// Neue Funktion zum Behandeln des Spool-In-Klicks +// Verbesserte Funktion zum Behandeln des Spool-In-Klicks function handleSpoolIn(amsId, trayId) { + console.log("handleSpoolIn called with amsId:", amsId, "trayId:", trayId); + // Prüfe WebSocket Verbindung zuerst if (!socket || socket.readyState !== WebSocket.OPEN) { showNotification("No active WebSocket connection!", false); - console.error("WebSocket not connected"); + console.error("WebSocket not connected, state:", socket ? socket.readyState : "no socket"); return; } // Hole das ausgewählte Filament const selectedText = document.getElementById("selected-filament").textContent; + console.log("Selected filament:", selectedText); + if (selectedText === "Please choose...") { showNotification("Choose Filament first", false); return; @@ -466,53 +448,55 @@ function handleSpoolIn(amsId, trayId) { if (!selectedSpool) { showNotification("Selected Spool not found", false); + console.error("Selected spool not found in spoolsData"); return; } + console.log("Found spool data:", selectedSpool); + // Temperaturwerte extrahieren let minTemp = "175"; let maxTemp = "275"; - if (Array.isArray(selectedSpool.filament.nozzle_temperature) && + if (selectedSpool.filament && + Array.isArray(selectedSpool.filament.nozzle_temperature) && selectedSpool.filament.nozzle_temperature.length >= 2) { minTemp = selectedSpool.filament.nozzle_temperature[0]; maxTemp = selectedSpool.filament.nozzle_temperature[1]; } + // Extrahiere bambu_idx + let bambuIdx = "GFL99"; // Default zu Generic PLA + if (selectedSpool.filament?.extra?.bambu_idx) { + bambuIdx = selectedSpool.filament.extra.bambu_idx.replace(/['"]/g, ''); + } else if (selectedSpool.extra?.bambu_idx) { + bambuIdx = selectedSpool.extra.bambu_idx.replace(/['"]/g, ''); + } + // Erstelle Payload const payload = { type: 'setBambuSpool', payload: { amsId: amsId, trayId: trayId, - color: selectedSpool.filament.color_hex || "FFFFFF", + color: selectedSpool.filament && selectedSpool.filament.color_hex ? selectedSpool.filament.color_hex : "FFFFFF", nozzle_temp_min: parseInt(minTemp), nozzle_temp_max: parseInt(maxTemp), - type: selectedSpool.filament.material, - brand: selectedSpool.filament.vendor.name, - tray_info_idx: selectedSpool.filament.extra.bambu_idx.replace(/['"]+/g, '').trim(), + type: selectedSpool.filament && selectedSpool.filament.material ? selectedSpool.filament.material : "PLA", + brand: selectedSpool.filament && selectedSpool.filament.vendor ? selectedSpool.filament.vendor.name : "", + tray_info_idx: bambuIdx, cali_idx: "-1" // Default-Wert setzen } }; - // Prüfe, ob der Key cali_idx vorhanden ist und setze ihn - if (selectedSpool.filament.extra.bambu_cali_id) { - payload.payload.cali_idx = selectedSpool.filament.extra.bambu_cali_id.replace(/['"]+/g, '').trim(); - } - - // Prüfe, ob der Key bambu_setting_id vorhanden ist - if (selectedSpool.filament.extra.bambu_setting_id) { - payload.payload.bambu_setting_id = selectedSpool.filament.extra.bambu_setting_id.replace(/['"]+/g, '').trim(); - } - - console.log("Spool-In Payload:", payload); + console.log("Sending payload:", payload); try { socket.send(JSON.stringify(payload)); - showNotification(`Spool set in AMS ${amsId} Tray ${trayId}. Pls wait`, true); + showNotification(`Spool settings sent to printer. Please wait...`, true); } catch (error) { - console.error("Fehler beim Senden der WebSocket Nachricht:", error); - showNotification("Error while sending", false); + console.error("Error sending WebSocket message:", error); + showNotification("Error sending spool settings!", false); } } @@ -687,3 +671,75 @@ function showNotification(message, isSuccess) { }, 300); }, 3000); } + +// Neue Handler-Funktionen für bessere Modularität +function handleHeartbeatResponse(data) { + const bambuDot = document.getElementById('bambuDot'); + const spoolmanDot = document.getElementById('spoolmanDot'); + const ramStatus = document.getElementById('ramStatus'); + + if (bambuDot) { + bambuDot.className = 'status-dot ' + (data.bambu_connected ? 'online' : 'offline'); + if (!data.bambu_connected) { + bambuDot.style.cursor = 'pointer'; + bambuDot.onclick = function() { + sendReconnectRequest('bambu'); + }; + } else { + bambuDot.style.cursor = 'default'; + bambuDot.onclick = null; + } + } + + if (spoolmanDot) { + spoolmanDot.className = 'status-dot ' + (data.spoolman_connected ? 'online' : 'offline'); + if (!data.spoolman_connected) { + spoolmanDot.style.cursor = 'pointer'; + spoolmanDot.onclick = function() { + sendReconnectRequest('spoolman'); + }; + } else { + spoolmanDot.style.cursor = 'default'; + spoolmanDot.onclick = null; + } + } + + if (ramStatus) { + ramStatus.textContent = `${data.freeHeap}k`; + } +} + +function handleSpoolmanSettingsResponse(data) { + if (data.payload === 'success') { + showNotification(`Spoolman Settings set successfully`, true); + } else { + showNotification(`Error setting Spoolman Settings`, false); + } +} + +function sendReconnectRequest(target) { + if (socket?.readyState === WebSocket.OPEN) { + socket.send(JSON.stringify({ + type: 'reconnect', + payload: target + })); + } +} + +// Verbesserte Funktion zum Senden von WebSocket-Nachrichten +function sendWebSocketMessage(message) { + if (!socket || socket.readyState !== WebSocket.OPEN) { + console.error('WebSocket is not connected'); + showNotification("Connection error - please try again", false); + return; + } + + try { + const jsonString = JSON.stringify(message); + console.log('Sending WebSocket message:', jsonString); + socket.send(jsonString); + } catch (error) { + console.error('Error sending WebSocket message:', error); + showNotification("Error sending message", false); + } +} diff --git a/src/bambu.cpp b/src/bambu.cpp index f36609f..daa6a7b 100644 --- a/src/bambu.cpp +++ b/src/bambu.cpp @@ -18,7 +18,7 @@ PubSubClient client(sslClient); TaskHandle_t BambuMqttTask; String report_topic = ""; -//String request_topic = ""; +String request_topic = ""; const char* bambu_username = "bblp"; const char* bambu_ip = nullptr; const char* bambu_accesscode = nullptr; @@ -197,18 +197,69 @@ FilamentResult findFilamentIdx(String brand, String type) { } bool sendMqttMessage(const String& payload) { - Serial.println("Sending MQTT message"); - Serial.println(payload); - if (client.publish(report_topic.c_str(), payload.c_str())) - { - return true; + // Check MQTT client state first + if (!client.connected()) { + Serial.println("Error: MQTT client not connected when trying to send message"); + Serial.print("MQTT client state: "); + Serial.println(client.state()); + return false; } - + + // Check if request_topic is empty + if (request_topic.isEmpty()) { + Serial.println("Error: request_topic is empty!"); + return false; + } + + // Check payload + if (payload.isEmpty()) { + Serial.println("Error: Empty payload!"); return false; + } + + Serial.println("Attempting to send MQTT message:"); + Serial.println("Topic: " + request_topic); + Serial.println("Payload: " + payload); + Serial.println("Payload length: " + String(payload.length())); + + // Try to publish with QoS 1 and retain flag false + bool published = client.publish(request_topic.c_str(), payload.c_str(), false); + + if (published) { + Serial.println("MQTT message published successfully"); + return true; + } else { + Serial.println("Failed to publish MQTT message"); + Serial.print("MQTT client state after publish attempt: "); + Serial.println(client.state()); + + // Additional error information + switch(client.state()) { + case -4: + Serial.println("Reason: Connection timeout"); + break; + case -3: + Serial.println("Reason: Connection lost"); + break; + case -2: + Serial.println("Reason: Connect failed"); + break; + case -1: + Serial.println("Reason: Disconnected"); + break; + case 0: + Serial.println("Reason: Connected but publish failed"); + break; + default: + Serial.println("Reason: Unknown error"); + break; + } + return false; + } } bool setBambuSpool(String payload) { - Serial.println("Spool settings in"); + Serial.println("Spool settings received from WebSocket"); Serial.println(payload); // Parse the JSON @@ -220,21 +271,28 @@ bool setBambuSpool(String payload) { return false; } - int amsId = doc["amsId"]; - int trayId = doc["trayId"]; + // Check if we have all required fields + if (!doc["amsId"].is() || !doc["trayId"].is()) { + Serial.println("Error: Missing or invalid required fields in payload"); + return false; + } + + int amsId = doc["amsId"].as(); + int trayId = doc["trayId"].as(); String color = doc["color"].as(); color.toUpperCase(); - int minTemp = doc["nozzle_temp_min"]; - int maxTemp = doc["nozzle_temp_max"]; - String type = doc["type"].as(); + int minTemp = doc["nozzle_temp_min"] | 0; // Default to 0 if not present + int maxTemp = doc["nozzle_temp_max"] | 0; // Default to 0 if not present + String type = doc["type"] | ""; // Default to empty string if not present (type == "PLA+") ? type = "PLA" : type; - String brand = doc["brand"].as(); + String brand = doc["brand"] | ""; // Default to empty string if not present String tray_info_idx = (doc["tray_info_idx"].as() != "-1") ? doc["tray_info_idx"].as() : ""; if (tray_info_idx == "") { if (brand != "" && type != "") { FilamentResult result = findFilamentIdx(brand, type); tray_info_idx = result.key; type = result.type; // Aktualisiere den type mit dem gefundenen Basistyp + Serial.println("Found filament idx: " + tray_info_idx + " for type: " + type); } } String setting_id = doc["bambu_setting_id"].as(); @@ -242,6 +300,7 @@ bool setBambuSpool(String payload) { doc.clear(); + // Create MQTT message doc["print"]["sequence_id"] = "0"; doc["print"]["command"] = "ams_filament_setting"; doc["print"]["ams_id"] = amsId < 200 ? amsId : 255; @@ -250,26 +309,25 @@ bool setBambuSpool(String payload) { doc["print"]["nozzle_temp_min"] = minTemp; doc["print"]["nozzle_temp_max"] = maxTemp; doc["print"]["tray_type"] = type; - //doc["print"]["cali_idx"] = (cali_idx != "") ? cali_idx : ""; doc["print"]["tray_info_idx"] = tray_info_idx; doc["print"]["setting_id"] = setting_id; - // Serialize the JSON + // Serialize and send MQTT message String output; serializeJson(doc, output); + Serial.println("Sending to Bambu printer:"); + Serial.println(output); - if (sendMqttMessage(output)) { - Serial.println("Spool successfully set"); - } - else - { - Serial.println("Failed to set spool"); + if (!sendMqttMessage(output)) { + Serial.println("Failed to send filament settings to printer"); return false; } + Serial.println("Filament settings sent successfully"); doc.clear(); yield(); + // Send calibration if available if (cali_idx != "") { yield(); doc["print"]["sequence_id"] = "0"; @@ -278,21 +336,19 @@ bool setBambuSpool(String payload) { doc["print"]["nozzle_diameter"] = "0.4"; doc["print"]["cali_idx"] = cali_idx.toInt(); doc["print"]["tray_id"] = trayId < 200 ? trayId : 254; - //doc["print"]["ams_id"] = amsId < 200 ? amsId : 255; // Serialize the JSON String output; serializeJson(doc, output); + Serial.println("Sending calibration to printer:"); + Serial.println(output); - if (sendMqttMessage(output)) { - Serial.println("Extrusion calibration successfully set"); - } - else - { - Serial.println("Failed to set extrusion calibration"); + if (!sendMqttMessage(output)) { + Serial.println("Failed to send calibration settings to printer"); return false; } + Serial.println("Calibration settings sent successfully"); doc.clear(); yield(); } @@ -549,36 +605,57 @@ void reconnect() { uint8_t retries = 0; while (!client.connected()) { Serial.println("Attempting MQTT re/connection..."); + Serial.print("State before connect: "); + Serial.println(client.state()); bambu_connected = false; oledShowTopRow(); - // Attempt to connect - String clientId = String(bambu_serialnr) + "_" + String(random(0, 100)); - if (client.connect(clientId.c_str(), bambu_username, bambu_accesscode)){ + // Generate a random client ID suffix + String clientId = String(bambu_serialnr) + "_" + String(random(0xffff), HEX); + Serial.print("Reconnecting with client ID: "); + Serial.println(clientId); + + // Attempt to connect with clean session and will message + if (client.connect(clientId.c_str(), bambu_username, bambu_accesscode, nullptr, 0, true, nullptr)) { Serial.println("MQTT re/connected"); - client.subscribe(report_topic.c_str()); + // Subscribe with QoS 1 + if (client.subscribe(report_topic.c_str(), 1)) { + Serial.println("Successfully subscribed to topic with QoS 1: " + report_topic); + } else { + Serial.println("Failed to subscribe to topic: " + report_topic); + } bambu_connected = true; oledShowTopRow(); - } - else - { + } else { + int state = client.state(); Serial.print("failed, rc="); - Serial.print(client.state()); - Serial.println(" try again in 5 seconds"); + Serial.print(state); + Serial.print(" ("); + // Print detailed error message + switch(state) { + case -4: Serial.print("MQTT_CONNECTION_TIMEOUT"); break; + case -3: Serial.print("MQTT_CONNECTION_LOST"); break; + case -2: Serial.print("MQTT_CONNECT_FAILED"); break; + case -1: Serial.print("MQTT_DISCONNECTED"); break; + case 1: Serial.print("MQTT_CONNECT_BAD_PROTOCOL"); break; + case 2: Serial.print("MQTT_CONNECT_BAD_CLIENT_ID"); break; + case 3: Serial.print("MQTT_CONNECT_UNAVAILABLE"); break; + case 4: Serial.print("MQTT_CONNECT_BAD_CREDENTIALS"); break; + case 5: Serial.print("MQTT_CONNECT_UNAUTHORIZED"); break; + default: Serial.print("UNKNOWN"); break; + } + Serial.println(") try again in 5 seconds"); bambu_connected = false; oledShowTopRow(); yield(); vTaskDelay(5000 / portTICK_PERIOD_MS); - if (retries > 5) - { + if (retries > 5) { Serial.println("Disable Bambu MQTT Task after 5 retries"); - // vTaskSuspend(BambuMqttTask); vTaskDelete(BambuMqttTask); break; } - retries++; } } @@ -586,17 +663,32 @@ void reconnect() { void mqtt_loop(void * parameter) { Serial.println("Bambu MQTT Task gestartet"); + unsigned long lastCheck = 0; + for(;;) { if (pauseBambuMqttTask) { vTaskDelay(10000); + continue; } + unsigned long now = millis(); + if (!client.connected()) { + Serial.println("Connection lost, attempting reconnect..."); reconnect(); yield(); esp_task_wdt_reset(); vTaskDelay(100); + continue; } + + // Periodically check connection status + if (now - lastCheck > 30000) { // Check every 30 seconds + Serial.print("MQTT Status Check - Connected: "); + Serial.println(client.connected() ? "Yes" : "No"); + lastCheck = now; + } + client.loop(); yield(); esp_task_wdt_reset(); @@ -621,15 +713,30 @@ bool setupMqtt() { sslClient.setInsecure(); client.setServer(bambu_ip, 8883); + // Generate a random client ID suffix + String clientId = String(bambu_serialnr) + "_" + String(random(0xffff), HEX); + + // MQTT Connection Options + client.setKeepAlive(60); + client.setSocketTimeout(60); // Increase socket timeout + + Serial.print("Connecting with client ID: "); + Serial.println(clientId); + // Verbinden mit dem MQTT-Server bool connected = true; - String clientId = String(bambu_serialnr) + "_" + String(random(0, 100)); - if (client.connect(clientId.c_str(), bambu_username, bambu_accesscode)) + if (client.connect(clientId.c_str(), bambu_username, bambu_accesscode, nullptr, 0, true, nullptr)) { client.setCallback(mqtt_callback); - client.setBufferSize(5120); - client.subscribe(report_topic.c_str()); - // client.subscribe(request_topic.c_str()); + client.setBufferSize(16384); // Increased to 16KB to handle larger JSON + + // Subscribe with QoS 1 + if (client.subscribe(report_topic.c_str(), 1)) { + Serial.println("Successfully subscribed to topic with QoS 1: " + report_topic); + } else { + Serial.println("Failed to subscribe to topic: " + report_topic); + } + Serial.println("MQTT-Client initialisiert"); oledShowMessage("Bambu Connected"); @@ -637,13 +744,13 @@ bool setupMqtt() { oledShowTopRow(); xTaskCreatePinnedToCore( - mqtt_loop, /* Function to implement the task */ - "BambuMqtt", /* Name of the task */ - 10240, /* Stack size in words */ - NULL, /* Task input parameter */ - mqttTaskPrio, /* Priority of the task */ - &BambuMqttTask, /* Task handle. */ - mqttTaskCore); /* Core where the task should run */ + mqtt_loop, /* Function to implement the task */ + "BambuMqtt", /* Name of the task */ + 16384, /* Stack size in words */ + NULL, /* Task input parameter */ + mqttTaskPrio, /* Priority of the task */ + &BambuMqttTask, /* Task handle. */ + mqttTaskCore); /* Core where the task should run */ } else { @@ -654,8 +761,7 @@ bool setupMqtt() { oledShowTopRow(); } - if (!connected) - return false; + if (!connected) return false; } else {