From 2078a1541d2e3f0b1ed7267836454ffc55547794 Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Wed, 23 Oct 2024 23:15:43 +0200 Subject: [PATCH] More work --- CYD-Klipper/.vscode/settings.json | 3 +- .../klipper/klipper_printer_integration.cpp | 380 +++++++++++++++++- .../klipper/klipper_printer_integration.hpp | 3 +- CYD-Klipper/src/core/printer_integration.hpp | 13 +- CYD-Klipper/src/ui/ui_utils.cpp | 11 + CYD-Klipper/src/ui/ui_utils.h | 1 + 6 files changed, 381 insertions(+), 30 deletions(-) diff --git a/CYD-Klipper/.vscode/settings.json b/CYD-Klipper/.vscode/settings.json index 55d6dcc..0ddaeec 100644 --- a/CYD-Klipper/.vscode/settings.json +++ b/CYD-Klipper/.vscode/settings.json @@ -14,7 +14,8 @@ "functional": "cpp", "*.tcc": "cpp", "cmath": "cpp", - "system_error": "cpp" + "system_error": "cpp", + "random": "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 7ecbcf4..11e15f5 100644 --- a/CYD-Klipper/src/core/klipper/klipper_printer_integration.cpp +++ b/CYD-Klipper/src/core/klipper/klipper_printer_integration.cpp @@ -3,12 +3,11 @@ #include #include #include +#include void KlipperPrinter::configure_http_client(HTTPClient &client, String url_part, bool stream, int timeout) { - if (stream){ - client.useHTTP10(true); - } + client.useHTTP10(stream); if (timeout > 0){ client.setTimeout(timeout); @@ -29,9 +28,9 @@ int KlipperPrinter::get_slicer_time_estimate_s() HTTPClient client; configure_http_client(client, "/server/files/metadata?filename=" + urlEncode(printer_data.print_filename), true, 5000); - int httpCode = client.GET(); + int http_code = client.GET(); - if (httpCode != 200) + if (http_code != 200) return 0; JsonDocument doc; @@ -165,10 +164,10 @@ bool KlipperPrinter::connect() HTTPClient client; configure_http_client(client, "/printer/info", false, 1000); - int httpCode; + int http_code; try { - httpCode = client.GET(); - return httpCode == 200; + http_code = client.GET(); + return http_code == 200; } catch (...) { LOG_LN("Failed to connect"); @@ -181,8 +180,8 @@ 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) + int http_code = client.GET(); + if (http_code == 200) { if (printer_data.state == PrinterStateOffline) { @@ -367,7 +366,7 @@ bool KlipperPrinter::fetch() else { klipper_request_consecutive_fail_count++; - LOG_F(("Failed to fetch printer data: %d\n", httpCode)); + LOG_F(("Failed to fetch printer data: %d\n", http_code)); if (klipper_request_consecutive_fail_count >= 5) { @@ -399,8 +398,8 @@ PrinterDataMinimal KlipperPrinter::fetch_min() HTTPClient client; configure_http_client(client, "/printer/objects/query?webhooks&print_stats&virtual_sdcard", true, 1000); - int httpCode = client.GET(); - if (httpCode == 200) + int http_code = client.GET(); + if (http_code == 200) { data.state = PrinterStateIdle; data.power_devices = get_power_devices_count(); @@ -459,19 +458,356 @@ PrinterDataMinimal KlipperPrinter::fetch_min() void KlipperPrinter::disconnect() { // Nothing to disconnect, everything is http request based +} - if (printer_data.state_message != NULL) - { - free(printer_data.state_message); +Macros KlipperPrinter::get_macros() +{ + HTTPClient client; + Macros macros; + + configure_http_client(client, "/printer/gcode/help", true, 1000); + int http_code = client.GET(); + + if (http_code == 200){ + JsonDocument doc; + deserializeJson(doc, client.getStream()); + auto result = doc["result"].as(); + macros.macros = (char**)malloc(sizeof(char*) * 32); + macros.count = 0; + macros.success = true; + + for (JsonPair i : result){ + const char *key = i.key().c_str(); + const char *value = i.value().as().c_str(); + if (strcmp(value, "CYD_SCREEN_MACRO") == 0) { + char* macro = (char*)malloc(strlen(key) + 1); + strcpy(macro, key); + macros.macros[macros.count++] = macro; + } + } + + if (global_config->sort_macros) + { + std::sort(macros.macros, macros.macros + macros.count, [](const char* a, const char* b) { + return strcmp(a, b) < 0; + }); + } } - if (printer_data.print_filename != NULL) - { - free(printer_data.print_filename); + return macros; +} + +int KlipperPrinter::get_macros_count() +{ + HTTPClient client; + configure_http_client(client, "/printer/gcode/help", true, 1000); + + int http_code = client.GET(); + + if (http_code == 200){ + JsonDocument doc; + deserializeJson(doc, client.getStream()); + auto result = doc["result"].as(); + + int count = 0; + + for (JsonPair i : result){ + const char *value = i.value().as().c_str(); + if (strcmp(value, "CYD_SCREEN_MACRO") == 0) { + count++; + } + } + + return count; + } + else { + return 0; + } +} + +bool KlipperPrinter::execute_macro(const char* macro) +{ + return send_gcode(macro); +} + +PowerDevices KlipperPrinter::get_power_devices() +{ + HTTPClient client; + PowerDevices power_devices; + configure_http_client(client, "/machine/device_power/devices", true, 1000); + + int http_code = client.GET(); + + if (http_code == 200){ + JsonDocument doc; + deserializeJson(doc, client.getStream()); + auto result = doc["result"]["devices"].as(); + power_devices.power_devices = (char**)malloc(sizeof(char*) * 16); + power_devices.power_states = (bool*)malloc(sizeof(bool) * 16); + power_devices.count = 0; + power_devices.success = true; + + for (auto i : result){ + const char * device_name = i["device"]; + const char * device_state = i["status"]; + power_devices.power_devices[power_devices.count] = (char*)malloc(strlen(device_name) + 1); + strcpy(power_devices.power_devices[power_devices.count], device_name); + power_devices.power_states[power_devices.count] = strcmp(device_state, "on") == 0; + power_devices.count++; + } } - if (printer_data.popup_message != NULL) - { - free(printer_data.popup_message); + return power_devices; +} + +int KlipperPrinter::get_power_devices_count() +{ + HTTPClient client; + configure_http_client(client, "/machine/device_power/devices", true, 1000); + + int http_code = client.GET(); + + if (http_code == 200){ + JsonDocument doc; + deserializeJson(doc, client.getStream()); + auto result = doc["result"]["devices"].as(); + + int count = 0; + + for (auto i : result){ + count++; + } + + return count; } + else { + return 0; + } +} + +bool KlipperPrinter::set_power_device_state(const char* device_name, bool state) +{ + HTTPClient client; + configure_http_client(client, "/machine/device_power/device?device=" + urlEncode(device_name) + "&action=" + (state ? "on" : "off"), true, 1000); + return client.POST("") == 200; +} + +typedef struct { + char* name; + float modified; +} FileSystemFile; + +#define KLIPPER_FILE_FETCH_LIMIT 20 + +Files KlipperPrinter::get_files() +{ + Files files_result; + HTTPClient client; + LOG_F(("Heap space pre-file-parse: %d bytes\n", esp_get_free_heap_size())) + std::list files; + + auto timer_request = millis(); + configure_http_client(client, "/server/files/list", true, 5000); + + int http_code = client.GET(); + auto timer_parse = millis(); + + if (http_code == 200){ + JsonDocument doc; + auto parseResult = deserializeJson(doc, client.getStream()); + LOG_F(("Json parse: %s\n", parseResult.c_str())) + auto result = doc["result"].as(); + + for (auto file : result){ + FileSystemFile f = {0}; + const char* path = file["path"]; + float modified = file["modified"]; + auto file_iter = files.begin(); + + while (file_iter != files.end()){ + if ((*file_iter).modified < modified) + break; + + file_iter++; + } + + if (file_iter == files.end() && files.size() >= KLIPPER_FILE_FETCH_LIMIT) + continue; + + f.name = (char*)malloc(strlen(path) + 1); + if (f.name == NULL){ + LOG_LN("Failed to allocate memory"); + continue; + } + strcpy(f.name, path); + f.modified = modified; + + if (file_iter != files.end()) + files.insert(file_iter, f); + else + files.push_back(f); + + if (files.size() > KLIPPER_FILE_FETCH_LIMIT){ + auto last_entry = files.back(); + + if (last_entry.name != NULL) + free(last_entry.name); + + files.pop_back(); + } + } + } + + files_result.available_files = (char**)malloc(sizeof(char*) * files.size()); + + if (files_result.available_files == NULL){ + LOG_LN("Failed to allocate memory"); + + for (auto file : files){ + free(file.name); + } + + return files_result; + } + + for (auto file : files){ + files_result.available_files[files_result.count++] = file.name; + } + + files_result.success = true; + + LOG_F(("Heap space post-file-parse: %d bytes\n", esp_get_free_heap_size())) + LOG_F(("Got %d files. Request took %dms, parsing took %dms\n", files.size(), timer_parse - timer_request, millis() - timer_parse)) + return files_result; +} + +bool KlipperPrinter::start_file(const char *filename) +{ + HTTPClient client; + configure_http_client(client, "/printer/print/start?filename=" + urlEncode(filename), false, 1000); + + int httpCode = client.POST(""); + LOG_F(("Print start: HTTP %d\n", httpCode)) +} + +bool KlipperPrinter::set_target_temperature(PrinterTemperatureDevice device, float temperature) +{ + char gcode[64] = {0}; + + switch (device) + { + case PrinterTemperatureDeviceBed: + sprintf(gcode, "M140 S%d", temperature); + break; + case PrinterTemperatureDeviceNozzle1: + sprintf(gcode, "M104 S%d", temperature); + break; + default: + LOG_F(("Unknown temperature device %d was requested to heat to %.2f", device, temperature)); + return false; + } + + return send_gcode(gcode); +} + +unsigned char* KlipperPrinter::get_32_32_png_image_thumbnail(const char* gcode_filename) +{ + HTTPClient client; + configure_http_client(client, "/server/files/thumbnails?filename=", true, 1000); + char* img_filename_path = NULL; + unsigned char* data_png = NULL; + + int http_code = 0; + try + { + http_code = client.GET(); + } + catch (...) + { + LOG_LN("Exception while fetching gcode img location"); + return NULL; + } + + if (http_code == 200) + { + JsonDocument doc; + deserializeJson(doc, client.getStream()); + auto result = doc["result"].as(); + const char* chosen_thumb = NULL; + + for (auto file : result){ + int width = file["width"]; + int height = file["height"]; + int size = file["size"]; + const char* thumbnail = file["thumbnail_path"]; + + if (width != height || width != 32) + continue; + + if (strcmp(thumbnail + strlen(thumbnail) - 4, ".png")) + continue; + + chosen_thumb = thumbnail; + break; + } + + if (chosen_thumb != NULL){ + LOG_F(("Found 32x32 PNG gcode img at %s\n", gcode_filename)) + img_filename_path = (char*)malloc(strlen(chosen_thumb) + 1); + strcpy(img_filename_path, chosen_thumb); + } + } + else + { + LOG_F(("Failed to fetch gcode image data: %d\n", http_code)) + } + + if (img_filename_path == NULL) + { + return NULL; + } + + client.end(); + + configure_http_client(client, "/server/files/gcodes/" + urlEncode(img_filename_path), false, 2000); + + int http_code = 0; + try + { + http_code = client.GET(); + } + catch (...) + { + LOG_LN("Exception while fetching gcode img"); + return NULL; + } + + if (http_code == 200) + { + size_t len = client.getSize(); + if (len <= 0) + { + LOG_LN("No gcode img data"); + return NULL; + } + + data_png = (unsigned char*)malloc(len + 1); + + if (data_png != NULL) + { + if (len != client.getStream().readBytes(data_png, len)) + { + LOG_LN("Failed to read gcode img data"); + free(data_png); + } + else + { + free(img_filename_path); + return data_png; + } + } + } + + free(img_filename_path); + return NULL; } \ 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 8849830..25c552b 100644 --- a/CYD-Klipper/src/core/klipper/klipper_printer_integration.hpp +++ b/CYD-Klipper/src/core/klipper/klipper_printer_integration.hpp @@ -43,7 +43,8 @@ class KlipperPrinter : BasePrinter 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 start_file(const char* filename); + unsigned char* get_32_32_png_image_thumbnail(const char* gcode_filename); bool set_target_temperature(PrinterTemperatureDevice device, float temperature); bool send_gcode(const char* gcode, bool wait = true); int get_slicer_time_estimate_s(); diff --git a/CYD-Klipper/src/core/printer_integration.hpp b/CYD-Klipper/src/core/printer_integration.hpp index def2719..540b212 100644 --- a/CYD-Klipper/src/core/printer_integration.hpp +++ b/CYD-Klipper/src/core/printer_integration.hpp @@ -106,14 +106,14 @@ typedef struct { } PrinterDataMinimal; typedef struct { - const char** macros; + char** macros; unsigned int count; bool success; } Macros; typedef struct { - const char** power_devices; - const bool* power_states; + char** power_devices; + bool* power_states; unsigned int count; bool success; } PowerDevices; @@ -125,8 +125,8 @@ typedef struct { } Files; typedef struct { - lv_event_ct_b set_label; - lv_event_ct_b open_panel; + lv_event_cb_t set_label; + lv_event_cb_t open_panel; } PrinterUiPanel; class BasePrinter @@ -156,7 +156,8 @@ class BasePrinter virtual int get_power_devices_count() = 0; virtual bool set_power_device_state(const char* device_name, bool state) = 0; virtual Files get_files() = 0; - virtual bool start_file(const char* file) = 0; + virtual bool start_file(const char* filename) = 0; + virtual unsigned char* get_32_32_png_image_thumbnail(const char* gcode_filename); virtual bool set_target_temperature(PrinterTemperatureDevice device, float temperature) = 0; BasePrinter(unsigned char index); diff --git a/CYD-Klipper/src/ui/ui_utils.cpp b/CYD-Klipper/src/ui/ui_utils.cpp index 013a13a..eff5efa 100644 --- a/CYD-Klipper/src/ui/ui_utils.cpp +++ b/CYD-Klipper/src/ui/ui_utils.cpp @@ -34,6 +34,17 @@ void destroy_event_user_data(lv_event_t * e){ lv_obj_del(obj); } +void destroy_event_free_data(lv_event_t * e) +{ + void* data = lv_event_get_user_data(e); + free(data); +} + +void on_destroy_free_data(lv_obj_t * element, void* ptr) +{ + lv_obj_add_event_cb(element, destroy_event_free_data, LV_EVENT_DELETE, ptr); +} + void lv_create_fullscreen_button_matrix_popup(lv_obj_t * root, lv_event_cb_t title, lv_button_column_t* columns, int column_count){ const auto full_panel_width = CYD_SCREEN_WIDTH_PX - CYD_SCREEN_GAP_PX * 3; const auto full_panel_inner_width = full_panel_width - CYD_SCREEN_GAP_PX * 2 - 4; diff --git a/CYD-Klipper/src/ui/ui_utils.h b/CYD-Klipper/src/ui/ui_utils.h index 66dd6fc..e50ead5 100644 --- a/CYD-Klipper/src/ui/ui_utils.h +++ b/CYD-Klipper/src/ui/ui_utils.h @@ -38,6 +38,7 @@ void lv_layout_flex_column(lv_obj_t* obj, lv_flex_align_t allign = LV_FLEX_ALIGN void lv_layout_flex_row(lv_obj_t* obj, lv_flex_align_t allign = LV_FLEX_ALIGN_START, lv_coord_t pad_column = CYD_SCREEN_GAP_PX, lv_coord_t pad_row = CYD_SCREEN_GAP_PX); void lv_create_fullscreen_button_matrix_popup(lv_obj_t * root, lv_event_cb_t title, lv_button_column_t* columns, int column_count); void destroy_event_user_data(lv_event_t * e); +void on_destroy_free_data(lv_obj_t * element, void* ptr); void lv_create_keyboard_text_entry(lv_event_cb_t keyboard_callback, const char* title = NULL, lv_keyboard_mode_t keyboard_mode = LV_KEYBOARD_MODE_NUMBER, lv_coord_t width = CYD_SCREEN_PANEL_WIDTH_PX / 2, uint8_t max_length = 3, const char* fill_text = "", bool contain_in_panel= true); void lv_create_custom_menu_entry(const char* label_text, lv_obj_t* object, lv_obj_t* root_panel, bool set_height = true, const char * comment = NULL); void lv_create_custom_menu_button(const char *label_text, lv_obj_t* root_panel, lv_event_cb_t on_click, const char *btn_text, void * user_data = NULL, const char * comment = NULL);