├── .gitattributes ├── LICENSE ├── README.md └── Bambu-Poop-Conveyor └── Bambu-Poop-Conveyor.ino /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Tony 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bambu Poop Conveyor for ESP32 2 | :arrow_right: :arrow_right: :poop: :arrow_right: :arrow_right: :poop: :arrow_right: :arrow_right: 3 | 4 | > [!TIP] 5 | > Be sure to check out the new web installer. https://t0nyz.com/flasher/index.html 6 | 7 | ### For more detailed project information visit: https://t0nyz.com/projects/bambuconveyor 8 | 9 | ## Overview 10 | > [!NOTE] 11 | > 5/9/2025 - Fixed major bug with Bambus latest firmware updates. Also, now works with H2D! 12 | 13 | The Bambu Conveyor is an application designed to manage the waste output of a [Bambu Labs printer](https://bambulab.com/en/x1). It utilizes the MQTT protocol (or Motion Detection) to monitor the printer's status and control a motor that moves waste material away from the printing area. 14 | 15 | ## Required parts used for this build: 16 | 17 | - Breakout board for ESP32: https://amzn.to/4dyjsx0 18 | - ESP32 board: https://amzn.to/4fBjh5L 19 | - 12 Volt power supply: https://amzn.to/3AfIm6a 20 | - Motor Controller: https://amzn.to/3yBPqcM 21 | - 12V 10RPM Motor: https://amzn.to/3M24VOd 22 | - Resistors (I use 470Ω - you need 3 from this kit): https://amzn.to/4cqCi8e 23 | - Wires: https://amzn.to/46EAtn3 24 | - LED's sourced from this kit: https://amzn.to/4dH5Dw7 25 | 26 | ## Optional parts: 27 | - Power connector: https://amzn.to/3T4xRsS 28 | - I use these bearings on my conveyor for smoother action: https://amzn.to/4hjW0WD 29 | - Motion sensor (For Motion Sensor mode): https://amzn.to/4gtN4gd 30 | 31 | 32 | # Conveyor Makerworld files 33 | 34 | - **Conveyor:** https://makerworld.com/en/models/148083#profileId-161573 35 | - **ESP32 Housing:** https://makerworld.com/en/models/1071359#profileId-1061316 36 | - **Conveyor Extension:** https://makerworld.com/en/models/249714#profileId-359905 37 | 38 | 39 | ### Two Modes of Operation: MQTT or Motion Detection 40 | 41 | The **Bambu Poop Conveyor** supports two methods for triggering the conveyor, depending on your printer model and preference. 42 | 43 | #### 1. MQTT Mode (Recommended for X1C) (Default setting) 44 | - Best suited for **X1C printers** due to their more powerful CPU, which handles MQTT updates more efficiently. 45 | - Listens for printer status changes (Change Filament status and Clean nozzle status) and automatically activates the conveyor when needed. 46 | - Requires a stable network connection and correct MQTT setup. 47 | 48 | #### 2. IR Motion Detection Mode (Better for P1 & A1 Series) 49 | - Ideal for **P1 and A1 series printers**, where MQTT performance can be inconsistent due to CPU constraints. 50 | - Uses the **HiLetgo AM312 PIR sensor** to detect movement and trigger the conveyor. 51 | - Works independently of network conditions, making it a more reliable option for some setups. 52 | 53 | 54 | # Bambu Poop Conveyor - Setup & Installation Guide 55 | 56 | ## Setup 57 | 58 | ## Flashing the ESP32 59 | 60 | To install the firmware, use one of the following methods: 61 | 62 | ### **Method 1: Web Installer (Easiest Method)** 63 | - Open **Google Chrome** or **Microsoft Edge**. 64 | - Go to **[Bambu ESP32 Installer](https://t0nyz.com/flasher)**. 65 | - Click the **"Install"** button and follow the on-screen instructions. 66 | --- 67 | 68 | ### **Method 2: Manual Installation** 69 | 70 | #### **1. Download and Install ESPTool** 71 | - Install `esptool` using pip: 72 | ```sh 73 | pip install esptool 74 | ``` 75 | - Alternatively, download the precompiled ESPTool from the official Espressif GitHub. 76 | 77 | #### **2. Download the Firmware File** 78 | - Download the latest firmware from the **[GitHub Releases](https://github.com/t0nyz0/Bambu-Poop-Conveyor-ESP32/releases/latest)**. 79 | 80 | #### **3. Connect Your ESP32** 81 | - Plug your ESP32 into your computer using a USB cable. 82 | - Ensure drivers for the USB-to-serial adapter are installed (CH340, CP210x, etc.). 83 | 84 | #### **4. Find the Serial Port** 85 | - On **macOS/Linux**, run: 86 | ```sh 87 | ls /dev/tty.* 88 | ``` 89 | Look for something like `/dev/tty.usbserial-1`. 90 | - On **Windows**, open **Device Manager** and check under **Ports (COM & LPT)**. 91 | 92 | #### **5. Flash the Firmware** 93 | - Replace `` with your ESP32’s serial port (e.g., `/dev/tty.usbserial-1`): 94 | ```sh 95 | esptool.py --chip esp32 --port /dev/tty.usbserial-1 --baud 460800 write_flash \ 96 | 0x0 Bambu-Poop-Conveyor.v1.3.3-final.bin 97 | ``` 98 | 99 | #### **6. Verify Flashing and Restart** 100 | - Once flashing completes, restart your ESP32 by unplugging/replugging it or pressing the **EN** or **RST** button. 101 | 102 | Your ESP32 should now be running the updated firmware. 103 | 104 | ## Configuring via Web Interface 105 | 106 | Once flashed, the ESP32 starts in AP Mode: 107 | 108 | 1. Connect to the **"BambuConveyor"** WiFi network. 109 | 2. Open a browser and go to **[192.168.4.1/config](http://192.168.4.1/config)**. 110 | 3. Enter your WiFi and MQTT credentials. 111 | 4. Click **Save**. The ESP32 will reboot and connect to your WiFi. 112 | 113 | For troubleshooting, open an issue on GitHub or check the discussions tab. 114 | 115 | ## GPIO Pins 116 | 117 | The application uses the following GPIO pins for motor and LED control: 118 | 119 | ```js copy 120 | const int greenLight = 19; 121 | const int yellowLight = 18; 122 | const int redLight = 4; 123 | 124 | int motor1Pin1 = 23; 125 | int motor1Pin2 = 21; 126 | int enable1Pin = 15; 127 | 128 | const int motionSensorPin = 22; 129 | ``` 130 | 131 | ## Usage 132 | 133 | ### Web Server 134 | 135 | The application hosts a web server to provide manual control and configuration. Access the following URLs for different functionalities: 136 | 137 | - **Root URL:** Opens Configuration page (`/`) 138 | - **Control URL:** Manual motor control page (`/control`) 139 | - **Config URL:** Configuration page to update settings (`/config`) 140 | - **Logs URL:** Log history page (`/logs`) 141 | - **Manual Run URL:** Opening this URL runs the motor manually (`/run`) 142 | 143 | ### FAQ / Troubleshooting 144 | 145 | *What do the flashing lights mean when its first turned on?* 146 | - Flashing yellow only = Connecting to WiFi 147 | - Solid Green = We are connected to Wifi and MQTT printer 148 | - Red Light on bootup = No Wifi / No MQTT (Solid red also when conveyor is running) 149 | - Green light / Yellow flashing = Wifi connected / Attempting to connect to printer 150 | - Green light / Yellow solid = Wifi conncted / Issue connecting to printer via MQTT / Will reattempt connection after 5 seconds 151 | 152 | *The ESP32 doesnt connect to the printer* 153 | - Double check that your printer is setup with Access Code and LAN only mode is **OFF** [See Bambu Wiki](https://wiki.bambulab.com/en/knowledge-sharing/enable-lan-mode) 154 | - Double check your SN matches the settings you put in 155 | - Make sure your printer has good Wifi signal 156 | - Make sure the ESP32 has good Wifi signal 157 | - Reach out to me if you still have issues 158 | 159 | ### Home Assistant Settings 160 | 161 | - Action: Run motor manually 162 | - Sensor: States if motor is running 163 | 164 | Example yaml 165 | ``` 166 | rest_command: 167 | bambu_run_motor: 168 | url: "http://11.0.1.54/run" 169 | method: POST 170 | 171 | sensor: 172 | - platform: rest 173 | name: "Bambu Motor Status" 174 | resource: "http://11.0.1.54/status" 175 | value_template: "{{ value_json.motor_running }}" 176 | ``` 177 | 178 | ## License 179 | 180 | This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. 181 | 182 | For more detailed project information visit: https://t0nyz.com/projects/bambuconveyor 183 | -------------------------------------------------------------------------------- /Bambu-Poop-Conveyor/Bambu-Poop-Conveyor.ino: -------------------------------------------------------------------------------- 1 | #include 2 | // Bambu Poop Conveyor 3 | // 8/6/24 - TZ 4 | // Last updated: 3/24/25 5 | char version[10] = "1.3.7"; 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | //---- SETTINGS YOU SHOULD ENTER -------------------------------------------------------------------------------------------------------------------------- 18 | 19 | // WiFi credentials 20 | char ssid[50] = ""; 21 | char password[50] = ""; 22 | 23 | // MQTT credentials 24 | char mqtt_server[40] = "your-bambu-printer-ip"; 25 | char mqtt_password[30] = "your-bambu-printer-accesscode"; 26 | char serial_number[35] = "your-bambu-printer-serial-number"; 27 | // Printer model selection (X1, P1, A1) 28 | char printer_model[5] = "X1"; // Default to X1 29 | 30 | 31 | // -------------------------------------------------------------------------------------------------------------------------------------------------------- 32 | 33 | // OPTIONAL: IF YOU WANT ACCURATE LOG TIMES UPDATE YOUR TIMEZONE HERE 34 | 35 | //const long gmtOffset_sec = -5 * 3600; // Adjust for your timezone (EST) 36 | int gmtOffset_sec = -6; // Default to CST (GMT-6 hours) 37 | 38 | // Daylight savings 39 | const int daylightOffset_sec = 3600; // Adjust for daylight saving time if applicable 40 | 41 | // GPIO Pins 42 | const int greenLight = 19; 43 | const int yellowLight = 18; 44 | const int redLight = 4; 45 | 46 | const int motionSensorPin = 22; // Adjust the pin as needed 47 | bool useMotionSensor = false; 48 | 49 | char mqtt_port[6] = "8883"; 50 | char mqtt_user[30] = "bblp"; 51 | char mqtt_topic[200]; 52 | 53 | // Poop Motor 54 | int motor1Pin1 = 23; 55 | int motorDirection = 0; // Default to 0 (Forward) 56 | int motor1Pin2 = 21; 57 | int enable1Pin = 15; 58 | 59 | int motorRunTime = 5000; // 5 seconds by default 60 | int motorWaitTime = 5000; // The time to wait to run the motor. 61 | int delayAfterRun = 120000; // Delay after motor run 62 | int additionalWaitTime = 0; // Variable to store additional wait time for specific stages 63 | 64 | // Setting PWM properties 65 | const int freq = 5000; 66 | const int pwmChannel = 0; 67 | const int pwmTimer = 0; 68 | const int resolution = 8; 69 | int dutyCycle = 225; 70 | 71 | const char* base64Image = "data:image/webp;base64,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"; 72 | 73 | // Debug flag 74 | bool debug = true; 75 | bool autoPushAllEnabled = true; 76 | bool pushAllCommandSent = false; 77 | bool wifiConnected = false; 78 | bool mqttConnected = false; 79 | bool isAPMode = false; 80 | 81 | 82 | unsigned long lastAttemptTime = 0; 83 | const unsigned long RECONNECT_INTERVAL = 15000; // 15 seconds 84 | 85 | unsigned int sequence_id = 20000; 86 | unsigned long previousMillis = 0; 87 | unsigned long redLightToggleTime = 0; 88 | unsigned long greenLightToggleTime = 0; 89 | unsigned long motorRunStartTime = 0; 90 | unsigned long motorWaitStartTime = 0; 91 | unsigned long delayAfterRunStartTime = 0; 92 | unsigned long yellowLightStartTime = 0; 93 | unsigned int yellowLightState = 0; 94 | bool motorRunning = false; 95 | bool motorWaiting = false; 96 | bool delayAfterRunning = false; 97 | 98 | 99 | DNSServer dnsServer; 100 | 101 | #define MAX_LOG_ENTRIES 200 102 | 103 | struct LogEntry { 104 | time_t timestamp; 105 | String action; 106 | }; 107 | 108 | LogEntry logs[MAX_LOG_ENTRIES]; 109 | int logIndex = 0; 110 | 111 | // Sync time so we have proper logging 112 | const char* ntpServer = "pool.ntp.org"; 113 | 114 | // MQTT state variables 115 | int printer_stage = -100; 116 | int printer_sub_stage = -100; 117 | String printer_real_stage = ""; 118 | String gcodeState = ""; 119 | 120 | // Create instances 121 | WiFiClientSecure espClient; 122 | PubSubClient client(espClient); 123 | Preferences preferences; 124 | WebServer server(80); 125 | 126 | // Function to get the printer stage description 127 | const char* getStageInfo(int stage) { 128 | switch (stage) { 129 | case -100: return "Connection Issue"; 130 | case -1: return "Idle"; 131 | case 0: return "Printing"; 132 | case 1: return "Auto Bed Leveling"; 133 | case 2: return "Heatbed Preheating"; 134 | case 3: return "Sweeping XY Mech Mode"; 135 | case 4: return "Changing Filament"; 136 | case 5: return "M400 Pause"; 137 | case 6: return "Paused due to filament runout"; 138 | case 7: return "Heating Hotend"; 139 | case 8: return "Calibrating Extrusion"; 140 | case 9: return "Scanning Bed Surface"; 141 | case 10: return "Inspecting First Layer"; 142 | case 11: return "Identifying Build Plate Type"; 143 | case 12: return "Calibrating Micro Lidar"; 144 | case 13: return "Homing Toolhead"; 145 | case 14: return "Cleaning Nozzle Tip"; 146 | case 15: return "Checking Extruder Temperature"; 147 | case 16: return "Printing was paused by the user"; 148 | case 17: return "Pause of front cover falling"; 149 | case 18: return "Calibrating Micro Lidar"; 150 | case 19: return "Calibrating Extrusion Flow"; 151 | case 20: return "Paused due to nozzle temperature malfunction"; 152 | case 21: return "Paused due to heat bed temperature malfunction"; 153 | case 22: return "Filament unloading"; 154 | case 23: return "Skip step pause"; 155 | case 24: return "Filament loading"; 156 | case 25: return "Motor noise calibration"; 157 | case 26: return "Paused due to AMS lost"; 158 | case 27: return "Paused due to low speed of the heat break fan"; 159 | case 28: return "Paused due to chamber temperature control error"; 160 | case 29: return "Cooling chamber"; 161 | case 30: return "Paused by the Gcode inserted by user"; 162 | case 31: return "Motor noise showoff"; 163 | case 32: return "Nozzle filament covered detected pause"; 164 | case 33: return "Cutter error pause"; 165 | case 34: return "First layer error pause"; 166 | case 35: return "Nozzle clog pause"; 167 | default: return "Unknown stage"; 168 | } 169 | } 170 | 171 | // Function to add log entries 172 | void addLogEntry(String action) { 173 | time_t now = time(nullptr); 174 | 175 | logs[logIndex].timestamp = now; // Store raw timestamp (UTC) 176 | logs[logIndex].action = action; 177 | logIndex = (logIndex + 1) % MAX_LOG_ENTRIES; 178 | } 179 | 180 | void syncTime() { 181 | addLogEntry("Syncing time..."); 182 | configTime(gmtOffset_sec * 3600, daylightOffset_sec, ntpServer); 183 | 184 | struct tm timeinfo; 185 | int retries = 0; 186 | while (!getLocalTime(&timeinfo) && retries < 10) { 187 | addLogEntry("Failed to obtain time, retrying..."); 188 | delay(1000); 189 | retries++; 190 | } 191 | 192 | if (retries < 10) { 193 | char timeString[50]; 194 | strftime(timeString, sizeof(timeString), "%Y-%m-%d %H:%M:%S", &timeinfo); 195 | addLogEntry("Time synchronized! ESP32 thinks current time is: " + String(timeString)); 196 | } else { 197 | addLogEntry("Failed to synchronize time after multiple attempts."); 198 | } 199 | } 200 | 201 | void handleFirmwareUpload() { 202 | HTTPUpload& upload = server.upload(); 203 | 204 | if (upload.status == UPLOAD_FILE_START) { 205 | Serial.printf("Firmware update initiated: %s\n", upload.filename.c_str()); 206 | if (!Update.begin(UPDATE_SIZE_UNKNOWN)) { // Start OTA update 207 | Update.printError(Serial); 208 | } 209 | } else if (upload.status == UPLOAD_FILE_WRITE) { 210 | if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { 211 | Update.printError(Serial); 212 | } 213 | } else if (upload.status == UPLOAD_FILE_END) { 214 | if (Update.end(true)) { // Finish OTA update 215 | Serial.println("Firmware update successful!"); 216 | server.send(200, "text/html", "

Update Successful! Rebooting...

"); 217 | delay(1000); 218 | ESP.restart(); 219 | } else { 220 | Update.printError(Serial); 221 | } 222 | } 223 | } 224 | 225 | // Function to handle the control page 226 | void handleControl() { 227 | if (server.method() == HTTP_GET) { 228 | String html = ""; 229 | html += ""; 237 | html += ""; 238 | html += "
"; 239 | html += "

Manual Motor Control

"; 240 | html += "
"; 241 | html += ""; 242 | html += "
"; 243 | html += "
"; 244 | html += ""; 245 | server.send(200, "text/html", html); 246 | } else if (server.method() == HTTP_POST) { 247 | server.send(200, "text/plain", "Motor activated manually"); 248 | motorWaiting = true; 249 | motorWaitStartTime = millis(); 250 | addLogEntry("Motor activated manually"); 251 | } 252 | } 253 | 254 | 255 | // Function to handle the root URL 256 | void handleManualRun() { 257 | server.send(200, "text/plain", "Motor activated"); 258 | motorWaiting = true; 259 | motorWaitStartTime = millis(); 260 | additionalWaitTime = 0; // Reset additional wait time for manual trigger 261 | digitalWrite(greenLight, LOW); 262 | digitalWrite(yellowLight, HIGH); 263 | addLogEntry("Motor activated from RUN url"); 264 | } 265 | 266 | void handleConfig() { 267 | if (server.method() == HTTP_GET) { 268 | String html = "Bambu Poop Conveyor"; 269 | html += ""; 285 | html += ""; 286 | html += "
\"Bambu
"; 287 | html += "
"; 288 | html += "

Bambu Poop Conveyor v" + String(version) + "

"; 289 | html += "
"; 290 | html += "
"; 291 | html += "
"; 292 | html += "
"; 293 | html += "
"; 294 | html += "
"; 295 | html += "
"; 296 | html += "
"; 297 | html += "
"; 298 | html += ""; 299 | html += "
"; 300 | html += ""; 301 | html += "
"; 306 | html += ""; 307 | html += "
"; 308 | html += ""; 309 | html += "
"; 313 | html += ""; 314 | html += "
"; 315 | html += ""; 316 | html += "
"; 317 | html += "
"; 318 | html += "Motor Manual Control Page"; 319 | html += "Logs Page"; 320 | 321 | html += "
"; 322 | 323 | server.send(200, "text/html", html); 324 | } 325 | else if (server.method() == HTTP_POST) { 326 | preferences.begin("my-config", false); 327 | 328 | // Retrieve and store the entered values 329 | strcpy(ssid, server.arg("ssid").c_str()); 330 | strcpy(password, server.arg("password").c_str()); 331 | strcpy(mqtt_server, server.arg("mqtt_server").c_str()); 332 | strcpy(mqtt_password, server.arg("mqtt_password").c_str()); 333 | strcpy(serial_number, server.arg("serial_number").c_str()); 334 | strcpy(printer_model, server.arg("printer_model").c_str()); 335 | 336 | dutyCycle = server.arg("dutyCycle").toInt(); 337 | motorRunTime = server.arg("motorRunTime").toInt(); 338 | motorWaitTime = server.arg("motorWaitTime").toInt(); 339 | delayAfterRun = server.arg("delayAfterRun").toInt(); 340 | useMotionSensor = server.hasArg("useMotionSensor"); 341 | debug = server.hasArg("debug"); 342 | motorDirection = server.arg("motorDirection").toInt(); 343 | gmtOffset_sec = server.arg("gmtOffset_sec").toInt(); 344 | 345 | // Store in Preferences for persistence 346 | preferences.putString("ssid", ssid); 347 | preferences.putString("password", password); 348 | preferences.putString("mqtt_server", mqtt_server); 349 | preferences.putString("mqtt_password", mqtt_password); 350 | preferences.putString("serial_number", serial_number); 351 | preferences.putInt("motorRunTime", motorRunTime); 352 | preferences.putInt("motorWaitTime", motorWaitTime); 353 | preferences.putInt("delayAfterRun", delayAfterRun); 354 | preferences.putBool("useMotionSensor", useMotionSensor); 355 | preferences.putString("printer_model", printer_model); 356 | preferences.putInt("motorDirection", motorDirection); 357 | preferences.putInt("dutyCycle", dutyCycle); 358 | preferences.putBool("debug", debug); 359 | preferences.putInt("gmtOffset_sec", gmtOffset_sec); 360 | 361 | preferences.end(); 362 | 363 | server.send(200, "text/html", "

Settings saved! This page will automatically refresh in 15 seconds...



Refresh now"); 364 | 365 | delay(1000); 366 | ESP.restart(); 367 | } 368 | } 369 | 370 | String formatDateTime(time_t timestamp) { 371 | struct tm timeinfo; 372 | time_t adjustedTime = timestamp; // Apply timezone 373 | localtime_r(&adjustedTime, &timeinfo); 374 | 375 | char buffer[25]; 376 | strftime(buffer, sizeof(buffer), "%Y-%m-%d %I:%M:%S %p", &timeinfo); // 12-hour format with AM/PM 377 | 378 | return String(buffer); 379 | } 380 | 381 | // Handle Home Assistant status check 382 | void handleMotorStatus() { 383 | String jsonResponse = "{ \"motor_running\": " + String(motorRunning ? "true" : "false") + " }"; 384 | server.send(200, "application/json", jsonResponse); 385 | } 386 | 387 | void handleLogs() { 388 | String html = "Debug Logs"; 389 | html += ""; 397 | html += ""; 398 | html += "
"; 399 | html += "

Logs

"; 400 | html += ""; 401 | 402 | for (int i = 0; i < MAX_LOG_ENTRIES; i++) { 403 | int index = (logIndex + i) % MAX_LOG_ENTRIES; 404 | if (logs[index].timestamp > 0) { 405 | html += ""; 406 | } 407 | } 408 | 409 | html += "
TimestampAction
" + formatDateTime(logs[index].timestamp) + "" + logs[index].action + "
"; 410 | server.send(200, "text/html", html); 411 | } 412 | 413 | // MQTT callback function 414 | void mqttCallback(char* topic, byte* payload, unsigned int length) { 415 | DynamicJsonDocument doc(40000); 416 | DeserializationError error = deserializeJson(doc, payload, length); 417 | 418 | if (error) { 419 | if (debug) { 420 | Serial.print(F("deserializeJson() failed: ")); 421 | Serial.println(error.c_str()); 422 | addLogEntry("deserializeJson() failed: "); 423 | addLogEntry(error.c_str()); 424 | } 425 | return; 426 | } 427 | 428 | if (doc.containsKey("print") && doc["print"].containsKey("stg_cur")) { 429 | printer_stage = doc["print"]["stg_cur"].as(); 430 | } 431 | 432 | if (doc.containsKey("print") && doc["print"].containsKey("mc_print_sub_stage")) { 433 | printer_sub_stage = doc["print"]["mc_print_sub_stage"].as(); 434 | } 435 | 436 | 437 | if (!useMotionSensor && !motorWaiting && !motorRunning && !delayAfterRunning && 438 | (printer_stage == 4 || printer_stage == 14 || (printer_sub_stage == 4 && printer_stage != -1))) { 439 | motorWaiting = true; 440 | motorWaitStartTime = millis(); 441 | addLogEntry("Status 4 or 14 detected! Running conveyor!!!"); 442 | 443 | if (debug) { 444 | Serial.println("Status 4 or 14 detected! Running conveyor!!!"); 445 | } 446 | 447 | if (printer_sub_stage == 4 && printer_stage != -1) { 448 | additionalWaitTime = 75000; 449 | } else { 450 | additionalWaitTime = 0; 451 | } 452 | 453 | yellowLightStartTime = millis(); 454 | yellowLightState = HIGH; 455 | digitalWrite(yellowLight, yellowLightState); 456 | } 457 | 458 | if (debug && !useMotionSensor) { 459 | Serial.println("Bambu Poop Conveyor v" + String(version) + 460 | " | Wifi: " + WiFi.localIP().toString() + 461 | " | Current Print Stage: " + String(getStageInfo(printer_stage)) + 462 | " | Sub stage: " + String(getStageInfo(printer_sub_stage))); 463 | addLogEntry("MQTT Callback - Getting data from printer. Data: Current print stage: " + String(getStageInfo(printer_stage)) + " | Current sub print stage: " + String(getStageInfo(printer_sub_stage))); 464 | } 465 | } 466 | 467 | void publishPushAllMessage() { 468 | if (client.connected()) { 469 | char publish_topic[128]; 470 | sprintf(publish_topic, "device/%s/request", serial_number); 471 | 472 | DynamicJsonDocument doc(1024); 473 | doc["pushing"]["sequence_id"] = sequence_id; 474 | doc["pushing"]["command"] = "pushall"; 475 | doc["user_id"] = "2222222"; 476 | 477 | String jsonMessage; 478 | serializeJson(doc, jsonMessage); 479 | 480 | bool success = client.publish(publish_topic, jsonMessage.c_str()); 481 | 482 | if (success) { 483 | if (debug) { 484 | Serial.print("Message successfully sent to: "); 485 | Serial.println(publish_topic); 486 | addLogEntry("MQTT message sent from publishPushAllMessage"); 487 | } 488 | } else { 489 | if (debug) Serial.println("Failed to send message."); 490 | addLogEntry("Failed to send MQTT push all - publishPushAllMessage"); 491 | } 492 | 493 | sequence_id++; 494 | } else { 495 | if (debug) { 496 | Serial.println("Not connected to MQTT broker!"); 497 | addLogEntry("Not connected to MQTT broker! - publishPushAllMessage"); 498 | } 499 | } 500 | } 501 | 502 | 503 | // Function to connect to MQTT 504 | void connectToMqtt() { 505 | if (!client.connected()) { 506 | if (debug) { 507 | Serial.print("Connecting to MQTT..."); 508 | addLogEntry("Connecting to MQTT..."); 509 | } 510 | if (client.connect("BambuConveyor", mqtt_user, mqtt_password)) { 511 | Serial.println("Connected to Bambu printer"); 512 | addLogEntry("Connected to Bambu printer"); 513 | sprintf(mqtt_topic, "device/%s/report", serial_number); 514 | client.subscribe(mqtt_topic); 515 | publishPushAllMessage(); 516 | digitalWrite(redLight, LOW); 517 | digitalWrite(yellowLight, LOW); 518 | if (debug) { 519 | Serial.print("Red light off"); 520 | addLogEntry("Red light off"); 521 | Serial.print("Yellow light off"); 522 | addLogEntry("Yellow light off"); 523 | } 524 | } else { 525 | if (debug) { 526 | Serial.print("Failed: "); 527 | Serial.print(client.state()); 528 | Serial.println(" try again in 5 seconds"); 529 | addLogEntry("Failed to connect to MQTT (Bambu Printer), trying again in 5 seconds"); 530 | } 531 | lastAttemptTime = millis(); 532 | digitalWrite(redLight, HIGH); 533 | addLogEntry("RED light turned on"); 534 | } 535 | } 536 | } 537 | 538 | // Function to send a push all command 539 | void sendPushAllCommand() { 540 | if (client.connected() && !pushAllCommandSent) { 541 | String mqtt_topic_request = "device/"; 542 | mqtt_topic_request += serial_number; 543 | mqtt_topic_request += "/request"; 544 | 545 | StaticJsonDocument<128> doc; 546 | doc["pushing"]["sequence_id"] = String(sequence_id++); 547 | doc["pushing"]["command"] = "pushall"; 548 | doc["user_id"] = "2222222"; 549 | 550 | String payload; 551 | serializeJson(doc, payload); 552 | if (debug) { 553 | Serial.println("MQTT Callback sent - Sendpushallcommand"); 554 | addLogEntry("MQTT Callback sent - Sendpushallcommand"); 555 | } 556 | client.publish(mqtt_topic_request.c_str(), payload.c_str()); 557 | pushAllCommandSent = true; 558 | } 559 | } 560 | 561 | void setup() { 562 | // Initialize logs 563 | for (int i = 0; i < MAX_LOG_ENTRIES; i++) { 564 | logs[i].timestamp = 0; 565 | } 566 | // Initialize GPIO pins 567 | pinMode(motor1Pin1, OUTPUT); 568 | pinMode(motor1Pin2, OUTPUT); 569 | pinMode(enable1Pin, OUTPUT); 570 | pinMode(yellowLight, OUTPUT); 571 | pinMode(redLight, OUTPUT); 572 | pinMode(greenLight, OUTPUT); 573 | pinMode(motionSensorPin, INPUT); 574 | 575 | // Start Serial communication 576 | Serial.begin(115200); 577 | client.setBufferSize(40000); 578 | 579 | // Configure LED PWM functionalities 580 | ledcAttachChannel(enable1Pin, freq, resolution, pwmChannel); 581 | ledcWrite(enable1Pin, dutyCycle); 582 | 583 | // Start Preferences storage 584 | preferences.begin("my-config", false); 585 | 586 | // Load all stored values from Preferences 587 | String storedSSID = preferences.getString("ssid", ""); 588 | String storedPassword = preferences.getString("password", ""); 589 | String storedMqttServer = preferences.getString("mqtt_server", ""); 590 | String storedMqttPassword = preferences.getString("mqtt_password", ""); 591 | String storedSerialNumber = preferences.getString("serial_number", ""); 592 | useMotionSensor = preferences.getBool("useMotionSensor", false); 593 | String storedPrinterModel = preferences.getString("printer_model", "X1"); // Default "X1" if missing 594 | debug = preferences.getBool("debug", false); 595 | 596 | storedSSID.toCharArray(ssid, sizeof(ssid)); 597 | storedPassword.toCharArray(password, sizeof(password)); 598 | storedMqttServer.toCharArray(mqtt_server, sizeof(mqtt_server)); 599 | storedMqttPassword.toCharArray(mqtt_password, sizeof(mqtt_password)); 600 | storedSerialNumber.toCharArray(serial_number, sizeof(serial_number)); 601 | storedPrinterModel.toCharArray(printer_model, sizeof(printer_model)); 602 | motorRunTime = preferences.getInt("motorRunTime", 10000); 603 | motorWaitTime = preferences.getInt("motorWaitTime", 5000); 604 | delayAfterRun = preferences.getInt("delayAfterRun", 120000); 605 | motorDirection = preferences.getInt("motorDirection", 0); 606 | gmtOffset_sec = preferences.getInt("gmtOffset_sec"); 607 | dutyCycle = preferences.getInt("dutyCycle", 225); 608 | 609 | // Close Preferences after reading all values 610 | preferences.end(); 611 | 612 | // Decide if we should connect to WiFi or enter AP mode 613 | if (strlen(ssid) > 0 && strlen(password) > 0) { 614 | connectToWiFi(); 615 | } else { 616 | startWiFiAPMode(); 617 | } 618 | 619 | delay(2000); 620 | 621 | // Set up MQTT if WiFi is connected 622 | if (WiFi.status() == WL_CONNECTED) { 623 | client.setServer(mqtt_server, 8883); // Default MQTT port 624 | espClient.setInsecure(); 625 | client.setCallback(mqttCallback); 626 | sprintf(mqtt_topic, "device/%s/report", serial_number); 627 | connectToMqtt(); 628 | } 629 | 630 | // Set up Web Server routes 631 | server.on("/", handleConfig); 632 | server.on("/control", handleControl); 633 | server.on("/config", handleConfig); 634 | server.on("/logs", handleLogs); 635 | // Register Home Assistant API endpoints 636 | server.on("/run", handleManualRun); 637 | server.on("/status", handleMotorStatus); 638 | server.on("/update", HTTP_POST, []() { 639 | server.send(200, "text/plain", "Upload complete!"); 640 | }, handleFirmwareUpload); 641 | 642 | 643 | 644 | // Start Web Server 645 | server.begin(); 646 | Serial.println("Web server started."); 647 | addLogEntry("Web server started."); 648 | 649 | if (!client.connected()) { 650 | sendPushAllCommand(); 651 | } 652 | } 653 | 654 | void handleRoot() { 655 | server.sendHeader("Location", "/config", true); 656 | server.send(302, "text/plain", "Redirecting..."); 657 | } 658 | 659 | void startWiFiAPMode() { 660 | Serial.println("Starting WiFi AP Mode..."); 661 | addLogEntry("Starting WiFi AP Mode..."); 662 | 663 | isAPMode = true; 664 | 665 | WiFi.mode(WIFI_AP); 666 | WiFi.softAP("BambuConveyor", "12345678"); // Open WiFi AP with password 667 | 668 | dnsServer.start(53, "*", WiFi.softAPIP()); // Captive portal redirection 669 | server.onNotFound(handleRoot); // Redirect users to /config 670 | 671 | Serial.print("Access Point IP: "); 672 | Serial.println(WiFi.softAPIP()); 673 | 674 | // Flash yellow light to indicate setup mode 675 | digitalWrite(yellowLight, HIGH); 676 | } 677 | 678 | 679 | void connectToWiFi() { 680 | const int maxRetries = 20; // Set a limit for retries 681 | int retryCount = 0; 682 | 683 | WiFi.mode(WIFI_STA); 684 | WiFi.begin(ssid, password); 685 | 686 | unsigned long startAttemptTime = millis(); 687 | while (WiFi.status() != WL_CONNECTED && millis() - startAttemptTime < 25000) { // Try for 15 sec 688 | 689 | if (debug) { 690 | Serial.print('.'); 691 | addLogEntry("Connecting to WiFi... Attempt " + String(retryCount + 1)); 692 | } 693 | 694 | digitalWrite(yellowLight, HIGH); 695 | delay(500); 696 | digitalWrite(yellowLight, LOW); 697 | delay(500); 698 | 699 | retryCount++; 700 | if (retryCount >= maxRetries) { 701 | Serial.println("\nExceeded max WiFi connection attempts, rebooting ESP32..."); 702 | addLogEntry("Exceeded max WiFi connection attempts, rebooting ESP32..."); 703 | delay(2000); // Small delay before reboot 704 | ESP.restart(); 705 | } 706 | } 707 | 708 | if (WiFi.status() == WL_CONNECTED) { 709 | digitalWrite(greenLight, HIGH); 710 | syncTime(); 711 | isAPMode = false; // Ensure AP mode is OFF when connected 712 | Serial.println("\nWiFi connected. IP: " + WiFi.localIP().toString()); 713 | addLogEntry("Connected to WiFi: " + WiFi.localIP().toString()); 714 | } else { 715 | Serial.println("\nWiFi failed to connect. Switching to AP mode."); 716 | startWiFiAPMode(); // Start AP mode if WiFi fails 717 | addLogEntry("WiFi failed to connect. Switching to AP mode."); 718 | } 719 | } 720 | 721 | 722 | // Loop function 723 | // Add this variable to track if MQTT is in reconnecting state 724 | unsigned long lastMQTTDisconnectTime = 0; 725 | bool mqttReconnecting = false; 726 | void loop() { 727 | server.handleClient(); 728 | dnsServer.processNextRequest(); // Handle captive portal redirects 729 | 730 | if (isAPMode) return; // Skip all WiFi/MQTT logic if in AP mode 731 | 732 | unsigned long currentMillis = millis(); 733 | static unsigned long disconnectedTime = 0; 734 | 735 | // Determine push interval based on printer model 736 | unsigned long pushInterval = (strcmp(printer_model, "X1") == 0) ? 30000 : 300000; // 30 sec for X1, 5 min for others 737 | 738 | if (useMotionSensor && digitalRead(motionSensorPin) == HIGH && !motorWaiting && !motorRunning && !delayAfterRunning) { 739 | motorWaitStartTime = millis(); 740 | yellowLightStartTime = millis(); 741 | yellowLightState = HIGH; 742 | digitalWrite(yellowLight, yellowLightState); 743 | addLogEntry("Motion detected, starting conveyor"); 744 | motorWaiting = true; 745 | } 746 | 747 | // Auto PushAll based on printer model interval 748 | if (!useMotionSensor && autoPushAllEnabled && client.connected() && (currentMillis - previousMillis >= pushInterval)) { 749 | previousMillis = currentMillis; 750 | if (debug) { 751 | Serial.println("Requesting pushAll..."); 752 | addLogEntry("Requesting pushAll... Push interval: " + String(pushInterval) + " Push interval: " + String(currentMillis - previousMillis)); 753 | } 754 | publishPushAllMessage(); 755 | } 756 | 757 | // Motor waiting logic 758 | if (motorWaiting && millis() - motorWaitStartTime >= (motorWaitTime + additionalWaitTime)) { 759 | motorWaiting = false; 760 | motorRunning = true; 761 | motorRunStartTime = millis(); 762 | digitalWrite(yellowLight, LOW); 763 | digitalWrite(redLight, HIGH); 764 | if (debug) Serial.println(String("Moving ") + (motorDirection == 0 ? "Forward" : "Reverse")); 765 | // Apply duty cycle before enabling motor 766 | ledcWrite(enable1Pin, dutyCycle); 767 | 768 | if (motorDirection == 0) { 769 | digitalWrite(motor1Pin1, LOW); 770 | digitalWrite(motor1Pin2, HIGH); 771 | } else { 772 | digitalWrite(motor1Pin1, HIGH); 773 | digitalWrite(motor1Pin2, LOW); 774 | } 775 | addLogEntry("Conveyor Running | MOTOR STARTED | Duty Cycle: " + String(dutyCycle) + " | Direction: " + (motorDirection == 0 ? "Forward" : "Reverse")); 776 | } 777 | 778 | // Motor running logic 779 | if (motorRunning && millis() - motorRunStartTime >= motorRunTime) { 780 | motorRunning = false; 781 | delayAfterRunning = true; 782 | delayAfterRunStartTime = millis(); 783 | if (debug) Serial.println("Motor stopped"); 784 | digitalWrite(motor1Pin1, LOW); 785 | digitalWrite(motor1Pin2, LOW); 786 | digitalWrite(redLight, LOW); 787 | digitalWrite(yellowLight, LOW); 788 | digitalWrite(greenLight, HIGH); 789 | addLogEntry("Motor stopped"); 790 | } 791 | 792 | // Delay after run logic 793 | if (delayAfterRunning && millis() - delayAfterRunStartTime >= delayAfterRun) { 794 | delayAfterRunning = false; 795 | if (debug) Serial.println("Delay after run complete"); 796 | addLogEntry("Delay after run complete"); 797 | } 798 | 799 | // Handle yellow light flashing based on MQTT/WiFi status 800 | if (motorWaiting) { 801 | digitalWrite(greenLight, LOW); 802 | if (currentMillis - yellowLightStartTime >= 500) { 803 | yellowLightStartTime = currentMillis; 804 | yellowLightState = !yellowLightState; 805 | digitalWrite(yellowLight, yellowLightState); 806 | } 807 | } 808 | 809 | if (!useMotionSensor && !client.connected()) { 810 | 811 | if (disconnectedTime == 0) { 812 | disconnectedTime = millis(); // Mark the time of disconnection 813 | } 814 | 815 | if (millis() - disconnectedTime >= 5000) { // Flash only if disconnected for 5+ seconds 816 | if (currentMillis - yellowLightStartTime >= 500) { 817 | yellowLightStartTime = currentMillis; 818 | yellowLightState = !yellowLightState; 819 | digitalWrite(yellowLight, yellowLightState); 820 | if (debug){ 821 | addLogEntry("MQTT disconnect detected, yellow light flashing due to 5s disconnection."); 822 | } 823 | } 824 | } 825 | 826 | if (millis() - lastAttemptTime >= RECONNECT_INTERVAL) { 827 | connectToMqtt(); 828 | lastAttemptTime = millis(); 829 | } 830 | } 831 | 832 | client.loop(); 833 | } --------------------------------------------------------------------------------