123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586 |
- ///|/ Copyright (c) Prusa Research 2016 - 2021 Vojtěch Bubník @bubnikv, Vojtěch Král @vojtechkral
- ///|/ Copyright (c) Slic3r 2014 - 2016 Alessandro Ranellucci @alranel
- ///|/ Copyright (c) 2016 Gregor Best
- ///|/
- ///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
- ///|/
- #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
|