0558b53493
The XS was left only for the unit / integration tests, and it links libslic3r only. No wxWidgets are allowed to be used from Perl starting from now.
581 lines
16 KiB
C++
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 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
|