diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index 972a1b094..cf319231f 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -26,7 +26,7 @@ include_directories(${LIBDIR}/libslic3r) if(WIN32) # BOOST_ALL_NO_LIB: Avoid the automatic linking of Boost libraries on Windows. Rather rely on explicit linking. - add_definitions(-D_USE_MATH_DEFINES -D_WIN32 -DBOOST_ALL_NO_LIB) + add_definitions(-D_USE_MATH_DEFINES -D_WIN32 -DBOOST_ALL_NO_LIB -DBOOST_USE_WINAPI_VERSION=0x601) # -D_ITERATOR_DEBUG_LEVEL) if(WIN10SDK_PATH) message("Building with Win10 Netfabb STL fixing service support") @@ -260,6 +260,8 @@ add_library(libslic3r_gui STATIC ${LIBDIR}/slic3r/Utils/PresetUpdater.hpp ${LIBDIR}/slic3r/Utils/Time.cpp ${LIBDIR}/slic3r/Utils/Time.hpp + ${LIBDIR}/slic3r/Utils/HexFile.cpp + ${LIBDIR}/slic3r/Utils/HexFile.hpp ${LIBDIR}/slic3r/IProgressIndicator.hpp ${LIBDIR}/slic3r/AppController.hpp ${LIBDIR}/slic3r/AppController.cpp diff --git a/xs/src/avrdude/CMakeLists.txt b/xs/src/avrdude/CMakeLists.txt index 043f8fb7b..d88563368 100644 --- a/xs/src/avrdude/CMakeLists.txt +++ b/xs/src/avrdude/CMakeLists.txt @@ -1,3 +1,4 @@ +cmake_minimum_required(VERSION 3.0) add_definitions(-D_BSD_SOURCE -D_DEFAULT_SOURCE) # To enable various useful macros and functions on Unices @@ -13,67 +14,74 @@ endif() set(AVRDUDE_SOURCES - ${LIBDIR}/avrdude/arduino.c - ${LIBDIR}/avrdude/avr.c - # ${LIBDIR}/avrdude/avrftdi.c - # ${LIBDIR}/avrdude/avrftdi_tpi.c - ${LIBDIR}/avrdude/avrpart.c - ${LIBDIR}/avrdude/avr910.c - ${LIBDIR}/avrdude/bitbang.c - ${LIBDIR}/avrdude/buspirate.c - ${LIBDIR}/avrdude/butterfly.c - ${LIBDIR}/avrdude/config.c - ${LIBDIR}/avrdude/config_gram.c - # ${LIBDIR}/avrdude/confwin.c - ${LIBDIR}/avrdude/crc16.c - # ${LIBDIR}/avrdude/dfu.c - ${LIBDIR}/avrdude/fileio.c - # ${LIBDIR}/avrdude/flip1.c - # ${LIBDIR}/avrdude/flip2.c - # ${LIBDIR}/avrdude/ft245r.c - # ${LIBDIR}/avrdude/jtagmkI.c - # ${LIBDIR}/avrdude/jtagmkII.c - # ${LIBDIR}/avrdude/jtag3.c - ${LIBDIR}/avrdude/lexer.c - ${LIBDIR}/avrdude/linuxgpio.c - ${LIBDIR}/avrdude/lists.c - # ${LIBDIR}/avrdude/par.c - ${LIBDIR}/avrdude/pgm.c - ${LIBDIR}/avrdude/pgm_type.c - ${LIBDIR}/avrdude/pickit2.c - ${LIBDIR}/avrdude/pindefs.c - # ${LIBDIR}/avrdude/ppi.c - # ${LIBDIR}/avrdude/ppiwin.c - ${LIBDIR}/avrdude/safemode.c - ${LIBDIR}/avrdude/ser_avrdoper.c - ${LIBDIR}/avrdude/serbb_posix.c - ${LIBDIR}/avrdude/serbb_win32.c - ${LIBDIR}/avrdude/ser_posix.c - ${LIBDIR}/avrdude/ser_win32.c - ${LIBDIR}/avrdude/stk500.c - ${LIBDIR}/avrdude/stk500generic.c - ${LIBDIR}/avrdude/stk500v2.c - ${LIBDIR}/avrdude/term.c - ${LIBDIR}/avrdude/update.c - # ${LIBDIR}/avrdude/usbasp.c - # ${LIBDIR}/avrdude/usb_hidapi.c - # ${LIBDIR}/avrdude/usb_libusb.c - # ${LIBDIR}/avrdude/usbtiny.c - ${LIBDIR}/avrdude/wiring.c + arduino.c + avr.c + # avrftdi.c + # avrftdi_tpi.c + avrpart.c + avr910.c + bitbang.c + buspirate.c + butterfly.c + config.c + config_gram.c + # confwin.c + crc16.c + # dfu.c + fileio.c + # flip1.c + # flip2.c + # ft245r.c + # jtagmkI.c + # jtagmkII.c + # jtag3.c + lexer.c + linuxgpio.c + lists.c + # par.c + pgm.c + pgm_type.c + pickit2.c + pindefs.c + # ppi.c + # ppiwin.c + safemode.c + ser_avrdoper.c + serbb_posix.c + serbb_win32.c + ser_posix.c + ser_win32.c + stk500.c + stk500generic.c + stk500v2.c + term.c + update.c + # usbasp.c + # usb_hidapi.c + # usb_libusb.c + # usbtiny.c + wiring.c - ${LIBDIR}/avrdude/main.c - ${LIBDIR}/avrdude/avrdude-slic3r.hpp - ${LIBDIR}/avrdude/avrdude-slic3r.cpp + main.c + avrdude-slic3r.hpp + avrdude-slic3r.cpp ) if (WIN32) set(AVRDUDE_SOURCES ${AVRDUDE_SOURCES} - ${LIBDIR}/avrdude/windows/unistd.cpp - ${LIBDIR}/avrdude/windows/getopt.c + windows/unistd.cpp + windows/getopt.c ) endif() add_library(avrdude STATIC ${AVRDUDE_SOURCES}) +set(STANDALONE_SOURCES + main-standalone.c +) +add_executable(avrdude-slic3r ${STANDALONE_SOURCES}) +target_link_libraries(avrdude-slic3r avrdude) +set_target_properties(avrdude-slic3r PROPERTIES EXCLUDE_FROM_ALL TRUE) + if (WIN32) target_compile_definitions(avrdude PRIVATE WIN32NATIVE=1) - target_include_directories(avrdude SYSTEM PRIVATE ${LIBDIR}/avrdude/windows) # So that sources find the getopt.h windows drop-in + target_include_directories(avrdude SYSTEM PRIVATE windows) # So that sources find the getopt.h windows drop-in endif() diff --git a/xs/src/avrdude/Makefile.standalone b/xs/src/avrdude/Makefile.standalone new file mode 100644 index 000000000..d9a773771 --- /dev/null +++ b/xs/src/avrdude/Makefile.standalone @@ -0,0 +1,54 @@ + +TARGET = avrdude-slic3r + +SOURCES = \ + arduino.c \ + avr.c \ + avrpart.c \ + avr910.c \ + bitbang.c \ + buspirate.c \ + butterfly.c \ + config.c \ + config_gram.c \ + crc16.c \ + fileio.c \ + lexer.c \ + linuxgpio.c \ + lists.c \ + pgm.c \ + pgm_type.c \ + pickit2.c \ + pindefs.c \ + safemode.c \ + ser_avrdoper.c \ + serbb_posix.c \ + serbb_win32.c \ + ser_posix.c \ + ser_win32.c \ + stk500.c \ + stk500generic.c \ + stk500v2.c \ + term.c \ + update.c \ + wiring.c \ + main.c \ + main-standalone.c + +OBJECTS = $(SOURCES:.c=.o) +CFLAGS = -std=c99 -Wall -D_BSD_SOURCE -D_DEFAULT_SOURCE -O3 -DNDEBUG -fPIC +LDFLAGS = -lm + +CC = gcc +RM = rm + +all: $(TARGET) + +$(TARGET): $(OBJECTS) + $(CC) -o ./$@ $(OBJECTS) $(LDFLAGS) + +$(OBJECTS): %.o: %.c + $(CC) $(CFLAGS) -o $@ -c $< + +clean: + $(RM) -f $(OBJECTS) $(TARGET) diff --git a/xs/src/avrdude/avrdude-slic3r.cpp b/xs/src/avrdude/avrdude-slic3r.cpp index 030353413..4a7f22d6e 100644 --- a/xs/src/avrdude/avrdude-slic3r.cpp +++ b/xs/src/avrdude/avrdude-slic3r.cpp @@ -36,6 +36,7 @@ struct AvrDude::priv std::string sys_config; std::deque> args; size_t current_args_set = 0; + bool cancelled = false; RunFn run_fn; MessageFn message_fn; ProgressFn progress_fn; @@ -141,11 +142,16 @@ AvrDude::Ptr AvrDude::run() if (self->p) { auto avrdude_thread = std::thread([self]() { + bool cancel = false; + int res = -1; + if (self->p->run_fn) { - self->p->run_fn(); + self->p->run_fn(*self); } - auto res = self->p->run(); + if (! self->p->cancelled) { + res = self->p->run(); + } if (self->p->complete_fn) { self->p->complete_fn(res, self->p->current_args_set); @@ -160,7 +166,10 @@ AvrDude::Ptr AvrDude::run() void AvrDude::cancel() { - ::avrdude_cancel(); + if (p) { + p->cancelled = true; + ::avrdude_cancel(); + } } void AvrDude::join() diff --git a/xs/src/avrdude/avrdude-slic3r.hpp b/xs/src/avrdude/avrdude-slic3r.hpp index 273aa2378..399df2358 100644 --- a/xs/src/avrdude/avrdude-slic3r.hpp +++ b/xs/src/avrdude/avrdude-slic3r.hpp @@ -12,7 +12,7 @@ class AvrDude { public: typedef std::shared_ptr Ptr; - typedef std::function RunFn; + typedef std::function RunFn; typedef std::function MessageFn; typedef std::function ProgressFn; typedef std::function CompleteFn; @@ -31,7 +31,8 @@ public: AvrDude& push_args(std::vector args); // Set a callback to be called just after run() before avrdude is ran - // This can be used to perform any needed setup tasks from the background thread. + // This can be used to perform any needed setup tasks from the background thread, + // and, optionally, to cancel by writing true to the `cancel` argument. // This has no effect when using run_sync(). AvrDude& on_run(RunFn fn); diff --git a/xs/src/avrdude/fileio.c b/xs/src/avrdude/fileio.c index aa57f5587..708159295 100644 --- a/xs/src/avrdude/fileio.c +++ b/xs/src/avrdude/fileio.c @@ -98,11 +98,11 @@ static int fileio_num(struct fioparms * fio, char * filename, FILE * f, AVRMEM * mem, int size, FILEFMT fmt); -static int fmt_autodetect(char * fname, size_t offset); +static int fmt_autodetect(char * fname, unsigned section); -static FILE *fopen_and_seek(const char *filename, const char *mode, size_t offset) +static FILE *fopen_and_seek(const char *filename, const char *mode, unsigned section) { FILE *file; // On Windows we need to convert the filename to UTF-16 @@ -118,16 +118,38 @@ static FILE *fopen_and_seek(const char *filename, const char *mode, size_t offse file = fopen(filename, mode); #endif - if (file != NULL) { - // Some systems allow seeking past the end of file, so we need check for that first and disallow - if (fseek(file, 0, SEEK_END) != 0 - || offset >= ftell(file) - || fseek(file, offset, SEEK_SET) != 0 - ) { - fclose(file); - file = NULL; - errno = EINVAL; + if (file == NULL) { + return NULL; + } + + // Seek to the specified 'section' + static const char *hex_terminator = ":00000001FF\r"; + unsigned terms_seen = 0; + char buffer[MAX_LINE_LEN + 1]; + + while (terms_seen < section && fgets(buffer, MAX_LINE_LEN, file) != NULL) { + size_t len = strlen(buffer); + + if (buffer[len - 1] == '\n') { + len--; + buffer[len] = 0; } + if (buffer[len - 1] != '\r') { + buffer[len] = '\r'; + len++; + buffer[len] = 0; + } + + if (strcmp(buffer, hex_terminator) == 0) { + // Found a section terminator + terms_seen++; + } + } + + if (feof(file)) { + // Section not found + fclose(file); + return NULL; } return file; @@ -1392,7 +1414,7 @@ int fileio_setparms(int op, struct fioparms * fp, -static int fmt_autodetect(char * fname, size_t offset) +static int fmt_autodetect(char * fname, unsigned section) { FILE * f; unsigned char buf[MAX_LINE_LEN]; @@ -1402,9 +1424,9 @@ static int fmt_autodetect(char * fname, size_t offset) int first = 1; #if defined(WIN32NATIVE) - f = fopen_and_seek(fname, "r", offset); + f = fopen_and_seek(fname, "r", section); #else - f = fopen_and_seek(fname, "rb", offset); + f = fopen_and_seek(fname, "rb", section); #endif if (f == NULL) { @@ -1480,7 +1502,7 @@ static int fmt_autodetect(char * fname, size_t offset) int fileio(int op, char * filename, FILEFMT format, - struct avrpart * p, char * memtype, int size, size_t offset) + struct avrpart * p, char * memtype, int size, unsigned section) { int rc; FILE * f; @@ -1539,7 +1561,7 @@ int fileio(int op, char * filename, FILEFMT format, return -1; } - format_detect = fmt_autodetect(fname, offset); + format_detect = fmt_autodetect(fname, section); if (format_detect < 0) { avrdude_message(MSG_INFO, "%s: can't determine file format for %s, specify explicitly\n", progname, fname); @@ -1570,7 +1592,7 @@ int fileio(int op, char * filename, FILEFMT format, if (format != FMT_IMM) { if (!using_stdio) { - f = fopen_and_seek(fname, fio.mode, offset); + f = fopen_and_seek(fname, fio.mode, section); if (f == NULL) { avrdude_message(MSG_INFO, "%s: can't open %s file %s: %s\n", progname, fio.iodesc, fname, strerror(errno)); diff --git a/xs/src/avrdude/libavrdude.h b/xs/src/avrdude/libavrdude.h index 536f1a2f7..aef792476 100644 --- a/xs/src/avrdude/libavrdude.h +++ b/xs/src/avrdude/libavrdude.h @@ -821,7 +821,7 @@ extern "C" { char * fmtstr(FILEFMT format); int fileio(int op, char * filename, FILEFMT format, - struct avrpart * p, char * memtype, int size, size_t offset); + struct avrpart * p, char * memtype, int size, unsigned section); #ifdef __cplusplus } @@ -870,7 +870,7 @@ enum updateflags { typedef struct update_t { char * memtype; int op; - size_t offset; + unsigned section; char * filename; int format; } UPDATE; @@ -882,7 +882,7 @@ extern "C" { extern UPDATE * parse_op(char * s); extern UPDATE * dup_update(UPDATE * upd); extern UPDATE * new_update(int op, char * memtype, int filefmt, - char * filename, size_t offset); + char * filename, unsigned section); extern void free_update(UPDATE * upd); extern int do_op(PROGRAMMER * pgm, struct avrpart * p, UPDATE * upd, enum updateflags flags); diff --git a/xs/src/avrdude/main-standalone.c b/xs/src/avrdude/main-standalone.c new file mode 100644 index 000000000..359a055ca --- /dev/null +++ b/xs/src/avrdude/main-standalone.c @@ -0,0 +1,9 @@ +#include "avrdude.h" + + +static const char* SYS_CONFIG = "/etc/avrdude-slic3r.conf"; + +int main(int argc, char *argv[]) +{ + return avrdude_main(argc, argv, SYS_CONFIG); +} diff --git a/xs/src/avrdude/main.c b/xs/src/avrdude/main.c index d4c34fe44..5d73403b0 100644 --- a/xs/src/avrdude/main.c +++ b/xs/src/avrdude/main.c @@ -194,7 +194,7 @@ static void usage(void) " -F Override invalid signature check.\n" " -e Perform a chip erase.\n" " -O Perform RC oscillator calibration (see AVR053). \n" - " -U :r|w|v::[:format]\n" + " -U :r|w|v:
:[:format]\n" " Memory operation specification.\n" " Multiple -U options are allowed, each request\n" " is performed in the order specified.\n" diff --git a/xs/src/avrdude/update.c b/xs/src/avrdude/update.c index e9dd6e325..417cbf71d 100644 --- a/xs/src/avrdude/update.c +++ b/xs/src/avrdude/update.c @@ -101,22 +101,22 @@ UPDATE * parse_op(char * s) p++; - // Extension: Parse file contents offset - size_t offset = 0; + // Extension: Parse file section number + unsigned section = 0; for (; *p != ':'; p++) { if (*p >= '0' && *p <= '9') { - offset *= 10; - offset += *p - 0x30; + section *= 10; + section += *p - 0x30; } else { - avrdude_message(MSG_INFO, "%s: invalid update specification: offset is not a number\n", progname); + avrdude_message(MSG_INFO, "%s: invalid update specification:
is not a number\n", progname); free(upd->memtype); free(upd); return NULL; } } - upd->offset = offset; + upd->section = section; p++; /* @@ -194,7 +194,7 @@ UPDATE * dup_update(UPDATE * upd) return u; } -UPDATE * new_update(int op, char * memtype, int filefmt, char * filename, size_t offset) +UPDATE * new_update(int op, char * memtype, int filefmt, char * filename, unsigned section) { UPDATE * u; @@ -208,7 +208,7 @@ UPDATE * new_update(int op, char * memtype, int filefmt, char * filename, size_t u->filename = strdup(filename); u->op = op; u->format = filefmt; - u->offset = offset; + u->section = section; return u; } @@ -286,7 +286,7 @@ int do_op(PROGRAMMER * pgm, struct avrpart * p, UPDATE * upd, enum updateflags f progname, strcmp(upd->filename, "-")==0 ? "" : upd->filename); } - rc = fileio(FIO_READ, upd->filename, upd->format, p, upd->memtype, -1, upd->offset); + rc = fileio(FIO_READ, upd->filename, upd->format, p, upd->memtype, -1, upd->section); if (rc < 0) { avrdude_message(MSG_INFO, "%s: read from file '%s' failed\n", progname, upd->filename); @@ -351,7 +351,7 @@ int do_op(PROGRAMMER * pgm, struct avrpart * p, UPDATE * upd, enum updateflags f progname, mem->desc, upd->filename); } - rc = fileio(FIO_READ, upd->filename, upd->format, p, upd->memtype, -1, upd->offset); + rc = fileio(FIO_READ, upd->filename, upd->format, p, upd->memtype, -1, upd->section); if (rc < 0) { avrdude_message(MSG_INFO, "%s: read from file '%s' failed\n", progname, upd->filename); 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/GUI/FirmwareDialog.cpp b/xs/src/slic3r/GUI/FirmwareDialog.cpp index d74743055..30339e3cb 100644 --- a/xs/src/slic3r/GUI/FirmwareDialog.cpp +++ b/xs/src/slic3r/GUI/FirmwareDialog.cpp @@ -1,12 +1,23 @@ -#include "FirmwareDialog.hpp" - #include #include +#include +#include #include +#include #include #include #include +#include "libslic3r/Utils.hpp" +#include "avrdude/avrdude-slic3r.hpp" +#include "GUI.hpp" +#include "MsgDialog.hpp" +#include "../Utils/HexFile.hpp" +#include "../Utils/Serial.hpp" + +// wx includes need to come after asio because of the WinSock.h problem +#include "FirmwareDialog.hpp" + #include #include #include @@ -22,16 +33,18 @@ #include #include -#include "libslic3r/Utils.hpp" -#include "avrdude/avrdude-slic3r.hpp" -#include "GUI.hpp" -#include "../Utils/Serial.hpp" namespace fs = boost::filesystem; +namespace asio = boost::asio; +using boost::system::error_code; namespace Slic3r { +using Utils::HexFile; +using Utils::SerialPortInfo; +using Utils::Serial; + // This enum discriminates the kind of information in EVT_AVRDUDE, // it's stored in the ExtraLong field of wxCommandEvent. @@ -40,6 +53,7 @@ enum AvrdudeEvent AE_MESSAGE, AE_PROGRESS, AE_EXIT, + AE_ERROR, }; wxDECLARE_EVENT(EVT_AVRDUDE, wxCommandEvent); @@ -52,7 +66,6 @@ struct FirmwareDialog::priv { enum AvrDudeComplete { - AC_NONE, AC_SUCCESS, AC_FAILURE, AC_CANCEL, @@ -61,7 +74,7 @@ struct FirmwareDialog::priv FirmwareDialog *q; // PIMPL back pointer ("Q-Pointer") wxComboBox *port_picker; - std::vector ports; + std::vector ports; wxFilePickerCtrl *hex_picker; wxStaticText *txt_status; wxGauge *progressbar; @@ -80,7 +93,9 @@ struct FirmwareDialog::priv AvrDude::Ptr avrdude; std::string avrdude_config; unsigned progress_tasks_done; + unsigned progress_tasks_bar; bool cancelled; + const bool extra_verbose; // For debugging priv(FirmwareDialog *q) : q(q), @@ -89,16 +104,25 @@ struct FirmwareDialog::priv timer_pulse(q), avrdude_config((fs::path(::Slic3r::resources_dir()) / "avrdude" / "avrdude.conf").string()), progress_tasks_done(0), - cancelled(false) + progress_tasks_bar(0), + cancelled(false), + extra_verbose(false) {} void find_serial_ports(); - void flashing_start(bool flashing_l10n); + void flashing_start(unsigned tasks); void flashing_done(AvrDudeComplete complete); - size_t hex_lang_offset(const wxString &path); + void check_model_id(const HexFile &metadata, const SerialPortInfo &port); + + void prepare_common(AvrDude &, const SerialPortInfo &port, const std::string &filename); + void prepare_mk2(AvrDude &, const SerialPortInfo &port, const std::string &filename); + void prepare_mk3(AvrDude &, const SerialPortInfo &port, const std::string &filename); + void prepare_mm_control(AvrDude &, const SerialPortInfo &port, const std::string &filename); void perform_upload(); + void cancel(); void on_avrdude(const wxCommandEvent &evt); + void ensure_joined(); }; void FirmwareDialog::priv::find_serial_ports() @@ -108,7 +132,7 @@ void FirmwareDialog::priv::find_serial_ports() this->ports = new_ports; port_picker->Clear(); for (const auto &port : this->ports) - port_picker->Append(port.friendly_name); + port_picker->Append(wxString::FromUTF8(port.friendly_name.data())); if (ports.size() > 0) { int idx = port_picker->GetValue().IsEmpty() ? 0 : -1; for (int i = 0; i < (int)this->ports.size(); ++ i) @@ -122,7 +146,7 @@ void FirmwareDialog::priv::find_serial_ports() } } -void FirmwareDialog::priv::flashing_start(bool flashing_l10n) +void FirmwareDialog::priv::flashing_start(unsigned tasks) { txt_stdout->Clear(); txt_status->SetLabel(_(L("Flashing in progress. Please do not disconnect the printer!"))); @@ -132,9 +156,10 @@ void FirmwareDialog::priv::flashing_start(bool flashing_l10n) hex_picker->Disable(); btn_close->Disable(); btn_flash->SetLabel(btn_flash_label_flashing); - progressbar->SetRange(flashing_l10n ? 500 : 200); // See progress callback below + progressbar->SetRange(200 * tasks); // See progress callback below progressbar->SetValue(0); progress_tasks_done = 0; + progress_tasks_bar = 0; cancelled = false; timer_pulse.Start(50); } @@ -158,63 +183,49 @@ void FirmwareDialog::priv::flashing_done(AvrDudeComplete complete) } } -size_t FirmwareDialog::priv::hex_lang_offset(const wxString &path) +void FirmwareDialog::priv::check_model_id(const HexFile &metadata, const SerialPortInfo &port) { - fs::ifstream file(fs::path(path.wx_str())); - if (! file.good()) { - return 0; + if (metadata.model_id.empty()) { + // No data to check against + return; + } + + asio::io_service io; + Serial serial(io, port.port, 115200); + serial.printer_setup(); + + enum { + TIMEOUT = 1000, + RETREIES = 3, + }; + + if (! serial.printer_ready_wait(RETREIES, TIMEOUT)) { + throw wxString::Format(_(L("Could not connect to the printer at %s")), port.port); } - static const char *hex_terminator = ":00000001FF\r"; - size_t res = 0; std::string line; - while (getline(file, line, '\n').good()) { - // Account for LF vs CRLF - if (!line.empty() && line.back() != '\r') { - line.push_back('\r'); + error_code ec; + serial.printer_write_line("PRUSA Rev"); + while (serial.read_line(TIMEOUT, line, ec)) { + if (ec) { throw wxString::Format(_(L("Could not connect to the printer at %s")), port.port); } + if (line == "ok") { continue; } + + if (line == metadata.model_id) { + return; + } else { + throw wxString::Format(_(L( + "The firmware hex file does not match the printer model.\n" + "The hex file is intended for:\n %s\n" + "Printer reports:\n %s" + )), metadata.model_id, line); } - if (line == hex_terminator) { - if (res == 0) { - // This is the first terminator seen, save the position - res = file.tellg(); - } else { - // We've found another terminator, return the offset just after the first one - // which is the start of the second 'section'. - return res; - } - } + line.clear(); } - - return 0; } -void FirmwareDialog::priv::perform_upload() +void FirmwareDialog::priv::prepare_common(AvrDude &avrdude, const SerialPortInfo &port, const std::string &filename) { - auto filename = hex_picker->GetPath(); - std::string port = port_picker->GetValue().ToStdString(); - int selection = port_picker->GetSelection(); - if (selection != -1) { - // Verify whether the combo box list selection equals to the combo box edit value. - if (this->ports[selection].friendly_name == port) - port = this->ports[selection].port; - } - if (filename.IsEmpty() || port.empty()) { return; } - - const bool extra_verbose = false; // For debugging - const auto lang_offset = hex_lang_offset(filename); - const auto filename_utf8 = filename.utf8_str(); - - flashing_start(lang_offset > 0); - - // It is ok here to use the q-pointer to the FirmwareDialog - // because the dialog ensures it doesn't exit before the background thread is done. - auto q = this->q; - - // Init the avrdude object - AvrDude avrdude(avrdude_config); - - // Build argument list(s) std::vector args {{ extra_verbose ? "-vvvvv" : "-v", "-p", "atmega2560", @@ -222,11 +233,10 @@ void FirmwareDialog::priv::perform_upload() // The Prusa's avrdude is patched to never send semicolons inside the data packets, as the USB to serial chip // is flashed with a buggy firmware. "-c", "wiring", - "-P", port, + "-P", port.port, "-b", "115200", // TODO: Allow other rates? Ditto below. "-D", - // XXX: Safe mode? - "-U", (boost::format("flash:w:0:%1%:i") % filename_utf8.data()).str(), + "-U", (boost::format("flash:w:0:%1%:i") % filename).str(), }}; BOOST_LOG_TRIVIAL(info) << "Invoking avrdude, arguments: " @@ -235,32 +245,167 @@ void FirmwareDialog::priv::perform_upload() }); avrdude.push_args(std::move(args)); - - if (lang_offset > 0) { - // The hex file also contains another section with l10n data to be flashed into the external flash on MK3 (Einsy) - // This is done via another avrdude invocation, here we build arg list for that: - std::vector args_l10n {{ - extra_verbose ? "-vvvvv" : "-v", - "-p", "atmega2560", - // Using the "Arduino" mode to program Einsy's external flash with languages, using the STK500 protocol (not the STK500v2). - // The Prusa's avrdude is patched again to never send semicolons inside the data packets. - "-c", "arduino", - "-P", port, - "-b", "115200", - "-D", - "-u", // disable safe mode - "-U", (boost::format("flash:w:%1%:%2%:i") % lang_offset % filename_utf8.data()).str(), - }}; +} - BOOST_LOG_TRIVIAL(info) << "Invoking avrdude for external flash flashing, arguments: " - << std::accumulate(std::next(args_l10n.begin()), args_l10n.end(), args_l10n[0], [](std::string a, const std::string &b) { - return a + ' ' + b; - }); +void FirmwareDialog::priv::prepare_mk2(AvrDude &avrdude, const SerialPortInfo &port, const std::string &filename) +{ + prepare_common(avrdude, port, filename); +} - avrdude.push_args(std::move(args_l10n)); +void FirmwareDialog::priv::prepare_mk3(AvrDude &avrdude, const SerialPortInfo &port, const std::string &filename) +{ + prepare_common(avrdude, port, filename); + + // The hex file also contains another section with l10n data to be flashed into the external flash on MK3 (Einsy) + // This is done via another avrdude invocation, here we build arg list for that: + std::vector args_l10n {{ + extra_verbose ? "-vvvvv" : "-v", + "-p", "atmega2560", + // Using the "Arduino" mode to program Einsy's external flash with languages, using the STK500 protocol (not the STK500v2). + // The Prusa's avrdude is patched again to never send semicolons inside the data packets. + "-c", "arduino", + "-P", port.port, + "-b", "115200", + "-D", + "-u", // disable safe mode + "-U", (boost::format("flash:w:1:%1%:i") % filename).str(), + }}; + + BOOST_LOG_TRIVIAL(info) << "Invoking avrdude for external flash flashing, arguments: " + << std::accumulate(std::next(args_l10n.begin()), args_l10n.end(), args_l10n[0], [](std::string a, const std::string &b) { + return a + ' ' + b; + }); + + avrdude.push_args(std::move(args_l10n)); +} + +void FirmwareDialog::priv::prepare_mm_control(AvrDude &avrdude, const SerialPortInfo &port_in, const std::string &filename) +{ + // Check if the port has the PID/VID of 0x2c99/3 + // If not, check if it is the MMU (0x2c99/4) and reboot the by opening @ 1200 bauds + BOOST_LOG_TRIVIAL(info) << "Flashing MMU 2.0, looking for VID/PID 0x2c99/3 or 0x2c99/4 ..."; + SerialPortInfo port = port_in; + if (! port.id_match(0x2c99, 3)) { + if (! port.id_match(0x2c99, 4)) { + // This is not a Prusa MMU 2.0 device + BOOST_LOG_TRIVIAL(error) << boost::format("Not a Prusa MMU 2.0 device: `%1%`") % port.port; + throw wxString::Format(_(L("The device at `%s` is not am Original Prusa i3 MMU 2.0 device")), port.port); + } + + BOOST_LOG_TRIVIAL(info) << boost::format("Found VID/PID 0x2c99/4 at `%1%`, rebooting the device ...") % port.port; + + { + asio::io_service io; + Serial serial(io, port.port, 1200); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } + + // Wait for the bootloader to show up + std::this_thread::sleep_for(std::chrono::milliseconds(2000)); + + // Look for the rebooted device + BOOST_LOG_TRIVIAL(info) << "... looking for VID/PID 0x2c99/3 ..."; + auto new_ports = Utils::scan_serial_ports_extended(); + unsigned hits = 0; + for (auto &&new_port : new_ports) { + if (new_port.id_match(0x2c99, 3)) { + hits++; + port = std::move(new_port); + } + } + + if (hits == 0) { + BOOST_LOG_TRIVIAL(error) << "No VID/PID 0x2c99/3 device found after rebooting the MMU 2.0"; + throw wxString::Format(_(L("Failed to reboot the device at `%s` for programming")), port.port); + } else if (hits > 1) { + // We found multiple 0x2c99/3 devices, this is bad, because there's no way to find out + // which one is the one user wants to flash. + BOOST_LOG_TRIVIAL(error) << "Several VID/PID 0x2c99/3 devices found after rebooting the MMU 2.0"; + throw wxString::Format(_(L("Multiple Original Prusa i3 MMU 2.0 devices found. Please only connect one at a time for flashing.")), port.port); + } } - + + BOOST_LOG_TRIVIAL(info) << boost::format("Found VID/PID 0x2c99/3 at `%1%`, flashing ...") % port.port; + + std::vector args {{ + extra_verbose ? "-vvvvv" : "-v", + "-p", "atmega32u4", + "-c", "avr109", + "-P", port.port, + "-b", "57600", + "-D", + "-U", (boost::format("flash:w:0:%1%:i") % filename).str(), + }}; + + BOOST_LOG_TRIVIAL(info) << "Invoking avrdude, arguments: " + << std::accumulate(std::next(args.begin()), args.end(), args[0], [](std::string a, const std::string &b) { + return a + ' ' + b; + }); + + avrdude.push_args(std::move(args)); +} + + +void FirmwareDialog::priv::perform_upload() +{ + auto filename = hex_picker->GetPath(); + if (filename.IsEmpty()) { return; } + + int selection = port_picker->GetSelection(); + if (selection == wxNOT_FOUND) { return; } + + const SerialPortInfo &port = this->ports[selection]; + // Verify whether the combo box list selection equals to the combo box edit value. + if (wxString::FromUTF8(this->ports[selection].friendly_name.data()) != port_picker->GetValue()) { + return; + } + + const bool extra_verbose = false; // For debugging + HexFile metadata(filename.wx_str()); + const std::string filename_utf8(filename.utf8_str().data()); + + flashing_start(metadata.device == HexFile::DEV_MK3 ? 2 : 1); + + // Init the avrdude object + AvrDude avrdude(avrdude_config); + + // It is ok here to use the q-pointer to the FirmwareDialog + // because the dialog ensures it doesn't exit before the background thread is done. + auto q = this->q; + this->avrdude = avrdude + .on_run([=](AvrDude &avrdude) { + auto queue_error = [&](wxString message) { + avrdude.cancel(); + + auto evt = new wxCommandEvent(EVT_AVRDUDE, this->q->GetId()); + evt->SetExtraLong(AE_ERROR); + evt->SetString(std::move(message)); + wxQueueEvent(this->q, evt); + }; + + try { + switch (metadata.device) { + case HexFile::DEV_MK3: + this->check_model_id(metadata, port); + this->prepare_mk3(avrdude, port, filename_utf8); + break; + + case HexFile::DEV_MM_CONTROL: + this->check_model_id(metadata, port); + this->prepare_mm_control(avrdude, port, filename_utf8); + break; + + default: + this->prepare_mk2(avrdude, port, filename_utf8); + break; + } + } catch (const wxString &message) { + queue_error(message); + } catch (const std::exception &ex) { + queue_error(wxString::Format(_(L("Error accessing port at %s: %s")), port.port, ex.what())); + } + }) .on_message(std::move([q, extra_verbose](const char *msg, unsigned /* size */) { if (extra_verbose) { BOOST_LOG_TRIVIAL(debug) << "avrdude: " << msg; @@ -313,12 +458,15 @@ void FirmwareDialog::priv::on_avrdude(const wxCommandEvent &evt) // and then display overall progress during the latter tasks. if (progress_tasks_done > 0) { - progressbar->SetValue(progress_tasks_done - 100 + evt.GetInt()); + progressbar->SetValue(progress_tasks_bar + evt.GetInt()); } if (evt.GetInt() == 100) { timer_pulse.Stop(); - progress_tasks_done += 100; + if (progress_tasks_done % 3 != 0) { + progress_tasks_bar += 100; + } + progress_tasks_done++; } break; @@ -328,11 +476,17 @@ void FirmwareDialog::priv::on_avrdude(const wxCommandEvent &evt) complete_kind = cancelled ? AC_CANCEL : (evt.GetInt() == 0 ? AC_SUCCESS : AC_FAILURE); flashing_done(complete_kind); + ensure_joined(); + break; - // Make sure the background thread is collected and the AvrDude object reset - if (avrdude) { avrdude->join(); } - avrdude.reset(); - + case AE_ERROR: + txt_stdout->AppendText(evt.GetString()); + flashing_done(AC_FAILURE); + ensure_joined(); + { + GUI::ErrorDialog dlg(this->q, evt.GetString()); + dlg.ShowModal(); + } break; default: @@ -340,6 +494,13 @@ void FirmwareDialog::priv::on_avrdude(const wxCommandEvent &evt) } } +void FirmwareDialog::priv::ensure_joined() +{ + // Make sure the background thread is collected and the AvrDude object reset + if (avrdude) { avrdude->join(); } + avrdude.reset(); +} + // Public diff --git a/xs/src/slic3r/Utils/HexFile.cpp b/xs/src/slic3r/Utils/HexFile.cpp new file mode 100644 index 000000000..ed26ddf37 --- /dev/null +++ b/xs/src/slic3r/Utils/HexFile.cpp @@ -0,0 +1,107 @@ +#include "HexFile.hpp" + +#include +#include +#include +#include + +namespace fs = boost::filesystem; +namespace pt = boost::property_tree; + + +namespace Slic3r { +namespace Utils { + + +static HexFile::DeviceKind parse_device_kind(const std::string &str) +{ + if (str == "mk2") { return HexFile::DEV_MK2; } + else if (str == "mk3") { return HexFile::DEV_MK3; } + else if (str == "mm-control") { return HexFile::DEV_MM_CONTROL; } + else { return HexFile::DEV_GENERIC; } +} + +static size_t hex_num_sections(fs::ifstream &file) +{ + file.seekg(0); + if (! file.good()) { + return 0; + } + + static const char *hex_terminator = ":00000001FF\r"; + size_t res = 0; + std::string line; + while (getline(file, line, '\n').good()) { + // Account for LF vs CRLF + if (!line.empty() && line.back() != '\r') { + line.push_back('\r'); + } + + if (line == hex_terminator) { + res++; + } + } + + return res; +} + +HexFile::HexFile(fs::path path) : + path(std::move(path)), + device(DEV_GENERIC) +{ + fs::ifstream file(this->path); + if (! file.good()) { + return; + } + + std::string line; + std::stringstream header_ini; + while (std::getline(file, line, '\n').good()) { + if (line.empty()) { + continue; + } + + // Account for LF vs CRLF + if (!line.empty() && line.back() == '\r') { + line.pop_back(); + } + + if (line.front() == ';') { + line.front() = ' '; + header_ini << line << std::endl; + } else if (line.front() == ':') { + break; + } + } + + pt::ptree ptree; + try { + pt::read_ini(header_ini, ptree); + } catch (std::exception &e) { + return; + } + + bool has_device_meta = false; + const auto device = ptree.find("device"); + if (device != ptree.not_found()) { + this->device = parse_device_kind(device->second.data()); + has_device_meta = true; + } + + const auto model_id = ptree.find("model_id"); + if (model_id != ptree.not_found()) { + this->model_id = model_id->second.data(); + } + + if (! has_device_meta) { + // No device metadata, look at the number of 'sections' + if (hex_num_sections(file) == 2) { + // Looks like a pre-metadata l10n firmware for the MK3, assume that's the case + this->device = DEV_MK3; + } + } +} + + +} +} diff --git a/xs/src/slic3r/Utils/HexFile.hpp b/xs/src/slic3r/Utils/HexFile.hpp new file mode 100644 index 000000000..d8d1e09ab --- /dev/null +++ b/xs/src/slic3r/Utils/HexFile.hpp @@ -0,0 +1,32 @@ +#ifndef slic3r_Hex_hpp_ +#define slic3r_Hex_hpp_ + +#include +#include + + +namespace Slic3r { +namespace Utils { + + +struct HexFile +{ + enum DeviceKind { + DEV_GENERIC, + DEV_MK2, + DEV_MK3, + DEV_MM_CONTROL, + }; + + boost::filesystem::path path; + DeviceKind device; + std::string model_id; + + HexFile(boost::filesystem::path path); +}; + + +} +} + +#endif diff --git a/xs/src/slic3r/Utils/Serial.cpp b/xs/src/slic3r/Utils/Serial.cpp index 32b6bb160..c3c16b314 100644 --- a/xs/src/slic3r/Utils/Serial.cpp +++ b/xs/src/slic3r/Utils/Serial.cpp @@ -3,17 +3,22 @@ #include #include #include +#include +#include #include +#include #include #include #include +#include #if _WIN32 #include #include #include #include + #include // Undefine min/max macros incompatible with the standard library // For example, std::numeric_limits::max() // produces some weird errors @@ -34,6 +39,23 @@ #include #endif +#ifndef _WIN32 + #include + #include + #include + #include +#endif + +#if defined(__APPLE__) || defined(__OpenBSD__) + #include +#elif defined __linux__ + #include + #include +#endif + +using boost::optional; + + namespace Slic3r { namespace Utils { @@ -42,15 +64,43 @@ static bool looks_like_printer(const std::string &friendly_name) return friendly_name.find("Original Prusa") != std::string::npos; } -#ifdef __linux__ -static std::string get_tty_friendly_name(const std::string &path, const std::string &name) +#if _WIN32 +void parse_hardware_id(const std::string &hardware_id, SerialPortInfo &spi) { - const auto sysfs_product = (boost::format("/sys/class/tty/%1%/device/../product") % name).str(); - std::ifstream file(sysfs_product); - std::string product; + unsigned vid, pid; + std::regex pattern("USB\\\\.*VID_([[:xdigit:]]+)&PID_([[:xdigit:]]+).*"); + std::smatch matches; + if (std::regex_match(hardware_id, matches, pattern)) { + try { + vid = std::stoul(matches[1].str(), 0, 16); + pid = std::stoul(matches[2].str(), 0, 16); + spi.id_vendor = vid; + spi.id_product = pid; + } + catch (...) {} + } +} +#endif - std::getline(file, product); - return file.good() ? (boost::format("%1% (%2%)") % product % path).str() : path; +#ifdef __linux__ +optional sysfs_tty_prop(const std::string &tty_dev, const std::string &name) +{ + const auto prop_path = (boost::format("/sys/class/tty/%1%/device/../%2%") % tty_dev % name).str(); + std::ifstream file(prop_path); + std::string res; + + std::getline(file, res); + if (file.good()) { return res; } + else { return boost::none; } +} + +optional sysfs_tty_prop_hex(const std::string &tty_dev, const std::string &name) +{ + auto prop = sysfs_tty_prop(tty_dev, name); + if (!prop) { return boost::none; } + + try { return std::stoul(*prop, 0, 16); } + catch (...) { return boost::none; } } #endif @@ -80,6 +130,7 @@ std::vector scan_serial_ports_extended() if (port_info.port.empty()) continue; } + // Find the size required to hold the device info. DWORD regDataType; DWORD reqSize = 0; @@ -88,7 +139,8 @@ std::vector scan_serial_ports_extended() // Now store it in a buffer. if (! SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_HARDWAREID, ®DataType, (BYTE*)hardware_id.data(), reqSize, nullptr)) continue; - port_info.hardware_id = boost::nowide::narrow(hardware_id.data()); + parse_hardware_id(boost::nowide::narrow(hardware_id.data()), port_info); + // Find the size required to hold the friendly name. reqSize = 0; SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_FRIENDLYNAME, nullptr, nullptr, 0, &reqSize); @@ -120,6 +172,8 @@ std::vector scan_serial_ports_extended() if (result) { SerialPortInfo port_info; port_info.port = path; + + // Attempt to read out the device friendly name if ((cf_property = IORegistryEntrySearchCFProperty(port, kIOServicePlane, CFSTR("USB Interface Name"), kCFAllocatorDefault, kIORegistryIterateRecursively | kIORegistryIterateParents)) || @@ -141,6 +195,23 @@ std::vector scan_serial_ports_extended() } if (port_info.friendly_name.empty()) port_info.friendly_name = port_info.port; + + // Attempt to read out the VID & PID + int vid, pid; + auto cf_vendor = IORegistryEntrySearchCFProperty(port, kIOServicePlane, CFSTR("idVendor"), + kCFAllocatorDefault, kIORegistryIterateRecursively | kIORegistryIterateParents); + auto cf_product = IORegistryEntrySearchCFProperty(port, kIOServicePlane, CFSTR("idProduct"), + kCFAllocatorDefault, kIORegistryIterateRecursively | kIORegistryIterateParents); + if (cf_vendor && cf_product) { + if (CFNumberGetValue((CFNumberRef)cf_vendor, kCFNumberIntType, &vid) && + CFNumberGetValue((CFNumberRef)cf_product, kCFNumberIntType, &pid)) { + port_info.id_vendor = vid; + port_info.id_product = pid; + } + } + if (cf_vendor) { CFRelease(cf_vendor); } + if (cf_product) { CFRelease(cf_product); } + output.emplace_back(std::move(port_info)); } } @@ -158,9 +229,15 @@ std::vector scan_serial_ports_extended() const auto path = dir_entry.path().string(); SerialPortInfo spi; spi.port = path; - spi.hardware_id = path; #ifdef __linux__ - spi.friendly_name = get_tty_friendly_name(path, name); + auto friendly_name = sysfs_tty_prop(name, "product"); + spi.friendly_name = friendly_name ? (boost::format("%1% (%2%)") % *friendly_name % path).str() : path; + auto vid = sysfs_tty_prop_hex(name, "idVendor"); + auto pid = sysfs_tty_prop_hex(name, "idProduct"); + if (vid && pid) { + spi.id_vendor = *vid; + spi.id_product = *pid; + } #else spi.friendly_name = path; #endif @@ -189,5 +266,224 @@ 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) +{ + set_baud_rate(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() +{ + 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..d15f249c0 100644 --- a/xs/src/slic3r/Utils/Serial.hpp +++ b/xs/src/slic3r/Utils/Serial.hpp @@ -1,31 +1,80 @@ #ifndef slic3r_GUI_Utils_Serial_hpp_ #define slic3r_GUI_Utils_Serial_hpp_ -#include #include #include -#include +#include +#include + namespace Slic3r { namespace Utils { struct SerialPortInfo { std::string port; - std::string hardware_id; + unsigned id_vendor = -1; + unsigned id_product = -1; std::string friendly_name; - bool is_printer = false; + bool is_printer = false; + + bool id_match(unsigned id_vendor, unsigned id_product) const { return id_vendor == this->id_vendor && id_product == this->id_product; } }; -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 && - sp1.is_printer == sp2.is_printer; + return + sp1.port == sp2.port && + sp1.id_vendor == sp2.id_vendor && + sp1.id_product == sp2.id_product && + sp1.is_printer == sp2.is_printer; } 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); + 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 + void printer_setup(); + + // 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