OctoPrint basics working, niceties to-do

This commit is contained in:
Vojtech Kral 2018-12-14 15:27:34 +01:00
parent b613334b81
commit 862217a6b3
16 changed files with 450 additions and 210 deletions

View file

@ -20,7 +20,6 @@
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/MsgDialog.hpp"
#include "slic3r/GUI/PrintHostDialogs.hpp" // XXX
#include "Http.hpp"
namespace fs = boost::filesystem;
@ -55,89 +54,90 @@ wxString Duet::get_test_failed_msg (wxString &msg) const
return wxString::Format("%s: %s", _(L("Could not connect to Duet")), msg);
}
bool Duet::send_gcode(const std::string &filename) const
{
enum { PROGRESS_RANGE = 1000 };
const auto errortitle = _(L("Error while uploading to the Duet"));
fs::path filepath(filename);
PrintHostSendDialog send_dialog(filepath.filename());
if (send_dialog.ShowModal() != wxID_OK) { return false; }
const bool print = send_dialog.start_print();
const auto upload_filepath = send_dialog.filename();
const auto upload_filename = upload_filepath.filename();
const auto upload_parent_path = upload_filepath.parent_path();
wxProgressDialog progress_dialog(
_(L("Duet upload")),
_(L("Sending G-code file to Duet...")),
PROGRESS_RANGE, nullptr, wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT);
progress_dialog.Pulse();
wxString connect_msg;
if (!connect(connect_msg)) {
auto errormsg = wxString::Format("%s: %s", errortitle, connect_msg);
GUI::show_error(&progress_dialog, std::move(errormsg));
return false;
}
bool res = true;
auto upload_cmd = get_upload_url(upload_filepath.string());
BOOST_LOG_TRIVIAL(info) << boost::format("Duet: Uploading file %1%, filename: %2%, path: %3%, print: %4%, command: %5%")
% filepath.string()
% upload_filename.string()
% upload_parent_path.string()
% print
% upload_cmd;
auto http = Http::post(std::move(upload_cmd));
http.set_post_body(filename)
.on_complete([&](std::string body, unsigned status) {
BOOST_LOG_TRIVIAL(debug) << boost::format("Duet: File uploaded: HTTP %1%: %2%") % status % body;
progress_dialog.Update(PROGRESS_RANGE);
int err_code = get_err_code_from_body(body);
if (err_code != 0) {
auto msg = format_error(body, L("Unknown error occured"), 0);
GUI::show_error(&progress_dialog, std::move(msg));
res = false;
} else if (print) {
wxString errormsg;
res = start_print(errormsg, upload_filepath.string());
if (!res) {
GUI::show_error(&progress_dialog, std::move(errormsg));
}
}
})
.on_error([&](std::string body, std::string error, unsigned status) {
BOOST_LOG_TRIVIAL(error) << boost::format("Duet: Error uploading file: %1%, HTTP %2%, body: `%3%`") % error % status % body;
auto errormsg = wxString::Format("%s: %s", errortitle, format_error(body, error, status));
GUI::show_error(&progress_dialog, std::move(errormsg));
res = false;
})
.on_progress([&](Http::Progress progress, bool &cancel) {
if (cancel) {
// Upload was canceled
res = false;
} else if (progress.ultotal > 0) {
int value = PROGRESS_RANGE * progress.ulnow / progress.ultotal;
cancel = !progress_dialog.Update(std::min(value, PROGRESS_RANGE - 1)); // Cap the value to prevent premature dialog closing
} else {
cancel = !progress_dialog.Pulse();
}
})
.perform_sync();
disconnect();
return res;
}
bool Duet::upload(PrintHostUpload upload_data) const
// bool Duet::send_gcode(const std::string &filename) const
// {
// enum { PROGRESS_RANGE = 1000 };
// const auto errortitle = _(L("Error while uploading to the Duet"));
// fs::path filepath(filename);
// GUI::PrintHostSendDialog send_dialog(filepath.filename());
// if (send_dialog.ShowModal() != wxID_OK) { return false; }
// const bool print = send_dialog.start_print();
// const auto upload_filepath = send_dialog.filename();
// const auto upload_filename = upload_filepath.filename();
// const auto upload_parent_path = upload_filepath.parent_path();
// wxProgressDialog progress_dialog(
// _(L("Duet upload")),
// _(L("Sending G-code file to Duet...")),
// PROGRESS_RANGE, nullptr, wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT);
// progress_dialog.Pulse();
// wxString connect_msg;
// if (!connect(connect_msg)) {
// auto errormsg = wxString::Format("%s: %s", errortitle, connect_msg);
// GUI::show_error(&progress_dialog, std::move(errormsg));
// return false;
// }
// bool res = true;
// auto upload_cmd = get_upload_url(upload_filepath.string());
// BOOST_LOG_TRIVIAL(info) << boost::format("Duet: Uploading file %1%, filename: %2%, path: %3%, print: %4%, command: %5%")
// % filepath.string()
// % upload_filename.string()
// % upload_parent_path.string()
// % print
// % upload_cmd;
// auto http = Http::post(std::move(upload_cmd));
// http.set_post_body(filename)
// .on_complete([&](std::string body, unsigned status) {
// BOOST_LOG_TRIVIAL(debug) << boost::format("Duet: File uploaded: HTTP %1%: %2%") % status % body;
// progress_dialog.Update(PROGRESS_RANGE);
// int err_code = get_err_code_from_body(body);
// if (err_code != 0) {
// auto msg = format_error(body, L("Unknown error occured"), 0);
// GUI::show_error(&progress_dialog, std::move(msg));
// res = false;
// } else if (print) {
// wxString errormsg;
// res = start_print(errormsg, upload_filepath.string());
// if (!res) {
// GUI::show_error(&progress_dialog, std::move(errormsg));
// }
// }
// })
// .on_error([&](std::string body, std::string error, unsigned status) {
// BOOST_LOG_TRIVIAL(error) << boost::format("Duet: Error uploading file: %1%, HTTP %2%, body: `%3%`") % error % status % body;
// auto errormsg = wxString::Format("%s: %s", errortitle, format_error(body, error, status));
// GUI::show_error(&progress_dialog, std::move(errormsg));
// res = false;
// })
// .on_progress([&](Http::Progress progress, bool &cancel) {
// if (cancel) {
// // Upload was canceled
// res = false;
// } else if (progress.ultotal > 0) {
// int value = PROGRESS_RANGE * progress.ulnow / progress.ultotal;
// cancel = !progress_dialog.Update(std::min(value, PROGRESS_RANGE - 1)); // Cap the value to prevent premature dialog closing
// } else {
// cancel = !progress_dialog.Pulse();
// }
// })
// .perform_sync();
// disconnect();
// return res;
// }
bool Duet::upload(PrintHostUpload upload_data, Http::ProgressFn prorgess_fn, Http::ErrorFn error_fn) const
{
// XXX: TODO
throw "unimplemented";
}

View file

@ -22,11 +22,10 @@ public:
bool test(wxString &curl_msg) const;
wxString get_test_ok_msg () const;
wxString get_test_failed_msg (wxString &msg) const;
// Send gcode file to duet, filename is expected to be in UTF-8
bool send_gcode(const std::string &filename) const;
bool upload(PrintHostUpload upload_data) const;
bool upload(PrintHostUpload upload_data, Http::ProgressFn prorgess_fn, Http::ErrorFn error_fn) const;
bool has_auto_discovery() const;
bool can_test() const;
virtual std::string get_host() const { return host; }
private:
std::string host;
std::string password;

View file

@ -29,7 +29,7 @@ public:
typedef std::shared_ptr<Http> Ptr;
typedef std::function<void(std::string /* body */, unsigned /* http_status */)> CompleteFn;
// A HTTP request may fail at various stages of completeness (URL parsing, DNS lookup, TCP connection, ...).
// If the HTTP request could not be made or failed before completion, the `error` arg contains a description
// of the error and `http_status` is zero.

View file

@ -4,9 +4,10 @@
#include <boost/format.hpp>
#include <boost/log/trivial.hpp>
#include <wx/progdlg.h>
#include "libslic3r/PrintConfig.hpp"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/PrintHostDialogs.hpp" // XXX
#include "Http.hpp"
@ -59,32 +60,19 @@ wxString OctoPrint::get_test_failed_msg (wxString &msg) const
_(L("Could not connect to OctoPrint")), msg, _(L("Note: OctoPrint version at least 1.1.0 is required.")));
}
bool OctoPrint::send_gcode(const std::string &filename) const
bool OctoPrint::upload(PrintHostUpload upload_data, Http::ProgressFn prorgess_fn, Http::ErrorFn error_fn) const
{
enum { PROGRESS_RANGE = 1000 };
const auto errortitle = _(L("Error while uploading to the OctoPrint server"));
fs::path filepath(filename);
PrintHostSendDialog send_dialog(filepath.filename());
if (send_dialog.ShowModal() != wxID_OK) { return false; }
const bool print = send_dialog.start_print();
const auto upload_filepath = send_dialog.filename();
const auto upload_filename = upload_filepath.filename();
const auto upload_parent_path = upload_filepath.parent_path();
wxProgressDialog progress_dialog(
_(L("OctoPrint upload")),
_(L("Sending G-code file to the OctoPrint server...")),
PROGRESS_RANGE, nullptr, wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT);
progress_dialog.Pulse();
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)) {
auto errormsg = wxString::Format("%s: %s", errortitle, test_msg);
GUI::show_error(&progress_dialog, std::move(errormsg));
return false;
if (! test(test_msg)) {
// TODO:
// auto errormsg = wxString::Format("%s: %s", errortitle, test_msg);
// GUI::show_error(&progress_dialog, std::move(errormsg));
// return false;
}
bool res = true;
@ -92,36 +80,31 @@ bool OctoPrint::send_gcode(const std::string &filename) const
auto url = make_url("api/files/local");
BOOST_LOG_TRIVIAL(info) << boost::format("Octoprint: Uploading file %1% at %2%, filename: %3%, path: %4%, print: %5%")
% filepath.string()
% upload_data.source_path.string()
% url
% upload_filename.string()
% upload_parent_path.string()
% print;
% upload_data.start_print;
auto http = Http::post(std::move(url));
set_auth(http);
http.form_add("print", print ? "true" : "false")
http.form_add("print", upload_data.start_print ? "true" : "false")
.form_add("path", upload_parent_path.string()) // XXX: slashes on windows ???
.form_add_file("file", filename, upload_filename.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("Octoprint: File uploaded: HTTP %1%: %2%") % status % body;
progress_dialog.Update(PROGRESS_RANGE);
})
.on_error([&](std::string body, std::string error, unsigned status) {
BOOST_LOG_TRIVIAL(error) << boost::format("Octoprint: Error uploading file: %1%, HTTP %2%, body: `%3%`") % error % status % body;
auto errormsg = wxString::Format("%s: %s", errortitle, format_error(body, error, status));
GUI::show_error(&progress_dialog, std::move(errormsg));
error_fn(std::move(body), std::move(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(error) << "Octoprint: Upload canceled";
res = false;
} else if (progress.ultotal > 0) {
int value = PROGRESS_RANGE * progress.ulnow / progress.ultotal;
cancel = !progress_dialog.Update(std::min(value, PROGRESS_RANGE - 1)); // Cap the value to prevent premature dialog closing
} else {
cancel = !progress_dialog.Pulse();
}
})
.perform_sync();
@ -129,11 +112,6 @@ bool OctoPrint::send_gcode(const std::string &filename) const
return res;
}
bool OctoPrint::upload(PrintHostUpload upload_data) const
{
throw "unimplemented";
}
bool OctoPrint::has_auto_discovery() const
{
return true;

View file

@ -22,11 +22,10 @@ public:
bool test(wxString &curl_msg) const;
wxString get_test_ok_msg () const;
wxString get_test_failed_msg (wxString &msg) const;
// Send gcode file to octoprint, filename is expected to be in UTF-8
bool send_gcode(const std::string &filename) const;
bool upload(PrintHostUpload upload_data) const;
bool upload(PrintHostUpload upload_data, Http::ProgressFn prorgess_fn, Http::ErrorFn error_fn) const;
bool has_auto_discovery() const;
bool can_test() const;
virtual std::string get_host() const { return host; }
private:
std::string host;
std::string apikey;

View file

@ -1,15 +1,21 @@
#include "OctoPrint.hpp"
#include "Duet.hpp"
#include "PrintHost.hpp"
#include <vector>
#include <thread>
#include <boost/optional.hpp>
#include <boost/filesystem.hpp>
#include <wx/app.h>
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/Channel.hpp"
#include "OctoPrint.hpp"
#include "Duet.hpp"
#include "../GUI/PrintHostDialogs.hpp"
namespace fs = boost::filesystem;
using boost::optional;
using Slic3r::GUI::PrintHostQueueDialog;
namespace Slic3r {
@ -30,30 +36,130 @@ PrintHost* PrintHost::get_print_host(DynamicPrintConfig *config)
struct PrintHostJobQueue::priv
{
std::vector<PrintHostJob> jobs;
Channel<unsigned> channel;
// XXX: comment on how bg thread works
PrintHostJobQueue *q;
Channel<PrintHostJob> channel_jobs;
Channel<size_t> channel_cancels;
size_t job_id = 0;
int prev_progress = -1;
std::thread bg_thread;
optional<PrintHostJob> bg_job;
bool bg_exit = false;
PrintHostQueueDialog *queue_dialog;
priv(PrintHostJobQueue *q) : q(q) {}
void start_bg_thread();
void bg_thread_main();
void progress_fn(Http::Progress progress, bool &cancel);
void error_fn(std::string body, std::string error, unsigned http_status);
void perform_job(PrintHostJob the_job);
};
PrintHostJobQueue::PrintHostJobQueue()
: p(new priv())
PrintHostJobQueue::PrintHostJobQueue(PrintHostQueueDialog *queue_dialog)
: p(new priv(this))
{
std::shared_ptr<priv> p2 = p;
p->bg_thread = std::thread([p2]() {
// Wait for commands on the channel:
auto cmd = p2->channel.pop();
// TODO
});
p->queue_dialog = queue_dialog;
}
PrintHostJobQueue::~PrintHostJobQueue()
{
// TODO: stop the thread
// if (p && p->bg_thread.joinable()) {
// p->bg_thread.detach();
// }
if (p && p->bg_thread.joinable()) {
p->bg_exit = true;
p->channel_jobs.push(PrintHostJob()); // Push an empty job to wake up bg_thread in case it's sleeping
p->bg_thread.detach(); // Let the background thread go, it should exit on its own
}
}
void PrintHostJobQueue::priv::start_bg_thread()
{
if (bg_thread.joinable()) { return; }
std::shared_ptr<priv> p2 = q->p;
bg_thread = std::thread([p2]() {
p2->bg_thread_main();
});
}
void PrintHostJobQueue::priv::bg_thread_main()
{
// bg thread entry point
try {
// Pick up jobs from the job channel:
while (! bg_exit) {
auto job = channel_jobs.pop(); // Sleeps in a cond var if there are no jobs
if (! job.cancelled) {
perform_job(std::move(job));
}
job_id++;
}
} catch (...) {
wxTheApp->OnUnhandledException();
}
}
void PrintHostJobQueue::priv::progress_fn(Http::Progress progress, bool &cancel)
{
if (bg_exit) {
cancel = true;
return;
}
if (channel_cancels.size_hint() > 0) {
// Lock both queues
auto cancels = channel_cancels.lock_rw();
auto jobs = channel_jobs.lock_rw();
for (size_t cancel_id : *cancels) {
if (cancel_id == job_id) {
cancel = true;
} else if (cancel_id > job_id) {
jobs->at(cancel_id - job_id).cancelled = true;
}
}
cancels->clear();
}
int gui_progress = progress.ultotal > 0 ? 100*progress.ulnow / progress.ultotal : 0;
if (gui_progress != prev_progress) {
auto evt = new PrintHostQueueDialog::Event(GUI::EVT_PRINTHOST_PROGRESS, queue_dialog->GetId(), job_id, gui_progress);
wxQueueEvent(queue_dialog, evt);
prev_progress = gui_progress;
}
}
void PrintHostJobQueue::priv::error_fn(std::string body, std::string error, unsigned http_status)
{
// TODO
}
void PrintHostJobQueue::priv::perform_job(PrintHostJob the_job)
{
if (bg_exit || the_job.empty()) { return; }
const fs::path gcode_path = the_job.upload_data.source_path;
the_job.printhost->upload(std::move(the_job.upload_data),
[this](Http::Progress progress, bool &cancel) { this->progress_fn(std::move(progress), cancel); },
[this](std::string body, std::string error, unsigned http_status) { this->error_fn(std::move(body), std::move(error), http_status); }
);
auto evt = new PrintHostQueueDialog::Event(GUI::EVT_PRINTHOST_PROGRESS, queue_dialog->GetId(), job_id, 100);
wxQueueEvent(queue_dialog, evt);
fs::remove(gcode_path); // XXX: error handling
}
void PrintHostJobQueue::enqueue(PrintHostJob job)
{
p->start_bg_thread();
p->queue_dialog->append_job(job);
p->channel_jobs.push(std::move(job));
}

View file

@ -7,6 +7,8 @@
#include <wx/string.h>
#include "Http.hpp"
namespace Slic3r {
@ -29,11 +31,10 @@ public:
virtual bool test(wxString &curl_msg) const = 0;
virtual wxString get_test_ok_msg () const = 0;
virtual wxString get_test_failed_msg (wxString &msg) const = 0;
// Send gcode file to print host, filename is expected to be in UTF-8
virtual bool send_gcode(const std::string &filename) const = 0; // XXX: remove in favor of upload()
virtual bool upload(PrintHostUpload upload_data) const = 0;
virtual bool upload(PrintHostUpload upload_data, Http::ProgressFn prorgess_fn, Http::ErrorFn error_fn) const = 0;
virtual bool has_auto_discovery() const = 0;
virtual bool can_test() const = 0;
virtual std::string get_host() const = 0;
static PrintHost* get_print_host(DynamicPrintConfig *config);
};
@ -43,6 +44,7 @@ struct PrintHostJob
{
PrintHostUpload upload_data;
std::unique_ptr<PrintHost> printhost;
bool cancelled = false;
PrintHostJob() {}
PrintHostJob(const PrintHostJob&) = delete;
@ -68,10 +70,12 @@ struct PrintHostJob
};
namespace GUI { class PrintHostQueueDialog; }
class PrintHostJobQueue
{
public:
PrintHostJobQueue();
PrintHostJobQueue(GUI::PrintHostQueueDialog *queue_dialog);
PrintHostJobQueue(const PrintHostJobQueue &) = delete;
PrintHostJobQueue(PrintHostJobQueue &&other) = delete;
~PrintHostJobQueue();
@ -79,6 +83,9 @@ public:
PrintHostJobQueue& operator=(const PrintHostJobQueue &) = delete;
PrintHostJobQueue& operator=(PrintHostJobQueue &&other) = delete;
void enqueue(PrintHostJob job);
void cancel(size_t id);
private:
struct priv;
std::shared_ptr<priv> p;