diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 33941656da..11bb9aa325 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -78,6 +78,7 @@ static t_config_enum_values s_keys_map_PrintHostType { { "prusalink", htPrusaLink }, { "prusaconnect", htPrusaConnect }, { "octoprint", htOctoPrint }, + { "crealityprint", htCrealityPrint }, { "duet", htDuet }, { "flashair", htFlashAir }, { "astrobox", htAstroBox }, @@ -3399,6 +3400,7 @@ void PrintConfigDef::init_fff_params() def->enum_values.push_back("repetier"); def->enum_values.push_back("mks"); def->enum_values.push_back("esp3d"); + def->enum_values.push_back("crealityprint"); def->enum_values.push_back("obico"); def->enum_values.push_back("flashforge"); def->enum_values.push_back("simplyprint"); @@ -3411,6 +3413,7 @@ void PrintConfigDef::init_fff_params() def->enum_labels.push_back("Repetier"); def->enum_labels.push_back("MKS"); def->enum_labels.push_back("ESP3D"); + def->enum_labels.push_back("CrealityPrint"); def->enum_labels.push_back("Obico"); def->enum_labels.push_back("Flashforge"); def->enum_labels.push_back("SimplyPrint"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 0b418f268e..e5255d3537 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -42,7 +42,7 @@ enum class FuzzySkinType { }; enum PrintHostType { - htPrusaLink, htPrusaConnect, htOctoPrint, htDuet, htFlashAir, htAstroBox, htRepetier, htMKS, htESP3D, htObico, htFlashforge, htSimplyPrint + htPrusaLink, htPrusaConnect, htOctoPrint, htDuet, htFlashAir, htAstroBox, htRepetier, htMKS, htESP3D, htCrealityPrint, htObico, htFlashforge, htSimplyPrint }; enum AuthorizationType { diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 87d9396578..0c54d835ad 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -535,6 +535,8 @@ set(SLIC3R_GUI_SOURCES Utils/MKS.hpp Utils/ESP3D.cpp Utils/ESP3D.hpp + Utils/CrealityPrint.cpp + Utils/CrealityPrint.hpp Utils/WxFontUtils.cpp Utils/WxFontUtils.hpp Utils/Duet.cpp diff --git a/src/slic3r/Utils/CrealityPrint.cpp b/src/slic3r/Utils/CrealityPrint.cpp new file mode 100644 index 0000000000..cc112c9518 --- /dev/null +++ b/src/slic3r/Utils/CrealityPrint.cpp @@ -0,0 +1,228 @@ +#include "CrealityPrint.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "slic3r/GUI/GUI.hpp" +#include "slic3r/GUI/I18N.hpp" +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/format.hpp" +#include "Http.hpp" +#include "libslic3r/AppConfig.hpp" +#include "Bonjour.hpp" +#include "slic3r/GUI/BonjourDialog.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +using json = nlohmann::json; +using std::to_string; + +namespace beast = boost::beast; +namespace http = beast::http; +namespace websocket = beast::websocket; +namespace net = boost::asio; +using tcp = boost::asio::ip::tcp; + +namespace fs = boost::filesystem; +namespace pt = boost::property_tree; + +namespace Slic3r { + +CrealityPrint::CrealityPrint(DynamicPrintConfig* config) : + m_host(config->opt_string("print_host")), + m_web_ui(config->opt_string("print_host_webui")), + m_cafile(config->opt_string("printhost_cafile")), + m_port(config->opt_string("printhost_port")), + m_apikey(config->opt_string("printhost_apikey")), + m_ssl_revoke_best_effort(config->opt_bool("printhost_ssl_ignore_revoke")) +{} + +const char* CrealityPrint::get_name() const { return "CrealityPrint"; } + +std::string CrealityPrint::get_host() const { + return m_host; +} +void CrealityPrint::set_auth(Http& http) const +{ + http.header("Authorization", "Bearer " + m_apikey); + if (!m_cafile.empty()) { + http.ca_file(m_cafile); + } +} + +wxString CrealityPrint::get_test_ok_msg() const { return _(L("Connected to CrealityPrint successfully!")); } + +wxString CrealityPrint::get_test_failed_msg(wxString& msg) const +{ + return GUI::format_wxstr("%s: %s", _L("Could not connect to CrealityPrint"), msg.Truncate(256)); +} + +bool CrealityPrint::test(wxString& msg) const +{ + bool res = true; + const char* name = get_name(); + auto url = make_url("info"); + + BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get version at: %2%") % name % url; + // Here we do not have to add custom "Host" header - the url contains host filled by user and libCurl will set the header by itself. + auto http = Http::get(std::move(url)); + set_auth(http); + http.on_error([&](std::string body, std::string error, unsigned status) { + BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting version: %2%, HTTP %3%, body: `%4%`") % name % error % status % + body; + res = false; + msg = format_error(body, error, status); + }) + .on_complete([&, this](std::string body, unsigned) { + BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got version: %2%") % name % body; + }) +#ifdef WIN32 + .ssl_revoke_best_effort(m_ssl_revoke_best_effort) + .on_ip_resolve([&](std::string address) { + // Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail. + // Remember resolved address to be reused at successive REST API call. + msg = GUI::from_u8(address); + }) +#endif // WIN32 + .perform_sync(); + + return res; +} + +PrintHostPostUploadActions CrealityPrint::get_post_upload_actions() const { + return PrintHostPostUploadAction::StartPrint; +} + +bool CrealityPrint::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const +{ + const char* name = get_name(); + const auto upload_filename = upload_data.upload_path.filename(); + const auto upload_parent_path = upload_data.upload_path.parent_path(); + wxString test_msg; + if (!test(test_msg)) { + error_fn(std::move(test_msg)); + return false; + } + + bool res = true; + auto url = make_url("upload/" + upload_filename.string()); + + auto http = Http::post(url); // std::move(url)); + set_auth(http); + http.form_add("path", upload_parent_path.string()) + .form_add_file("file", upload_data.source_path.string(), upload_filename.string()) + + .on_complete([&](std::string body, unsigned status) { + BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: File uploaded: HTTP %2%: %3%") % name % status % body; + + if (upload_data.post_action == PrintHostPostUploadAction::StartPrint) { + start_print(upload_filename.string()); + } + }) + .on_error([&](std::string body, std::string error, unsigned status) { + BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error uploading file to %2%: %3%, HTTP %4%, body: `%5%`") % name % url % error % + status % body; + error_fn(format_error(body, error, status)); + res = false; + }) + .on_progress([&](Http::Progress progress, bool& cancel) { + prorgess_fn(std::move(progress), cancel); + if (cancel) { + // Upload was canceled + BOOST_LOG_TRIVIAL(info) << name << ": Upload canceled"; + res = false; + } + }) +#ifdef WIN32 + .ssl_revoke_best_effort(m_ssl_revoke_best_effort) +#endif + .perform_sync(); + return res; +} + +std::string CrealityPrint::make_url(const std::string &path) const +{ + if (m_host.find("http://") == 0 || m_host.find("https://") == 0) { + if (m_host.back() == '/') { + return (boost::format("%1%%2%") % m_host % path).str(); + } else { + return (boost::format("%1%/%2%") % m_host % path).str(); + } + } else { + return (boost::format("http://%1%/%2%") % m_host % path).str(); + } +} + +std::string CrealityPrint::start_print(const std::string &filename) const +{ + try { + std::string host = m_host; + auto const port = "9999"; + + json j2 = { + { "method", "set" }, + { + "params", { + { "opGcodeFile", "printprt:/usr/data/printer_data/gcodes/" + filename } + } + } + }; + + net::io_context ioc; + + tcp::resolver resolver{ioc}; + websocket::stream ws{ioc}; + + auto const results = resolver.resolve(host, port); + + auto ep = net::connect(ws.next_layer(), results); + + host += ':' + std::to_string(ep.port()); + + ws.set_option(websocket::stream_base::decorator( + [](websocket::request_type& req) + { + req.set(http::field::user_agent, + std::string(BOOST_BEAST_VERSION_STRING) + + " websocket-client-coro"); + })); + + ws.handshake(host, "/"); + + ws.write(net::buffer(to_string(j2))); + + beast::flat_buffer buffer; + + ws.read(buffer); + + ws.close(websocket::close_code::normal); + } catch(std::exception const& e) { + std::cerr << "Error: " << e.what() << std::endl; + return "false"; + } + + return "true"; +} + +} diff --git a/src/slic3r/Utils/CrealityPrint.hpp b/src/slic3r/Utils/CrealityPrint.hpp new file mode 100644 index 0000000000..888b260492 --- /dev/null +++ b/src/slic3r/Utils/CrealityPrint.hpp @@ -0,0 +1,48 @@ +#ifndef slic3r_CrealityPrint_hpp_ +#define slic3r_CrealityPrint_hpp_ + +#include +#include +#include +#include + +#include "PrintHost.hpp" +#include "libslic3r/PrintConfig.hpp" + +namespace Slic3r { + +class DynamicPrintConfig; +class Http; +class CrealityPrint : public PrintHost +{ +public: + CrealityPrint(DynamicPrintConfig* config); + ~CrealityPrint() override = default; + + const char* get_name() const override; + virtual bool can_test() const { return true; }; + std::string get_host() const override; + bool has_auto_discovery() const override { return true; } + + wxString get_test_ok_msg() const override; + wxString get_test_failed_msg(wxString& msg) const override; + virtual bool test(wxString& curl_msg) const override; + PrintHostPostUploadActions get_post_upload_actions() const; + bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const override; + +protected: + virtual void set_auth(Http& http) const; +private: + std::string m_host; + std::string m_port; + std::string m_apikey; + std::string m_cafile; + std::string m_web_ui; + bool m_ssl_revoke_best_effort; + + std::string make_url(const std::string& path) const; + std::string start_print(const std::string& path) const; +}; +} // namespace Slic3r + +#endif \ No newline at end of file diff --git a/src/slic3r/Utils/ESP3D.cpp b/src/slic3r/Utils/ESP3D.cpp index 531e9d08e9..ff33a61a68 100644 --- a/src/slic3r/Utils/ESP3D.cpp +++ b/src/slic3r/Utils/ESP3D.cpp @@ -71,7 +71,7 @@ bool ESP3D::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn http.header("Connection", "keep-alive") .form_add_file("file", upload_data.source_path, short_name) .on_complete([&](std::string body, unsigned status) { - // check for OK + // check for OK if (upload_data.post_action == PrintHostPostUploadAction::StartPrint) { wxString errormsg; res = start_print(errormsg, short_name); diff --git a/src/slic3r/Utils/PrintHost.cpp b/src/slic3r/Utils/PrintHost.cpp index f88dceb14a..256b764133 100644 --- a/src/slic3r/Utils/PrintHost.cpp +++ b/src/slic3r/Utils/PrintHost.cpp @@ -20,6 +20,7 @@ #include "Repetier.hpp" #include "MKS.hpp" #include "ESP3D.hpp" +#include "CrealityPrint.hpp" #include "../GUI/PrintHostDialogs.hpp" #include "../GUI/MainFrame.hpp" #include "Obico.hpp" @@ -60,6 +61,7 @@ PrintHost* PrintHost::get_print_host(DynamicPrintConfig *config) case htPrusaConnect: return new PrusaConnect(config); case htMKS: return new MKS(config); case htESP3D: return new ESP3D(config); + case htCrealityPrint: return new CrealityPrint(config); case htObico: return new Obico(config); case htFlashforge: return new Flashforge(config); case htSimplyPrint: return new SimplyPrint(config);