Klipper connection over serial

This commit is contained in:
Sims
2024-11-08 18:07:32 +01:00
parent 0e57aed87c
commit d4ce7a71ba
6 changed files with 284 additions and 28 deletions

View File

@@ -301,6 +301,7 @@ bool send_command_without_response(WiFiClientSecure& client, const char* command
return wifi_client_response_pass(client); 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) 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())); LOG_F(("Heap space pre-file-parse: %d bytes\n", esp_get_free_heap_size()));

View File

@@ -0,0 +1,235 @@
#include "serial_klipper_printer_integration.hpp"
#include <HardwareSerial.h>
#include <UrlEncode.h>
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;
}

View File

@@ -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);
};

View File

@@ -226,7 +226,7 @@ PrinterDataMinimal KlipperPrinter::fetch_min()
JsonDocument doc; JsonDocument doc;
deserializeJson(doc, client.getStream()); deserializeJson(doc, client.getStream());
return parse_state_min(doc); parse_state_min(doc, &data);
} }
else else
{ {

View File

@@ -13,20 +13,21 @@ typedef struct {
class KlipperPrinter : public BasePrinter class KlipperPrinter : public BasePrinter
{ {
private: private:
unsigned char lock_absolute_relative_mode_swap{};
unsigned char klipper_request_consecutive_fail_count{};
unsigned int slicer_estimated_print_time_s{}; unsigned int slicer_estimated_print_time_s{};
unsigned int last_slicer_time_query{}; unsigned int last_slicer_time_query{};
void configure_http_client(HTTPClient &client, String url_part, bool stream, int timeout); void configure_http_client(HTTPClient &client, String url_part, bool stream, int timeout);
protected: protected:
unsigned char lock_absolute_relative_mode_swap{};
unsigned char klipper_request_consecutive_fail_count{};
bool send_emergency_stop(); bool send_emergency_stop();
int get_slicer_time_estimate_s(); int get_slicer_time_estimate_s();
void init_ui_panels(); void init_ui_panels();
int parse_slicer_time_estimate(JsonDocument& in); int parse_slicer_time_estimate(JsonDocument& in);
void parse_state(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); Macros parse_macros(JsonDocument &in);
int parse_macros_count(JsonDocument &in); int parse_macros_count(JsonDocument &in);
PowerDevices parse_power_devices(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 move_printer(const char* axis, float amount, bool relative);
bool execute_feature(PrinterFeatures feature); bool execute_feature(PrinterFeatures feature);
bool connect(); virtual bool connect();
bool fetch(); virtual bool fetch();
PrinterDataMinimal fetch_min(); virtual PrinterDataMinimal fetch_min();
void disconnect(); void disconnect();
Macros get_macros(); virtual Macros get_macros();
int get_macros_count(); virtual int get_macros_count();
bool execute_macro(const char* macro); bool execute_macro(const char* macro);
PowerDevices get_power_devices(); virtual PowerDevices get_power_devices();
int get_power_devices_count(); virtual int get_power_devices_count();
bool set_power_device_state(const char* device_name, bool state); virtual bool set_power_device_state(const char* device_name, bool state);
Files get_files(); virtual Files get_files();
bool start_file(const char* filename); virtual bool start_file(const char* filename);
Thumbnail get_32_32_png_image_thumbnail(const char* gcode_filename); virtual Thumbnail get_32_32_png_image_thumbnail(const char* gcode_filename);
bool set_target_temperature(PrinterTemperatureDevice device, unsigned int temperature); 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 { enum KlipperConnectionStatus {

View File

@@ -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"]; auto status = in["result"]["status"];
PrinterDataMinimal data = {};
data.success = true;
if (status.containsKey("webhooks")) if (status.containsKey("webhooks"))
{ {
@@ -195,15 +193,15 @@ PrinterDataMinimal KlipperPrinter::parse_state_min(JsonDocument &in)
if (strcmp(state, "shutdown") == 0) 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")) if (status.containsKey("virtual_sdcard"))
{ {
data.print_progress = status["virtual_sdcard"]["progress"]; data->print_progress = status["virtual_sdcard"]["progress"];
} }
if (status.containsKey("print_stats")) if (status.containsKey("print_stats"))
@@ -212,24 +210,22 @@ PrinterDataMinimal KlipperPrinter::parse_state_min(JsonDocument &in)
if (state == nullptr) if (state == nullptr)
{ {
data.state = PrinterState::PrinterStateError; data->state = PrinterState::PrinterStateError;
} }
else if (strcmp(state, "printing") == 0) else if (strcmp(state, "printing") == 0)
{ {
data.state = PrinterState::PrinterStatePrinting; data->state = PrinterState::PrinterStatePrinting;
} }
else if (strcmp(state, "paused") == 0) 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) 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) Macros KlipperPrinter::parse_macros(JsonDocument &in)