diff --git a/CYD-Klipper/.vscode/settings.json b/CYD-Klipper/.vscode/settings.json index 8be69ef..55d6dcc 100644 --- a/CYD-Klipper/.vscode/settings.json +++ b/CYD-Klipper/.vscode/settings.json @@ -13,7 +13,8 @@ "cstddef": "cpp", "functional": "cpp", "*.tcc": "cpp", - "cmath": "cpp" + "cmath": "cpp", + "system_error": "cpp" }, "cmake.configureOnOpen": false } \ 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 2f38c0c..7271847 100644 --- a/CYD-Klipper/src/core/klipper/klipper_printer_integration.cpp +++ b/CYD-Klipper/src/core/klipper/klipper_printer_integration.cpp @@ -2,8 +2,9 @@ #include "../../conf/global_config.h" #include #include +#include -void configure_http_client(HTTPClient &client, String url_part, bool stream, int timeout, PRINTER_CONFIG* printer) +void KlipperPrinter::configure_http_client(HTTPClient &client, String url_part, bool stream, int timeout) { if (stream){ client.useHTTP10(true); @@ -14,17 +15,36 @@ void configure_http_client(HTTPClient &client, String url_part, bool stream, int client.setConnectTimeout(timeout); } - client.begin("http://" + String(printer->klipper_host) + ":" + String(printer->klipper_port) + url_part); + client.begin("http://" + String(printer_config->klipper_host) + ":" + String(printer_config->klipper_port) + url_part); - if (printer->auth_configured) { - client.addHeader("X-Api-Key", printer->klipper_auth); + if (printer_config->auth_configured) { + client.addHeader("X-Api-Key", printer_config->klipper_auth); } } +int KlipperPrinter::get_slicer_time_estimate_s() +{ + if (printer_data.state != PrinterStatePrinting && printer_data.state != PrinterStatePaused) + return 0; + + HTTPClient client; + configure_http_client(client, "/server/files/metadata?filename=" + urlEncode(printer_data.print_filename), true, 5000); + int httpCode = client.GET(); + + if (httpCode != 200) + return 0; + + JsonDocument doc; + deserializeJson(doc, client.getStream()); + int time_estimate_s = doc["result"]["estimated_time"]; + LOG_F(("Got slicer time estimate: %ds\n", time_estimate_s)) + return time_estimate_s; +} + bool KlipperPrinter::send_gcode(const char *gcode, bool wait) { HTTPClient client; - configure_http_client(client, "/printer/gcode/script?script=" + urlEncode(gcode), false, wait ? 5000 : 750, printer_config); + configure_http_client(client, "/printer/gcode/script?script=" + urlEncode(gcode), false, wait ? 5000 : 750); LOG_F(("Sending gcode: %s\n", gcode)) try @@ -92,7 +112,7 @@ bool KlipperPrinter::execute_feature(PrinterFeatures feature) case PrinterFeatureEmergencyStop: LOG_LN("Sending estop"); send_gcode("M112", false); - configure_http_client(client, "/printer/emergency_stop", false, 5000, printer_config); + configure_http_client(client, "/printer/emergency_stop", false, 5000); try { @@ -116,7 +136,7 @@ bool KlipperPrinter::execute_feature(PrinterFeatures feature) } else { - return send_gcode("M83") && send_gcode("G1 E25 F300"); + return send_gcode("M83\nG1 E25 F300"); } case PrinterFeatureRetract: if (printer_data.state == PrinterStatePrinting) @@ -130,10 +150,10 @@ bool KlipperPrinter::execute_feature(PrinterFeatures feature) } else { - return send_gcode("M83") && send_gcode("G1 E-25 F300"); + return send_gcode("M83\nG1 E-25 F300"); } case PrinterFeatureCooldown: - return send_gcode("M104 S0") && send_gcode("M140 S0"); + return send_gcode("M104 S0\nM140 S0"); default: LOG_F(("Unsupported printer feature %d", feature)); return false; @@ -142,7 +162,301 @@ bool KlipperPrinter::execute_feature(PrinterFeatures feature) bool KlipperPrinter::connect() { - // Pass + HTTPClient client; + configure_http_client(client, "/printer/info", false, 1000); + + int httpCode; + try { + httpCode = client.GET(); + return httpCode == 200; + } + catch (...) { + LOG_LN("Failed to connect"); + return false; + } } +bool KlipperPrinter::fetch() +{ + HTTPClient client; + configure_http_client(client, "/printer/objects/query?extruder&heater_bed&toolhead&gcode_move&virtual_sdcard&print_stats&webhooks&fan&display_status", true, 1000); + + int httpCode = client.GET(); + if (httpCode == 200) + { + if (printer_data.state == PrinterStateOffline) + { + printer_data.state = PrinterStateError; + } + + klipper_request_consecutive_fail_count = 0; + JsonDocument doc; + deserializeJson(doc, client.getStream()); + auto status = doc["result"]["status"]; + bool emit_state_update = false; + + if (status.containsKey("webhooks")) + { + const char *state = status["webhooks"]["state"]; + const char *message = status["webhooks"]["state_message"]; + + if (strcmp(state, "ready") == 0 && printer_data.state == PrinterStateError) + { + printer_data.state = PrinterStateIdle; + } + else if ((strcmp(state, "shutdown") == 0 || strcmp(state, "error") == 0) && printer_data.state != PrinterStateError) + { + printer_data.state = PrinterStateError; + } + + if (printer_data.state_message == NULL || strcmp(printer_data.state_message, message)) + { + if (printer_data.state_message != NULL) + { + free(printer_data.state_message); + } + + printer_data.state_message = (char *)malloc(strlen(message) + 1); + strcpy(printer_data.state_message, message); + } + } + + if (printer_data.state != PrinterStateError) + { + if (status.containsKey("extruder")) + { + printer_data.temperatures[PrinterTemperatureDeviceIndexNozzle1] = status["extruder"]["temperature"]; + printer_data.target_temperatures[PrinterTemperatureDeviceIndexNozzle1] = status["extruder"]["target"]; + bool can_extrude = status["extruder"]["can_extrude"]; + printer_data.pressure_advance = status["extruder"]["pressure_advance"]; + printer_data.smooth_time = status["extruder"]["smooth_time"]; + printer_data.can_extrude = can_extrude == true; + } + + if (status.containsKey("heater_bed")) + { + printer_data.temperatures[PrinterTemperatureDeviceIndexBed] = status["heater_bed"]["temperature"]; + printer_data.target_temperatures[PrinterTemperatureDeviceIndexBed] = status["heater_bed"]["target"]; + } + + if (status.containsKey("toolhead")) + { + const char *homed_axis = status["toolhead"]["homed_axes"]; + printer_data.homed_axis = strcmp(homed_axis, "xyz") == 0; + } + + if (status.containsKey("gcode_move")) + { + printer_data.position[0] = status["gcode_move"]["gcode_position"][0]; + printer_data.position[1] = status["gcode_move"]["gcode_position"][1]; + printer_data.position[2] = status["gcode_move"]["gcode_position"][2]; + gcode_offset[0] = status["gcode_move"]["homing_origin"][0]; + gcode_offset[1] = status["gcode_move"]["homing_origin"][1]; + gcode_offset[2] = status["gcode_move"]["homing_origin"][2]; + bool absolute_coords = status["gcode_move"]["absolute_coordinates"]; + + if (lock_absolute_relative_mode_swap > 0) + { + lock_absolute_relative_mode_swap--; + } + else + { + printer_data.absolute_coords = absolute_coords == true; + } + + printer_data.speed_mult = status["gcode_move"]["speed_factor"]; + printer_data.extrude_mult = status["gcode_move"]["extrude_factor"]; + printer_data.feedrate_mm_per_s = status["gcode_move"]["speed"]; + printer_data.feedrate_mm_per_s /= 60; // convert mm/m to mm/s + } + + if (status.containsKey("fan")) + { + printer_data.fan_speed = status["fan"]["speed"]; + } + + if (status.containsKey("virtual_sdcard")) + { + printer_data.print_progress = status["virtual_sdcard"]["progress"]; + } + + if (status.containsKey("print_stats")) + { + const char *filename = status["print_stats"]["filename"]; + + if (filename != NULL && (printer_data.print_filename == NULL || strcmp(printer_data.print_filename, filename))) + { + if (printer_data.print_filename != NULL) + { + free(printer_data.print_filename); + } + + printer_data.print_filename = (char *)malloc(strlen(filename) + 1); + strcpy(printer_data.print_filename, filename); + } + + printer_data.elapsed_time_s = status["print_stats"]["total_duration"]; + printer_data.printed_time_s = status["print_stats"]["print_duration"]; + printer_data.filament_used_mm = status["print_stats"]["filament_used"]; + printer_data.total_layers = status["print_stats"]["info"]["total_layer"]; + printer_data.current_layer = status["print_stats"]["info"]["current_layer"]; + + const char *state = status["print_stats"]["state"]; + if (state == nullptr) + { + // Continue + } + else if (strcmp(state, "printing") == 0) + { + printer_data.state = PrinterStatePrinting; + } + else if (strcmp(state, "paused") == 0) + { + printer_data.state = PrinterStatePaused; + } + else if (strcmp(state, "complete") == 0 || strcmp(state, "cancelled") == 0 || strcmp(state, "standby") == 0) + { + printer_data.state = PrinterStateIdle; + } + } + + if (status.containsKey("display_status")) + { + printer_data.print_progress = status["display_status"]["progress"]; + const char* message = status["display_status"]["message"]; + store_available_popup_message(message); + } + + if (printer_data.state == PrinterStatePrinting && printer_data.print_progress > 0) + { + float remaining_time_s_percentage = (printer_data.printed_time_s / printer_data.print_progress) - printer_data.printed_time_s; + float remaining_time_s_slicer = 0; + + if (slicer_estimated_print_time_s > 0) + { + remaining_time_s_slicer = slicer_estimated_print_time_s - printer_data.printed_time_s; + } + + if (remaining_time_s_slicer <= 0 || printer_config->remaining_time_calc_mode == REMAINING_TIME_CALC_PERCENTAGE) + { + printer_data.remaining_time_s = remaining_time_s_percentage; + } + else if (printer_config->remaining_time_calc_mode == REMAINING_TIME_CALC_INTERPOLATED) + { + printer_data.remaining_time_s = remaining_time_s_percentage * printer_data.print_progress + remaining_time_s_slicer * (1 - printer_data.print_progress); + } + else if (printer_config->remaining_time_calc_mode == REMAINING_TIME_CALC_SLICER) + { + printer_data.remaining_time_s = remaining_time_s_slicer; + } + } + + if (printer_data.remaining_time_s < 0) + { + printer_data.remaining_time_s = 0; + } + + if (printer_data.state == PrinterStateIdle) + { + slicer_estimated_print_time_s = 0; + } + } + + if (printer_data.state == PrinterStatePrinting && millis() - last_slicer_time_query > 30000 && slicer_estimated_print_time_s <= 0) + { + last_slicer_time_query = millis(); + slicer_estimated_print_time_s = get_slicer_time_estimate_s(); + } + } + else + { + klipper_request_consecutive_fail_count++; + LOG_F(("Failed to fetch printer data: %d\n", httpCode)); + + if (klipper_request_consecutive_fail_count >= 5) + { + printer_data.state = PrinterStateOffline; + return false; + } + } + + return true; +} + +int KlipperPrinter::get_power_devices_count() +{ + return 0; +} + +PrinterDataMinimal KlipperPrinter::fetch_min() +{ + PrinterDataMinimal data = {0}; + + if (!printer_config->ip_configured) + { + data.state = PrinterStateOffline; + return data; + } + + data.success = true; + + HTTPClient client; + configure_http_client(client, "/printer/objects/query?webhooks&print_stats&virtual_sdcard", true, 1000); + + int httpCode = client.GET(); + if (httpCode == 200) + { + data.state = PrinterStateIdle; + data.power_devices = get_power_devices_count(); + + JsonDocument doc; + deserializeJson(doc, client.getStream()); + auto status = doc["result"]["status"]; + + if (status.containsKey("webhooks")) + { + const char *state = status["webhooks"]["state"]; + + if (strcmp(state, "shutdown") == 0) + { + data.state = PrinterStateError; + } + } + + if (data.state != PrinterStateError) + { + if (status.containsKey("virtual_sdcard")) + { + data.print_progress = status["virtual_sdcard"]["progress"]; + } + + if (status.containsKey("print_stats")) + { + const char *state = status["print_stats"]["state"]; + + if (state == nullptr) + { + data.state = PrinterStateError; + } + else if (strcmp(state, "printing") == 0) + { + data.state = PrinterStatePrinting; + } + else if (strcmp(state, "paused") == 0) + { + data.state = PrinterStatePaused; + } + else if (strcmp(state, "complete") == 0 || strcmp(state, "cancelled") == 0 || strcmp(state, "standby") == 0) + { + data.state = PrinterStateIdle; + } + } + } + } + else + { + data.state = PrinterStateOffline; + data.power_devices = get_power_devices_count(); + } +} \ No newline at end of file diff --git a/CYD-Klipper/src/core/klipper/klipper_printer_integration.hpp b/CYD-Klipper/src/core/klipper/klipper_printer_integration.hpp index ea299ef..8849830 100644 --- a/CYD-Klipper/src/core/klipper/klipper_printer_integration.hpp +++ b/CYD-Klipper/src/core/klipper/klipper_printer_integration.hpp @@ -6,6 +6,10 @@ class KlipperPrinter : 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{}; + float gcode_offset[3]{}; public: KlipperPrinter(int index) : BasePrinter(index) @@ -26,19 +30,22 @@ class KlipperPrinter : BasePrinter | PrinterTemperatureDeviceNozzle1; } - bool move_printer(const char* axis, float amount, bool relative) = 0; - bool execute_feature(PrinterFeatures feature) = 0; - bool connect() = 0; - bool fetch(PrinterData& data) = 0; - void commit_fetch(PrinterData& data) = 0; - bool fetch_min(PrinterDataMinimal& data) = 0; - void disconnect() = 0; - bool get_macros(Macros& macros) = 0; - bool execute_macro(const char* macro) = 0; - bool get_power_devices(PowerDevices& power_devices) = 0; - bool set_power_device_state(const char* device_name, bool state) = 0; - bool get_files(Files& files) = 0; - bool start_file(const char* file) = 0; - bool set_target_temperature(PrinterTemperatureDevice device, float temperature) = 0; + bool move_printer(const char* axis, float amount, bool relative); + bool execute_feature(PrinterFeatures feature); + bool connect(); + bool fetch(); + PrinterDataMinimal fetch_min(); + void disconnect(); + Macros get_macros(); + 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* file); + bool set_target_temperature(PrinterTemperatureDevice device, float temperature); bool send_gcode(const char* gcode, bool wait = true); + int get_slicer_time_estimate_s(); + void configure_http_client(HTTPClient &client, String url_part, bool stream, int timeout); }; \ No newline at end of file diff --git a/CYD-Klipper/src/core/printer_integration.cpp b/CYD-Klipper/src/core/printer_integration.cpp index e69de29..a5afdec 100644 --- a/CYD-Klipper/src/core/printer_integration.cpp +++ b/CYD-Klipper/src/core/printer_integration.cpp @@ -0,0 +1,96 @@ +#include "printer_integration.hpp" + +unsigned char current_printer_index = 0; +BasePrinter* registered_printers; + +PrinterData* printer_data_copy; +char* state_message_copy; +char* print_filename_copy; +char* popup_message_copy = NULL; + +bool available_data_message; +bool available_state_message; +bool available_popup_message; + +BasePrinter::BasePrinter(unsigned char index) + { + config_index = index; + // TODO: Fetch printer config and global config +} + +PrinterData* BasePrinter::CopyPrinterData() +{ + available_data_message = true; + available_state_message = printer_data.state != printer_data_copy->state; + + memcpy(printer_data_copy, &printer_data, sizeof(PrinterData)); + printer_data_copy->state_message = state_message_copy; + printer_data_copy->print_filename = print_filename_copy; + strcpy(state_message_copy, printer_data.state_message); + strcpy(print_filename_copy, printer_data.print_filename); +} + +void initialize_printers() +{ + printer_data_copy = (PrinterData*)malloc(sizeof(PrinterData)); + state_message_copy = (char*)malloc(256); + print_filename_copy = (char*)malloc(256); +} + +BasePrinter* get_current_printer() +{ + return get_printer(current_printer_index); +} + +BasePrinter* get_printer(int idx) +{ + return registered_printers + idx; +} + +void store_available_popup_message(const char *message) +{ + if (message != NULL && (!get_current_printer()->global_config->disable_m117_messaging) && (popup_message_copy == NULL || strcmp(popup_message_copy, message))) + { + if (popup_message_copy != NULL) + { + free(popup_message_copy); + } + + popup_message_copy = (char*)malloc(strlen(message) + 1); + strcpy(popup_message_copy, message); + available_popup_message = true; + } +} + +#define DATA_PRINTER_STATE 1 +#define DATA_PRINTER_DATA 2 +#define DATA_PRINTER_TEMP_PRESET 3 +#define DATA_PRINTER_MINIMAL 4 +#define DATA_PRINTER_POPUP 5 + +void send_available_popup_message() +{ + if (available_popup_message) + { + available_data_message = false; + lv_msg_send(DATA_PRINTER_POPUP, popup_message_copy); + } +} + +void send_available_data_message() +{ + if (available_data_message) + { + available_data_message = false; + lv_msg_send(DATA_PRINTER_DATA, get_current_printer()); + } +} + +void send_available_state_message() +{ + if (available_state_message) + { + available_state_message = false; + lv_msg_send(DATA_PRINTER_STATE, get_current_printer()); + } +} \ No newline at end of file diff --git a/CYD-Klipper/src/core/printer_integration.hpp b/CYD-Klipper/src/core/printer_integration.hpp index 7bd233b..54ee345 100644 --- a/CYD-Klipper/src/core/printer_integration.hpp +++ b/CYD-Klipper/src/core/printer_integration.hpp @@ -39,6 +39,20 @@ enum PrinterTemperatureDevice PrinterTemperatureDeviceChamber = BIT(9), }; +enum PrinterTemperatureDeviceIndex +{ + PrinterTemperatureDeviceIndexBed = 0, + PrinterTemperatureDeviceIndexNozzle1 = 1, + PrinterTemperatureDeviceIndexNozzle2 = 2, + PrinterTemperatureDeviceIndexNozzle3 = 3, + PrinterTemperatureDeviceIndexNozzle4 = 4, + PrinterTemperatureDeviceIndexNozzle5 = 5, + PrinterTemperatureDeviceIndexNozzle6 = 6, + PrinterTemperatureDeviceIndexNozzle7 = 7, + PrinterTemperatureDeviceIndexNozzle8 = 8, + PrinterTemperatureDeviceIndexChamber = 9, +}; + inline PrinterTemperatureDevice operator|(PrinterTemperatureDevice a, PrinterTemperatureDevice b) { return static_cast(static_cast(a) | static_cast(b)); @@ -83,26 +97,30 @@ typedef struct _PrinterData { int feedrate_mm_per_s; } PrinterData; -typedef struct _PrinterDataMinimal { +typedef struct { unsigned char state; float print_progress; // 0 -> 1 unsigned int power_devices; + bool success; } PrinterDataMinimal; typedef struct { const char** macros; unsigned int count; + bool success; } Macros; typedef struct { const char** power_devices; const bool* power_states; unsigned int count; + bool success; } PowerDevices; typedef struct { const char** available_files; unsigned int count; + bool success; } Files; typedef struct { @@ -127,24 +145,28 @@ class BasePrinter virtual bool move_printer(const char* axis, float amount, bool relative) = 0; virtual bool execute_feature(PrinterFeatures feature) = 0; virtual bool connect() = 0; - virtual bool fetch(PrinterData& data) = 0; - virtual void commit_fetch(PrinterData& data) = 0; - virtual bool fetch_min(PrinterDataMinimal& data) = 0; + virtual bool fetch() = 0; + virtual PrinterDataMinimal fetch_min() = 0; virtual void disconnect() = 0; - virtual bool get_macros(Macros& macros) = 0; + virtual Macros get_macros() = 0; + virtual int get_macros_count() = 0; virtual bool execute_macro(const char* macro) = 0; - virtual bool get_power_devices(PowerDevices& power_devices) = 0; + virtual PowerDevices get_power_devices() = 0; + virtual int get_power_devices_count() = 0; virtual bool set_power_device_state(const char* device_name, bool state) = 0; - virtual bool get_files(Files& files) = 0; + virtual Files get_files() = 0; virtual bool start_file(const char* file) = 0; virtual bool set_target_temperature(PrinterTemperatureDevice device, float temperature) = 0; - BasePrinter(unsigned char index) { - config_index = index; - // TODO: Fetch printer config and global config - } + BasePrinter(unsigned char index); + PrinterData* CopyPrinterData(); }; BasePrinter* get_current_printer(); BasePrinter* get_printer(int idx); -void initialize_printer(); \ No newline at end of file +void initialize_printers(); + +void store_available_popup_message(const char *message); +void send_available_popup_message(); +void send_available_data_message(); +void send_available_state_message(); \ No newline at end of file