diff --git a/.gitignore b/.gitignore index 79dac986c..d9fd6c074 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ xs/buildtmp *.o MANIFEST.bak xs/MANIFEST.bak +xs/assertlib* diff --git a/utils/send-gcode.pl b/utils/send-gcode.pl index 63affd506..989b17872 100644 --- a/utils/send-gcode.pl +++ b/utils/send-gcode.pl @@ -10,17 +10,27 @@ BEGIN { use Slic3r; +die "Usage: send-gcode.pl SERIALPORT BAUDRATE GCODE_FILE\n" + if @ARGV != 3; + my $serial = Slic3r::GCode::Sender->new($ARGV[0], $ARGV[1]); +1 until $serial->is_connected; +print "Connected to printer\n"; -$serial->send($ARGV[2]); - -exit; - -while (1) { - $serial->send("1"); - sleep 1; - $serial->send("0"); - sleep 1; +{ + local $/ = "\n"; + Slic3r::open(\my $fh, '<', $ARGV[2]) + or die "Unable to open $ARGV[2]: $!\n"; + binmode $fh, ':utf8'; + while (<$fh>) { + $serial->send($_); + } + close $fh; } +while ((my $queue_size = $serial->queue_size) > 0) { + printf "Queue size: %d\n", $queue_size; +} +$serial->disconnect; + __END__ diff --git a/xs/MANIFEST b/xs/MANIFEST index 808240797..fb06bec85 100644 --- a/xs/MANIFEST +++ b/xs/MANIFEST @@ -1,21 +1,3 @@ -assertlib2WNuZugY.dSYM/Contents/Info.plist -assertlib2WNuZugY.dSYM/Contents/Resources/DWARF/assertlib2WNuZugY -assertlib_1AsN89M.dSYM/Contents/Info.plist -assertlib_1AsN89M.dSYM/Contents/Resources/DWARF/assertlib_1AsN89M -assertlibCHIyOgiI.dSYM/Contents/Info.plist -assertlibCHIyOgiI.dSYM/Contents/Resources/DWARF/assertlibCHIyOgiI -assertlibePik23eL.dSYM/Contents/Info.plist -assertlibePik23eL.dSYM/Contents/Resources/DWARF/assertlibePik23eL -assertlibk8tKRIAD.dSYM/Contents/Info.plist -assertlibk8tKRIAD.dSYM/Contents/Resources/DWARF/assertlibk8tKRIAD -assertlibNTySykmH.dSYM/Contents/Info.plist -assertlibNTySykmH.dSYM/Contents/Resources/DWARF/assertlibNTySykmH -assertlibpBddNlll.dSYM/Contents/Info.plist -assertlibpBddNlll.dSYM/Contents/Resources/DWARF/assertlibpBddNlll -assertlibtdgvvqTu.dSYM/Contents/Info.plist -assertlibtdgvvqTu.dSYM/Contents/Resources/DWARF/assertlibtdgvvqTu -assertlibxg2L4e5_.dSYM/Contents/Info.plist -assertlibxg2L4e5_.dSYM/Contents/Resources/DWARF/assertlibxg2L4e5_ Build.PL include/boost/aligned_storage.hpp include/boost/array.hpp diff --git a/xs/MANIFEST.SKIP b/xs/MANIFEST.SKIP index 0123bf375..14a73467e 100644 --- a/xs/MANIFEST.SKIP +++ b/xs/MANIFEST.SKIP @@ -71,3 +71,5 @@ # Avoid archives of this distribution \bSlic3r-XS-[\d\.\_]+ + +^assertlib diff --git a/xs/src/libslic3r/GCodeSender.cpp b/xs/src/libslic3r/GCodeSender.cpp index 7ba4a68bf..f5398a699 100644 --- a/xs/src/libslic3r/GCodeSender.cpp +++ b/xs/src/libslic3r/GCodeSender.cpp @@ -1,13 +1,18 @@ #ifdef BOOST_LIBS #include "GCodeSender.hpp" #include +#include +#include +#include +#include +#include namespace Slic3r { namespace asio = boost::asio; GCodeSender::GCodeSender(std::string devname, unsigned int baud_rate) - : io(), serial(io) + : io(), serial(io), can_send(false), sent(0), error(false), connected(false) { this->serial.open(devname); this->serial.set_option(asio::serial_port_base::baud_rate(baud_rate)); @@ -18,56 +23,188 @@ GCodeSender::GCodeSender(std::string devname, unsigned int baud_rate) this->serial.close(); this->serial.open(devname); this->serial.set_option(asio::serial_port_base::parity(asio::serial_port_base::parity::none)); + this->open = true; - std::string greeting; - this->read_line(&greeting); + // 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(&asio::io_service::run, &this->io)); + this->background_thread.swap(t); +} + +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")); + } +} + +bool +GCodeSender::is_connected() const +{ + return this->connected; +} + +size_t +GCodeSender::queue_size() const +{ + boost::lock_guard l(this->queue_mutex); + return this->queue.size(); +} + +void +GCodeSender::do_close() +{ + 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 + asio::async_read_until( + this->serial, + this->read_buffer, + '\n', + boost::bind( + &GCodeSender::on_read, + this, + asio::placeholders::error, + asio::placeholders::bytes_transferred + ) + ); +} + +void +GCodeSender::on_read(const boost::system::error_code& error, + size_t bytes_transferred) +{ + if (error) { + // 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; + } + + // copy the read buffer into string + std::string line((std::istreambuf_iterator(&this->read_buffer)), + std::istreambuf_iterator()); + + // parse incoming line + if (!this->connected + && (boost::starts_with(line, "start") + || boost::starts_with(line, "Grbl "))) { + this->connected = true; + this->can_send = true; + this->send(); + } else if (boost::starts_with(line, "ok")) { + { + boost::lock_guard l(this->queue_mutex); + this->queue.pop(); + } + this->can_send = true; + this->send(); + } else if (boost::istarts_with(line, "resend") + || boost::istarts_with(line, "rs")) { + // extract the first number from line + using boost::lexical_cast; + using boost::bad_lexical_cast; + boost::algorithm::trim_left_if(line, !boost::algorithm::is_digit()); + size_t toresend = lexical_cast(line.substr(0, line.find_first_not_of("0123456789"))); + if (toresend == this->sent) { + this->sent--; + this->can_send = true; + this->send(); + } else { + printf("Cannot resend %lu (last was %lu)\n", toresend, this->sent); + } + } + + this->do_read(); } void GCodeSender::send(const std::vector &lines) { - this->lines = lines; -} - -void -GCodeSender::send(const std::string &s) -{ + // append lines to queue { - std::stringstream ss(s); - std::string line; - while (std::getline(ss, line, '\n')) - this->lines.push_back(line); - } - - for (std::vector::const_iterator line = this->lines.begin(); line != this->lines.end(); ++line) { - this->send_line(*line); + boost::lock_guard l(this->queue_mutex); + for (std::vector::const_iterator line = lines.begin(); line != lines.end(); ++line) + this->queue.push(*line); } + this->send(); } void -GCodeSender::send_line(const std::string &line) +GCodeSender::send(const std::string &line) { + // append line to queue + { + boost::lock_guard l(this->queue_mutex); + this->queue.push(line); + } + this->send(); +} + +void +GCodeSender::send() +{ + // printer is not connected or we're still waiting for the previous ack + if (!this->can_send) return; + + boost::lock_guard l(this->queue_mutex); + if (this->queue.empty()) return; + + // get line and strip any comment + std::string line = this->queue.front(); + if (size_t comment_pos = line.find_first_of(';') != std::string::npos) + line.erase(comment_pos, std::string::npos); + boost::algorithm::trim(line); + + // calculate checksum + int cs = 0; + for (std::string::const_iterator it = line.begin(); it != line.end(); ++it) + cs = cs ^ *it; + + sent++; asio::streambuf b; std::ostream os(&b); - os << line << "\n"; + os << "N" << sent << " " << line + << "*" << cs << "\n"; asio::write(this->serial, b); -} - -void -GCodeSender::read_line(std::string* line) -{ - for (;;) { - char c; - asio::read(this->serial, asio::buffer(&c, 1)); - switch (c) { - case '\r': - break; - case '\n': - return; - default: - *line += c; - } - } + this->can_send = false; } #ifdef SLIC3RXS diff --git a/xs/src/libslic3r/GCodeSender.hpp b/xs/src/libslic3r/GCodeSender.hpp index c5a67fd51..ef68849fb 100644 --- a/xs/src/libslic3r/GCodeSender.hpp +++ b/xs/src/libslic3r/GCodeSender.hpp @@ -3,28 +3,47 @@ #ifdef BOOST_LIBS #include +#include #include #include #include +#include +#include namespace Slic3r { namespace asio = boost::asio; -class GCodeSender { +class GCodeSender : private boost::noncopyable { public: GCodeSender(std::string devname, unsigned int baud_rate); void send(const std::vector &lines); void send(const std::string &s); - + void disconnect(); + bool error_status() const; + bool is_connected() const; + size_t queue_size() const; private: asio::io_service io; asio::serial_port serial; - std::vector lines; + boost::thread background_thread; + boost::asio::streambuf read_buffer; + bool open; // whether the serial socket is connected + bool connected; // whether the printer is online + bool error; + mutable boost::mutex error_mutex; - void send_line(const std::string &line); - void read_line(std::string* line); + mutable boost::mutex queue_mutex; + std::queue queue; + bool can_send; + size_t sent; + + void set_error_status(bool e); + void do_close(); + void do_read(); + void on_read(const boost::system::error_code& error, size_t bytes_transferred); + void send(); }; } diff --git a/xs/xsp/GCodeSender.xsp b/xs/xsp/GCodeSender.xsp index 37cdd6fd3..150c8c64b 100644 --- a/xs/xsp/GCodeSender.xsp +++ b/xs/xsp/GCodeSender.xsp @@ -11,6 +11,9 @@ GCodeSender(std::string port, unsigned int baud_rate); ~GCodeSender(); + bool is_connected() const; + int queue_size() const; + void disconnect(); void send(std::string s); };