diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index 976943d64..95ff990aa 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") @@ -258,6 +258,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/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/slic3r/GUI/FirmwareDialog.cpp b/xs/src/slic3r/GUI/FirmwareDialog.cpp index 17ff42245..38c2937fc 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_num_sections(const wxString &path); + void check_model_id(const HexFile &metadata, const SerialPortInfo &port); + + void prepare_common(AvrDude &, const SerialPortInfo &port); + void prepare_mk2(AvrDude &, const SerialPortInfo &port); + void prepare_mk3(AvrDude &, const SerialPortInfo &port); + void prepare_mm_control(AvrDude &, const SerialPortInfo &port); void perform_upload(); + void cancel(); void on_avrdude(const wxCommandEvent &evt); + void ensure_joined(); }; void FirmwareDialog::priv::find_serial_ports() @@ -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,56 +183,51 @@ void FirmwareDialog::priv::flashing_done(AvrDudeComplete complete) } } -size_t FirmwareDialog::priv::hex_num_sections(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) { - res++; - } + line.clear(); } - - return res; } -void FirmwareDialog::priv::perform_upload() +void FirmwareDialog::priv::prepare_common(AvrDude &avrdude, const SerialPortInfo &port) { 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 num_secions = hex_num_sections(filename); - const auto filename_utf8 = filename.utf8_str(); - - flashing_start(num_secions > 1); - - // 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", @@ -215,11 +235,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.utf8_str().data()).str(), }}; BOOST_LOG_TRIVIAL(info) << "Invoking avrdude, arguments: " @@ -228,32 +247,172 @@ void FirmwareDialog::priv::perform_upload() }); avrdude.push_args(std::move(args)); - - if (num_secions > 1) { - // 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:%1%:i") % 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) +{ + flashing_start(1); + prepare_common(avrdude, port); +} - avrdude.push_args(std::move(args_l10n)); +void FirmwareDialog::priv::prepare_mk3(AvrDude &avrdude, const SerialPortInfo &port) +{ + flashing_start(2); + prepare_common(avrdude, port); + + auto filename = hex_picker->GetPath(); + + // 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.utf8_str().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; + }); + + avrdude.push_args(std::move(args_l10n)); +} + +void FirmwareDialog::priv::prepare_mm_control(AvrDude &avrdude, const SerialPortInfo &port_in) +{ + // 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; + + auto filename = hex_picker->GetPath(); + + 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.utf8_str().data()).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; } + + std::string port_selected = port_picker->GetValue().ToStdString(); + const SerialPortInfo &port = this->ports[selection]; + // Verify whether the combo box list selection equals to the combo box edit value. + if (this->ports[selection].friendly_name != port_selected) { return; } + + const bool extra_verbose = false; // For debugging + HexFile metadata(filename.wx_str()); + // const auto filename_utf8 = filename.utf8_str(); + + 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([this, metadata, port](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); + break; + + case HexFile::DEV_MM_CONTROL: + this->check_model_id(metadata, port); + this->prepare_mm_control(avrdude, port); + break; + + default: + this->prepare_mk2(avrdude, port); + 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; @@ -306,12 +465,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; @@ -321,11 +483,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: @@ -333,6 +501,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 8a454b119..c3c16b314 100644 --- a/xs/src/slic3r/Utils/Serial.cpp +++ b/xs/src/slic3r/Utils/Serial.cpp @@ -11,12 +11,14 @@ #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 @@ -51,6 +53,8 @@ #include #endif +using boost::optional; + namespace Slic3r { namespace Utils { @@ -60,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 @@ -98,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; @@ -106,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); @@ -138,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)) || @@ -159,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)); } } @@ -176,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 @@ -221,7 +280,7 @@ Serial::Serial(asio::io_service& 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); + set_baud_rate(baud_rate); } Serial::~Serial() {} @@ -359,9 +418,8 @@ bool Serial::read_line(unsigned timeout, std::string &line, error_code &ec) } } -void Serial::printer_setup(unsigned baud_rate) +void Serial::printer_setup() { - 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 } diff --git a/xs/src/slic3r/Utils/Serial.hpp b/xs/src/slic3r/Utils/Serial.hpp index 5df33916f..d15f249c0 100644 --- a/xs/src/slic3r/Utils/Serial.hpp +++ b/xs/src/slic3r/Utils/Serial.hpp @@ -12,16 +12,21 @@ 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) { - 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(); @@ -32,7 +37,6 @@ 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; @@ -48,8 +52,7 @@ public: 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); + void printer_setup(); // Write data from a string size_t write_string(const std::string &str);