33 Commits
0.0 ... v1.1.4

Author SHA1 Message Date
suchmememanyskill
2a2fff27d6 Merge branch 'master' into dev 2024-01-19 21:10:39 +01:00
suchmememanyskill
082d66ca10 Longer watchdog timeout, refactor, use duty cycle for backlight 2024-01-19 21:05:57 +01:00
Sims
41b4bff940 Merge pull request #12 from suchmememanyskill/dev
Dev
2024-01-15 13:28:57 +01:00
suchmememanyskill
9136f4c94b Add some delay within data loop to give other processes on the core time to process 2024-01-08 21:33:01 +01:00
suchmememanyskill
50f4984231 Insert sort the fetched files, discard any extras 2024-01-07 21:07:35 +01:00
suchmememanyskill
48466cfb44 Lower data fetch task priority 2024-01-07 21:07:19 +01:00
suchmememanyskill
a7acd49d60 Make all colors somewhat worth using 2024-01-06 20:40:37 +01:00
suchmememanyskill
91920a679a Fix #10 2024-01-06 19:59:13 +01:00
suchmememanyskill
53441c86c4 Add kofi to site 2024-01-05 23:20:47 +01:00
Sims
ffdc8ae87e Merge pull request #7 from suchmememanyskill/dev
v1.1.2
2023-12-16 17:53:14 +01:00
suchmememanyskill
7c786d1e6b Don't continously call unfreeze_render_thread() 2023-12-15 19:24:55 +01:00
suchmememanyskill
34c6a5e031 Offload API request loop to core 0 2023-12-15 19:22:48 +01:00
suchmememanyskill
7a430f81c5 Change back to klipper connect screen when connection to klipper gets severed 2023-12-11 22:23:18 +01:00
Sims
230884c2cc Merge pull request #6 from suchmememanyskill/dev
V1.1.1
2023-12-03 01:25:45 +01:00
suchmememanyskill
f2d232d9eb Add visual Klipper connect retry 2023-12-02 02:01:26 +01:00
suchmememanyskill
1e3f0ab637 Lower CPU speed if screen is off 2023-12-02 01:13:40 +01:00
suchmememanyskill
e15c7e37ff update readme 2023-11-23 12:31:32 +01:00
suchmememanyskill
e15ba8d852 Merge branch 'dev' 2023-11-23 12:01:12 +01:00
suchmememanyskill
a759ccbbf7 Merge branch 'master' of https://github.com/suchmememanyskill/CYD-Klipper-Display 2023-11-23 12:01:09 +01:00
suchmememanyskill
84662a8fab Make sure the macro name doesn't overflow 2023-11-23 11:58:10 +01:00
suchmememanyskill
cb47286784 Add macro support 2023-11-23 11:46:09 +01:00
suchmememanyskill
6717e53fc9 Fix filenames with spaces 2023-11-23 10:43:39 +01:00
suchmememanyskill
dc5a3b5efd Add temp presets 2023-11-21 04:01:35 +01:00
Sims
48520f652a Print wifi connect status while connecting, retry ip more times (#3) 2023-11-20 15:57:40 +01:00
suchmememanyskill
dccb10cc6f Print wifi connect status while connecting, retry ip more times 2023-11-14 00:45:08 +01:00
suchmememanyskill
c5d08253a7 Retry IP connect on boot, fix network c string issue 2023-11-14 00:31:52 +01:00
suchmememanyskill
5224e34f8c use absolute coords for position, rather than ajusted ones 2023-11-13 21:55:35 +01:00
suchmememanyskill
fb65bc8068 Readme 2023-11-13 17:41:16 +01:00
suchmememanyskill
c0651a50a7 Initial release 2023-11-13 17:07:54 +01:00
suchmememanyskill
e04e3204eb Try 5 2023-11-13 16:37:54 +01:00
suchmememanyskill
ecc9e5ea99 Try 4 2023-11-13 16:30:12 +01:00
suchmememanyskill
ed024077ee Try 3 2023-11-13 16:24:54 +01:00
suchmememanyskill
91db5036c0 Try 2 2023-11-13 16:20:42 +01:00
52 changed files with 970 additions and 326 deletions

View File

@@ -1,5 +1,9 @@
name: PlatformIO CI
permissions:
pages: write
id-token: write
on: [push, pull_request]
jobs:
@@ -24,22 +28,44 @@ jobs:
- name: Build PlatformIO Project
run: |
cd CYD-Klipper-Display
cd CYD-Klipper
pio run
- name: Make output dir
run: mkdir -p output
run: |
mkdir -p output
- name: Build Binary
run: |
cp ./CYD-Klipper-Display/.pio/build/esp32dev/bootloader.bin output
cp ./CYD-Klipper-Display/.pio/build/esp32dev/partitions.bin output
cp ./CYD-Klipper-Display/.pio/build/esp32dev/firmware.bin output
cp ~/platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin output
esptool --chip esp32 merge_bin -o ./output/merged-firmware.bin --flash_mode dio --flash_freq 40m --flash_size 4MB 0x1000 ./output/bootloader.bin 0x8000 ./output/partitions.bin 0xe000 ./output/boot_app0.bin 0x10000 ./output/firmware.bin
cp ./CYD-Klipper/.pio/build/esp32dev/bootloader.bin output
cp ./CYD-Klipper/.pio/build/esp32dev/partitions.bin output
cp ./CYD-Klipper/.pio/build/esp32dev/firmware.bin output
cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin output
python3 -m esptool --chip esp32 merge_bin -o ./output/merged-firmware.bin --flash_mode dio --flash_freq 40m --flash_size 4MB 0x1000 ./output/bootloader.bin 0x8000 ./output/partitions.bin 0xe000 ./output/boot_app0.bin 0x10000 ./output/firmware.bin
cp -r ./output ./_site
- name: Upload artefact
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
name: firmware
path: ./output
- name: Upload GitHub Page Artifact
uses: actions/upload-pages-artifact@v2
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
steps:
- name: Print GitHub event name
run: |
echo "${{ github.event_name }}"
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v2

View File

@@ -1,63 +0,0 @@
#include <list>
#include "files_query.h"
#include "../conf/global_config.h"
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <HardwareSerial.h>
// Always has +1 entry with a null'd name
FILESYSTEM_FILE* last_query = NULL;
FILESYSTEM_FILE* get_files(){
if (last_query != NULL){
FILESYSTEM_FILE* current = last_query;
while (current->name != NULL){
free(current->name);
current += 1;
}
free(last_query);
}
std::list<FILESYSTEM_FILE> files;
char buff[256] = {};
sprintf(buff, "http://%s:%d/server/files/list", global_config.klipperHost, global_config.klipperPort);
HTTPClient client;
client.begin(buff);
int httpCode = client.GET();
int count = 0;
if (httpCode == 200){
String payload = client.getString();
DynamicJsonDocument doc(60000);
auto a = deserializeJson(doc, payload);
Serial.printf("JSON PARSE: %s\n", a.c_str());
auto result = doc["result"].as<JsonArray>();
for (auto file : result){
FILESYSTEM_FILE f = {0};
const char* path = file["path"];
f.name = (char*)malloc(strlen(path) + 1);
strcpy(f.name, path);
f.modified = file["modified"];
files.push_back(f);
count++;
}
}
//Serial.printf("Found %d files\n", count);
files.sort([](FILESYSTEM_FILE a, FILESYSTEM_FILE b){return a.modified < b.modified;});
files.reverse(); // TODO: Reverse is unneeded here, we can iterate backwards
size_t size = sizeof(FILESYSTEM_FILE) * (files.size() + 1);
FILESYSTEM_FILE* result = (FILESYSTEM_FILE*)malloc(size);
//Serial.printf("Allocated %d bytes\n", size);
last_query = result;
result[files.size()].name = NULL;
for (auto file : files){
*result = file;
result += 1;
}
return last_query;
}

View File

@@ -1 +0,0 @@
void ip_setup();

View File

@@ -1,165 +0,0 @@
#include "lvgl.h"
#include "panel.h"
#include "../../core/data_setup.h"
#include <HardwareSerial.h>
// False: Hotend, True: Bed
static bool hotend_or_bed = true;
static char hotend_buff[40];
static char bed_buff[40];
static void update_printer_data_hotend_temp(lv_event_t * e){
lv_obj_t * label = lv_event_get_target(e);
sprintf(hotend_buff, "Hotend: %.0f C\nTarget: %.0f C", printer.extruder_temp, printer.extruder_target_temp);
lv_label_set_text(label, hotend_buff);
}
static void update_printer_data_bed_temp(lv_event_t * e){
lv_obj_t * label = lv_event_get_target(e);
sprintf(bed_buff, "Bed: %.0f C\nTarget: %.0f C", printer.bed_temp, printer.bed_target_temp);
lv_label_set_text(label, bed_buff);
}
static void keyboard_callback(lv_event_t * e){
lv_event_code_t code = lv_event_get_code(e);
lv_obj_t * ta = lv_event_get_target(e);
lv_obj_t * kb = (lv_obj_t *)lv_event_get_user_data(e);
if (code == LV_EVENT_READY) {
const char * text = lv_textarea_get_text(ta);
int temp = atoi(text);
if (temp < 0 || temp > 500){
return;
}
Serial.printf("%d %s %d\n", hotend_or_bed, text, temp);
char gcode[64];
const char* space = "%20";
if (hotend_or_bed){
sprintf(gcode, "M140%sS%d", space, temp);
} else {
sprintf(gcode, "M104%sS%d", space, temp);
}
send_gcode(true, gcode);
}
if(code == LV_EVENT_DEFOCUSED || code == LV_EVENT_CANCEL || code == LV_EVENT_READY) {
lv_keyboard_set_textarea(kb, NULL);
lv_obj_del(kb);
lv_obj_del(ta);
}
}
static void show_keyboard(lv_event_t * e){
lv_obj_t * panel = (lv_obj_t *)lv_event_get_user_data(e);
lv_obj_t * keyboard = lv_keyboard_create(panel);
lv_obj_t * ta = lv_textarea_create(panel);
lv_obj_set_size(ta, 100, 30);
lv_obj_align(ta, LV_ALIGN_TOP_MID, 0, 40);
lv_textarea_set_max_length(ta, 3);
lv_textarea_set_one_line(ta, true);
lv_textarea_set_text(ta, "");
lv_obj_add_event_cb(ta, keyboard_callback, LV_EVENT_ALL, keyboard);
lv_keyboard_set_mode(keyboard, LV_KEYBOARD_MODE_NUMBER);
lv_keyboard_set_textarea(keyboard, ta);
}
static void show_keyboard_with_hotend(lv_event_t * e){
hotend_or_bed = false;
show_keyboard(e);
}
static void show_keyboard_with_bed(lv_event_t * e){
hotend_or_bed = true;
show_keyboard(e);
}
static void cooldown_temp(lv_event_t * e){
if (printer.state == PRINTER_STATE_PRINTING){
return;
}
send_gcode(true, "M104%20S0");
send_gcode(true, "M140%20S0");
}
static void btn_extrude(lv_event_t * e){
if (printer.state == PRINTER_STATE_PRINTING){
return;
}
send_gcode(true, "M83");
send_gcode(true, "G1%20E25%20F300");
}
static void btn_retract(lv_event_t * e){
if (printer.state == PRINTER_STATE_PRINTING){
return;
}
send_gcode(true, "M83");
send_gcode(true, "G1%20E-25%20F300");
}
void temp_panel_init(lv_obj_t* panel){
auto panel_width = TFT_HEIGHT - 40;
lv_obj_t * label = lv_label_create(panel);
lv_label_set_text(label, "Hotend");
lv_obj_align(label, LV_ALIGN_TOP_LEFT, 10, 10);
lv_obj_add_event_cb(label, update_printer_data_hotend_temp, LV_EVENT_MSG_RECEIVED, NULL);
lv_msg_subscribe_obj(DATA_PRINTER_DATA, label, NULL);
label = lv_label_create(panel);
lv_label_set_text(label, "Bed");
lv_obj_align(label, LV_ALIGN_TOP_LEFT, 10, 50);
lv_obj_add_event_cb(label, update_printer_data_bed_temp, LV_EVENT_MSG_RECEIVED, NULL);
lv_msg_subscribe_obj(DATA_PRINTER_DATA, label, NULL);
lv_obj_t * btn = lv_btn_create(panel);
lv_obj_align(btn, LV_ALIGN_TOP_RIGHT, -10, 10);
lv_obj_add_event_cb(btn, show_keyboard_with_hotend, LV_EVENT_CLICKED, panel);
label = lv_label_create(btn);
lv_label_set_text(label, "Set");
lv_obj_center(label);
btn = lv_btn_create(panel);
lv_obj_align(btn, LV_ALIGN_TOP_RIGHT, -10, 50);
lv_obj_add_event_cb(btn, show_keyboard_with_bed, LV_EVENT_CLICKED, panel);
label = lv_label_create(btn);
lv_label_set_text(label, "Set");
lv_obj_center(label);
btn = lv_btn_create(panel);
lv_obj_align(btn, LV_ALIGN_TOP_MID, 0, 90);
lv_obj_add_event_cb(btn, cooldown_temp, LV_EVENT_CLICKED, panel);
label = lv_label_create(btn);
lv_label_set_text(label, "Cooldown");
lv_obj_center(label);
btn = lv_btn_create(panel);
lv_obj_align(btn, LV_ALIGN_BOTTOM_LEFT, 10, -5);
lv_obj_add_event_cb(btn, btn_extrude, LV_EVENT_CLICKED, NULL);
lv_obj_set_size(btn, panel_width / 2 - 15, 30);
label = lv_label_create(btn);
lv_label_set_text(label, LV_SYMBOL_DOWN " Extrude");
lv_obj_center(label);
btn = lv_btn_create(panel);
lv_obj_align(btn, LV_ALIGN_BOTTOM_RIGHT, -10, -5);
lv_obj_add_event_cb(btn, btn_retract, LV_EVENT_CLICKED, NULL);
lv_obj_set_size(btn, panel_width / 2 - 15, 30);
label = lv_label_create(btn);
lv_label_set_text(label, LV_SYMBOL_UP " Retract");
lv_obj_center(label);
lv_msg_send(DATA_PRINTER_DATA, &printer);
}

View File

@@ -8,6 +8,7 @@
"unordered_set": "cpp",
"vector": "cpp",
"string_view": "cpp",
"initializer_list": "cpp"
"initializer_list": "cpp",
"algorithm": "cpp"
}
}

View File

@@ -1,3 +1,3 @@
cmake_minimum_required(VERSION 3.16.0)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(CYD-Klipper-Display)
project(CYD-Klipper)

View File

@@ -18,7 +18,8 @@ lib_deps =
lvgl/lvgl@^8.3.9
https://github.com/Bodmer/TFT_eSPI.git
https://github.com/PaulStoffregen/XPT2046_Touchscreen.git
bblanchon/ArduinoJson@^6.21.3
bblanchon/ArduinoJson@^7.0.0
monitor_filters = esp32_exception_decoder
build_flags =
-DLV_CONF_PATH="../../../../src/conf/lv_conf.h"
-DUSER_SETUP_LOADED=1

View File

@@ -5,13 +5,13 @@
GLOBAL_CONFIG global_config = {0};
COLOR_DEF color_defs[] = {
{LV_PALETTE_BLUE, LV_PALETTE_RED},
{LV_PALETTE_GREEN, LV_PALETTE_PURPLE},
{LV_PALETTE_GREY, LV_PALETTE_CYAN},
{LV_PALETTE_YELLOW, LV_PALETTE_PINK},
{LV_PALETTE_ORANGE, LV_PALETTE_BLUE},
{LV_PALETTE_RED, LV_PALETTE_GREEN},
{LV_PALETTE_PURPLE, LV_PALETTE_GREY},
{LV_PALETTE_BLUE, 0, LV_PALETTE_RED},
{LV_PALETTE_LIME, -2, LV_PALETTE_PURPLE},
{LV_PALETTE_GREY, 0, LV_PALETTE_CYAN},
{LV_PALETTE_YELLOW, -2, LV_PALETTE_PINK},
{LV_PALETTE_ORANGE, -2, LV_PALETTE_BLUE},
{LV_PALETTE_RED, 0, LV_PALETTE_GREEN},
{LV_PALETTE_PURPLE, 0, LV_PALETTE_GREY},
};
void WriteGlobalConfig() {
@@ -41,6 +41,12 @@ void LoadGlobalConfig() {
global_config.version = CONFIG_VERSION;
global_config.brightness = 255;
global_config.screenTimeout = 5;
global_config.hotend_presets[0] = 0;
global_config.hotend_presets[1] = 200;
global_config.hotend_presets[2] = 240;
global_config.bed_presets[0] = 0;
global_config.bed_presets[1] = 60;
global_config.bed_presets[2] = 70;
VerifyVersion();
Preferences preferences;
preferences.begin("global_config", true);

View File

@@ -3,16 +3,19 @@
#include "lvgl.h"
#define CONFIG_VERSION 2
#define CONFIG_VERSION 3
typedef struct _GLOBAL_CONFIG {
unsigned char version;
union {
unsigned char raw;
struct {
// Internal
bool screenCalibrated : 1;
bool wifiConfigured : 1;
bool ipConfigured : 1;
// External
bool lightMode : 1;
bool invertColors : 1;
bool rotateScreen : 1;
@@ -33,10 +36,14 @@ typedef struct _GLOBAL_CONFIG {
unsigned char color_scheme;
unsigned char brightness;
unsigned char screenTimeout;
unsigned short hotend_presets[3];
unsigned short bed_presets[3];
} GLOBAL_CONFIG;
typedef struct _COLOR_DEF {
lv_palette_t primary_color;
short primary_color_light;
lv_palette_t secondary_color;
} COLOR_DEF;

View File

@@ -4,6 +4,8 @@
#include "../conf/global_config.h"
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <esp_task_wdt.h>
#include "macros_query.h"
const char *printer_state_messages[] = {
"Error",
@@ -11,6 +13,33 @@ const char *printer_state_messages[] = {
"Printing"};
Printer printer = {0};
int klipper_request_consecutive_fail_count = 0;
char filename_buff[512] = {0};
SemaphoreHandle_t freezeRenderThreadSemaphore, freezeRequestThreadSemaphore;
const long data_update_interval = 780;
void semaphore_init(){
freezeRenderThreadSemaphore = xSemaphoreCreateMutex();
freezeRequestThreadSemaphore = xSemaphoreCreateMutex();
xSemaphoreGive(freezeRenderThreadSemaphore);
xSemaphoreGive(freezeRequestThreadSemaphore);
}
void freeze_request_thread(){
xSemaphoreTake(freezeRequestThreadSemaphore, portMAX_DELAY);
}
void unfreeze_request_thread(){
xSemaphoreGive(freezeRequestThreadSemaphore);
}
void freeze_render_thread(){
xSemaphoreTake(freezeRenderThreadSemaphore, portMAX_DELAY);
}
void unfreeze_render_thread(){
xSemaphoreGive(freezeRenderThreadSemaphore);
}
void send_gcode(bool wait, const char *gcode)
{
@@ -34,23 +63,27 @@ void send_gcode(bool wait, const char *gcode)
}
}
char filename_buff[512] = {0};
void fetch_printer_data()
{
freeze_request_thread();
char buff[256] = {};
sprintf(buff, "http://%s:%d/printer/objects/query?extruder&heater_bed&toolhead&gcode_move&virtual_sdcard&print_stats&webhooks", global_config.klipperHost, global_config.klipperPort);
HTTPClient client;
client.useHTTP10(true);
client.begin(buff);
int httpCode = client.GET();
delay(10);
if (httpCode == 200)
{
String payload = client.getString();
DynamicJsonDocument doc(4096);
deserializeJson(doc, payload);
klipper_request_consecutive_fail_count = 0;
JsonDocument doc;
deserializeJson(doc, client.getStream());
auto status = doc["result"]["status"];
bool emit_state_update = false;
int printer_state = printer.state;
delay(10);
unfreeze_request_thread();
freeze_render_thread();
if (status.containsKey("webhooks"))
{
@@ -97,15 +130,15 @@ void fetch_printer_data()
if (status.containsKey("toolhead"))
{
printer.position[0] = status["toolhead"]["position"][0];
printer.position[1] = status["toolhead"]["position"][1];
printer.position[2] = status["toolhead"]["position"][2];
const char *homed_axis = status["toolhead"]["homed_axes"];
printer.homed_axis = strcmp(homed_axis, "xyz") == 0;
}
if (status.containsKey("gcode_move"))
{
printer.position[0] = status["gcode_move"]["gcode_position"][0];
printer.position[1] = status["gcode_move"]["gcode_position"][1];
printer.position[2] = status["gcode_move"]["gcode_position"][2];
bool absolute_coords = status["gcode_move"]["absolute_coordinates"];
printer.absolute_coords = absolute_coords == true;
}
@@ -155,28 +188,41 @@ void fetch_printer_data()
printer.state = printer_state;
lv_msg_send(DATA_PRINTER_STATE, &printer);
}
unfreeze_render_thread();
}
else
{
klipper_request_consecutive_fail_count++;
Serial.printf("Failed to fetch printer data: %d\n", httpCode);
unfreeze_request_thread();
}
}
long last_data_update = 0;
const long data_update_interval = 1500;
void data_loop()
{
if (millis() - last_data_update < data_update_interval)
return;
last_data_update = millis();
// Causes other threads that are trying to lock the thread to actually lock it
unfreeze_render_thread();
delay(1);
freeze_render_thread();
}
void data_loop_background(void * param){
esp_task_wdt_init(10, true);
while (true){
delay(data_update_interval);
fetch_printer_data();
}
}
TaskHandle_t background_loop;
void data_setup()
{
semaphore_init();
printer.print_filename = filename_buff;
fetch_printer_data();
macros_query_setup();
freeze_render_thread();
xTaskCreatePinnedToCore(data_loop_background, "data_loop_background", 5000, NULL, 0, &background_loop, 0);
}

View File

@@ -28,10 +28,15 @@ typedef struct _Printer {
} Printer;
extern Printer printer;
extern int klipper_request_consecutive_fail_count;
#define DATA_PRINTER_STATE 1
#define DATA_PRINTER_DATA 2
#define DATA_PRINTER_TEMP_PRESET 3
void data_loop();
void data_setup();
void send_gcode(bool wait, const char* gcode);
void freeze_request_thread();
void unfreeze_request_thread();

View File

@@ -0,0 +1,108 @@
#include <list>
#include "files_query.h"
#include "../conf/global_config.h"
#include "data_setup.h"
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <HardwareSerial.h>
// Always has +1 entry with a null'd name
FILESYSTEM_FILE* last_query = NULL;
FILESYSTEM_FILE* get_files(int limit){
freeze_request_thread();
if (last_query != NULL){
FILESYSTEM_FILE* current = last_query;
while (current->name != NULL){
free(current->name);
current += 1;
}
free(last_query);
}
Serial.printf("Heap space pre-file-parse: %d bytes\n", esp_get_free_heap_size());
std::list<FILESYSTEM_FILE> files;
auto timer_request = millis();
char buff[256] = {};
sprintf(buff, "http://%s:%d/server/files/list", global_config.klipperHost, global_config.klipperPort);
HTTPClient client;
client.useHTTP10(true);
client.begin(buff);
int httpCode = client.GET();
auto timer_parse = millis();
if (httpCode == 200){
JsonDocument doc;
auto parseResult = deserializeJson(doc, client.getStream());
Serial.printf("Json parse: %s\n", parseResult.c_str());
auto result = doc["result"].as<JsonArray>();
for (auto file : result){
FILESYSTEM_FILE 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++;
}
// Little inefficient as it always allocates a string, even if it doesn't have to
f.name = (char*)malloc(strlen(path) + 1);
if (f.name == NULL){
Serial.println("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() > limit){
auto last_entry = files.back();
if (last_entry.name != NULL)
free(last_entry.name);
files.pop_back();
}
}
}
size_t size = sizeof(FILESYSTEM_FILE) * (files.size() + 1);
FILESYSTEM_FILE* result = (FILESYSTEM_FILE*)malloc(size);
if (result == NULL){
Serial.println("Failed to allocate memory");
for (auto file : files){
free(file.name);
}
unfreeze_request_thread();
return NULL;
}
last_query = result;
result[files.size()].name = NULL;
for (auto file : files){
*result = file;
result += 1;
}
Serial.printf("Heap space post-file-parse: %d bytes\n", esp_get_free_heap_size());
Serial.printf("Got %d files. Request took %dms, parsing took %dms\n", files.size(), timer_parse - timer_request, millis() - timer_parse);
unfreeze_request_thread();
return last_query;
}

View File

@@ -19,4 +19,4 @@ typedef struct _FILESYSTEM_FILE {
float modified;
} FILESYSTEM_FILE;
FILESYSTEM_FILE* get_files();
FILESYSTEM_FILE* get_files(int limit);

View File

@@ -0,0 +1,51 @@
#include "lvgl.h"
#include "macros_query.h"
#include "./data_setup.h"
#include <HTTPClient.h>
#include "../conf/global_config.h"
#include <ArduinoJson.h>
static char* macros[64] = {0};
static int macros_count = 0;
static void on_state_change(void * s, lv_msg_t * m) {
if (printer.state == PRINTER_STATE_ERROR || printer.state == PRINTER_STATE_PAUSED){
return;
}
String url = "http://" + String(global_config.klipperHost) + ":" + String(global_config.klipperPort) + "/printer/gcode/help";
HTTPClient client;
client.useHTTP10(true);
client.begin(url.c_str());
int httpCode = client.GET();
if (httpCode == 200){
JsonDocument doc;
deserializeJson(doc, client.getStream());
auto result = doc["result"].as<JsonObject>();
for (int i = 0; i < macros_count; i++){
free(macros[i]);
}
macros_count = 0;
for (JsonPair i : result){
const char *key = i.key().c_str();
const char *value = i.value().as<String>().c_str();
if (strcmp(value, "CYD_SCREEN_MACRO") == 0) {
char* macro = (char*)malloc(strlen(key) + 1);
strcpy(macro, key);
macros[macros_count++] = macro;
}
}
}
}
MACROSQUERY macros_query() {
return {(const char**)macros, macros_count};
}
void macros_query_setup(){
lv_msg_subscribe(DATA_PRINTER_STATE, on_state_change, NULL);
on_state_change(NULL, NULL);
}

View File

@@ -0,0 +1,9 @@
#pragma once
typedef struct {
const char** macros;
uint32_t count;
} MACROSQUERY;
MACROSQUERY macros_query();
void macros_query_setup();

View File

@@ -84,7 +84,11 @@ void touchscreen_calibrate(bool force)
void screen_setBrightness(byte brightness)
{
analogWrite(TFT_BL, brightness);
// calculate duty, 4095 from 2 ^ 12 - 1
uint32_t duty = (4095 / 255) * brightness;
// write duty to LEDC
ledcWrite(0, duty);
}
void set_screen_brightness()
@@ -100,12 +104,20 @@ void screen_timer_wake()
lv_timer_reset(screenSleepTimer);
isScreenInSleep = false;
set_screen_brightness();
// Reset cpu freq
setCpuFrequencyMhz(CPU_FREQ_HIGH);
Serial.printf("CPU Speed: %d MHz\n", ESP.getCpuFreqMHz());
}
void screen_timer_sleep(lv_timer_t *timer)
{
screen_setBrightness(0);
isScreenInSleep = true;
// Screen is off, no need to make the cpu run fast, the user won't notice ;)
setCpuFrequencyMhz(CPU_FREQ_LOW);
Serial.printf("CPU Speed: %d MHz\n", ESP.getCpuFreqMHz());
}
void screen_timer_setup()
@@ -175,7 +187,20 @@ void screen_lv_touchRead(lv_indev_drv_t *indev_driver, lv_indev_data_t *data)
void set_color_scheme(){
lv_disp_t *dispp = lv_disp_get_default();
lv_theme_t *theme = lv_theme_default_init(dispp, lv_palette_main(color_defs[global_config.color_scheme].primary_color), lv_palette_main(color_defs[global_config.color_scheme].secondary_color), !global_config.lightMode, LV_FONT_DEFAULT);
lv_color_t main_color = {0};
COLOR_DEF color_def = color_defs[global_config.color_scheme];
if (color_defs[global_config.color_scheme].primary_color_light > 0){
main_color = lv_palette_lighten(color_def.primary_color, color_def.primary_color_light);
}
else if (color_defs[global_config.color_scheme].primary_color_light < 0) {
main_color = lv_palette_darken(color_def.primary_color, color_def.primary_color_light * -1);
}
else {
main_color = lv_palette_main(color_defs[global_config.color_scheme].primary_color);
}
lv_theme_t *theme = lv_theme_default_init(dispp, main_color, lv_palette_main(color_def.secondary_color), !global_config.lightMode, LV_FONT_DEFAULT);
lv_disp_set_theme(dispp, theme);
}
@@ -192,6 +217,10 @@ void screen_setup()
lv_init();
tft.init();
ledcSetup(0, 5000, 12);
ledcAttachPin(21, 0);
tft.setRotation(global_config.rotateScreen ? 3 : 1);
tft.fillScreen(TFT_BLACK);
set_screen_brightness();

View File

@@ -4,6 +4,9 @@
#ifndef _SCREEN_DRIVER_INIT
#define _SCREEN_DRIVER_INIT
#define CPU_FREQ_HIGH 240
#define CPU_FREQ_LOW 80
#include <XPT2046_Touchscreen.h>
#include <TFT_eSPI.h>

View File

@@ -25,37 +25,16 @@ void setup() {
Serial.println("Screen init done");
wifi_init();
ip_setup();
ip_init();
data_setup();
nav_style_setup();
main_ui_setup();
/*
lv_obj_clean(lv_scr_act());
lv_obj_t * label;
lv_obj_t * btn1 = lv_btn_create(lv_scr_act());
lv_obj_add_event_cb(btn1, event_handler, LV_EVENT_CLICKED, NULL);
lv_obj_align(btn1, LV_ALIGN_CENTER, 0, 0);
label = lv_label_create(btn1);
lv_label_set_text(label, "Reset Configuration");
lv_obj_center(label);
lv_obj_t * slider = lv_slider_create(lv_scr_act());
lv_obj_set_width(slider, 200);
lv_obj_align(slider, LV_ALIGN_CENTER, 0, 40);
lv_slider_set_range(slider, 0, 100);
lv_slider_set_value(slider, 50, LV_ANIM_OFF);
*/
}
void loop(){
wifi_ok();
ip_ok();
data_loop();
lv_timer_handler();
lv_task_handler();

View File

@@ -3,18 +3,22 @@
#include "lvgl.h"
#include <TFT_eSPI.h>
#include <HTTPClient.h>
#include "core/data_setup.h"
bool connect_ok = false;
lv_obj_t * ipEntry;
lv_obj_t * portEntry;
lv_obj_t * label = NULL;
void ip_init_inner();
bool verify_ip(){
HTTPClient client;
String url = "http://" + String(global_config.klipperHost) + ":" + String(global_config.klipperPort) + "/printer/info";
int httpCode;
try {
Serial.println(url);
client.setTimeout(500);
client.begin(url.c_str());
httpCode = client.GET();
return httpCode == 200;
@@ -42,8 +46,8 @@ static void ta_event_cb(lv_event_t * e) {
{
strcpy(global_config.klipperHost, lv_textarea_get_text(ipEntry));
global_config.klipperPort = atoi(lv_textarea_get_text(portEntry));
bool result = verify_ip();
if (result)
if (verify_ip())
{
global_config.ipConfigured = true;
WriteGlobalConfig();
@@ -56,9 +60,33 @@ static void ta_event_cb(lv_event_t * e) {
}
}
void ip_setup_inner(){
static void reset_btn_event_handler(lv_event_t * e){
lv_event_code_t code = lv_event_get_code(e);
if(code == LV_EVENT_CLICKED) {
global_config.ipConfigured = false;
ip_init_inner();
}
}
void ip_init_inner(){
lv_obj_clean(lv_scr_act());
if (global_config.ipConfigured) {
label = lv_label_create(lv_scr_act());
lv_label_set_text(label, "Connecting to Klipper");
lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
lv_obj_t * resetBtn = lv_btn_create(lv_scr_act());
lv_obj_add_event_cb(resetBtn, reset_btn_event_handler, LV_EVENT_ALL, NULL);
lv_obj_align(resetBtn, LV_ALIGN_CENTER, 0, 40);
lv_obj_t * btnLabel = lv_label_create(resetBtn);
lv_label_set_text(btnLabel, "Reset");
lv_obj_center(btnLabel);
return;
}
lv_obj_t * keyboard = lv_keyboard_create(lv_scr_act());
label = lv_label_create(lv_scr_act());
lv_label_set_text(label, "Enter Klipper IP and Port");
@@ -84,18 +112,37 @@ void ip_setup_inner(){
lv_keyboard_set_textarea(keyboard, ipEntry);
}
void ip_setup(){
long last_data_update_ip = -10000;
const long data_update_interval_ip = 10000;
int retry_count = 0;
void ip_init(){
connect_ok = false;
retry_count = 0;
if (global_config.ipConfigured && verify_ip()){
return;
}
ip_setup_inner();
ip_init_inner();
while (!connect_ok)
{
lv_timer_handler();
lv_task_handler();
if (!connect_ok && global_config.ipConfigured && (millis() - last_data_update_ip) > data_update_interval_ip){
connect_ok = verify_ip();
last_data_update_ip = millis();
retry_count++;
String retry_count_text = "Connecting to Klipper (Try " + String(retry_count + 1) + ")";
lv_label_set_text(label, retry_count_text.c_str());
}
}
}
void ip_ok(){
if (klipper_request_consecutive_fail_count > 5){
freeze_request_thread();
ip_init();
unfreeze_request_thread();
klipper_request_consecutive_fail_count = 0;
lv_msg_send(DATA_PRINTER_STATE, &printer);
}
}

View File

@@ -0,0 +1,2 @@
void ip_init();
void ip_ok();

View File

@@ -71,6 +71,10 @@ static void btn_click_settings(lv_event_t * e){
nav_buttons_setup(3);
}
static void btn_click_macros(lv_event_t * e){
nav_buttons_setup(4);
}
void nav_buttons_setup(unsigned char active_panel){
lv_obj_clean(lv_scr_act());
lv_obj_clear_flag(lv_scr_act(), LV_OBJ_FLAG_SCROLLABLE);
@@ -140,14 +144,14 @@ void nav_buttons_setup(unsigned char active_panel){
lv_obj_set_size(btn, button_width, button_height);
lv_obj_align(btn, LV_ALIGN_TOP_LEFT, 0, button_height * 3);
lv_obj_add_style(btn, &nav_button_style, 0);
lv_obj_add_event_cb(btn, btn_click_settings, LV_EVENT_CLICKED, NULL);
lv_obj_add_event_cb(btn, btn_click_macros, LV_EVENT_CLICKED, NULL);
label = lv_label_create(btn);
lv_label_set_text(label, LV_SYMBOL_SETTINGS);
lv_label_set_text(label, LV_SYMBOL_GPS);
lv_obj_align(label, LV_ALIGN_CENTER, 0, -1 * icon_text_spacing);
label = lv_label_create(btn);
lv_label_set_text(label, "Screen");
lv_label_set_text(label, "Macro");
lv_obj_align(label, LV_ALIGN_CENTER, 0, icon_text_spacing);
lv_obj_add_style(label, &nav_button_text_style, 0);
@@ -171,6 +175,9 @@ void nav_buttons_setup(unsigned char active_panel){
case 3:
settings_panel_init(panel);
break;
case 4:
macros_panel_init(panel);
break;
}
}

View File

@@ -0,0 +1,81 @@
#include "lvgl.h"
#include "panel.h"
#include "../nav_buttons.h"
#include "../../core/data_setup.h"
#include "../../core/macros_query.h"
#include <HardwareSerial.h>
int y_offset_macros = 40;
const int y_element_size = 50;
const int y_seperator_size = 1;
const int y_seperator_x_padding = 50;
const int panel_width = TFT_HEIGHT - 40;
const int y_element_x_padding = 30;
const static lv_point_t line_points[] = { {0, 0}, {panel_width - y_seperator_x_padding, 0} };
static void btn_press(lv_event_t * e){
lv_obj_t * btn = lv_event_get_target(e);
const char* macro = (const char*)lv_event_get_user_data(e);
Serial.printf("Macro: %s\n", macro);
send_gcode(false, macro);
}
static void btn_goto_settings(lv_event_t * e){
nav_buttons_setup(3);
}
void create_macro_widget(const char* macro, lv_obj_t* root_panel){
lv_obj_t * panel = lv_obj_create(root_panel);
lv_obj_set_style_border_width(panel, 0, 0);
lv_obj_set_style_bg_opa(panel, LV_OPA_TRANSP, 0);
lv_obj_set_style_pad_all(panel, 0, 0);
lv_obj_align(panel, LV_ALIGN_TOP_MID, 0, y_offset_macros);
lv_obj_set_size(panel, panel_width - y_element_x_padding, y_element_size);
lv_obj_t * line = lv_line_create(panel);
lv_line_set_points(line, line_points, 2);
lv_obj_set_style_line_width(line, y_seperator_size, 0);
lv_obj_set_style_line_color(line, lv_color_hex(0xAAAAAA), 0);
lv_obj_align(line, LV_ALIGN_BOTTOM_MID, 0, 0);
lv_obj_t * label = lv_label_create(panel);
lv_label_set_text(label, macro);
lv_obj_align(label, LV_ALIGN_LEFT_MID, 0, 0);
lv_label_set_long_mode(label, LV_LABEL_LONG_SCROLL_CIRCULAR);
lv_obj_set_width(label, (TFT_HEIGHT - 40) * 0.75f);
lv_obj_t * btn = lv_btn_create(panel);
lv_obj_align(btn, LV_ALIGN_RIGHT_MID, 0, 0);
lv_obj_add_event_cb(btn, btn_press, LV_EVENT_CLICKED, (void*)macro);
label = lv_label_create(btn);
lv_label_set_text(label, "Run");
lv_obj_center(label);
y_offset_macros += y_element_size;
}
void macros_panel_init(lv_obj_t* panel) {
y_offset_macros = 40;
lv_obj_t * btn = lv_btn_create(panel);
lv_obj_add_event_cb(btn, btn_goto_settings, LV_EVENT_CLICKED, NULL);
lv_obj_set_size(btn, TFT_HEIGHT - 40 - 20, 30);
lv_obj_align(btn, LV_ALIGN_TOP_MID, 0, 5);
lv_obj_t * label = lv_label_create(btn);
lv_label_set_text(label, LV_SYMBOL_SETTINGS " Screen Settings");
lv_obj_center(label);
MACROSQUERY query = macros_query();
if (query.count == 0){
label = lv_label_create(panel);
lv_label_set_text(label, "No macros found.\nMacros with the description\n\"CYD_SCREEN_MACRO\"\nwill show up here.");
lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
return;
}
for (int i = 0; i < query.count; i++){
create_macro_widget(query.macros[i], panel);
}
}

View File

@@ -7,3 +7,4 @@ void temp_panel_init(lv_obj_t* panel);
void print_panel_init(lv_obj_t* panel);
void move_panel_init(lv_obj_t* panel);
void progress_panel_init(lv_obj_t* panel);
void macros_panel_init(lv_obj_t* panel);

View File

@@ -12,8 +12,27 @@ static void btn_print_file(lv_event_t * e){
lv_obj_t * panel = (lv_obj_t*)lv_event_get_user_data(e);
lv_obj_del(panel);
char* buff = (char*)malloc(128 + strlen(selected_file->name));
sprintf(buff, "http://%s:%d/printer/print/start?filename=%s", global_config.klipperHost, global_config.klipperPort, selected_file->name);
char* buff = (char*)malloc(128 + (strlen(selected_file->name) * 3));
sprintf(buff, "http://%s:%d/printer/print/start?filename=", global_config.klipperHost, global_config.klipperPort);
char* ptr = buff + strlen(buff);
int filename_length = strlen(selected_file->name);
for (int i = 0; i < filename_length; i++){
char c = selected_file->name[i];
if (c == ' '){
*ptr = '%';
ptr++;
*ptr = '2';
ptr++;
*ptr = '0';
} else {
*ptr = c;
}
ptr++;
}
*ptr = 0;
HTTPClient client;
client.begin(buff);
int httpCode = client.POST("");
@@ -76,13 +95,20 @@ void print_panel_init(lv_obj_t* panel){
lv_obj_set_size(list, panel_width_margin, panel_height_margin);
lv_obj_align(list, LV_ALIGN_CENTER, 0, 0);
FILESYSTEM_FILE* files = get_files();
FILESYSTEM_FILE* files = get_files(25);
int count = 0;
while (files->name != NULL && count <= 20){
while (files != NULL && files->name != NULL && count <= 20){
lv_obj_t * btn = lv_list_add_btn(list, LV_SYMBOL_FILE, files->name);
lv_obj_add_event_cb(btn, btn_print_file_verify, LV_EVENT_CLICKED, (void*)files);
files += 1;
count++;
}
if (count <= 0){
lv_obj_del(list);
lv_obj_t * label = lv_label_create(panel);
lv_label_set_text(label, "Failed to read files.");
lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
}
}

View File

@@ -0,0 +1,307 @@
#include "lvgl.h"
#include "../../core/data_setup.h"
#include "../../conf/global_config.h"
#include <HardwareSerial.h>
enum temp_target{
TARGET_HOTEND,
TARGET_BED,
TARGET_HOTEND_CONFIG_1,
TARGET_HOTEND_CONFIG_2,
TARGET_HOTEND_CONFIG_3,
TARGET_BED_CONFIG_1,
TARGET_BED_CONFIG_2,
TARGET_BED_CONFIG_3,
};
static temp_target keyboard_target;
static char hotend_buff[40];
static char bed_buff[40];
static bool edit_mode = false;
lv_obj_t* root_panel;
static void update_printer_data_hotend_temp(lv_event_t * e){
lv_obj_t * label = lv_event_get_target(e);
sprintf(hotend_buff, "Hotend: %.0f C (Target: %.0f C)", printer.extruder_temp, printer.extruder_target_temp);
lv_label_set_text(label, hotend_buff);
}
static void update_printer_data_bed_temp(lv_event_t * e){
lv_obj_t * label = lv_event_get_target(e);
sprintf(bed_buff, "Bed: %.0f C (Target: %.0f C)", printer.bed_temp, printer.bed_target_temp);
lv_label_set_text(label, bed_buff);
}
static short get_temp_preset(int target){
switch (target){
case TARGET_HOTEND_CONFIG_1:
return global_config.hotend_presets[0];
case TARGET_HOTEND_CONFIG_2:
return global_config.hotend_presets[1];
case TARGET_HOTEND_CONFIG_3:
return global_config.hotend_presets[2];
case TARGET_BED_CONFIG_1:
return global_config.bed_presets[0];
case TARGET_BED_CONFIG_2:
return global_config.bed_presets[1];
case TARGET_BED_CONFIG_3:
return global_config.bed_presets[2];
default:
return -1;
}
}
static void update_temp_preset_label(lv_event_t * e){
lv_obj_t * label = lv_event_get_target(e);
int target = static_cast<int>(reinterpret_cast<intptr_t>(lv_event_get_user_data(e)));
short value = get_temp_preset(target);
String text_label = String(value) + " C";
lv_label_set_text(label, text_label.c_str());
}
void UpdateConfig(){
WriteGlobalConfig();
lv_msg_send(DATA_PRINTER_TEMP_PRESET, &printer);
}
static void keyboard_callback(lv_event_t * e){
lv_event_code_t code = lv_event_get_code(e);
lv_obj_t * ta = lv_event_get_target(e);
lv_obj_t * kb = (lv_obj_t *)lv_event_get_user_data(e);
if (code == LV_EVENT_READY) {
const char * text = lv_textarea_get_text(ta);
int temp = atoi(text);
if (temp < 0 || temp > 500){
return;
}
char gcode[64];
const char* space = "%20";
switch (keyboard_target){
case TARGET_HOTEND:
sprintf(gcode, "M104%sS%d", space, temp);
send_gcode(true, gcode);
break;
case TARGET_BED:
sprintf(gcode, "M140%sS%d", space, temp);
send_gcode(true, gcode);
break;
case TARGET_HOTEND_CONFIG_1:
global_config.hotend_presets[0] = temp;
UpdateConfig();
break;
case TARGET_HOTEND_CONFIG_2:
global_config.hotend_presets[1] = temp;
UpdateConfig();
break;
case TARGET_HOTEND_CONFIG_3:
global_config.hotend_presets[2] = temp;
UpdateConfig();
break;
case TARGET_BED_CONFIG_1:
global_config.bed_presets[0] = temp;
UpdateConfig();
break;
case TARGET_BED_CONFIG_2:
global_config.bed_presets[1] = temp;
UpdateConfig();
break;
case TARGET_BED_CONFIG_3:
global_config.bed_presets[2] = temp;
UpdateConfig();
break;
}
}
if(code == LV_EVENT_DEFOCUSED || code == LV_EVENT_CANCEL || code == LV_EVENT_READY) {
lv_keyboard_set_textarea(kb, NULL);
lv_obj_del(kb);
lv_obj_del(ta);
}
}
static void show_keyboard(lv_event_t * e){
lv_obj_t * keyboard = lv_keyboard_create(root_panel);
lv_obj_t * ta = lv_textarea_create(root_panel);
lv_obj_set_size(ta, TFT_HEIGHT - 40, 120);
lv_obj_align(ta, LV_ALIGN_TOP_MID, 0, 0);
lv_textarea_set_max_length(ta, 3);
//lv_textarea_set_one_line(ta, true);
lv_textarea_set_text(ta, "");
lv_textarea_set_align(ta, LV_TEXT_ALIGN_CENTER);
lv_obj_add_event_cb(ta, keyboard_callback, LV_EVENT_ALL, keyboard);
lv_keyboard_set_mode(keyboard, LV_KEYBOARD_MODE_NUMBER);
lv_keyboard_set_textarea(keyboard, ta);
}
static void show_keyboard_with_hotend(lv_event_t * e){
keyboard_target = TARGET_HOTEND;
show_keyboard(e);
}
static void show_keyboard_with_bed(lv_event_t * e){
keyboard_target = TARGET_BED;
show_keyboard(e);
}
static void cooldown_temp(lv_event_t * e){
if (printer.state == PRINTER_STATE_PRINTING){
return;
}
send_gcode(true, "M104%20S0");
send_gcode(true, "M140%20S0");
}
static void btn_extrude(lv_event_t * e){
if (printer.state == PRINTER_STATE_PRINTING){
return;
}
send_gcode(true, "M83");
send_gcode(true, "G1%20E25%20F300");
}
static void set_temp_via_preset(lv_event_t * e){
int target = static_cast<int>(reinterpret_cast<intptr_t>(lv_event_get_user_data(e)));
int value = get_temp_preset(target);
if (edit_mode) {
keyboard_target = (temp_target)target;
show_keyboard(e);
return;
}
char gcode[64];
const char* space = "%20";
if (target <= TARGET_HOTEND_CONFIG_3)
sprintf(gcode, "M104%sS%d", space, value);
else
sprintf(gcode, "M140%sS%d", space, value);
send_gcode(true, gcode);
}
static void btn_toggleable_edit(lv_event_t * e){
lv_obj_t * btn = lv_event_get_target(e);
auto state = lv_obj_get_state(btn);
edit_mode = (state & LV_STATE_CHECKED == LV_STATE_CHECKED);
}
static void btn_retract(lv_event_t * e){
if (printer.state == PRINTER_STATE_PRINTING){
return;
}
send_gcode(true, "M83");
send_gcode(true, "G1%20E-25%20F300");
}
void temp_panel_init(lv_obj_t* panel){
root_panel = panel;
edit_mode = false;
const int btn_row_y_one = 30;
const int btn_row_y_two = 100;
auto panel_width = TFT_HEIGHT - 40;
lv_obj_t * label = lv_label_create(panel);
lv_label_set_text(label, "???");
lv_obj_align(label, LV_ALIGN_TOP_LEFT, 10, 10);
lv_obj_add_event_cb(label, update_printer_data_hotend_temp, LV_EVENT_MSG_RECEIVED, NULL);
lv_msg_subscribe_obj(DATA_PRINTER_DATA, label, NULL);
label = lv_label_create(panel);
lv_label_set_text(label, "???");
lv_obj_align(label, LV_ALIGN_TOP_LEFT, 10, 80);
lv_obj_add_event_cb(label, update_printer_data_bed_temp, LV_EVENT_MSG_RECEIVED, NULL);
lv_msg_subscribe_obj(DATA_PRINTER_DATA, label, NULL);
lv_obj_t * btn = lv_btn_create(panel);
lv_obj_align(btn, LV_ALIGN_TOP_RIGHT, -10, btn_row_y_one);
lv_obj_add_event_cb(btn, show_keyboard_with_hotend, LV_EVENT_CLICKED, panel);
lv_obj_set_width(btn, panel_width / 4 - 10);
label = lv_label_create(btn);
lv_label_set_text(label, "Set");
lv_obj_center(label);
btn = lv_btn_create(panel);
lv_obj_align(btn, LV_ALIGN_TOP_RIGHT, -10, btn_row_y_two);
lv_obj_add_event_cb(btn, show_keyboard_with_bed, LV_EVENT_CLICKED, panel);
lv_obj_set_width(btn, panel_width / 4 - 10);
label = lv_label_create(btn);
lv_label_set_text(label, "Set");
lv_obj_center(label);
// Presets
for (int i = 0; i < 3; i++){
int x_pos = 10 + (panel_width / 4) * i - (3 * i);
btn = lv_btn_create(panel);
lv_obj_align(btn, LV_ALIGN_TOP_LEFT, x_pos, btn_row_y_one);
lv_obj_add_event_cb(btn, set_temp_via_preset, LV_EVENT_CLICKED, reinterpret_cast<void*>(TARGET_HOTEND_CONFIG_1 + i));
lv_obj_set_width(btn, panel_width / 4 - 10);
label = lv_label_create(btn);
lv_label_set_text(label, "???");
lv_obj_center(label);
lv_obj_add_event_cb(label, update_temp_preset_label, LV_EVENT_MSG_RECEIVED, reinterpret_cast<void*>(TARGET_HOTEND_CONFIG_1 + i));
lv_msg_subscribe_obj(DATA_PRINTER_TEMP_PRESET, label, NULL);
btn = lv_btn_create(panel);
lv_obj_align(btn, LV_ALIGN_TOP_LEFT, x_pos, btn_row_y_two);
lv_obj_add_event_cb(btn, set_temp_via_preset, LV_EVENT_CLICKED, reinterpret_cast<void*>(TARGET_BED_CONFIG_1 + i));
lv_obj_set_width(btn, panel_width / 4 - 10);
label = lv_label_create(btn);
lv_label_set_text(label, "???");
lv_obj_center(label);
lv_obj_add_event_cb(label, update_temp_preset_label, LV_EVENT_MSG_RECEIVED, reinterpret_cast<void*>(TARGET_BED_CONFIG_1 + i));
lv_msg_subscribe_obj(DATA_PRINTER_TEMP_PRESET, label, NULL);
}
btn = lv_btn_create(panel);
lv_obj_align(btn, LV_ALIGN_BOTTOM_LEFT, 10, -50);
lv_obj_set_size(btn, panel_width / 2 - 15, 40);
lv_obj_add_event_cb(btn, cooldown_temp, LV_EVENT_CLICKED, panel);
label = lv_label_create(btn);
lv_label_set_text(label, "Cooldown");
lv_obj_center(label);
btn = lv_btn_create(panel);
lv_obj_align(btn, LV_ALIGN_BOTTOM_RIGHT, -10, -50);
lv_obj_add_event_cb(btn, btn_toggleable_edit, LV_EVENT_CLICKED, NULL);
lv_obj_add_flag(btn, LV_OBJ_FLAG_CHECKABLE);
lv_obj_set_size(btn, panel_width / 2 - 15, 40);
label = lv_label_create(btn);
lv_label_set_text(label, "Edit Presets");
lv_obj_center(label);
btn = lv_btn_create(panel);
lv_obj_align(btn, LV_ALIGN_BOTTOM_LEFT, 10, -5);
lv_obj_add_event_cb(btn, btn_extrude, LV_EVENT_CLICKED, NULL);
lv_obj_set_size(btn, panel_width / 2 - 15, 40);
label = lv_label_create(btn);
lv_label_set_text(label, LV_SYMBOL_DOWN " Extrude");
lv_obj_center(label);
btn = lv_btn_create(panel);
lv_obj_align(btn, LV_ALIGN_BOTTOM_RIGHT, -10, -5);
lv_obj_add_event_cb(btn, btn_retract, LV_EVENT_CLICKED, NULL);
lv_obj_set_size(btn, panel_width / 2 - 15, 40);
label = lv_label_create(btn);
lv_label_set_text(label, LV_SYMBOL_UP " Retract");
lv_obj_center(label);
lv_msg_send(DATA_PRINTER_DATA, &printer);
lv_msg_send(DATA_PRINTER_TEMP_PRESET, &printer);
}

View File

@@ -77,12 +77,11 @@ static void wifi_btn_event_handler(lv_event_t * e){
void wifi_init_inner(){
WiFi.disconnect();
lv_obj_clean(lv_scr_act());
if (global_config.wifiConfigured){
WiFi.begin(global_config.wifiSSID, global_config.wifiPassword);
lv_obj_clean(lv_scr_act());
lv_obj_t * label = lv_label_create(lv_scr_act());
lv_label_set_text(label, "Connecting to WiFi");
lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
@@ -98,8 +97,6 @@ void wifi_init_inner(){
return;
}
lv_obj_clean(lv_scr_act());
lv_obj_t * label = lv_label_create(lv_scr_act());
lv_label_set_text(label, "Scanning for networks...");
lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
@@ -108,8 +105,6 @@ void wifi_init_inner(){
lv_task_handler();
lv_refr_now(NULL);
int n = WiFi.scanNetworks();
lv_obj_clean(lv_scr_act());
lv_obj_t * refreshBtn = lv_btn_create(lv_scr_act());
@@ -128,25 +123,50 @@ void wifi_init_inner(){
lv_obj_align(list, LV_ALIGN_TOP_LEFT, 10, 40);
lv_obj_set_size(list, TFT_HEIGHT - 20, TFT_WIDTH - 40 - 5);
for (int i = 0; i < n; ++i) {
const char* ssid = WiFi.SSID(i).c_str();
int len = strlen(ssid);
int n = WiFi.scanNetworks();
if (len == 0)
for (int i = 0; i < n; ++i) {
String ssid = WiFi.SSID(i);
char* ssid_copy = (char*)malloc(ssid.length() + 1);
int j = 0;
for (; j < ssid.length(); ++j){
if (ssid[j] == '\0')
continue;
const char* ssid_copy = (const char*)malloc(len + 1);
strcpy((char*)ssid_copy, ssid);
ssid_copy[j] = ssid[j];
}
ssid_copy[j] = '\0';
lv_obj_t * btn = lv_list_add_btn(list, LV_SYMBOL_WIFI, ssid_copy);
lv_obj_add_event_cb(btn, wifi_btn_event_handler, LV_EVENT_ALL, (void*)ssid_copy);
}
}
const char* errs[] = {
"Idle",
"No SSID Available",
"Scan Completed",
"Connected",
"Connection Failed",
"Connection Lost",
"Disconnected"
};
const int print_freq = 1000;
int print_timer = 0;
void wifi_init(){
WiFi.mode(WIFI_STA);
wifi_init_inner();
while (!global_config.wifiConfigured || WiFi.status() != WL_CONNECTED){
if (millis() - print_timer > print_freq){
print_timer = millis();
Serial.printf("WiFi Status: %s\n", errs[WiFi.status()]);
}
lv_timer_handler();
lv_task_handler();
}

View File

@@ -1,3 +1,44 @@
# CYD-Klipper-Display
![GitHub release (with filter)](https://img.shields.io/github/v/release/suchmememanyskill/CYD-Klipper)
[![Donations](https://img.shields.io/badge/Support%20on-Ko--Fi-red)](https://ko-fi.com/suchmememanyskill)
WIP!
# CYD-Klipper
An implementation of a wireless Klipper status display on an ESP32 + screen. Uses Moonraker to fetch data.
A simple and cheap solution to use a dedicated screen with Klipper, a 3d printing Firmware.
![showcase_image](readme/PXL_20231113_171629383.jpg)
### Required hardware
A ESP32-2432S028R is required to run this project. You can find out where to buy these on the ["ESP32 Cheap Yellow Display"](https://github.com/witnessmenow/ESP32-Cheap-Yellow-Display#where-to-buy) repository.
### Features
- View printer status
- View print progress
- Start a print
- Move the printer
- Manage temperature
- Extrude/Retract filament
- Execute predefined gcode macros
### Install
[There is a web-based installer available. This is only supported on Chrome, Edge or Opera, and only on Desktop.](https://suchmememanyskill.github.io/CYD-Klipper/)
On initial install, all data should be wiped. On updates, data should be able to be kept without issues.
There are no 'over the air' updates. Each update has to be applied manually.
### Screenshots
(Quite literally shots of the screen. I'm sorry)
-|-
:-:|:-:
![1](readme/PXL_20231113_142717308.jpg)|![2](readme/PXL_20231113_171701876.jpg)
![3](readme/PXL_20231113_171715809.jpg)|![4](readme/PXL_20231113_171724404.jpg)
![5](readme/PXL_20231113_171751745.jpg)|![6](readme/PXL_20231113_171809315.jpg)
### Credits
- [xtouch](https://github.com/xperiments-in/xtouch)
- [ESP32-Cheap-Yellow-Display](https://github.com/witnessmenow/ESP32-Cheap-Yellow-Display)

33
_site/index.html Normal file
View File

@@ -0,0 +1,33 @@
<!DOCTYPE html>
<head>
<style>
@import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap');
* {
font-family: 'Roboto', sans-serif;
}
.main {
width: fit-content;
margin: auto;
}
</style>
<script type="module" src="https://unpkg.com/esp-web-tools@9/dist/web/install-button.js?module"></script>
</head>
<body>
<section class="main">
<h2>CYD-Klipper</h2>
<p>An implementation of a Klipper status display on an ESP32 + screen.<br>Uses Moonraker to fetch data.</p>
<img alt="GitHub release (with filter)" src="https://img.shields.io/github/v/release/suchmememanyskill/CYD-Klipper">
<a href="https://github.com/suchmememanyskill/CYD-Klipper"><img alt="GitHub repo" src="https://img.shields.io/badge/Source-Github-blue.svg"></a>
<a href="https://ko-fi.com/suchmememanyskill"><img alt="Donate KoFi" src="https://img.shields.io/badge/Support%2FDonate%20On-Ko%20Fi-red"></a>
<section class="install">
<h3>Install</h3>
<p>Note: You may need to hold the 'BOOT' button on the device while pressing install</p>
<esp-web-install-button
manifest="https://suchmememanyskill.github.io/CYD-Klipper/manifest.json"></esp-web-install-button>
</section>
</section>
</body>

27
_site/manifest.json Normal file
View File

@@ -0,0 +1,27 @@
{
"name": "CYD-Klipper",
"new_install_prompt_erase": true,
"builds": [
{
"chipFamily": "ESP32",
"parts": [
{
"path": "output/bootloader.bin",
"offset": 4096
},
{
"path": "output/partitions.bin",
"offset": 32768
},
{
"path": "output/boot_app0.bin",
"offset": 57344
},
{
"path": "output/firmware.bin",
"offset": 65536
}
]
}
]
}

10
_site/manifest_wipe.json Normal file
View File

@@ -0,0 +1,10 @@
{
"name": "CYD-Klipper",
"new_install_prompt_erase": false,
"builds": [
{
"path": "output/merged-firmware.bin",
"offset": 0
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 533 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 890 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 552 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 911 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB