From a7eaf38853c1c804c4f4c7d5e30801f196713727 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Tue, 24 Jul 2018 17:41:57 +0200 Subject: [PATCH] Utils: Serial port printer communication abstraction --- xs/src/libslic3r/GCodeSender.cpp | 17 +-- xs/src/slic3r/Utils/Serial.cpp | 238 +++++++++++++++++++++++++++++++ xs/src/slic3r/Utils/Serial.hpp | 52 ++++++- 3 files changed, 294 insertions(+), 13 deletions(-) diff --git a/xs/src/libslic3r/GCodeSender.cpp b/xs/src/libslic3r/GCodeSender.cpp index c3530e00f..0988091ce 100644 --- a/xs/src/libslic3r/GCodeSender.cpp +++ b/xs/src/libslic3r/GCodeSender.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -568,16 +569,12 @@ GCodeSender::set_DTR(bool on) void GCodeSender::reset() { - this->set_DTR(false); - boost::this_thread::sleep(boost::posix_time::milliseconds(200)); - this->set_DTR(true); - boost::this_thread::sleep(boost::posix_time::milliseconds(200)); - this->set_DTR(false); - boost::this_thread::sleep(boost::posix_time::milliseconds(1000)); - { - boost::lock_guard l(this->queue_mutex); - this->can_send = true; - } + 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 diff --git a/xs/src/slic3r/Utils/Serial.cpp b/xs/src/slic3r/Utils/Serial.cpp index 32b6bb160..8a454b119 100644 --- a/xs/src/slic3r/Utils/Serial.cpp +++ b/xs/src/slic3r/Utils/Serial.cpp @@ -3,7 +3,10 @@ #include #include #include +#include +#include #include +#include #include #include @@ -34,6 +37,21 @@ #include #endif +#ifndef _WIN32 + #include + #include + #include + #include +#endif + +#if defined(__APPLE__) || defined(__OpenBSD__) + #include +#elif defined __linux__ + #include + #include +#endif + + namespace Slic3r { namespace Utils { @@ -189,5 +207,225 @@ std::vector scan_serial_ports() return output; } + + +// Class Serial + +namespace asio = boost::asio; +using boost::system::error_code; + +Serial::Serial(asio::io_service& io_service) : + asio::serial_port(io_service) +{} + +Serial::Serial(asio::io_service& io_service, const std::string &name, unsigned baud_rate) : + asio::serial_port(io_service, name) +{ + printer_setup(baud_rate); +} + +Serial::~Serial() {} + +void Serial::set_baud_rate(unsigned baud_rate) +{ + try { + // This does not support speeds > 115200 + set_option(boost::asio::serial_port_base::baud_rate(baud_rate)); + } catch (boost::system::system_error &) { + auto handle = native_handle(); + + auto handle_errno = [](int retval) { + if (retval != 0) { + throw std::runtime_error( + (boost::format("Could not set baud rate: %1%") % strerror(errno)).str() + ); + } + }; + +#if __APPLE__ + termios ios; + handle_errno(::tcgetattr(handle, &ios)); + handle_errno(::cfsetspeed(&ios, baud_rate)); + speed_t newSpeed = baud_rate; + handle_errno(::ioctl(handle, IOSSIOSPEED, &newSpeed)); + handle_errno(::tcsetattr(handle, TCSANOW, &ios)); +#elif __linux + + /* 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 + + termios2 ios; + handle_errno(::ioctl(handle, TCGETS2, &ios)); + 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; + handle_errno(::ioctl(handle, TCSETS2, &ios)); + +#elif __OpenBSD__ + struct termios ios; + handle_errno(::tcgetattr(handle, &ios)); + handle_errno(::cfsetspeed(&ios, baud_rate)); + handle_errno(::tcsetattr(handle, TCSAFLUSH, &ios)); +#else + throw std::runtime_error("Custom baud rates are not currently supported on this OS"); +#endif + } +} + +void Serial::set_DTR(bool on) +{ + auto handle = native_handle(); +#if defined(_WIN32) && !defined(__SYMBIAN32__) + if (! EscapeCommFunction(handle, on ? SETDTR : CLRDTR)) { + throw std::runtime_error("Could not set serial port DTR"); + } +#else + int status; + if (::ioctl(handle, TIOCMGET, &status) == 0) { + on ? status |= TIOCM_DTR : status &= ~TIOCM_DTR; + if (::ioctl(handle, TIOCMSET, &status) == 0) { + return; + } + } + + throw std::runtime_error( + (boost::format("Could not set serial port DTR: %1%") % strerror(errno)).str() + ); +#endif +} + +void Serial::reset_line_num() +{ + // See https://github.com/MarlinFirmware/Marlin/wiki/M110 + printer_write_line("M110 N0", 0); + m_line_num = 0; +} + +bool Serial::read_line(unsigned timeout, std::string &line, error_code &ec) +{ + auto &io_service = get_io_service(); + asio::deadline_timer timer(io_service); + char c = 0; + bool fail = false; + + while (true) { + io_service.reset(); + + asio::async_read(*this, boost::asio::buffer(&c, 1), [&](const error_code &read_ec, size_t size) { + if (ec || size == 0) { + fail = true; + ec = read_ec; + } + timer.cancel(); + }); + + if (timeout > 0) { + timer.expires_from_now(boost::posix_time::milliseconds(timeout)); + timer.async_wait([&](const error_code &ec) { + // Ignore timer aborts + if (!ec) { + fail = true; + this->cancel(); + } + }); + } + + io_service.run(); + + if (fail) { + return false; + } else if (c != '\n') { + line += c; + } else { + return true; + } + } +} + +void Serial::printer_setup(unsigned baud_rate) +{ + set_baud_rate(baud_rate); + printer_reset(); + write_string("\r\r\r\r\r\r\r\r\r\r"); // Gets rid of line noise, if any +} + +size_t Serial::write_string(const std::string &str) +{ + // TODO: might be wise to timeout here as well + return asio::write(*this, asio::buffer(str)); +} + +bool Serial::printer_ready_wait(unsigned retries, unsigned timeout) +{ + std::string line; + error_code ec; + + for (; retries > 0; retries--) { + reset_line_num(); + + while (read_line(timeout, line, ec)) { + if (line == "ok") { + return true; + } + line.clear(); + } + line.clear(); + } + + return false; +} + +size_t Serial::printer_write_line(const std::string &line, unsigned line_num) +{ + const auto formatted_line = Utils::Serial::printer_format_line(line, line_num); + return write_string(formatted_line); +} + +size_t Serial::printer_write_line(const std::string &line) +{ + m_line_num++; + return printer_write_line(line, m_line_num); +} + +void Serial::printer_reset() +{ + this->set_DTR(false); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + this->set_DTR(true); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + this->set_DTR(false); + std::this_thread::sleep_for(std::chrono::milliseconds(2000)); +} + +std::string Serial::printer_format_line(const std::string &line, unsigned line_num) +{ + const auto line_num_str = std::to_string(line_num); + + unsigned checksum = 'N'; + for (auto c : line_num_str) { checksum ^= c; } + checksum ^= ' '; + for (auto c : line) { checksum ^= c; } + + return (boost::format("N%1% %2%*%3%\n") % line_num_str % line % checksum).str(); +} + + } // namespace Utils } // namespace Slic3r diff --git a/xs/src/slic3r/Utils/Serial.hpp b/xs/src/slic3r/Utils/Serial.hpp index 9381ab398..5df33916f 100644 --- a/xs/src/slic3r/Utils/Serial.hpp +++ b/xs/src/slic3r/Utils/Serial.hpp @@ -1,10 +1,11 @@ #ifndef slic3r_GUI_Utils_Serial_hpp_ #define slic3r_GUI_Utils_Serial_hpp_ -#include #include #include -#include +#include +#include + namespace Slic3r { namespace Utils { @@ -16,7 +17,7 @@ struct SerialPortInfo { bool is_printer = false; }; -inline bool operator==(const SerialPortInfo &sp1, const SerialPortInfo &sp2) +inline bool operator==(const SerialPortInfo &sp1, const SerialPortInfo &sp2) { return sp1.port == sp2.port && sp1.hardware_id == sp2.hardware_id && @@ -26,6 +27,51 @@ inline bool operator==(const SerialPortInfo &sp1, const SerialPortInfo &sp2) extern std::vector scan_serial_ports(); extern std::vector scan_serial_ports_extended(); + +class Serial : public boost::asio::serial_port +{ +public: + Serial(boost::asio::io_service &io_service); + // This c-tor opens the port for communication with a printer - it sets a baud rate and calls printer_reset() + Serial(boost::asio::io_service &io_service, const std::string &name, unsigned baud_rate); + Serial(const Serial &) = delete; + Serial &operator=(const Serial &) = delete; + ~Serial(); + + void set_baud_rate(unsigned baud_rate); + void set_DTR(bool on); + + // Resets the line number both internally as well as with the firmware using M110 + void reset_line_num(); + + // Reads a line or times out, the timeout is in milliseconds + bool read_line(unsigned timeout, std::string &line, boost::system::error_code &ec); + + // Perform setup for communicating with a printer + // Sets a baud rate and calls printer_reset() + void printer_setup(unsigned baud_rate); + + // Write data from a string + size_t write_string(const std::string &str); + + bool printer_ready_wait(unsigned retries, unsigned timeout); + + // Write Marlin-formatted line, with a line number and a checksum + size_t printer_write_line(const std::string &line, unsigned line_num); + + // Same as above, but with internally-managed line number + size_t printer_write_line(const std::string &line); + + // Toggles DTR to reset the printer + void printer_reset(); + + // Formats a line Marlin-style, ie. with a sequential number and a checksum + static std::string printer_format_line(const std::string &line, unsigned line_num); +private: + unsigned m_line_num = 0; +}; + + } // Utils } // Slic3r