diff --git a/README.md b/README.md index a6899f6..64b94a0 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ The BL Led Controller is an ESP32 based device that connects to your Bambulab X1,X1C,P1P Or P1S and controls the LED strip based on the state of the printer. ### Flashing and Setup -1. go to the [Web Flasher](https://dutchdevelop.github.io/blledsetup/) +1. go to the [Web Flasher](https://dutchdevelop.github.io/blledsetup/) (Or [here](https://softwarecrash.github.io/BLLED-Flasher/) for Nightly and Dev builds) 2. connect your ESP32 3. Select the Firmware build you want 4. Click on Flash diff --git a/bblp_sim.py b/bblp_sim.py new file mode 100644 index 0000000..4f16f87 --- /dev/null +++ b/bblp_sim.py @@ -0,0 +1,270 @@ +import tkinter as tk +from tkinter import messagebox +import threading +import time +import json +import ssl +import paho.mqtt.client as mqtt + +# === MQTT Configuration === +mqtt_config = { + "broker": "127.0.0.1", + "port": 8883, + "username": "bblp", + "password": "bblp", + "serial": "0000" +} + +# === GUI Root and Variables === +root = tk.Tk() +root.title("BLLED Printer MQTT Simulator") + +chamber_light_state = tk.StringVar(value="ON") # Light is ON by default after printer startup + +# === MQTT Client Setup === +client = mqtt.Client() # Use default (v3.1.1) for compatibility +client.tls_set(cert_reqs=ssl.CERT_NONE) +client.tls_insecure_set(True) + +# === MQTT Handlers === +def on_connect(client, userdata, flags, rc): + client.subscribe(f"device/{mqtt_config['serial']}/request") + log_message("✅ Connected and subscribed") + +def on_message(client, userdata, msg): + try: + payload = json.loads(msg.payload.decode()) + if msg.topic.endswith("/request"): + if "system" in payload: + system = payload["system"] + if system.get("command") == "ledctrl" and system.get("led_node") == "chamber_light": + mode = system.get("led_mode", "unknown") + chamber_light_state.set(mode.upper()) + log_message(f"→ Light command received: {mode.upper()}") + # Simulate printer sending updated state back + send_status() + if "print" in payload and "hms" in payload["print"]: + hms_arr = payload["print"]["hms"] + if isinstance(hms_arr, list) and len(hms_arr) > 0: + h = hms_arr[0] + code = ((int(h.get("attr", 0)) << 32) + int(h.get("code", 0))) + chunk1 = (code >> 48) & 0xFFFF + chunk2 = (code >> 32) & 0xFFFF + chunk3 = (code >> 16) & 0xFFFF + chunk4 = code & 0xFFFF + parsed = f"HMS_{chunk1:04X}_{chunk2:04X}_{chunk3:04X}_{chunk4:04X}" + parsed_hms_code.set(parsed) + log_message(f"← HMS Received: {parsed}") + except Exception as e: + log_message(f"[Error] MQTT message: {e}") + +client.on_connect = on_connect +client.on_message = on_message + +# Track last HMS code +door_open_state = tk.BooleanVar(value=False) +parsed_hms_code = tk.StringVar(value="None") +simulation_running = tk.BooleanVar(value=False) + +# === GUI Functions === +def periodic_status_report(): + while True: + send_status() + time.sleep(1.0) + +def connect_mqtt(): + # Start periodic reporting after connect + threading.Thread(target=periodic_status_report, daemon=True).start() + client.username_pw_set(mqtt_config["username"], mqtt_config["password"]) + try: + client.connect(mqtt_config["broker"], mqtt_config["port"]) + threading.Thread(target=client.loop_forever, daemon=True).start() + log_message("Connecting to MQTT...") + except Exception as e: + messagebox.showerror("MQTT Connection Failed", str(e)) + +def update_config(): + mqtt_config["broker"] = broker_entry.get() + mqtt_config["port"] = int(port_entry.get()) + mqtt_config["username"] = user_entry.get() + mqtt_config["password"] = pass_entry.get() + mqtt_config["serial"] = serial_entry.get() + connect_mqtt() + +def send_payload(payload): + topic = f"device/{mqtt_config['serial']}/report" + client.publish(topic, json.dumps(payload)) + log_message(f"MQTT → {json.dumps(payload)}") + +def send_status(stage=None, gcode_state=None, door_open=None, hms_code=None): + payload = {"print": {}} + + if stage is not None: + payload["print"]["stg_cur"] = stage + if gcode_state is not None: + payload["print"]["gcode_state"] = gcode_state + + payload["print"]["home_flag"] = (1 << 23) if door_open_state.get() else 0 + payload["print"]["lights_report"] = [{"node": "chamber_light", "mode": "on" if chamber_light_state.get() == "ON" else "off"}] + + current_hms = parsed_hms_code.get() + if current_hms != "None": + try: + chunks = current_hms.split("_")[1:] + parts = [int(x, 16) for x in chunks] + hms_code_val = ((parts[0] << 48) + (parts[1] << 32) + (parts[2] << 16) + parts[3]) + payload["print"]["hms"] = [{"attr": 0x00000000, "code": hms_code_val}] + except: + pass + + send_payload(payload) + +def simulate_print(): + if simulation_running.get(): + return + simulation_running.set(True) + simulate_btn.config(state=tk.DISABLED, text="Simulating...") + steps = [ + (1, "RUNNING", "Bed Leveling"), + (14, "RUNNING", "Cleaning Nozzle"), + (8, "RUNNING", "Extrusion Calibration"), + (2, "RUNNING", "Preheating"), + (0, "RUNNING", "Printing"), + (10, "RUNNING", "First Layer Inspection"), + (16, "PAUSE", "Paused"), + (0, "RUNNING", "Resumed"), + (-1, "FINISH", "Finished"), + (-1, "IDLE", "Idle") + ] + for s, state, desc in steps: + log_message(f"→ {desc}") + send_status(stage=s, gcode_state=state) + time.sleep(5) + simulate_btn.config(state=tk.NORMAL, text="Simulate Print") + simulation_running.set(False) + +def simulate_print_thread(): + if not simulation_running.get(): + threading.Thread(target=simulate_print, daemon=True).start() + +def simulate_light_off(): + payload = { + "system": { + "command": "ledctrl", + "led_node": "chamber_light", + "led_mode": "off" + } + } + topic = f"device/{mqtt_config['serial']}/request" + client.publish(topic, json.dumps(payload)) + log_message("← Simulated: chamber_light OFF") + +def simulate_light_on(): + payload = { + "system": { + "command": "ledctrl", + "led_node": "chamber_light", + "led_mode": "on" + } + } + topic = f"device/{mqtt_config['serial']}/request" + client.publish(topic, json.dumps(payload)) + log_message("← Simulated: chamber_light ON") + +def simulate_hms(event): + sel = hms_listbox.curselection() + if sel: + code = hms_codes[sel[0]] + name = hms_listbox.get(sel[0]) + parsed_hms_code.set(f"HMS_{(code >> 48) & 0xFFFF:04X}_{(code >> 32) & 0xFFFF:04X}_{(code >> 16) & 0xFFFF:04X}_{code & 0xFFFF:04X}") + log_message(f"→ Simulating HMS: {name}") + send_status(hms_code=code) + +def log_message(text): + log_output.insert(tk.END, text + "\n") + log_output.see(tk.END) + +# === GUI Layout === +tk.Label(root, text="MQTT Broker IP:").grid(row=0, column=0, sticky="e") +broker_entry = tk.Entry(root) +broker_entry.insert(0, mqtt_config["broker"]) +broker_entry.grid(row=0, column=1) + +tk.Label(root, text="Port:").grid(row=0, column=2, sticky="e") +port_entry = tk.Entry(root, width=6) +port_entry.insert(0, str(mqtt_config["port"])) +port_entry.grid(row=0, column=3) + +tk.Label(root, text="Username:").grid(row=1, column=0, sticky="e") +user_entry = tk.Entry(root) +user_entry.insert(0, mqtt_config["username"]) +user_entry.grid(row=1, column=1) + +tk.Label(root, text="Password:").grid(row=1, column=2, sticky="e") +pass_entry = tk.Entry(root, show="*") +pass_entry.insert(0, mqtt_config["password"]) +pass_entry.grid(row=1, column=3) + +tk.Label(root, text="Printer Serial:").grid(row=2, column=0, sticky="e") +serial_entry = tk.Entry(root) +serial_entry.insert(0, mqtt_config["serial"]) +serial_entry.grid(row=2, column=1) + +tk.Button(root, text="Connect", command=update_config).grid(row=2, column=3) + +button_frame = tk.Frame(root) +button_frame.grid(row=3, column=0, columnspan=4, pady=5) + + + +buttons = [ + ("Door Open", lambda: door_open_state.set(True)), + ("Door Close", lambda: door_open_state.set(False)), + ("Pause", lambda: send_status(gcode_state="PAUSE")), + ("Resume", lambda: send_status(gcode_state="RUNNING")), + ("Finish", lambda: send_status(stage=-1, gcode_state="FINISH")), + ("Simulate Light ON", simulate_light_on), + ("Simulate Light OFF", simulate_light_off), + ("Clear HMS", lambda: parsed_hms_code.set("None")) +] +buttons.insert(5, ("Simulate Print", simulate_print_thread)) +for i, (txt, cmd) in enumerate(buttons): + if txt == "Simulate Print": + global simulate_btn + simulate_btn = tk.Button(button_frame, text=txt, command=cmd) + simulate_btn.grid(row=0, column=i, padx=3) + else: + tk.Button(button_frame, text=txt, command=cmd).grid(row=0, column=i, padx=3) + +# HMS List +hms_frame = tk.Frame(root) +hms_frame.grid(row=4, column=0, columnspan=4, sticky="w", padx=10) +tk.Label(hms_frame, text="HMS Errors:").pack(anchor="w") + +hms_codes = [ + 0x0300120000020001, 0x0C0003000003000B, 0x0700200000030001, + 0x0300020000010001, 0x0300010000010007, 0x0600100000010004, + 0x0500200000030001, 0x0800100000010001 +] +hms_labels = [ + "Front Cover Removed", "First Layer Inspection", "Filament Runout", + "Nozzle Temp Fail", "Bed Temp Fail", "Extruder Error", + "Toolhead Error", "Camera Initialization Error" +] +hms_listbox = tk.Listbox(hms_frame, height=5, width=35) +for lbl in hms_labels: + hms_listbox.insert(tk.END, lbl) +hms_listbox.pack(side="left") +hms_listbox.bind("<>", simulate_hms) + +# Log and status +tk.Label(root, text="HMS Code:").grid(row=5, column=2, sticky="e") +tk.Label(root, textvariable=parsed_hms_code, fg="red").grid(row=5, column=3, sticky="w") +tk.Label(root, text="Chamber Light State:").grid(row=5, column=0, sticky="e") +tk.Label(root, textvariable=chamber_light_state, fg="blue").grid(row=5, column=1, sticky="w") + +log_output = tk.Text(root, height=12, width=100) +log_output.grid(row=6, column=0, columnspan=4, padx=10, pady=10) + +# Start GUI +root.mainloop() diff --git a/platformio.ini b/platformio.ini index f3af2dc..b4eb014 100644 --- a/platformio.ini +++ b/platformio.ini @@ -11,7 +11,7 @@ [env:esp32dev] custom_project_name = BLLC custom_project_codename = Balder -custom_version = 2.2.0 #BLLC_[Major].[Minor].[Patch] +custom_version = 2.2.1 #BLLC_[Major].[Minor].[Patch] platform = espressif32 board = esp32dev framework = arduino diff --git a/src/blled/leds.h b/src/blled/leds.h index 1d7172a..45e17ad 100644 --- a/src/blled/leds.h +++ b/src/blled/leds.h @@ -3,7 +3,7 @@ #include #include "mqttparsingutility.h" -void controlChamberLight(bool on); // Forward declaration +void controlChamberLight(bool on); // Forward declaration const int redPin = 19; const int greenPin = 18; @@ -57,11 +57,17 @@ void tweenToColor(int targetRed, int targetGreen, int targetBlue, int targetWarm float brightness = (float)printerConfig.brightness / 100.0; - int brightenedRed = round(targetRed * brightness); - int brightenedGreen = round(targetGreen * brightness); - int brightenedBlue = round(targetBlue * brightness); - int brightenedWarm = round(targetWarm * brightness); - int brightenedCold = round(targetCold * brightness); + /* int brightenedRed = round(targetRed * brightness); + int brightenedGreen = round(targetGreen * brightness); + int brightenedBlue = round(targetBlue * brightness); + int brightenedWarm = round(targetWarm * brightness); + int brightenedCold = round(targetCold * brightness); */ + //round implizit downward + int brightenedRed = (int)(targetRed * brightness); + int brightenedGreen = (int)(targetGreen * brightness); + int brightenedBlue = (int)(targetBlue * brightness); + int brightenedWarm = (int)(targetWarm * brightness); + int brightenedCold = (int)(targetCold * brightness); if (brightenedRed == currentRed && brightenedGreen == currentGreen && brightenedBlue == currentBlue && brightenedWarm == currentWarm && brightenedCold == currentCold) { @@ -264,7 +270,6 @@ void printLogs(String Desc, COLOR thisColor) lastPrintTime = millis(); } - void printLogs(String Desc, short r, short g, short b, short ww, short cw) { COLOR tempColor; @@ -277,6 +282,12 @@ void printLogs(String Desc, short r, short g, short b, short ww, short cw) } void updateleds() { + // Prevent replicate OFF immediately after door event + if ((millis() - printerVariables.lastdoorOpenms) < 1000 || (millis() - printerVariables.lastdoorClosems) < 1000) + { + printerConfig.replicate_update = false; + } + // Maintenance Mode - White lights on regardless of printer power, WiFi or MQTT connection // priortised over Wifi Strength Display or Custom TEST color if (printerConfig.maintMode && printerConfig.maintMode_update) @@ -335,23 +346,23 @@ void updateleds() // From here the BBLP status sets the colors if (printerConfig.debuging == true) { -/* LogSerial.println(F("Updating LEDs")); + /* LogSerial.println(F("Updating LEDs")); - LogSerial.println(printerVariables.stage); - LogSerial.println(printerVariables.gcodeState); - LogSerial.println(printerVariables.printerledstate); - LogSerial.println(printerVariables.hmsstate); - LogSerial.println(printerVariables.parsedHMSlevel); */ + LogSerial.println(printerVariables.stage); + LogSerial.println(printerVariables.gcodeState); + LogSerial.println(printerVariables.printerledstate); + LogSerial.println(printerVariables.hmsstate); + LogSerial.println(printerVariables.parsedHMSlevel); */ - char ledDbgStr[128]; - snprintf(ledDbgStr, sizeof(ledDbgStr), - "[LED] Stage:%d gcodeState:%s printerLedState:%s HMSErr:%s ParsedHMS:%s", - printerVariables.stage, - printerVariables.gcodeState.c_str(), - printerVariables.printerledstate ? "true" : "false", - printerVariables.hmsstate ? "true" : "false", - printerVariables.parsedHMSlevel.c_str()); - LogSerial.println(ledDbgStr); + char ledDbgStr[128]; + snprintf(ledDbgStr, sizeof(ledDbgStr), + "[LED] Stage:%d gcodeState:%s printerLedState:%s HMSErr:%s ParsedHMS:%s", + printerVariables.stage, + printerVariables.gcodeState.c_str(), + printerVariables.printerledstate ? "true" : "false", + printerVariables.hmsstate ? "true" : "false", + printerVariables.parsedHMSlevel.c_str()); + LogSerial.println(ledDbgStr); } // Initial Boot @@ -375,91 +386,90 @@ void updateleds() // TOGGLE LIGHTS via DOOR // If door is closed twice in 6 seconds, it will flip the state of the lights -/* if (printerVariables.doorSwitchTriggered == true) + /* if (printerVariables.doorSwitchTriggered == true) + { + if (printerConfig.debugingchange) + { + LogSerial.print(F("Door closed twice within 6 seconds - Toggling LEDs to ")); + } + if (currentWarm == 0 && currentCold == 0) + { + tweenToColor(0, 0, 0, 255, 255); // WHITE + if (printerConfig.debuging || printerConfig.debugingchange) + { + LogSerial.println(F("ON")); + } + printerConfig.isIdleOFFActive = false; + } + else + { + tweenToColor(0, 0, 0, 0, 0); // OFF + // Shortcut to idle state - note: light will go back on immediately if there is an MQTT change of any sort + printerConfig.isIdleOFFActive = true; + printerConfig.inactivityStartms = (millis() - printerConfig.inactivityTimeOut); + if (printerConfig.debuging || printerConfig.debugingchange) + { + LogSerial.println(F("OFF")); + } + } + printerVariables.doorSwitchTriggered = false; + return; + } */ + if (printerVariables.doorSwitchTriggered == true) { + bool ledsAreOff = (currentWarm == 0 && currentCold == 0); + bool chamberLightIsOff = (printerVariables.printerledstate == false); + if (printerConfig.debugingchange) { - LogSerial.print(F("Door closed twice within 6 seconds - Toggling LEDs to ")); + LogSerial.print(F("Door closed twice within 6 seconds - ")); + + if (ledsAreOff) + LogSerial.print(F("Turning LEDs ON")); + else + LogSerial.print(F("Turning LEDs OFF")); + + if (printerConfig.controlChamberLight) + { + if (ledsAreOff && chamberLightIsOff) + LogSerial.println(F(" + Chamber Light ON")); + else if (!ledsAreOff) + LogSerial.println(F(" + Chamber Light OFF")); + else + LogSerial.println(); + } + else + { + LogSerial.println(); + } } - if (currentWarm == 0 && currentCold == 0) + + if (ledsAreOff) { tweenToColor(0, 0, 0, 255, 255); // WHITE - if (printerConfig.debuging || printerConfig.debugingchange) + printerConfig.isIdleOFFActive = false; + + if (printerConfig.controlChamberLight && chamberLightIsOff) { - LogSerial.println(F("ON")); + controlChamberLight(true); // Turn ON chamber light only if off } - printerConfig.isIdleOFFActive = false; } else { tweenToColor(0, 0, 0, 0, 0); // OFF - // Shortcut to idle state - note: light will go back on immediately if there is an MQTT change of any sort printerConfig.isIdleOFFActive = true; - printerConfig.inactivityStartms = (millis() - printerConfig.inactivityTimeOut); - if (printerConfig.debuging || printerConfig.debugingchange) + printerConfig.inactivityStartms = millis() - printerConfig.inactivityTimeOut; + + if (printerConfig.controlChamberLight) { - LogSerial.println(F("OFF")); + controlChamberLight(false); // Always OFF on manual off } } + printerVariables.doorSwitchTriggered = false; return; - } */ - if (printerVariables.doorSwitchTriggered == true) -{ - bool ledsAreOff = (currentWarm == 0 && currentCold == 0); - bool chamberLightIsOff = (printerVariables.printerledstate == false); - - if (printerConfig.debugingchange) - { - LogSerial.print(F("Door closed twice within 6 seconds - ")); - - if (ledsAreOff) - LogSerial.print(F("Turning LEDs ON")); - else - LogSerial.print(F("Turning LEDs OFF")); - - if (printerConfig.controlChamberLight) - { - if (ledsAreOff && chamberLightIsOff) - LogSerial.println(F(" + Chamber Light ON")); - else if (!ledsAreOff) - LogSerial.println(F(" + Chamber Light OFF")); - else - LogSerial.println(); - } - else - { - LogSerial.println(); - } } - if (ledsAreOff) - { - tweenToColor(0, 0, 0, 255, 255); // WHITE - printerConfig.isIdleOFFActive = false; - - if (printerConfig.controlChamberLight && chamberLightIsOff) - { - controlChamberLight(true); // Turn ON chamber light only if off - } - } - else - { - tweenToColor(0, 0, 0, 0, 0); // OFF - printerConfig.isIdleOFFActive = true; - printerConfig.inactivityStartms = millis() - printerConfig.inactivityTimeOut; - - if (printerConfig.controlChamberLight) - { - controlChamberLight(false); // Always OFF on manual off - } - } - - printerVariables.doorSwitchTriggered = false; - return; -} - - // RED -- RED -- RED -- RED // allow errordetection to turn ledstrip red @@ -704,6 +714,12 @@ void updateleds() printerConfig.replicate_update = false; return; } + + // Ensure doorSwitchTriggered is processed immediately + if (printerVariables.doorSwitchTriggered) + { + updateleds(); + } } void setupLeds() @@ -766,7 +782,7 @@ void ledsloop() printerConfig.inactivityStartms = millis(); printerConfig.isIdleOFFActive = false; updateleds(); - controlChamberLight(false); // Turn off chamber light via MQTT + controlChamberLight(false); // Turn off chamber light via MQTT } // Need an trigger action to run updateleds() so lights turn off @@ -776,13 +792,25 @@ void ledsloop() // Opening or Closing the Door will turn LEDs back on and restart the timer. updateleds(); } - //disabled, need testing for future use -/* // Periodic fallback update to ensure MQTT timeout or other updates are evaluated - static unsigned long lastPeriodicUpdate = 0; - if (millis() - lastPeriodicUpdate > 10000) { // every 10 seconds - updateleds(); - lastPeriodicUpdate = millis(); - } */ + // disabled, need testing for future use + /* // Periodic fallback update to ensure MQTT timeout or other updates are evaluated + static unsigned long lastPeriodicUpdate = 0; + if (millis() - lastPeriodicUpdate > 10000) { // every 10 seconds + updateleds(); + lastPeriodicUpdate = millis(); + } */ + + // Auto turn off chamber light after timeout when door is closed +if (printerVariables.chamberLightLocked && + !printerVariables.doorOpen && + (millis() - printerConfig.inactivityStartms > printerConfig.inactivityTimeOut)) +{ + controlChamberLight(false); + printerVariables.chamberLightLocked = false; + + if (printerConfig.debugingchange) + LogSerial.println(F("[LED] Timeout – Chamber light OFF and lock released")); +} delay(10); } diff --git a/src/blled/mqttmanager.h b/src/blled/mqttmanager.h index 987749f..c590b26 100644 --- a/src/blled/mqttmanager.h +++ b/src/blled/mqttmanager.h @@ -267,40 +267,110 @@ void ParseCallback(char *topic, byte *payload, unsigned int length) } // Check for Door Status - if (!messageobject["print"]["home_flag"].isNull()) + /* if (!messageobject["print"]["home_flag"].isNull()) + { + // https://github.com/greghesp/ha-bambulab/blob/main/custom_components/bambu_lab/pybambu/const.py#L324 + + bool doorState = false; + long homeFlag = 0; + homeFlag = messageobject["print"]["home_flag"]; + doorState = bitRead(homeFlag, 23); + + if (printerVariables.doorOpen != doorState) + { + printerVariables.doorOpen = doorState; + + if (printerConfig.debugingchange) + LogSerial.print(F("[MQTT] Door ")); + if (printerVariables.doorOpen) + { + printerVariables.lastdoorOpenms = millis(); + if (printerConfig.debugingchange) + LogSerial.println(F("Opened")); + } + else + { + if ((millis() - printerVariables.lastdoorClosems) < 6000) + { + printerVariables.doorSwitchTriggered = true; + } + printerVariables.lastdoorClosems = millis(); + if (printerConfig.debugingchange) + LogSerial.println(F("Closed")); + } + Changed = true; + } + } */ + // Check for Door Status +if (!messageobject["print"]["home_flag"].isNull()) +{ + long homeFlag = messageobject["print"]["home_flag"]; + bool doorState = bitRead(homeFlag, 23); // Bit 23 = door open + + if (printerVariables.doorOpen != doorState) + { + printerVariables.doorOpen = doorState; + + if (printerConfig.debugingchange) { - // https://github.com/greghesp/ha-bambulab/blob/main/custom_components/bambu_lab/pybambu/const.py#L324 + LogSerial.print(F("[MQTT] Door ")); + LogSerial.println(doorState ? F("Opened") : F("Closed")); + } - bool doorState = false; - long homeFlag = 0; - homeFlag = messageobject["print"]["home_flag"]; - doorState = bitRead(homeFlag, 23); + // Door opened + if (doorState) + { + printerVariables.lastdoorOpenms = millis(); - if (printerVariables.doorOpen != doorState) + // If light is off, turn it on and lock it + if (printerConfig.controlChamberLight && !printerVariables.printerledstate) { - printerVariables.doorOpen = doorState; + printerVariables.chamberLightLocked = true; + printerVariables.printerledstate = true; + printerConfig.replicate_update = false; + controlChamberLight(true); + LogSerial.println(F("[MQTT] Door opened – Light forced ON")); + } - if (printerConfig.debugingchange) - LogSerial.print(F("[MQTT] Door ")); - if (printerVariables.doorOpen) - { - printerVariables.lastdoorOpenms = millis(); - if (printerConfig.debugingchange) - LogSerial.println(F("Opened")); - } - else - { - if ((millis() - printerVariables.lastdoorClosems) < 6000) - { - printerVariables.doorSwitchTriggered = true; - } - printerVariables.lastdoorClosems = millis(); - if (printerConfig.debugingchange) - LogSerial.println(F("Closed")); - } - Changed = true; + // Restart inactivity timer + printerConfig.inactivityStartms = millis(); + printerConfig.isIdleOFFActive = false; + + Changed = true; + updateleds(); + } + + // Door closed + else + { + printerVariables.lastdoorClosems = millis(); + + // If light was forced on by door, turn it off now + if (printerVariables.chamberLightLocked) + { + printerVariables.chamberLightLocked = false; + printerVariables.printerledstate = false; + controlChamberLight(false); + LogSerial.println(F("[MQTT] Door closed – Light OFF and lock released")); + } + + // Restart inactivity timer + printerConfig.inactivityStartms = millis(); + printerConfig.isIdleOFFActive = false; + + // Detect double-close toggle + if ((millis() - printerVariables.lastdoorOpenms) < 6000) + { + printerVariables.doorSwitchTriggered = true; } + + Changed = true; + updateleds(); } + } +} + + // Check BBLP Stage if (!messageobject["print"]["stg_cur"].isNull()) @@ -337,6 +407,11 @@ void ParseCallback(char *topic, byte *payload, unsigned int length) // Onchange of gcodeState... if (printerVariables.gcodeState != mqttgcodeState) { + if (mqttgcodeState == "RUNNING") + { + printerVariables.overridestage = 999; // Reset after special HMS override + } + if (mqttgcodeState == "FINISH") { printerVariables.finished = true; @@ -565,7 +640,7 @@ void controlChamberLight(bool on) LogSerial.printf("[DEBUG] controlChamberLight called with: %s\n", on ? "true" : "false"); if (!printerConfig.controlChamberLight) return; - + printerVariables.printerledstate = on; // <-- Set state flag to avoid replicate overwrite if (!mqttClient.connected()) { LogSerial.println(F("[MQTT] Skipped chamber_light control – MQTT not connected")); diff --git a/src/blled/types.h b/src/blled/types.h index 8f5b85e..0e1f49f 100644 --- a/src/blled/types.h +++ b/src/blled/types.h @@ -41,6 +41,7 @@ extern "C" bool waitingForDoor = false; // Are we waiting for the door to be actuated? unsigned long lastdoorClosems = 0; // Last time door was opened unsigned long lastdoorOpenms = 0; // Last time door was closed + bool chamberLightLocked = false; // blocks replicate while true } PrinterVariables; typedef struct SecurityVariables{ @@ -121,6 +122,7 @@ extern "C" COLOR bedTempRGB; // HMS Error Handling String hmsIgnoreList; // comma-separated list of HMS_XXXX_XXXX_XXXX_XXXX codes to ignore + } PrinterConfig; diff --git a/src/main.cpp b/src/main.cpp index 5bfe818..12f83bb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -93,7 +93,8 @@ void setup() tweenToColor(34, 224, 238, 0, 0); // CYAN setupMqtt(); - + // >>> Fix: prevent false offline after 30s + printerVariables.disconnectMQTTms = millis(); Serial.println(); Serial.print(F("** BLLED Controller started ")); Serial.print(F("using firmware version: ")); diff --git a/src/www/setupPage.html b/src/www/setupPage.html index 95a0fcb..13e42ad 100644 --- a/src/www/setupPage.html +++ b/src/www/setupPage.html @@ -767,8 +767,8 @@

Firmware version: ??.??.??

try { var configData = JSON.parse(xhr.responseText); document.getElementById("firmwareversion").textContent = "Firmware Version: " + (configData.firmwareversion || "Unknown version"); - document.getElementById('brightnessslider').value = configData.brightness || 100; - document.getElementById("brightnesssliderDisplay").textContent = "Brightness: " + (configData.brightness || 100) + "%"; + document.getElementById('brightnessslider').value = (configData.brightness !== undefined) ? configData.brightness : 100; + document.getElementById("brightnesssliderDisplay").textContent = "Brightness: " + ((configData.brightness !== undefined) ? configData.brightness : 100) + "%"; document.getElementById('maintMode').checked = configData.maintMode || false; document.getElementById('discoMode').checked = configData.discoMode || false; document.getElementById('replicateLedState').checked = configData.replicateled || false; diff --git a/test/README b/test/README deleted file mode 100644 index 9b1e87b..0000000 --- a/test/README +++ /dev/null @@ -1,11 +0,0 @@ - -This directory is intended for PlatformIO Test Runner and project tests. - -Unit Testing is a software testing method by which individual units of -source code, sets of one or more MCU program modules together with associated -control data, usage procedures, and operating procedures, are tested to -determine whether they are fit for use. Unit testing finds problems early -in the development cycle. - -More information about PlatformIO Unit Testing: -- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html diff --git a/test/printerjsonexample.json b/test/printerjsonexample.json deleted file mode 100644 index 7390adb..0000000 --- a/test/printerjsonexample.json +++ /dev/null @@ -1,774 +0,0 @@ -{ - "2D": { - "bs": { - "bi": [ - { - "clock_in": false, - "est_time": 0, - "idx": 0, - "print_then": false, - "proc_list": [], - "step_type": 1, - "tool_info": { - "color": "", - "diameter": 0.4000000059604645, - "id": 8978432 - }, - "type": 137 - } - ], - "total_time": 0 - }, - "cond": 14, - "cur_stage": { - "clock_in_time": 0, - "idx": 0, - "left_time": 0, - "process": 0, - "state": 0 - }, - "first_confirm": false, - "makeable": false, - "material": { - "cur_id_list": [], - "state": 0, - "tar_id": "", - "tar_name": "" - } - }, - "3D": { - "layer_num": 1, - "total_layer_num": 435 - }, - "ams": { - "ams": [ - { - "dry_time": 0, - "humidity": "4", - "humidity_raw": "25", - "id": "0", - "info": "1003", - "temp": "27.5", - "tray": [ - { - "bed_temp": "0", - "bed_temp_type": "0", - "cali_idx": -1, - "cols": [ - "000000FF" - ], - "ctype": 0, - "drying_temp": "80", - "drying_time": "8", - "id": "0", - "nozzle_temp_max": "270", - "nozzle_temp_min": "240", - "remain": 100, - "state": 11, - "tag_uid": "CAD4127600000100", - "total_len": 330000, - "tray_color": "000000FF", - "tray_diameter": "1.75", - "tray_id_name": "B00-K0", - "tray_info_idx": "GFB00", - "tray_sub_brands": "ABS", - "tray_type": "ABS", - "tray_uuid": "4CD95CB1ABC44B0EA1EDA631CB0595A8", - "tray_weight": "1000", - "xcam_info": "803E1027E803E8033333333F" - }, - { - "bed_temp": "0", - "bed_temp_type": "0", - "cali_idx": -1, - "cols": [ - "000000FF" - ], - "ctype": 0, - "drying_temp": "80", - "drying_time": "8", - "id": "1", - "nozzle_temp_max": "270", - "nozzle_temp_min": "240", - "remain": 100, - "state": 11, - "tag_uid": "FA261DF400000100", - "total_len": 330000, - "tray_color": "000000FF", - "tray_diameter": "1.75", - "tray_id_name": "B00-K0", - "tray_info_idx": "GFB00", - "tray_sub_brands": "ABS", - "tray_type": "ABS", - "tray_uuid": "8A892CB7DF894F68A08C0A8E28DCFDF1", - "tray_weight": "1000", - "xcam_info": "803E1027E803E8033333333F" - }, - { - "bed_temp": "70", - "bed_temp_type": "2", - "cali_idx": -1, - "cols": [ - "F2EADAFF" - ], - "ctype": 0, - "drying_temp": "65", - "drying_time": "8", - "id": "2", - "nozzle_temp_max": "270", - "nozzle_temp_min": "220", - "remain": 100, - "state": 11, - "tag_uid": "324BBF7400000100", - "total_len": 330000, - "tray_color": "F2EADAFF", - "tray_diameter": "1.75", - "tray_id_name": "G00-W4", - "tray_info_idx": "GFG00", - "tray_sub_brands": "PETG Basic", - "tray_type": "PETG", - "tray_uuid": "E3604E1558A54D2B8F65FB017E85D649", - "tray_weight": "1000", - "xcam_info": "8813D007E803E8039A99193F" - }, - { - "bed_temp": "80", - "bed_temp_type": "1", - "cali_idx": -1, - "cols": [ - "000000FF" - ], - "ctype": 0, - "drying_temp": "80", - "drying_time": "8", - "id": "3", - "nozzle_temp_max": "270", - "nozzle_temp_min": "240", - "remain": 53, - "state": 11, - "tag_uid": "60347D2F00000100", - "total_len": 330000, - "tray_color": "000000FF", - "tray_diameter": "1.75", - "tray_id_name": "B01-K0", - "tray_info_idx": "GFB01", - "tray_sub_brands": "ASA", - "tray_type": "ASA", - "tray_uuid": "E746641127C74A8A8276FB4A6A1D9B97", - "tray_weight": "1000", - "xcam_info": "803E1027E803E8030000803F" - } - ] - }, - { - "dry_time": 0, - "humidity": "4", - "humidity_raw": "25", - "id": "1", - "info": "1103", - "temp": "27.7", - "tray": [ - { - "bed_temp": "90", - "bed_temp_type": "2", - "cali_idx": -1, - "cols": [ - "FFFFFFFF" - ], - "ctype": 0, - "drying_temp": "80", - "drying_time": "8", - "id": "0", - "nozzle_temp_max": "270", - "nozzle_temp_min": "240", - "remain": 60, - "state": 11, - "tag_uid": "70F7792E00000100", - "total_len": 330000, - "tray_color": "FFFFFFFF", - "tray_diameter": "1.75", - "tray_id_name": "B00-W0", - "tray_info_idx": "GFB00", - "tray_sub_brands": "ABS", - "tray_type": "ABS", - "tray_uuid": "DE09C5C1E84A41F8981AA97FC30187CE", - "tray_weight": "1000", - "xcam_info": "D007D007E803E803CDCC4C3F" - }, - { - "bed_temp": "35", - "bed_temp_type": "1", - "cali_idx": -1, - "cols": [ - "000000FF" - ], - "ctype": 0, - "drying_temp": "55", - "drying_time": "8", - "id": "1", - "nozzle_temp_max": "230", - "nozzle_temp_min": "190", - "remain": 100, - "state": 11, - "tag_uid": "6CE9285500000100", - "total_len": 330000, - "tray_color": "000000FF", - "tray_diameter": "1.75", - "tray_id_name": "A00-K0", - "tray_info_idx": "GFA00", - "tray_sub_brands": "PLA Basic", - "tray_type": "PLA", - "tray_uuid": "F849A72301624376B132C2FF96A5C498", - "tray_weight": "1000", - "xcam_info": "803E803EE803E803CDCC4C3F" - }, - { - "bed_temp": "35", - "bed_temp_type": "1", - "cali_idx": -1, - "cols": [ - "F17B8FFF" - ], - "ctype": 0, - "drying_temp": "55", - "drying_time": "8", - "id": "2", - "nozzle_temp_max": "230", - "nozzle_temp_min": "190", - "remain": 100, - "state": 11, - "tag_uid": "BC2C04D000000100", - "total_len": 330000, - "tray_color": "F17B8FFF", - "tray_diameter": "1.75", - "tray_id_name": "A12-R0", - "tray_info_idx": "GFA12", - "tray_sub_brands": "PLA Glow", - "tray_type": "PLA", - "tray_uuid": "DEF3509E89094379A419EADEF3BE9F45", - "tray_weight": "1000", - "xcam_info": "AC0DD0078403E8033333333F" - }, - { - "bed_temp": "35", - "bed_temp_type": "1", - "cali_idx": -1, - "cols": [ - "A1FFACFF" - ], - "ctype": 0, - "drying_temp": "55", - "drying_time": "8", - "id": "3", - "nozzle_temp_max": "230", - "nozzle_temp_min": "190", - "remain": 100, - "state": 11, - "tag_uid": "AA0A93AA00000100", - "total_len": 330000, - "tray_color": "A1FFACFF", - "tray_diameter": "1.75", - "tray_id_name": "A12-G0", - "tray_info_idx": "GFA12", - "tray_sub_brands": "PLA Glow", - "tray_type": "PLA", - "tray_uuid": "17A15669F8524BAEA8A0F5CA89E609D2", - "tray_weight": "1000", - "xcam_info": "881388138403E8033333333F" - } - ] - } - ], - "ams_exist_bits": "3", - "ams_exist_bits_raw": "3", - "cali_id": 255, - "cali_stat": 0, - "insert_flag": true, - "power_on_flag": false, - "tray_exist_bits": "ff", - "tray_is_bbl_bits": "ff", - "tray_now": "1", - "tray_pre": "1", - "tray_read_done_bits": "ff", - "tray_reading_bits": "0", - "tray_tar": "1", - "unbind_ams_stat": 0, - "version": 69132 - }, - "ams_rfid_status": 0, - "ams_status": 768, - "ap_err": 0, - "aux": "2001004", - "aux_part_fan": false, - "batch_id": 0, - "bed_target_temper": 55, - "bed_temper": 55, - "big_fan1_speed": "0", - "big_fan2_speed": "4", - "cali_version": 0, - "canvas_id": 0, - "care": [ - { - "id": "lr", - "info": "1864" - }, - { - "id": "fa", - "info": "1864" - }, - { - "id": "ls", - "info": "1864" - }, - { - "id": "cr", - "info": "1864" - }, - { - "id": "ld", - "info": "1864" - } - ], - "cfg": "385FDAD9", - "chamber_temper": 32, - "command": "push_status", - "cooling_fan_speed": "0", - "ctt": 0, - "design_id": "", - "device": { - "airduct": { - "modeCur": 0, - "modeList": [ - { - "ctrl": [ - 16, - 32, - 48 - ], - "modeId": 0, - "off": [ - 96 - ] - }, - { - "ctrl": [ - 16 - ], - "modeId": 1, - "off": [ - 32, - 48 - ] - }, - { - "ctrl": [ - 48 - ], - "modeId": 2, - "off": [] - } - ], - "parts": [ - { - "func": 0, - "id": 16, - "range": 6553600, - "state": 0 - }, - { - "func": 1, - "id": 32, - "range": 6553600, - "state": 0 - }, - { - "func": 2, - "id": 48, - "range": 6553600, - "state": 30 - }, - { - "func": 3, - "id": 96, - "range": 6553600, - "state": 0 - } - ] - }, - "bed": { - "info": { - "temp": 3604535 - }, - "state": 2 - }, - "bed_temp": 3604535, - "cam": { - "laser": { - "cond": 252, - "state": 0 - } - }, - "cham_temp": 32, - "ctc": { - "info": { - "temp": 31 - }, - "state": 0 - }, - "ext_tool": { - "calib": 2, - "low_prec": true, - "mount": 0, - "th_temp": 0, - "type": "" - }, - "extruder": { - "info": [ - { - "filam_bak": [ - 3 - ], - "hnow": 0, - "hpre": 0, - "htar": 0, - "id": 0, - "info": 9, - "snow": 65535, - "spre": 65535, - "star": 65535, - "stat": 0, - "temp": 44 - }, - { - "filam_bak": [], - "hnow": 1, - "hpre": 1, - "htar": 1, - "id": 1, - "info": 94, - "snow": 257, - "spre": 257, - "star": 257, - "stat": 197376, - "temp": 14418140 - } - ], - "state": 33042 - }, - "fan": 410080, - "laser": { - "power": 0 - }, - "nozzle": { - "exist": 3, - "info": [ - { - "diameter": 0.4, - "id": 0, - "tm": 0, - "type": "HS01", - "wear": 0 - }, - { - "diameter": 0.4, - "id": 1, - "tm": 0, - "type": "HS01", - "wear": 0 - } - ], - "state": 0 - }, - "plate": { - "base": 4, - "cali2d_id": "", - "cur_id": "P0101", - "mat": 1, - "tar_id": "" - }, - "type": 1 - }, - "err": "0", - "fail_reason": "0", - "fan_gear": 4980736, - "file": "/data/Metadata/plate_1.gcode", - "force_upgrade": false, - "fun": "41AFFF9CFF", - "gcode_file": "/data/Metadata/plate_1.gcode", - "gcode_file_prepare_percent": "100", - "gcode_state": "RUNNING", - "heatbreak_fan_speed": "15", - "hms": [], - "home_flag": -1066934785, - "hw_switch_state": 2, - "ipcam": { - "agora_service": "disable", - "brtc_service": "enable", - "bs_state": 0, - "ipcam_dev": "1", - "ipcam_record": "enable", - "laser_preview_res": 5, - "mode_bits": 2, - "resolution": "1080p", - "rtsp_url": "disable", - "timelapse": "disable", - "tl_store_hpd_type": 2, - "tl_store_path_type": 2, - "tutk_server": "enable" - }, - "job": { - "cur_stage": { - "idx": 0, - "state": 2 - }, - "stage": [ - { - "clock_in": false, - "color": [ - "", - "" - ], - "diameter": [ - 0.4000000059604645, - 0.4000000059604645 - ], - "est_time": 0, - "heigh": 0, - "idx": 0, - "platform": "", - "print_then": false, - "proc_list": [], - "tool": [ - "HS01", - "HS01" - ], - "type": 2 - } - ] - }, - "job_attr": 17, - "job_id": "352511791", - "lan_task_id": "0", - "layer_num": 1, - "lights_report": [ - { - "mode": "on", - "node": "chamber_light" - }, - { - "mode": "flashing", - "node": "work_light" - }, - { - "mode": "on", - "node": "chamber_light2" - } - ], - "maintain": 3, - "mapping": [ - 65535, - 65535, - 65535, - 65535, - 65535, - 65535, - 65535, - 257 - ], - "mc_action": 0, - "mc_err": 0, - "mc_percent": 8, - "mc_print_error_code": "0", - "mc_print_stage": "2", - "mc_print_sub_stage": 0, - "mc_remaining_time": 313, - "mc_stage": 2, - "model_id": "US4932babe82ce32", - "net": { - "conf": 16, - "info": [ - { - "ip": 2713495744, - "mask": 16777215 - }, - { - "ip": 0, - "mask": 0 - } - ] - }, - "nozzle_diameter": "0.4", - "nozzle_target_temper": 0, - "nozzle_temper": 44, - "nozzle_type": "stainless_steel", - "online": { - "ahb": true, - "ext": true, - "version": 8 - }, - "percent": 8, - "plate_cnt": 2, - "plate_id": 1, - "plate_idx": 1, - "prepare_per": 100, - "print_error": 0, - "print_gcode_action": 0, - "print_real_action": 0, - "print_type": "cloud", - "profile_id": "305233194", - "project_id": "331855685", - "queue": 0, - "queue_est": 0, - "queue_number": 0, - "queue_sts": 0, - "queue_total": 0, - "remain_time": 313, - "s_obj": [], - "sdcard": true, - "sequence_id": "2021", - "spd_lvl": 2, - "spd_mag": 100, - "stat": "16382081F0", - "state": 4, - "stg": [ - 29, - 13, - 4, - 8, - 14, - 1, - 3, - 39 - ], - "stg_cur": 0, - "subtask_id": "675488755", - "subtask_name": "QBRICK-Akkufach v3-SOLID-1", - "task_id": "675488754", - "total_layer_num": 435, - "upgrade_state": { - "ahb_new_version_number": "", - "ams_new_version_number": "", - "consistency_request": false, - "dis_state": 0, - "err_code": 0, - "ext_new_version_number": "", - "force_upgrade": false, - "idx": 8, - "idx2": 1579120524, - "lower_limit": "00.00.00.00", - "message": "", - "module": "", - "new_version_state": 1, - "ota_new_version_number": "01.01.02.04", - "progress": "0", - "sequence_id": 0, - "sn": "0948BB510200105", - "status": "IDLE" - }, - "upload": { - "file_size": 0, - "finish_size": 0, - "message": "Good", - "oss_url": "", - "progress": 0, - "sequence_id": "0903", - "speed": 0, - "status": "idle", - "task_id": "", - "time_remaining": 0, - "trouble_id": "" - }, - "ver": "20000", - "vir_slot": [ - { - "bed_temp": "0", - "bed_temp_type": "0", - "cali_idx": -1, - "cols": [ - "FFFFFF00" - ], - "ctype": 0, - "drying_temp": "0", - "drying_time": "0", - "id": "254", - "nozzle_temp_max": "0", - "nozzle_temp_min": "0", - "remain": 0, - "tag_uid": "0000000000000000", - "total_len": 330000, - "tray_color": "FFFFFF00", - "tray_diameter": "1.75", - "tray_id_name": "", - "tray_info_idx": "", - "tray_sub_brands": "", - "tray_type": "", - "tray_uuid": "00000000000000000000000000000000", - "tray_weight": "0", - "xcam_info": "000000000000000000000000" - }, - { - "bed_temp": "0", - "bed_temp_type": "0", - "cali_idx": -1, - "cols": [ - "00000000" - ], - "ctype": 0, - "drying_temp": "0", - "drying_time": "0", - "id": "255", - "nozzle_temp_max": "0", - "nozzle_temp_min": "0", - "remain": 0, - "tag_uid": "0000000000000000", - "total_len": 330000, - "tray_color": "00000000", - "tray_diameter": "1.75", - "tray_id_name": "", - "tray_info_idx": "", - "tray_sub_brands": "", - "tray_type": "", - "tray_uuid": "00000000000000000000000000000000", - "tray_weight": "0", - "xcam_info": "000000000000000000000000" - } - ], - "vt_tray": { - "bed_temp": "0", - "bed_temp_type": "0", - "cali_idx": -1, - "cols": [ - "00000000" - ], - "ctype": 0, - "drying_temp": "0", - "drying_time": "0", - "id": "255", - "nozzle_temp_max": "0", - "nozzle_temp_min": "0", - "remain": 0, - "tag_uid": "0000000000000000", - "total_len": 330000, - "tray_color": "00000000", - "tray_diameter": "1.75", - "tray_id_name": "", - "tray_info_idx": "", - "tray_sub_brands": "", - "tray_type": "", - "tray_uuid": "00000000000000000000000000000000", - "tray_weight": "0", - "xcam_info": "000000000000000000000000" - }, - "wifi_signal": "-68dBm", - "xcam": { - "allow_skip_parts": false, - "buildplate_marker_detector": true, - "cfg": 224695, - "first_layer_inspector": true, - "halt_print_sensitivity": "medium", - "print_halt": true, - "printing_monitor": true, - "spaghetti_detector": true - }, - "xcam_status": "0" -} \ No newline at end of file