From f9444829ee8eaf6fc952ee760e99bb8ce0645bcb Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Sat, 9 Nov 2024 01:30:40 +0100 Subject: [PATCH] Gcode images, fixes to serial --- .gitignore | 1 + .../serial_klipper_printer_integration.cpp | 133 +++++++++++++++++- .../serial_klipper_printer_integration.hpp | 3 + .../klipper/klipper_printer_integration.cpp | 2 +- .../klipper/klipper_printer_integration.hpp | 4 +- serial.py => serial_server.py | 20 ++- 6 files changed, 151 insertions(+), 12 deletions(-) rename serial.py => serial_server.py (72%) diff --git a/.gitignore b/.gitignore index ab1e7fe..48ddf06 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ out/ lib lib64 +__pycache__/ \ No newline at end of file diff --git a/CYD-Klipper/src/core/klipper-serial/serial_klipper_printer_integration.cpp b/CYD-Klipper/src/core/klipper-serial/serial_klipper_printer_integration.cpp index 0e2c181..7100db0 100644 --- a/CYD-Klipper/src/core/klipper-serial/serial_klipper_printer_integration.cpp +++ b/CYD-Klipper/src/core/klipper-serial/serial_klipper_printer_integration.cpp @@ -110,6 +110,83 @@ bool make_serial_request(JsonDocument &out, int timeout_ms, HttpRequestType requ return success; } +typedef struct +{ + int len; + unsigned char* data; +} BinaryResponse; + +bool make_binary_request(BinaryResponse* data, int timeout_ms, HttpRequestType requestType, const char* endpoint) +{ + semaphore_init_serial(); + freeze_serial_thread(); + serial_console::global_disable_serial_console = true; + temporary_config.debug = false; + char buff[10]; + clear_serial_buffer(); + + // TODO: Add semaphore here + if (!Serial.availableForWrite() || timeout_ms <= 0) + { + unfreeze_serial_thread(); + return false; + } + + Serial.printf("HTTP_BINARY %d %s %s\n", timeout_ms, requestType == HttpGet ? "GET" : "POST", endpoint); + + unsigned long _m = millis(); + while (!Serial.available() && millis() < _m + timeout_ms + 10) delay(1); + + if (!Serial.available()) + { + Serial.println("Timeout..."); + unfreeze_serial_thread(); + return false; + } + + Serial.readBytes(buff, 8); + buff[9] = 0; + + if (buff[0] < '0' || buff[0] > '9') + { + Serial.println("Invalid length"); + clear_serial_buffer(); + unfreeze_serial_thread(); + return false; + } + + int data_length = atoi(buff); + + if (data_length <= 0) + { + Serial.println("0 Length"); + clear_serial_buffer(); + unfreeze_serial_thread(); + return false; + } + + data->len = data_length; + data->data = (unsigned char*)malloc(data_length); + + if (data->data == NULL) + { + Serial.println("Failed to allocate memory"); + clear_serial_buffer(); + unfreeze_serial_thread(); + return false; + } + + bool result = Serial.readBytes((char*)data->data, data_length) == data_length; + unfreeze_serial_thread(); + + if (!result) + { + free(data->data); + } + + return result; +} + bool make_serial_request_nocontent(HttpRequestType requestType, const char* endpoint) { JsonDocument doc; @@ -254,7 +331,7 @@ Files SerialKlipperPrinter::get_files() { return files_result; } - + parse_file_list(doc, files, 20); unfreeze_serial_thread(); @@ -285,14 +362,38 @@ bool SerialKlipperPrinter::start_file(const char* filename) { JsonDocument doc; String request = "/printer/print/start?filename=" + urlEncode(filename); - return make_serial_request_nocontent(HttpGet, request.c_str());; + return make_serial_request_nocontent(HttpPost, request.c_str());; } Thumbnail SerialKlipperPrinter::get_32_32_png_image_thumbnail(const char* gcode_filename) { - // TODO: Stubbed Thumbnail thumbnail = {0}; - thumbnail.success = false; + JsonDocument doc; + char* img_filename_path = NULL; + + String request = "/server/files/thumbnails?filename=" + urlEncode(gcode_filename); + if (make_serial_request(doc, 1000, HttpGet, request.c_str())) + { + img_filename_path = parse_thumbnails(doc); + unfreeze_serial_thread(); + doc.clear(); + } + + if (img_filename_path == NULL) + { + return thumbnail; + } + + request = "/server/files/gcodes/" + urlEncode(img_filename_path); + BinaryResponse data = {0}; + if (make_binary_request(&data, 2000, HttpGet, request.c_str())) + { + thumbnail.png = data.data; + thumbnail.size = data.len; + thumbnail.success = true; + } + + free(img_filename_path); return thumbnail; } @@ -311,6 +412,30 @@ bool SerialKlipperPrinter::send_gcode(const char* gcode, bool wait) return result; } +bool SerialKlipperPrinter::send_emergency_stop() +{ + return make_serial_request_nocontent(HttpGet, "/printer/emergency_stop"); +} + +int SerialKlipperPrinter::get_slicer_time_estimate_s() +{ + if (printer_data.state != PrinterStatePrinting && printer_data.state != PrinterStatePaused) + return 0; + + String request = "/server/files/metadata?filename=" + urlEncode(printer_data.print_filename); + JsonDocument doc; + + if (!make_serial_request(doc, 2000, HttpGet, request.c_str())) + { + return 0; + } + + int estimate = parse_slicer_time_estimate(doc); + unfreeze_serial_thread(); + + return estimate; +} + KlipperConnectionStatus connection_test_serial_klipper(PrinterConfiguration* config) { serial_console::global_disable_serial_console = true; diff --git a/CYD-Klipper/src/core/klipper-serial/serial_klipper_printer_integration.hpp b/CYD-Klipper/src/core/klipper-serial/serial_klipper_printer_integration.hpp index 022de5b..7d50ac4 100644 --- a/CYD-Klipper/src/core/klipper-serial/serial_klipper_printer_integration.hpp +++ b/CYD-Klipper/src/core/klipper-serial/serial_klipper_printer_integration.hpp @@ -2,6 +2,9 @@ class SerialKlipperPrinter : public KlipperPrinter { + protected: + bool send_emergency_stop(); + int get_slicer_time_estimate_s(); public: SerialKlipperPrinter(int index) : KlipperPrinter(index) {} diff --git a/CYD-Klipper/src/core/klipper/klipper_printer_integration.cpp b/CYD-Klipper/src/core/klipper/klipper_printer_integration.cpp index 70c71cb..7279453 100644 --- a/CYD-Klipper/src/core/klipper/klipper_printer_integration.cpp +++ b/CYD-Klipper/src/core/klipper/klipper_printer_integration.cpp @@ -147,7 +147,7 @@ bool KlipperPrinter::execute_feature(PrinterFeatures feature) return false; } - if (get_current_printer()->printer_config->custom_filament_move_macros) + if (printer_config->custom_filament_move_macros) { return send_gcode("FILAMENT_RETRACT"); } diff --git a/CYD-Klipper/src/core/klipper/klipper_printer_integration.hpp b/CYD-Klipper/src/core/klipper/klipper_printer_integration.hpp index 9309afd..3487029 100644 --- a/CYD-Klipper/src/core/klipper/klipper_printer_integration.hpp +++ b/CYD-Klipper/src/core/klipper/klipper_printer_integration.hpp @@ -21,8 +21,8 @@ class KlipperPrinter : public BasePrinter unsigned char lock_absolute_relative_mode_swap{}; unsigned char klipper_request_consecutive_fail_count{}; - bool send_emergency_stop(); - int get_slicer_time_estimate_s(); + virtual bool send_emergency_stop(); + virtual int get_slicer_time_estimate_s(); void init_ui_panels(); int parse_slicer_time_estimate(JsonDocument& in); diff --git a/serial.py b/serial_server.py similarity index 72% rename from serial.py rename to serial_server.py index 3b449e8..d75f6ae 100644 --- a/serial.py +++ b/serial_server.py @@ -30,7 +30,7 @@ def main(): # Read a line from the serial port if ser.in_waiting > 0: line = ser.readline().decode('utf-8').strip() - if line.startswith("HTTP_REQUEST"): + if line.startswith("HTTP_REQUEST") or line.startswith("HTTP_BINARY"): print(f">>> {line}") # Parse the parameters try: @@ -38,6 +38,7 @@ def main(): timeout_ms, request_type, url_path = int(parts[1]), parts[2], parts[3] ignore_timeout = timeout_ms <= 0 + binary = line.startswith("HTTP_BINARY") if ignore_timeout: timeout_ms = 1000; @@ -57,10 +58,19 @@ def main(): # Send response back over serial if response != None: - status_code = response.status_code - body = response.text.replace('\n', ' ') # Trim and sanitize body for serial - message = f"{status_code} {body}" - write(message, not ignore_timeout) + if binary: + if response.status_code != 200: + write("00000000", not ignore_timeout) + else: + length = len(response.content) + ser.write(f"{length:>08}".encode('utf-8')) + ser.write(response.content) + print(f"<<< (Binary data of {length} bytes)") + else: + status_code = response.status_code + body = response.text.replace('\n', ' ') # Trim and sanitize body for serial + message = f"{status_code} {body}" + write(message, not ignore_timeout) except (IndexError, ValueError) as e: write(f"400 Malformed request {str(e)}", not ignore_timeout) except requests.exceptions.ReadTimeout as e: