diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 75887d17c..94b8bae6f 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -237,6 +237,8 @@ set(SLIC3R_GUI_SOURCES Utils/UndoRedo.hpp Utils/HexFile.cpp Utils/HexFile.hpp + Utils/TCPConsole.cpp + Utils/TCPConsole.hpp Utils/MKS.cpp Utils/MKS.hpp ) diff --git a/src/slic3r/Utils/MKS.cpp b/src/slic3r/Utils/MKS.cpp index aa7eee48e..c25ae9d0d 100644 --- a/src/slic3r/Utils/MKS.cpp +++ b/src/slic3r/Utils/MKS.cpp @@ -32,14 +32,21 @@ namespace pt = boost::property_tree; namespace Slic3r { MKS::MKS(DynamicPrintConfig *config) : - host(config->opt_string("print_host")), console_port(8080) + host(config->opt_string("print_host")), console(config->opt_string("print_host"), "8080") {} const char* MKS::get_name() const { return "MKS"; } bool MKS::test(wxString &msg) const { - return run_simple_gcode("M105", msg); + console.enqueue_cmd("M105"); + bool ret = console.run_queue(); + + if (!ret) { + msg = console.error_message(); + } + + return ret; } wxString MKS::get_test_ok_msg () const @@ -100,15 +107,7 @@ bool MKS::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn er .perform_sync(); if (res && upload_data.start_print) { - // For some reason printer firmware does not want to respond on gcode commands immediately after file upload. - // So we just introduce artificial delay to workaround it. - std::this_thread::sleep_for(std::chrono::milliseconds(1500)); - - wxString msg; - res &= run_simple_gcode(std::string("M23 ") + upload_data.upload_path.string(), msg); - if (res) { - res &= run_simple_gcode(std::string("M24"), msg); - } + start_print(upload_data.upload_path); } return res; @@ -123,8 +122,21 @@ std::string MKS::get_upload_url(const std::string &filename) const bool MKS::start_print(wxString &msg, const std::string &filename) const { - BOOST_LOG_TRIVIAL(warning) << boost::format("MKS: start_print is not implemented yet, called stub"); - return true; + // For some reason printer firmware does not want to respond on gcode commands immediately after file upload. + // So we just introduce artificial delay to workaround it. + // TODO: Inspect reasons + std::this_thread::sleep_for(std::chrono::milliseconds(1500)); + + console.enqueue_cmd("M23 " + upload_data.upload_path.string()); + console.enqueue_cmd("M24"); + + bool ret = console.run_queue(); + + if (!ret) { + msg = console.error_message(); + } + + return ret; } int MKS::get_err_code_from_body(const std::string& body) const @@ -136,38 +148,4 @@ int MKS::get_err_code_from_body(const std::string& body) const return root.get("err", 0); } -bool MKS::run_simple_gcode(const std::string &cmd, wxString &msg) const -{ - using boost::asio::ip::tcp; - - try - { - boost::asio::io_context io_context; - tcp::socket s(io_context); - - tcp::resolver resolver(io_context); - boost::asio::connect(s, resolver.resolve(host, std::to_string(console_port))); - boost::asio::write(s, boost::asio::buffer(cmd + "\r\n")); - - msg = "request:" + cmd + "\r\n"; - - boost::asio::streambuf input_buffer; - size_t reply_length = boost::asio::read_until(s, input_buffer, '\n'); - - std::string response((std::istreambuf_iterator(&input_buffer)), std::istreambuf_iterator()); - if (response.length() == 0) { - msg += "Empty response"; - return false; - } - - msg += "response:" + response; - return true; - } - catch (std::exception& e) - { - msg = std::string("exception:") + e.what(); - return false; - } -} - -} +} // Slic3r diff --git a/src/slic3r/Utils/MKS.hpp b/src/slic3r/Utils/MKS.hpp index 4fed921d5..0414e3335 100644 --- a/src/slic3r/Utils/MKS.hpp +++ b/src/slic3r/Utils/MKS.hpp @@ -5,6 +5,7 @@ #include #include "PrintHost.hpp" +#include "TCPConsole.hpp" namespace Slic3r { @@ -27,16 +28,15 @@ public: bool can_test() const override { return true; } bool can_start_print() const override { return true; } std::string get_host() const override { return host; } - + private: std::string host; - int console_port; + Utils::TCPConsole console; std::string get_upload_url(const std::string &filename) const; std::string timestamp_str() const; bool start_print(wxString &msg, const std::string &filename) const; int get_err_code_from_body(const std::string &body) const; - bool run_simple_gcode(const std::string& cmd, wxString& msg) const; }; } diff --git a/src/slic3r/Utils/TCPConsole.cpp b/src/slic3r/Utils/TCPConsole.cpp new file mode 100644 index 000000000..4e1ca1f7c --- /dev/null +++ b/src/slic3r/Utils/TCPConsole.cpp @@ -0,0 +1,191 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +using boost::asio::steady_timer; +using boost::asio::ip::tcp; + +namespace Slic3r { +namespace Utils { + +void TCPConsole::transmit_next_command() +{ + if (cmd_queue_.empty()) { + io_context_.stop(); + return; + } + + std::string cmd = cmd_queue_.front(); + cmd_queue_.pop_front(); + + BOOST_LOG_TRIVIAL(debug) << boost::format("TCPConsole: transmitting '%3%' to %1%:%2%") + % host_name_ + % port_name_ + % cmd; + + auto data = boost::asio::buffer(cmd + newline_); + + boost::asio::async_write( + socket_, + data, + boost::bind(&TCPConsole::handle_write, this, _1, _2) + ); +} + +void TCPConsole::wait_next_line() +{ + boost::asio::async_read_until( + socket_, + recv_buffer_, + newline_, + boost::bind(&TCPConsole::handle_read, this, _1, _2) + ); +} + +// TODO: Use std::optional here +std::string TCPConsole::extract_next_line() +{ + char linebuf[1024]; + + std::istream is(&recv_buffer_); + is.getline(linebuf, sizeof(linebuf)); + if (is.good()) { + return linebuf; + } + + return ""; +} + +void TCPConsole::handle_read( + const boost::system::error_code& ec, + std::size_t bytes_transferred) +{ + error_code_ = ec; + + if (ec) { + BOOST_LOG_TRIVIAL(error) << boost::format("TCPConsole: Can't read from %1%:%2%: %3%") + % host_name_ + % port_name_ + % ec.message(); + + io_context_.stop(); + } else { + std::string line = extract_next_line(); + boost::trim(line); + + BOOST_LOG_TRIVIAL(debug) << boost::format("TCPConsole: received '%3%' from %1%:%2%") + % host_name_ + % port_name_ + % line; + + boost::to_lower(line); + + if (line == done_string_) { + transmit_next_command(); + } else { + wait_next_line(); + } + } +} + +void TCPConsole::handle_write( + const boost::system::error_code& ec, + std::size_t) +{ + error_code_ = ec; + if (ec) { + BOOST_LOG_TRIVIAL(error) << boost::format("TCPConsole: Can't write to %1%:%2%: %3%") + % host_name_ + % port_name_ + % ec.message(); + + io_context_.stop(); + } else { + wait_next_line(); + } +} + +void TCPConsole::handle_connect(const boost::system::error_code& ec) +{ + error_code_ = ec; + + if (ec) { + BOOST_LOG_TRIVIAL(error) << boost::format("TCPConsole: Can't connect to %1%:%2%: %3%") + % host_name_ + % port_name_ + % ec.message(); + + io_context_.stop(); + } else { + BOOST_LOG_TRIVIAL(info) << boost::format("TCPConsole: connected to %1%:%2%") + % host_name_ + % port_name_; + + + transmit_next_command(); + } +} + +bool TCPConsole::run_queue() +{ + try { + // TODO: Add more resets and initializations after previous run + + auto endpoints = resolver_.resolve(host_name_, port_name_); + + socket_.async_connect(endpoints->endpoint(), + boost::bind(&TCPConsole::handle_connect, this, _1) + ); + + // TODO: Add error and timeout processing + io_context_.restart(); + while (!io_context_.stopped()) { + BOOST_LOG_TRIVIAL(debug) << ".\n"; + if (error_code_) { + io_context_.stop(); + } + io_context_.run_for(boost::asio::chrono::milliseconds(100)); + } + + // Socket is not closed automatically by boost + socket_.close(); + + if (error_code_) { + // We expect that message is logged in handler + return false; + } + + // It's expected to have empty queue after successful exchange + if (!cmd_queue_.empty()) { + BOOST_LOG_TRIVIAL(error) << "TCPConsole: command queue is not empty after end of exchange"; + return false; + } + } + catch (std::exception& e) + { + BOOST_LOG_TRIVIAL(error) << boost::format("TCPConsole: Exception while talking with %1%:%2%: %3%") + % host_name_ + % port_name_ + % e.what(); + + return false; + } + + return true; +} + + +} +} diff --git a/src/slic3r/Utils/TCPConsole.hpp b/src/slic3r/Utils/TCPConsole.hpp new file mode 100644 index 000000000..6ff65ad8a --- /dev/null +++ b/src/slic3r/Utils/TCPConsole.hpp @@ -0,0 +1,81 @@ +#ifndef slic3r_Utils_TCPConsole_hpp_ +#define slic3r_Utils_TCPConsole_hpp_ + +#include +#include +#include +#include +#include + +namespace Slic3r { +namespace Utils { + +const char * default_newline = "\n"; +const char * default_done_string = "ok"; + +using boost::asio::ip::tcp; + +class TCPConsole +{ +public: + TCPConsole(): resolver_(io_context_), socket_(io_context_), newline_(default_newline), done_string_(default_done_string) {} + + TCPConsole(const std::string &host_name, const std::string &port_name): + resolver_(io_context_), socket_(io_context_), newline_(default_newline), done_string_(default_done_string) + { + set_remote(host_name, port_name); + } + ~TCPConsole(){} + + void set_line_delimiter(const std::string &newline) { + newline_ = newline; + } + void set_command_done_string(const std::string &done_string) { + done_string_ = done_string; + } + + void set_remote(const std::string &host_name, const std::string &port_name) + { + host_name_ = host_name; + port_name_ = port_name; + } + + bool enqueue_cmd(const std::string &cmd) { + // TODO: Add multithread protection to queue + cmd_queue_.push_back(cmd); + return true; + } + + bool run_queue(); + std::string error_message() { + return error_code_.message(); + } + +private: + void handle_connect(const boost::system::error_code& ec); + void handle_read(const boost::system::error_code& ec, std::size_t bytes_transferred); + void handle_write(const boost::system::error_code& ec, std::size_t bytes_transferred); + + void transmit_next_command(); + void wait_next_line(); + std::string extract_next_line(); + + std::string host_name_; + std::string port_name_; + std::string newline_; + std::string done_string_; + + std::list cmd_queue_; + + boost::asio::io_context io_context_; + tcp::resolver resolver_; + tcp::socket socket_; + boost::asio::streambuf recv_buffer_; + + boost::system::error_code error_code_; +}; + +} // Utils +} // Slic3r + +#endif