diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 33105bff3..e639a6734 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -1327,8 +1327,10 @@ void PrintConfigDef::init_fff_params() def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); def->enum_values.push_back("octoprint"); def->enum_values.push_back("duet"); + def->enum_values.push_back("flashair"); def->enum_labels.push_back("OctoPrint"); def->enum_labels.push_back("Duet"); + def->enum_labels.push_back("FlashAir"); def->mode = comAdvanced; def->set_default_value(new ConfigOptionEnum(htOctoPrint)); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 5c287ba93..4b007fc51 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -30,7 +30,7 @@ enum GCodeFlavor : unsigned char { }; enum PrintHostType { - htOctoPrint, htDuet + htOctoPrint, htDuet, htFlashAir }; enum InfillPattern { @@ -102,6 +102,7 @@ template<> inline const t_config_enum_values& ConfigOptionEnum::g if (keys_map.empty()) { keys_map["octoprint"] = htOctoPrint; keys_map["duet"] = htDuet; + keys_map["flashair"] = htFlashAir; } return keys_map; } diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 17b76e629..84a60da6e 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -144,6 +144,8 @@ set(SLIC3R_GUI_SOURCES Utils/OctoPrint.hpp Utils/Duet.cpp Utils/Duet.hpp + Utils/FlashAir.cpp + Utils/FlashAir.hpp Utils/PrintHost.cpp Utils/PrintHost.hpp Utils/Bonjour.cpp diff --git a/src/slic3r/Utils/FlashAir.cpp b/src/slic3r/Utils/FlashAir.cpp new file mode 100644 index 000000000..d7b97fc54 --- /dev/null +++ b/src/slic3r/Utils/FlashAir.cpp @@ -0,0 +1,220 @@ +#include "FlashAir.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "libslic3r/PrintConfig.hpp" +#include "slic3r/GUI/GUI.hpp" +#include "slic3r/GUI/I18N.hpp" +#include "slic3r/GUI/MsgDialog.hpp" +#include "Http.hpp" + +namespace fs = boost::filesystem; +namespace pt = boost::property_tree; + +namespace Slic3r { + +FlashAir::FlashAir(DynamicPrintConfig *config) : + host(config->opt_string("print_host")) +{} + +FlashAir::~FlashAir() {} + +const char* FlashAir::get_name() const { return "FlashAir"; } + +bool FlashAir::test(wxString &msg) const +{ + // Since the request is performed synchronously here, + // it is ok to refer to `msg` from within the closure + + const char *name = get_name(); + + bool res = false; + auto url = make_url("command.cgi", "op", "118"); + + BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get upload enabled at: %2%") % name % url; + + auto http = Http::get(std::move(url)); + http.on_error([&](std::string body, std::string error, unsigned status) { + BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting upload enabled: %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 upload enabled: %2%") % name % body; + + res = boost::starts_with(body, "1"); + if (! res) { + msg = _(L("Upload not enabled on FlashAir card.")); + } + }) + .perform_sync(); + + return res; +} + +wxString FlashAir::get_test_ok_msg () const +{ + return _(L("Connection to FlashAir works correctly and upload is enabled.")); +} + +wxString FlashAir::get_test_failed_msg (wxString &msg) const +{ + return wxString::Format("%s: %s", _(L("Could not connect to FlashAir")), msg, _(L("Note: FlashAir with firmware 2.00.02 or newer and activated upload function is required.")); +} + +bool FlashAir::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_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 = false; + + auto urlPrepare = make_url("upload.cgi", "WRITEPROTECT=ON&FTIME", timestamp_str()); + auto urlUpload = make_url("upload.cgi"); + + BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Uploading file %2% at %3% / %4%, filename: %5%") + % name + % upload_data.source_path + % urlPrepare + % urlUpload + % upload_filename.string(); + + // set filetime for upload and make card writeprotect to prevent filesystem damage + auto httpPrepare = Http::get(std::move(urlPrepare)); + httpPrepare.on_error([&](std::string body, std::string error, unsigned status) { + BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error prepareing upload: %2%, HTTP %3%, body: `%4%`") % name % error % status % body; + error_fn(format_error(body, error, status)); + res = false; + }) + .on_complete([&, this](std::string body, unsigned) { + BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got prepare result: %2%") % name % body; + res = boost::icontains(body, "SUCCESS"); + if (! res) { + BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Request completed but no SUCCESS message was received.") % name; + error_fn(format_error(body, L("Unknown error occured"), 0)); + } + }) + .perform_sync(); + + if(! res ) { + return res; + } + + // start file upload + auto http = Http::post(std::move(urlUpload)); + http.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; + res = boost::icontains(body, "SUCCESS"); + if (! res) { + BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Request completed but no SUCCESS message was received.") % name; + error_fn(format_error(body, L("Unknown error occured"), 0)); + } + }) + .on_error([&](std::string body, std::string error, unsigned status) { + BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error uploading file: %2%, HTTP %3%, body: `%4%`") % name % 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) << boost::format("%1%: Upload canceled") % name; + res = false; + } + }) + .perform_sync(); + + return res; +} + +bool FlashAir::has_auto_discovery() const +{ + return false; +} + +bool FlashAir::can_test() const +{ + return true; +} + +bool FlashAir::can_start_print() const +{ + return false; +} + +std::string FlashAir::timestamp_str() const +{ + auto t = std::time(nullptr); + auto tm = *std::localtime(&t); + + const char *name = get_name(); + + unsigned long fattime = ((tm.tm_year - 80) << 25) | + ((tm.tm_mon + 1) << 21) | + (tm.tm_mday << 16) | + (tm.tm_hour << 11) | + (tm.tm_min << 5) | + (tm.tm_sec >> 1); + + return (boost::format("%1$#x") % fattime).str(); +} + +std::string FlashAir::make_url(const std::string &path) const +{ + if (host.find("http://") == 0 || host.find("https://") == 0) { + if (host.back() == '/') { + return (boost::format("%1%%2%") % host % path).str(); + } else { + return (boost::format("%1%/%2%") % host % path).str(); + } + } else { + if (host.back() == '/') { + return (boost::format("http://%1%%2%") % host % path).str(); + } else { + return (boost::format("http://%1%/%2%") % host % path).str(); + } + } +} + +std::string FlashAir::make_url(const std::string &path, const std::string &arg, const std::string &val) const +{ + if (host.find("http://") == 0 || host.find("https://") == 0) { + if (host.back() == '/') { + return (boost::format("%1%%2%?%3%=%4%") % host % path % arg % val).str(); + } else { + return (boost::format("%1%/%2%?%3%=%4%") % host % path % arg % val).str(); + } + } else { + if (host.back() == '/') { + return (boost::format("http://%1%%2%?%3%=%4%") % host % path % arg % val).str(); + } else { + return (boost::format("http://%1%/%2%?%3%=%4%") % host % path % arg % val).str(); + } + } +} + +} diff --git a/src/slic3r/Utils/FlashAir.hpp b/src/slic3r/Utils/FlashAir.hpp new file mode 100644 index 000000000..1499eee5d --- /dev/null +++ b/src/slic3r/Utils/FlashAir.hpp @@ -0,0 +1,44 @@ +#ifndef slic3r_FlashAir_hpp_ +#define slic3r_FlashAir_hpp_ + +#include +#include + +#include "PrintHost.hpp" + + +namespace Slic3r { + + +class DynamicPrintConfig; +class Http; + +class FlashAir : public PrintHost +{ +public: + FlashAir(DynamicPrintConfig *config); + virtual ~FlashAir(); + + virtual const char* get_name() const; + + virtual bool test(wxString &curl_msg) const; + virtual wxString get_test_ok_msg () const; + virtual wxString get_test_failed_msg (wxString &msg) const; + virtual bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const; + virtual bool has_auto_discovery() const; + virtual bool can_test() const; + virtual bool can_start_print() const; + virtual std::string get_host() const { return host; } + +private: + std::string host; + + std::string timestamp_str() const; + std::string make_url(const std::string &path) const; + std::string make_url(const std::string &path, const std::string &arg, const std::string &val) const; +}; + + +} + +#endif diff --git a/src/slic3r/Utils/PrintHost.cpp b/src/slic3r/Utils/PrintHost.cpp index ab52b2344..59a929ecc 100644 --- a/src/slic3r/Utils/PrintHost.cpp +++ b/src/slic3r/Utils/PrintHost.cpp @@ -14,6 +14,7 @@ #include "libslic3r/Channel.hpp" #include "OctoPrint.hpp" #include "Duet.hpp" +#include "FlashAir.hpp" #include "../GUI/PrintHostDialogs.hpp" namespace fs = boost::filesystem; @@ -43,6 +44,7 @@ PrintHost* PrintHost::get_print_host(DynamicPrintConfig *config) switch (host_type) { case htOctoPrint: return new OctoPrint(config); case htDuet: return new Duet(config); + case htFlashAir: return new FlashAir(config); default: return nullptr; } } else {