Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/prusa3d/PrusaSlicer.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'src/libslic3r/GCodeSender.cpp')
-rw-r--r--src/libslic3r/GCodeSender.cpp580
1 files changed, 580 insertions, 0 deletions
diff --git a/src/libslic3r/GCodeSender.cpp b/src/libslic3r/GCodeSender.cpp
new file mode 100644
index 000000000..0988091ce
--- /dev/null
+++ b/src/libslic3r/GCodeSender.cpp
@@ -0,0 +1,580 @@
+#include "GCodeSender.hpp"
+#include <iostream>
+#include <istream>
+#include <string>
+#include <thread>
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/algorithm/string/trim.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/lexical_cast.hpp>
+
+#if defined(__APPLE__) || defined(__OpenBSD__)
+#include <termios.h>
+#endif
+#ifdef __APPLE__
+#include <sys/ioctl.h>
+#include <IOKit/serial/ioss.h>
+#endif
+#ifdef __linux__
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include "/usr/include/asm-generic/ioctls.h"
+
+/* The following definitions are kindly borrowed from:
+ /usr/include/asm-generic/termbits.h
+ Unfortunately we cannot just include that one because
+ it would redefine the "struct termios" already defined
+ the <termios.h> already included by Boost.ASIO. */
+#define K_NCCS 19
+struct termios2 {
+ tcflag_t c_iflag;
+ tcflag_t c_oflag;
+ tcflag_t c_cflag;
+ tcflag_t c_lflag;
+ cc_t c_line;
+ cc_t c_cc[K_NCCS];
+ speed_t c_ispeed;
+ speed_t c_ospeed;
+};
+#define BOTHER CBAUDEX
+
+#endif
+
+//#define DEBUG_SERIAL
+#ifdef DEBUG_SERIAL
+#include <cstdlib>
+#include <fstream>
+std::fstream fs;
+#endif
+
+#define KEEP_SENT 20
+
+namespace Slic3r {
+
+GCodeSender::GCodeSender()
+ : io(), serial(io), can_send(false), sent(0), open(false), error(false),
+ connected(false), queue_paused(false)
+{
+#ifdef DEBUG_SERIAL
+ std::srand(std::time(nullptr));
+#endif
+}
+
+GCodeSender::~GCodeSender()
+{
+ this->disconnect();
+}
+
+bool
+GCodeSender::connect(std::string devname, unsigned int baud_rate)
+{
+ this->disconnect();
+
+ this->set_error_status(false);
+ try {
+ this->serial.open(devname);
+
+ this->serial.set_option(boost::asio::serial_port_base::parity(boost::asio::serial_port_base::parity::odd));
+ this->serial.set_option(boost::asio::serial_port_base::character_size(boost::asio::serial_port_base::character_size(8)));
+ this->serial.set_option(boost::asio::serial_port_base::flow_control(boost::asio::serial_port_base::flow_control::none));
+ this->serial.set_option(boost::asio::serial_port_base::stop_bits(boost::asio::serial_port_base::stop_bits::one));
+ this->set_baud_rate(baud_rate);
+
+ this->serial.close();
+ this->serial.open(devname);
+ this->serial.set_option(boost::asio::serial_port_base::parity(boost::asio::serial_port_base::parity::none));
+
+ // set baud rate again because set_option overwrote it
+ this->set_baud_rate(baud_rate);
+ this->open = true;
+ this->reset();
+ } catch (boost::system::system_error &) {
+ this->set_error_status(true);
+ return false;
+ }
+
+ // a reset firmware expect line numbers to start again from 1
+ this->sent = 0;
+ this->last_sent.clear();
+
+ /* Initialize debugger */
+#ifdef DEBUG_SERIAL
+ fs.open("serial.txt", std::fstream::out | std::fstream::trunc);
+#endif
+
+ // this gives some work to the io_service before it is started
+ // (post() runs the supplied function in its thread)
+ this->io.post(boost::bind(&GCodeSender::do_read, this));
+
+ // start reading in the background thread
+ boost::thread t(boost::bind(&boost::asio::io_service::run, &this->io));
+ this->background_thread.swap(t);
+
+ // always send a M105 to check for connection because firmware might be silent on connect
+ //FIXME Vojtech: This is being sent too early, leading to line number synchronization issues,
+ // from which the GCodeSender never recovers.
+ // this->send("M105", true);
+
+ return true;
+}
+
+void
+GCodeSender::set_baud_rate(unsigned int baud_rate)
+{
+ try {
+ // This does not support speeds > 115200
+ this->serial.set_option(boost::asio::serial_port_base::baud_rate(baud_rate));
+ } catch (boost::system::system_error &) {
+ boost::asio::serial_port::native_handle_type handle = this->serial.native_handle();
+
+#if __APPLE__
+ termios ios;
+ ::tcgetattr(handle, &ios);
+ ::cfsetspeed(&ios, baud_rate);
+ speed_t newSpeed = baud_rate;
+ ioctl(handle, IOSSIOSPEED, &newSpeed);
+ ::tcsetattr(handle, TCSANOW, &ios);
+#elif __linux
+ termios2 ios;
+ if (ioctl(handle, TCGETS2, &ios))
+ printf("Error in TCGETS2: %s\n", strerror(errno));
+ ios.c_ispeed = ios.c_ospeed = baud_rate;
+ ios.c_cflag &= ~CBAUD;
+ ios.c_cflag |= BOTHER | CLOCAL | CREAD;
+ ios.c_cc[VMIN] = 1; // Minimum of characters to read, prevents eof errors when 0 bytes are read
+ ios.c_cc[VTIME] = 1;
+ if (ioctl(handle, TCSETS2, &ios))
+ printf("Error in TCSETS2: %s\n", strerror(errno));
+
+#elif __OpenBSD__
+ struct termios ios;
+ ::tcgetattr(handle, &ios);
+ ::cfsetspeed(&ios, baud_rate);
+ if (::tcsetattr(handle, TCSAFLUSH, &ios) != 0)
+ printf("Failed to set baud rate: %s\n", strerror(errno));
+#else
+ //throw invalid_argument ("OS does not currently support custom bauds");
+#endif
+ }
+}
+
+void
+GCodeSender::disconnect()
+{
+ if (!this->open) return;
+ this->open = false;
+ this->connected = false;
+ this->io.post(boost::bind(&GCodeSender::do_close, this));
+ this->background_thread.join();
+ this->io.reset();
+ /*
+ if (this->error_status()) {
+ throw(boost::system::system_error(boost::system::error_code(),
+ "Error while closing the device"));
+ }
+ */
+
+#ifdef DEBUG_SERIAL
+ fs << "DISCONNECTED" << std::endl << std::flush;
+ fs.close();
+#endif
+}
+
+bool
+GCodeSender::is_connected() const
+{
+ return this->connected;
+}
+
+bool
+GCodeSender::wait_connected(unsigned int timeout) const
+{
+ using namespace boost::posix_time;
+ ptime t0 = second_clock::local_time() + seconds(timeout);
+ while (!this->connected) {
+ if (second_clock::local_time() > t0) return false;
+ boost::this_thread::sleep(boost::posix_time::milliseconds(100));
+ }
+ return true;
+}
+
+size_t
+GCodeSender::queue_size() const
+{
+ boost::lock_guard<boost::mutex> l(this->queue_mutex);
+ return this->queue.size();
+}
+
+void
+GCodeSender::pause_queue()
+{
+ boost::lock_guard<boost::mutex> l(this->queue_mutex);
+ this->queue_paused = true;
+}
+
+void
+GCodeSender::resume_queue()
+{
+ {
+ boost::lock_guard<boost::mutex> l(this->queue_mutex);
+ this->queue_paused = false;
+ }
+ this->send();
+}
+
+void
+GCodeSender::purge_queue(bool priority)
+{
+ boost::lock_guard<boost::mutex> l(this->queue_mutex);
+ if (priority) {
+ // clear priority queue
+ std::list<std::string> empty;
+ std::swap(this->priqueue, empty);
+ } else {
+ // clear queue
+ std::queue<std::string> empty;
+ std::swap(this->queue, empty);
+ this->queue_paused = false;
+ }
+}
+
+// purge log and return its contents
+std::vector<std::string>
+GCodeSender::purge_log()
+{
+ boost::lock_guard<boost::mutex> l(this->log_mutex);
+ std::vector<std::string> retval;
+ retval.reserve(this->log.size());
+ while (!this->log.empty()) {
+ retval.push_back(this->log.front());
+ this->log.pop();
+ }
+ return retval;
+}
+
+std::string
+GCodeSender::getT() const
+{
+ boost::lock_guard<boost::mutex> l(this->log_mutex);
+ return this->T;
+}
+
+std::string
+GCodeSender::getB() const
+{
+ boost::lock_guard<boost::mutex> l(this->log_mutex);
+ return this->B;
+}
+
+void
+GCodeSender::do_close()
+{
+ this->set_error_status(false);
+ boost::system::error_code ec;
+ this->serial.cancel(ec);
+ if (ec) this->set_error_status(true);
+ this->serial.close(ec);
+ if (ec) this->set_error_status(true);
+}
+
+void
+GCodeSender::set_error_status(bool e)
+{
+ boost::lock_guard<boost::mutex> l(this->error_mutex);
+ this->error = e;
+}
+
+bool
+GCodeSender::error_status() const
+{
+ boost::lock_guard<boost::mutex> l(this->error_mutex);
+ return this->error;
+}
+
+void
+GCodeSender::do_read()
+{
+ // read one line
+ boost::asio::async_read_until(
+ this->serial,
+ this->read_buffer,
+ '\n',
+ boost::bind(
+ &GCodeSender::on_read,
+ this,
+ boost::asio::placeholders::error,
+ boost::asio::placeholders::bytes_transferred
+ )
+ );
+}
+
+void
+GCodeSender::on_read(const boost::system::error_code& error,
+ size_t bytes_transferred)
+{
+ this->set_error_status(false);
+ if (error) {
+ #ifdef __APPLE__
+ if (error.value() == 45) {
+ // OS X bug: http://osdir.com/ml/lib.boost.asio.user/2008-08/msg00004.html
+ this->do_read();
+ return;
+ }
+ #endif
+
+ // printf("ERROR: [%d] %s\n", error.value(), error.message().c_str());
+ // error can be true even because the serial port was closed.
+ // In this case it is not a real error, so ignore.
+ if (this->open) {
+ this->do_close();
+ this->set_error_status(true);
+ }
+ return;
+ }
+
+ std::istream is(&this->read_buffer);
+ std::string line;
+ std::getline(is, line);
+ if (!line.empty()) {
+#ifdef DEBUG_SERIAL
+ fs << "<< " << line << std::endl << std::flush;
+#endif
+
+ // note that line might contain \r at its end
+ // parse incoming line
+ if (!this->connected
+ && (boost::starts_with(line, "start")
+ || boost::starts_with(line, "Grbl ")
+ || boost::starts_with(line, "ok")
+ || boost::contains(line, "T:"))) {
+ this->connected = true;
+ {
+ boost::lock_guard<boost::mutex> l(this->queue_mutex);
+ this->can_send = true;
+ }
+ this->send();
+ } else if (boost::starts_with(line, "ok")) {
+ {
+ boost::lock_guard<boost::mutex> l(this->queue_mutex);
+ this->can_send = true;
+ }
+ this->send();
+ } else if (boost::istarts_with(line, "resend") // Marlin uses "Resend: "
+ || boost::istarts_with(line, "rs")) {
+ // extract the first number from line
+ boost::algorithm::trim_left_if(line, !boost::algorithm::is_digit());
+ size_t toresend = boost::lexical_cast<size_t>(line.substr(0, line.find_first_not_of("0123456789")));
+
+#ifdef DEBUG_SERIAL
+ fs << "!! line num out of sync: toresend = " << toresend << ", sent = " << sent << ", last_sent.size = " << last_sent.size() << std::endl;
+#endif
+
+ if (toresend > this->sent - this->last_sent.size() && toresend <= this->sent) {
+ {
+ boost::lock_guard<boost::mutex> l(this->queue_mutex);
+
+ const auto lines_to_resend = this->sent - toresend + 1;
+#ifdef DEBUG_SERIAL
+ fs << "!! resending " << lines_to_resend << " lines" << std::endl;
+#endif
+ // move the unsent lines to priqueue
+ this->priqueue.insert(
+ this->priqueue.begin(), // insert at the beginning
+ this->last_sent.begin() + this->last_sent.size() - lines_to_resend,
+ this->last_sent.end()
+ );
+
+ // we can empty last_sent because it's not useful anymore
+ this->last_sent.clear();
+
+ // start resending with the requested line number
+ this->sent = toresend - 1;
+ this->can_send = true;
+ }
+ this->send();
+ } else {
+ printf("Cannot resend " PRINTF_ZU " (oldest we have is " PRINTF_ZU ")\n", toresend, this->sent - this->last_sent.size());
+ }
+ } else if (boost::starts_with(line, "wait")) {
+ // ignore
+ } else {
+ // push any other line into the log
+ boost::lock_guard<boost::mutex> l(this->log_mutex);
+ this->log.push(line);
+ }
+
+ // parse temperature info
+ {
+ size_t pos = line.find("T:");
+ if (pos != std::string::npos && line.size() > pos + 2) {
+ // we got temperature info
+ boost::lock_guard<boost::mutex> l(this->log_mutex);
+ this->T = line.substr(pos+2, line.find_first_not_of("0123456789.", pos+2) - (pos+2));
+
+ pos = line.find("B:");
+ if (pos != std::string::npos && line.size() > pos + 2) {
+ // we got bed temperature info
+ this->B = line.substr(pos+2, line.find_first_not_of("0123456789.", pos+2) - (pos+2));
+ }
+ }
+ }
+ }
+ this->do_read();
+}
+
+void
+GCodeSender::send(const std::vector<std::string> &lines, bool priority)
+{
+ // append lines to queue
+ {
+ boost::lock_guard<boost::mutex> l(this->queue_mutex);
+ for (std::vector<std::string>::const_iterator line = lines.begin(); line != lines.end(); ++line) {
+ if (priority) {
+ this->priqueue.push_back(*line);
+ } else {
+ this->queue.push(*line);
+ }
+ }
+ }
+ this->send();
+}
+
+void
+GCodeSender::send(const std::string &line, bool priority)
+{
+ // append line to queue
+ {
+ boost::lock_guard<boost::mutex> l(this->queue_mutex);
+ if (priority) {
+ this->priqueue.push_back(line);
+ } else {
+ this->queue.push(line);
+ }
+ }
+ this->send();
+}
+
+void
+GCodeSender::send()
+{
+ this->io.post(boost::bind(&GCodeSender::do_send, this));
+}
+
+void
+GCodeSender::do_send()
+{
+ boost::lock_guard<boost::mutex> l(this->queue_mutex);
+
+ // printer is not connected or we're still waiting for the previous ack
+ if (!this->can_send) return;
+
+ std::string line;
+ while (!this->priqueue.empty() || (!this->queue.empty() && !this->queue_paused)) {
+ if (!this->priqueue.empty()) {
+ line = this->priqueue.front();
+ this->priqueue.pop_front();
+ } else {
+ line = this->queue.front();
+ this->queue.pop();
+ }
+
+ // strip comments
+ size_t comment_pos = line.find_first_of(';');
+ if (comment_pos != std::string::npos)
+ line.erase(comment_pos, std::string::npos);
+ boost::algorithm::trim(line);
+
+ // if line is not empty, send it
+ if (!line.empty()) break;
+ // if line is empty, process next item in queue
+ }
+ if (line.empty()) return;
+
+ // compute full line
+ ++ this->sent;
+#ifndef DEBUG_SERIAL
+ const auto line_num = this->sent;
+#else
+ // In DEBUG_SERIAL mode, test line re-synchronization by sending bad line number 1/4 of the time
+ const auto line_num = std::rand() < RAND_MAX/4 ? 0 : this->sent;
+#endif
+ std::string full_line = "N" + boost::lexical_cast<std::string>(line_num) + " " + line;
+
+ // calculate checksum
+ int cs = 0;
+ for (std::string::const_iterator it = full_line.begin(); it != full_line.end(); ++it)
+ cs = cs ^ *it;
+
+ // write line to device
+ full_line += "*";
+ full_line += boost::lexical_cast<std::string>(cs);
+ full_line += "\n";
+
+#ifdef DEBUG_SERIAL
+ fs << ">> " << full_line << std::flush;
+#endif
+
+ this->last_sent.push_back(line);
+ this->can_send = false;
+
+ while (this->last_sent.size() > KEEP_SENT) {
+ this->last_sent.pop_front();
+ }
+
+ // we can't supply boost::asio::buffer(full_line) to async_write() because full_line is on the
+ // stack and the buffer would lose its underlying storage causing memory corruption
+ std::ostream os(&this->write_buffer);
+ os << full_line;
+ boost::asio::async_write(this->serial, this->write_buffer, boost::bind(&GCodeSender::on_write, this, boost::asio::placeholders::error,
+ boost::asio::placeholders::bytes_transferred));
+}
+
+void
+GCodeSender::on_write(const boost::system::error_code& error,
+ size_t bytes_transferred)
+{
+ this->set_error_status(false);
+ if (error) {
+ if (this->open) {
+ this->do_close();
+ this->set_error_status(true);
+ }
+ return;
+ }
+
+ this->do_send();
+}
+
+void
+GCodeSender::set_DTR(bool on)
+{
+#if defined(_WIN32) && !defined(__SYMBIAN32__)
+ boost::asio::serial_port_service::native_handle_type handle = this->serial.native_handle();
+ if (on)
+ EscapeCommFunction(handle, SETDTR);
+ else
+ EscapeCommFunction(handle, CLRDTR);
+#else
+ int fd = this->serial.native_handle();
+ int status;
+ ioctl(fd, TIOCMGET, &status);
+ if (on)
+ status |= TIOCM_DTR;
+ else
+ status &= ~TIOCM_DTR;
+ ioctl(fd, TIOCMSET, &status);
+#endif
+}
+
+void
+GCodeSender::reset()
+{
+ set_DTR(false);
+ std::this_thread::sleep_for(std::chrono::milliseconds(200));
+ set_DTR(true);
+ std::this_thread::sleep_for(std::chrono::milliseconds(200));
+ set_DTR(false);
+ std::this_thread::sleep_for(std::chrono::milliseconds(500));
+}
+
+} // namespace Slic3r