PrusaSlicer-NonPlainar/src/libslic3r/GCodeSender.cpp
Vojtech Bubnik 067cde85f1 WIP Refactoring of exceptions:
1) All slicer's exceptions are now derived from Slic3r::Exception.
2) New exceptions are defined for slicing errors.
3) Exceptions are propagated to the Plater to show.
It remains to modify the slicing back-end to throw the new SlicingError
exceptions instead of std::runtime_error and to show the other exceptions
by a message dialog instead of a notification.
2020-09-14 18:03:22 +02:00

581 lines
16 KiB
C++

#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 Slic3r::InvalidArgument("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 %zu (oldest we have is %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