From d4ce7a71ba7f3fa72d2a464775b0c7233c09921f Mon Sep 17 00:00:00 2001 From: Sims <38142618+suchmememanyskill@users.noreply.github.com> Date: Fri, 8 Nov 2024 18:07:32 +0100 Subject: [PATCH] Klipper connection over serial --- .../src/core/bambu/bambu_printer_parsers.cpp | 1 + .../serial_klipper_printer_integration.cpp | 235 ++++++++++++++++++ .../serial_klipper_printer_integration.hpp | 23 ++ .../klipper/klipper_printer_integration.cpp | 2 +- .../klipper/klipper_printer_integration.hpp | 31 +-- .../core/klipper/klipper_printer_parsers.cpp | 20 +- 6 files changed, 284 insertions(+), 28 deletions(-) create mode 100644 CYD-Klipper/src/core/klipper-serial/serial_klipper_printer_integration.cpp create mode 100644 CYD-Klipper/src/core/klipper-serial/serial_klipper_printer_integration.hpp diff --git a/CYD-Klipper/src/core/bambu/bambu_printer_parsers.cpp b/CYD-Klipper/src/core/bambu/bambu_printer_parsers.cpp index 621cbf1..3add9df 100644 --- a/CYD-Klipper/src/core/bambu/bambu_printer_parsers.cpp +++ b/CYD-Klipper/src/core/bambu/bambu_printer_parsers.cpp @@ -301,6 +301,7 @@ bool send_command_without_response(WiFiClientSecure& client, const char* command return wifi_client_response_pass(client); } +// TODO: This isn't a 'pure' parser implementation. Remove network calls, only do parsing Files BambuPrinter::parse_files(WiFiClientSecure& wifi_client, int max_files) { LOG_F(("Heap space pre-file-parse: %d bytes\n", esp_get_free_heap_size())); 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 new file mode 100644 index 0000000..1543b4e --- /dev/null +++ b/CYD-Klipper/src/core/klipper-serial/serial_klipper_printer_integration.cpp @@ -0,0 +1,235 @@ +#include "serial_klipper_printer_integration.hpp" +#include +#include + +enum HttpRequestType +{ + HttpPost, + HttpGet +}; + +void clear_serial_buffer() +{ + while (Serial.available()) + { + Serial.read(); + }; +} + +// Request: {timeout} {method} {endpoint} +// Response: {status code} {body} +int make_serial_request(JsonDocument &out, int timeout_ms, HttpRequestType requestType, const char* endpoint) +{ + clear_serial_buffer(); + // TODO: Add semaphore here + if (!Serial.availableForWrite()) + { + return -1; + } + + char buff[10]; + sprintf(buff, "%d ", timeout_ms); + + // TODO: Maybe use printf? + Serial.write(buff); + Serial.write(requestType == HttpGet ? "GET" : "POST"); + Serial.write(' '); + Serial.write(endpoint); + Serial.write('\n'); + + if (timeout_ms <= 0) + { + return 200; + } + unsigned long _m = millis(); + while (!Serial.available() && millis() < _m + timeout_ms + 10) delay(1); + + if (!Serial.available()) + { + return -2; + } + + Serial.readBytes(buff, 4); + buff[3] = 0; + + if (buff[0] < '0' || buff[0] > '9') + { + clear_serial_buffer(); + return -3; + } + + int status_code = atoi(buff); + + if (status_code < 200 || status_code >= 300) + { + clear_serial_buffer(); + return -4; + } + + auto result = deserializeJson(out, Serial); + return result == DeserializationError::Ok; +} + +bool make_serial_request_nocontent(HttpRequestType requestType, const char* endpoint) +{ + JsonDocument doc; + make_serial_request(doc, 0, requestType, endpoint); + return true; +} + +bool SerialKlipperPrinter::connect() +{ + return connection_test_serial_klipper(printer_config) == KlipperConnectionStatus::ConnectOk; +} + +bool SerialKlipperPrinter::fetch() +{ + JsonDocument doc; + if (make_serial_request(doc, 1000, HttpGet, "/printer/objects/query?extruder&heater_bed&toolhead&gcode_move&virtual_sdcard&print_stats&webhooks&fan&display_status") == 200) + { + if (printer_data.state == PrinterStateOffline) + { + printer_data.state = PrinterStateError; + } + + klipper_request_consecutive_fail_count = 0; + parse_state(doc); + } + else + { + klipper_request_consecutive_fail_count++; + if (klipper_request_consecutive_fail_count >= 5) + { + printer_data.state = PrinterStateOffline; + return false; + } + } + + return true; +} + +PrinterDataMinimal SerialKlipperPrinter::fetch_min() +{ + JsonDocument doc; + PrinterDataMinimal data = {}; + data.success = false; + + if (!printer_config->setup_complete) + { + data.state = PrinterStateOffline; + return data; + } + + data.success = true; + + if (make_serial_request(doc, 1000, HttpGet, "/printer/objects/query?webhooks&print_stats&virtual_sdcard") == 200) + { + data.state = PrinterState::PrinterStateIdle; + parse_state_min(doc, &data); + doc.clear(); + data.power_devices = get_power_devices_count(); + } + else + { + data.state = PrinterState::PrinterStateOffline; + data.power_devices = get_power_devices_count(); + } + + return data; +} + +Macros SerialKlipperPrinter::get_macros() +{ + Macros macros = {0}; + JsonDocument doc; + if (make_serial_request(doc, 1000, HttpGet, "/printer/gcode/help") == 200) + { + return parse_macros(doc); + } + + return macros; +} + +int SerialKlipperPrinter::get_macros_count() +{ + JsonDocument doc; + if (make_serial_request(doc, 1000, HttpGet, "/printer/gcode/help") == 200) + { + return parse_macros_count(doc); + } + + return 0; +} + +PowerDevices SerialKlipperPrinter::get_power_devices() +{ + PowerDevices power_devices = {0}; + JsonDocument doc; + if (make_serial_request(doc, 1000, HttpGet, "/machine/device_power/devices") == 200) + { + return parse_power_devices(doc); + } + + return power_devices; +} + +int SerialKlipperPrinter::get_power_devices_count() +{ + JsonDocument doc; + if (make_serial_request(doc, 1000, HttpGet, "/machine/device_power/devices") == 200) + { + return parse_power_devices_count(doc); + } + + return 0; +} + +bool SerialKlipperPrinter::set_power_device_state(const char* device_name, bool state) +{ + JsonDocument doc; + String request = "/machine/device_power/device?device=" + urlEncode(device_name) + "&action=" + (state ? "on" : "off"); + return make_serial_request(doc, 1000, HttpPost, request.c_str()); +} + +Files SerialKlipperPrinter::get_files() +{ + // TODO: Stubbed + Files files = {0}; + files.success = false; + return files; +} + +bool SerialKlipperPrinter::start_file(const char* filename) +{ + JsonDocument doc; + String request = "/printer/print/start?filename=" + urlEncode(filename); + return make_serial_request(doc, 1000, HttpPost, request.c_str()); +} + +Thumbnail SerialKlipperPrinter::get_32_32_png_image_thumbnail(const char* gcode_filename) +{ + // TODO: Stubbed + Thumbnail thumbnail = {0}; + thumbnail.success = false; + return thumbnail; +} + +bool SerialKlipperPrinter::send_gcode(const char* gcode, bool wait) +{ + JsonDocument doc; + String request = "/printer/gcode/script?script=" + urlEncode(gcode); + return wait + ? make_serial_request(doc, 5000, HttpGet, request.c_str()) + : make_serial_request_nocontent(HttpGet, request.c_str()); +} + +KlipperConnectionStatus connection_test_serial_klipper(PrinterConfiguration* config) +{ + JsonDocument doc; + if (make_serial_request(doc, 1000, HttpGet, "/printer/info") != 200) + { + return KlipperConnectionStatus::ConnectOk; + } + + return KlipperConnectionStatus::ConnectFail; +} \ No newline at end of file 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 new file mode 100644 index 0000000..09e680b --- /dev/null +++ b/CYD-Klipper/src/core/klipper-serial/serial_klipper_printer_integration.hpp @@ -0,0 +1,23 @@ +#include "../klipper/klipper_printer_integration.hpp" + +class SerialKlipperPrinter : public KlipperPrinter +{ + public: + SerialKlipperPrinter(int index) : KlipperPrinter(index) + { + + } + + bool connect(); + bool fetch(); + PrinterDataMinimal fetch_min(); + Macros get_macros(); + int get_macros_count(); + PowerDevices get_power_devices(); + int get_power_devices_count(); + bool set_power_device_state(const char* device_name, bool state); + Files get_files(); + bool start_file(const char* filename); + Thumbnail get_32_32_png_image_thumbnail(const char* gcode_filename); + bool send_gcode(const char* gcode, bool wait = true); +}; \ No newline at end of file diff --git a/CYD-Klipper/src/core/klipper/klipper_printer_integration.cpp b/CYD-Klipper/src/core/klipper/klipper_printer_integration.cpp index c4e92b8..70c71cb 100644 --- a/CYD-Klipper/src/core/klipper/klipper_printer_integration.cpp +++ b/CYD-Klipper/src/core/klipper/klipper_printer_integration.cpp @@ -226,7 +226,7 @@ PrinterDataMinimal KlipperPrinter::fetch_min() JsonDocument doc; deserializeJson(doc, client.getStream()); - return parse_state_min(doc); + parse_state_min(doc, &data); } else { diff --git a/CYD-Klipper/src/core/klipper/klipper_printer_integration.hpp b/CYD-Klipper/src/core/klipper/klipper_printer_integration.hpp index 9187280..9309afd 100644 --- a/CYD-Klipper/src/core/klipper/klipper_printer_integration.hpp +++ b/CYD-Klipper/src/core/klipper/klipper_printer_integration.hpp @@ -13,20 +13,21 @@ typedef struct { class KlipperPrinter : public BasePrinter { private: - unsigned char lock_absolute_relative_mode_swap{}; - unsigned char klipper_request_consecutive_fail_count{}; unsigned int slicer_estimated_print_time_s{}; unsigned int last_slicer_time_query{}; void configure_http_client(HTTPClient &client, String url_part, bool stream, int timeout); protected: + unsigned char lock_absolute_relative_mode_swap{}; + unsigned char klipper_request_consecutive_fail_count{}; + bool send_emergency_stop(); int get_slicer_time_estimate_s(); void init_ui_panels(); int parse_slicer_time_estimate(JsonDocument& in); void parse_state(JsonDocument& in); - PrinterDataMinimal parse_state_min(JsonDocument& in); + void parse_state_min(JsonDocument &in, PrinterDataMinimal* data); Macros parse_macros(JsonDocument &in); int parse_macros_count(JsonDocument &in); PowerDevices parse_power_devices(JsonDocument &in); @@ -60,21 +61,21 @@ class KlipperPrinter : public BasePrinter bool move_printer(const char* axis, float amount, bool relative); bool execute_feature(PrinterFeatures feature); - bool connect(); - bool fetch(); - PrinterDataMinimal fetch_min(); + virtual bool connect(); + virtual bool fetch(); + virtual PrinterDataMinimal fetch_min(); void disconnect(); - Macros get_macros(); - int get_macros_count(); + virtual Macros get_macros(); + virtual int get_macros_count(); bool execute_macro(const char* macro); - PowerDevices get_power_devices(); - int get_power_devices_count(); - bool set_power_device_state(const char* device_name, bool state); - Files get_files(); - bool start_file(const char* filename); - Thumbnail get_32_32_png_image_thumbnail(const char* gcode_filename); + virtual PowerDevices get_power_devices(); + virtual int get_power_devices_count(); + virtual bool set_power_device_state(const char* device_name, bool state); + virtual Files get_files(); + virtual bool start_file(const char* filename); + virtual Thumbnail get_32_32_png_image_thumbnail(const char* gcode_filename); bool set_target_temperature(PrinterTemperatureDevice device, unsigned int temperature); - bool send_gcode(const char* gcode, bool wait = true); + virtual bool send_gcode(const char* gcode, bool wait = true); }; enum KlipperConnectionStatus { diff --git a/CYD-Klipper/src/core/klipper/klipper_printer_parsers.cpp b/CYD-Klipper/src/core/klipper/klipper_printer_parsers.cpp index eab6d02..391cd9f 100644 --- a/CYD-Klipper/src/core/klipper/klipper_printer_parsers.cpp +++ b/CYD-Klipper/src/core/klipper/klipper_printer_parsers.cpp @@ -183,11 +183,9 @@ void KlipperPrinter::parse_state(JsonDocument &in) } } -PrinterDataMinimal KlipperPrinter::parse_state_min(JsonDocument &in) +void KlipperPrinter::parse_state_min(JsonDocument &in, PrinterDataMinimal* data) { auto status = in["result"]["status"]; - PrinterDataMinimal data = {}; - data.success = true; if (status.containsKey("webhooks")) { @@ -195,15 +193,15 @@ PrinterDataMinimal KlipperPrinter::parse_state_min(JsonDocument &in) if (strcmp(state, "shutdown") == 0) { - data.state = PrinterState::PrinterStateError; + data->state = PrinterState::PrinterStateError; } } - if (data.state != PrinterStateError) + if (data->state != PrinterStateError) { if (status.containsKey("virtual_sdcard")) { - data.print_progress = status["virtual_sdcard"]["progress"]; + data->print_progress = status["virtual_sdcard"]["progress"]; } if (status.containsKey("print_stats")) @@ -212,24 +210,22 @@ PrinterDataMinimal KlipperPrinter::parse_state_min(JsonDocument &in) if (state == nullptr) { - data.state = PrinterState::PrinterStateError; + data->state = PrinterState::PrinterStateError; } else if (strcmp(state, "printing") == 0) { - data.state = PrinterState::PrinterStatePrinting; + data->state = PrinterState::PrinterStatePrinting; } else if (strcmp(state, "paused") == 0) { - data.state = PrinterState::PrinterStatePaused; + data->state = PrinterState::PrinterStatePaused; } else if (strcmp(state, "complete") == 0 || strcmp(state, "cancelled") == 0 || strcmp(state, "standby") == 0) { - data.state = PrinterState::PrinterStateIdle; + data->state = PrinterState::PrinterStateIdle; } } } - - return data; } Macros KlipperPrinter::parse_macros(JsonDocument &in)