#include "GCodeSender.hpp" #include #include #include #include #include #include #include #include #if defined(__APPLE__) || defined(__OpenBSD__) #include #endif #ifdef __APPLE__ #include #include #endif #ifdef __linux__ #include #include #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 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 #include 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 l(this->queue_mutex); return this->queue.size(); } void GCodeSender::pause_queue() { boost::lock_guard l(this->queue_mutex); this->queue_paused = true; } void GCodeSender::resume_queue() { { boost::lock_guard l(this->queue_mutex); this->queue_paused = false; } this->send(); } void GCodeSender::purge_queue(bool priority) { boost::lock_guard l(this->queue_mutex); if (priority) { // clear priority queue std::list empty; std::swap(this->priqueue, empty); } else { // clear queue std::queue empty; std::swap(this->queue, empty); this->queue_paused = false; } } // purge log and return its contents std::vector GCodeSender::purge_log() { boost::lock_guard l(this->log_mutex); std::vector 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 l(this->log_mutex); return this->T; } std::string GCodeSender::getB() const { boost::lock_guard 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 l(this->error_mutex); this->error = e; } bool GCodeSender::error_status() const { boost::lock_guard 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 l(this->queue_mutex); this->can_send = true; } this->send(); } else if (boost::starts_with(line, "ok")) { { boost::lock_guard 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(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 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 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 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 &lines, bool priority) { // append lines to queue { boost::lock_guard l(this->queue_mutex); for (std::vector::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 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 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(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(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