mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-10-31 12:41:20 -06:00 
			
		
		
		
	
		
			
				
	
	
		
			580 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			580 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
 | 
