Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/supermerill/SuperSlicer.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/xs
diff options
context:
space:
mode:
authorVojtech Kral <vojtech@kral.hk>2018-07-24 18:42:12 +0300
committerbubnikv <bubnikv@gmail.com>2018-07-27 14:27:52 +0300
commita32bd17b752317450e35c398b61356cdbe13998d (patch)
treeee589896b5b849c5ab2596c78a9af6200bef4ca5 /xs
parenta7eaf38853c1c804c4f4c7d5e30801f196713727 (diff)
FirmwareUpdater: MMU 2.0 / Caterina flashing
Diffstat (limited to 'xs')
-rw-r--r--xs/CMakeLists.txt4
-rw-r--r--xs/src/avrdude/avrdude-slic3r.cpp15
-rw-r--r--xs/src/avrdude/avrdude-slic3r.hpp5
-rw-r--r--xs/src/slic3r/GUI/FirmwareDialog.cpp343
-rw-r--r--xs/src/slic3r/Utils/HexFile.cpp107
-rw-r--r--xs/src/slic3r/Utils/HexFile.hpp32
-rw-r--r--xs/src/slic3r/Utils/Serial.cpp82
-rw-r--r--xs/src/slic3r/Utils/Serial.hpp19
8 files changed, 497 insertions, 110 deletions
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<std::vector<std::string>> 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<AvrDude> Ptr;
- typedef std::function<void()> RunFn;
+ typedef std::function<void(AvrDude&)> RunFn;
typedef std::function<void(const char * /* msg */, unsigned /* size */)> MessageFn;
typedef std::function<void(const char * /* task */, unsigned /* progress */)> ProgressFn;
typedef std::function<void(int /* exit status */, size_t /* args_id */)> CompleteFn;
@@ -31,7 +31,8 @@ public:
AvrDude& push_args(std::vector<std::string> 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 <numeric>
#include <algorithm>
+#include <thread>
+#include <stdexcept>
#include <boost/format.hpp>
+#include <boost/asio.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/filesystem/fstream.hpp>
#include <boost/log/trivial.hpp>
+#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 <wx/app.h>
#include <wx/event.h>
#include <wx/sizer.h>
@@ -22,16 +33,18 @@
#include <wx/collpane.h>
#include <wx/msgdlg.h>
-#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<Utils::SerialPortInfo> ports;
+ std::vector<SerialPortInfo> 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<std::string> 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<std::string> 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;
- });
-
- avrdude.push_args(std::move(args_l10n));
+}
+
+void FirmwareDialog::priv::prepare_mk2(AvrDude &avrdude, const SerialPortInfo &port)
+{
+ flashing_start(1);
+ prepare_common(avrdude, port);
+}
+
+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<std::string> 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<std::string> 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 <sstream>
+#include <boost/filesystem/fstream.hpp>
+#include <boost/property_tree/ptree.hpp>
+#include <boost/property_tree/ini_parser.hpp>
+
+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 <string>
+#include <boost/filesystem/path.hpp>
+
+
+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 <boost/algorithm/string/predicate.hpp>
#include <boost/filesystem.hpp>
#include <boost/format.hpp>
+#include <boost/optional.hpp>
#if _WIN32
#include <Windows.h>
#include <Setupapi.h>
#include <initguid.h>
#include <devguid.h>
+ #include <regex>
// Undefine min/max macros incompatible with the standard library
// For example, std::numeric_limits<std::streamsize>::max()
// produces some weird errors
@@ -51,6 +53,8 @@
#include <asm-generic/ioctls.h>
#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;
}
+#if _WIN32
+void parse_hardware_id(const std::string &hardware_id, SerialPortInfo &spi)
+{
+ 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
+
#ifdef __linux__
-static std::string get_tty_friendly_name(const std::string &path, const std::string &name)
+optional<std::string> 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<unsigned long> sysfs_tty_prop_hex(const std::string &tty_dev, const std::string &name)
{
- const auto sysfs_product = (boost::format("/sys/class/tty/%1%/device/../product") % name).str();
- std::ifstream file(sysfs_product);
- std::string product;
+ auto prop = sysfs_tty_prop(tty_dev, name);
+ if (!prop) { return boost::none; }
- std::getline(file, product);
- return file.good() ? (boost::format("%1% (%2%)") % product % path).str() : path;
+ try { return std::stoul(*prop, 0, 16); }
+ catch (...) { return boost::none; }
}
#endif
@@ -98,6 +130,7 @@ std::vector<SerialPortInfo> 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<SerialPortInfo> scan_serial_ports_extended()
// Now store it in a buffer.
if (! SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_HARDWAREID, &regDataType, (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<SerialPortInfo> 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<SerialPortInfo> 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<SerialPortInfo> 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<std::string> 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);