diff --git a/src/core/atcore.cpp b/src/core/atcore.cpp index a099a7a..2b03a79 100644 --- a/src/core/atcore.cpp +++ b/src/core/atcore.cpp @@ -1,973 +1,959 @@ /* AtCore Copyright (C) <2016 - 2019> Authors: Tomaz Canabrava Chris Rizzitello Patrick José Pereira Lays Rodrigues This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include "atcore.h" #include "atcore_version.h" #include "seriallayer.h" #include "gcodecommands.h" #include "printthread.h" #include "atcore_default_folders.h" Q_LOGGING_CATEGORY(ATCORE_PLUGIN, "org.kde.atelier.core.plugin") Q_LOGGING_CATEGORY(ATCORE_CORE, "org.kde.atelier.core") /** * @brief The AtCorePrivate struct * Provides a private data set for atcore. */ struct AtCore::AtCorePrivate { /** firmwarePlugin: pointer to firmware plugin */ IFirmware *firmwarePlugin = nullptr; /** serial: pointer to the serial layer */ SerialLayer *serial = nullptr; /** pluginLoader: QPluginLoader */ QPluginLoader pluginLoader; /** plugins: Map of plugins name / path */ QMap plugins; /** lastMessage: lastMessage from the printer */ QByteArray lastMessage; /** lastCommand: the last command sent to the printer */ QString lastCommand; /** extruderCount: extruder count */ int extruderCount = 1; /** temperature: Temperature object */ std::shared_ptr temperature = nullptr; /** autoTemperatureReport: True if using auto Temperature Reporting*/ bool autoTemperatureReport = false; /** bedDeform: BedDeform object */ std::shared_ptr bedDeform = nullptr; /** commandQueue: the list of commands to send to the printer */ QStringList commandQueue; /** ready: True if printer is ready for a command */ bool ready = false; /** temperatureTimer: timer connected to the checkTemperature function */ QTimer temperatureTimer; /** sdPrintProgressTimer: timer used to check in on sdJobs */ QTimer sdPrintProgressTimer; /** percentage: print job percent */ float percentage = 0.0; /** posString: stored string from last M114 return */ QByteArray posString; /** printerState: State of the Printer */ AtCore::STATES printerState = AtCore::DISCONNECTED; /** seralPorts: Detected serial Ports */ QStringList serialPorts; /** serialTimer: Timer connected to locateSerialPorts */ QTimer serialTimer; /** sdCardMounted: True if Sd Card is mounted. */ bool sdCardMounted = false; /** sdCardReadingFileList: True while getting file names from sd card */ bool sdCardReadingFileList = false; /** sdCardPrinting: True if currently printing from sd card. */ bool sdCardPrinting = false; /** sdCardFileName: name of file being used from sd card. */ QString sdCardFileName; /** sdCardFileList: List of files on sd card. */ QStringList sdCardFileList; /** tempMultiString: Hold temp returns for multiline returns when needed */ QStringList tempMultiString; }; AtCore::AtCore(QObject *parent) : QObject(parent), d(new AtCorePrivate) { d->temperature.reset(new Temperature); d->bedDeform.reset(new BedDeform); //Register MetaTypes qRegisterMetaType("AtCore::STATES"); setState(AtCore::STATES::DISCONNECTED); //Connect our Timers connect(&d->sdPrintProgressTimer, &QTimer::timeout, this, &AtCore::sdCardPrintStatus); connect(&d->serialTimer, &QTimer::timeout, this, &AtCore::locateSerialPort); connect(&d->temperatureTimer, &QTimer::timeout, this, &AtCore::checkTemperature); //<<<<<>>>>>>> connect(d->bedDeform.get(), &BedDeform::dataChanged, this, [](const QVariantList & bedData) { qDebug() << bedData; }); //Attempt to find our plugins qCDebug(ATCORE_PLUGIN) << "Detecting Plugin path"; QStringList paths = AtCoreDirectories::pluginDir; //add our current runtime path paths.prepend(qApp->applicationDirPath() + QStringLiteral("/../Plugins/AtCore")); paths.prepend(qApp->applicationDirPath() + QStringLiteral("/AtCore")); paths.prepend(qApp->applicationDirPath() + QStringLiteral("/plugins")); for (const auto &path : paths) { qCDebug(ATCORE_PLUGIN) << "Checking: " << path; QMap tempMap = findFirmwarePlugins(path); if (!tempMap.isEmpty()) { d->plugins = tempMap; emit availableFirmwarePluginsChanged(); return; } } setState(AtCore::STATES::DISCONNECTED); } QString AtCore::version() const { QString versionString = QString::fromLatin1(ATCORE_VERSION_STRING); #if defined GIT_REVISION if (!QStringLiteral(GIT_REVISION).isEmpty()) { versionString.append(QString::fromLatin1("-%1").arg(QStringLiteral(GIT_REVISION))); } #endif return versionString; } IFirmware *AtCore::firmwarePlugin() const { return d->firmwarePlugin; } void AtCore::close() { exit(0); } Temperature *AtCore::temperature() { return d->temperature.get(); } void AtCore::findFirmware(const QByteArray &message) { emit receivedMessage(message); emit atcoreMessage(tr("Waiting for firmware detect.")); qCDebug(ATCORE_CORE) << "Find Firmware: " << message; if (!message.contains("FIRMWARE_NAME:")) { qCDebug(ATCORE_CORE) << "No firmware yet."; return; } qCDebug(ATCORE_CORE) << "Found firmware string, Looking for Firmware Name."; QString fwName = QString::fromLocal8Bit(message); fwName = fwName.split(QChar::fromLatin1(':')).at(1); if (fwName.indexOf(QChar::fromLatin1(' ')) == 0) { //remove leading space fwName.remove(0, 1); } if (fwName.contains(QChar::fromLatin1(' '))) { //check there is a space or dont' resize fwName.resize(fwName.indexOf(QChar::fromLatin1(' '))); } fwName = fwName.toLower().simplified(); if (fwName.contains(QChar::fromLatin1('_'))) { fwName.resize(fwName.indexOf(QChar::fromLatin1('_'))); } qCDebug(ATCORE_CORE) << "Firmware Name:" << fwName; if (message.contains("EXTRUDER_COUNT:")) { //this code is broken if more then 9 extruders are detected. since only one char is returned setExtruderCount(message.at(message.indexOf("EXTRUDER_COUNT:") + 15) - '0'); } loadFirmwarePlugin(fwName); } void AtCore::loadFirmwarePlugin(const QString &fwName) { qCDebug(ATCORE_CORE) << "Loading plugin: " << d->plugins[fwName]; if (d->plugins.contains(fwName)) { d->pluginLoader.setFileName(d->plugins[fwName]); if (!d->pluginLoader.load()) { //Plugin was not loaded, Provide some debug info. qCDebug(ATCORE_CORE) << "Plugin Loading: Failed."; qCDebug(ATCORE_CORE) << d->pluginLoader.errorString(); setState(AtCore::STATES::CONNECTING); } else { //Plugin was loaded successfully. d->firmwarePlugin = qobject_cast(d->pluginLoader.instance()); firmwarePlugin()->init(this); disconnect(d->serial, &SerialLayer::receivedCommand, this, {}); connect(d->serial, &SerialLayer::receivedCommand, this, &AtCore::newMessage); connect(firmwarePlugin(), &IFirmware::readyForCommand, this, &AtCore::processQueue); d->ready = true; // ready on new firmware load if (firmwarePlugin()->name() != QStringLiteral("Grbl")) { setTemperatureTimerInterval(5000); } setState(IDLE); emit atcoreMessage(tr("Connected to printer using %1 plugin").arg(d->firmwarePlugin->name())); } } else { qCDebug(ATCORE_CORE) << "Plugin:" << fwName << ": Not found."; emit atcoreMessage(tr("No plugin found for %1.").arg(fwName)); } } void AtCore::waitForPrinterReboot(const QByteArray &message, const QString &fwName) { emit receivedMessage(message); if (message.isEmpty()) { return; } if (message.contains("Grbl")) { loadFirmwarePlugin(QString::fromLatin1("grbl")); } else if (message.contains("Smoothie")) { loadFirmwarePlugin(QString::fromLatin1("smoothie")); } else if (message.contains("start")) { if (!d->plugins.contains(fwName)) { disconnect(d->serial, &SerialLayer::receivedCommand, this, {}); connect(d->serial, &SerialLayer::receivedCommand, this, &AtCore::findFirmware); QTimer::singleShot(500, this, &AtCore::requestFirmware); } else { loadFirmwarePlugin(fwName); } } } bool AtCore::newConnection(const QString &port, int baud, const QString &fwName, bool disableROC) { if (disableROC) { disableResetOnConnect(port); } d->serial = new SerialLayer(port, baud, this); connect(d->serial, &SerialLayer::serialError, this, &AtCore::handleSerialError); if (serialInitialized() && d->serial->isWritable()) { setState(AtCore::STATES::CONNECTING); connect(d->serial, &SerialLayer::pushedCommand, this, &AtCore::newCommand); if (!disableROC) { emit atcoreMessage(tr("Waiting for machine restart")); connect(d->serial, &SerialLayer::receivedCommand, this, [this, fwName](const QByteArray & message) { waitForPrinterReboot(message, fwName); }); } else { loadFirmwarePlugin(fwName); } if (d->serialTimer.isActive()) { d->serialTimer.stop(); } return true; } qCDebug(ATCORE_CORE) << "Failed to open device for Read / Write."; emit atcoreMessage(tr("Failed to open device in read/write mode.")); return false; } bool AtCore::serialInitialized() const { if (!d->serial) { return false; } return d->serial->isOpen(); } QString AtCore::connectedPort() const { return d->serial->portName(); } QStringList AtCore::serialPorts() const { QStringList ports; QList serialPortInfoList = QSerialPortInfo::availablePorts(); if (!serialPortInfoList.isEmpty()) { for (const QSerialPortInfo &serialPortInfo : serialPortInfoList) { #ifdef Q_OS_MAC //Mac OS has callout serial ports starting with cu these devices are read only. //It is necessary to filter them out to help prevent user error. if (!serialPortInfo.portName().startsWith(QStringLiteral("cu."), Qt::CaseInsensitive)) { ports.append(serialPortInfo.portName()); } #else ports.append(serialPortInfo.portName()); #endif } } return ports; } void AtCore::locateSerialPort() { QStringList ports = serialPorts(); if (d->serialPorts != ports) { d->serialPorts = ports; emit portsChanged(d->serialPorts); } } int AtCore::serialTimerInterval() const { return d->serialTimer.interval(); } void AtCore::setSerialTimerInterval(int newTime) { newTime = std::max(newTime, 0); if (newTime != d->serialTimer.interval()) { d->serialTimer.setInterval(newTime); emit serialTimerIntervalChanged(newTime); } if (newTime == 0 && d->serialTimer.isActive()) { d->serialTimer.stop(); } else { d->serialTimer.start(newTime); } } int AtCore::temperatureTimerInterval() const { return d->temperatureTimer.interval(); } void AtCore::setTemperatureTimerInterval(int newTime) { newTime = std::max(newTime, 0); if (newTime != d->temperatureTimer.interval()) { d->temperatureTimer.setInterval(newTime); emit temperatureTimerIntervalChanged(newTime); } if (!newTime && d->temperatureTimer.isActive()) { d->temperatureTimer.stop(); } else { d->temperatureTimer.start(newTime); } } void AtCore::setAutoTemperatureReport(bool autoReport) { if (autoReport == d->autoTemperatureReport) { return; } d->autoTemperatureReport = autoReport; emit autoTemperatureReportChanged(autoReport); if (autoReport) { setTemperatureTimerInterval(0); d->commandQueue.removeAll(GCode::toCommand(GCode::M105)); setAutoCheckTemperatureInterval(5); } else { setAutoCheckTemperatureInterval(0); setTemperatureTimerInterval(5000); } } void AtCore::setAutoCheckTemperatureInterval(int newTime) { if (state() >= 2 && state() != AtCore::ERRORSTATE) { pushCommand(GCode::toCommand(GCode::M155, QString::number(newTime))); } emit autoCheckTemperatureIntervalChanged(newTime); } bool AtCore::autoTemperatureReport() const { return d->autoTemperatureReport; } void AtCore::newMessage(const QByteArray &message) { d->lastMessage = message; if (d->lastMessage.contains(QString::fromLatin1("Cap:AUTOREPORT_TEMP:1").toLocal8Bit())) { setAutoTemperatureReport(true); } if (d->lastCommand.startsWith(GCode::toCommand(GCode::GCode::G29))) { if (d->lastMessage.contains("ok")) { d->bedDeform.get()->decodeDeform(d->tempMultiString); } d->tempMultiString.append(QString::fromLatin1(d->lastMessage)); } //Check if the message has current coordinates. if (d->lastCommand.startsWith(GCode::toCommand(GCode::MCommands::M114)) && d->lastMessage.startsWith(QString::fromLatin1("X:").toLocal8Bit())) { d->posString = message; d->posString.resize(d->posString.indexOf('E')); d->posString.replace(':', ""); } //Check if have temperature info and decode it if (d->lastMessage.contains("T:") || d->lastMessage.contains("B:")) { temperature()->decodeTemp(d->lastMessage); } emit receivedMessage(d->lastMessage); } void AtCore::newCommand(const QByteArray &command) { emit pushedCommand(command); } void AtCore::setRelativePosition() { pushCommand(GCode::toCommand(GCode::GCommands::G91)); } void AtCore::setAbsolutePosition() { pushCommand(GCode::toCommand(GCode::GCommands::G90)); } float AtCore::percentagePrinted() const { return d->percentage; } void AtCore::print(const QString &fileName, bool sdPrint) { if (state() == AtCore::STATES::CONNECTING) { qCDebug(ATCORE_CORE) << "Load a firmware plugin to print."; return; } //Start a print job. setState(AtCore::STATES::STARTPRINT); //Only try to print from Sd if the firmware has support for sd cards if (firmwarePlugin()->isSdSupported()) { if (sdPrint) { //Printing from the sd card requires us to send some M commands. pushCommand(GCode::toCommand(GCode::MCommands::M23, fileName)); d->sdCardFileName = fileName; pushCommand(GCode::toCommand(GCode::MCommands::M24)); setState(AtCore::STATES::BUSY); d->sdCardPrinting = true; d->sdPrintProgressTimer.start(5000); return; } } //Process the gcode with a printThread. //The Thread processes the gcode without freezing the libary. //Only sends a command back when the printer is ready, avoiding buffer overflow in the printer. auto thread = new QThread(this); auto printThread = new PrintThread(this, fileName); printThread->moveToThread(thread); connect(printThread, &PrintThread::printProgressChanged, this, &AtCore::printProgressChanged, Qt::QueuedConnection); connect(thread, &QThread::started, printThread, &PrintThread::start); connect(printThread, &PrintThread::finished, thread, &QThread::quit); connect(thread, &QThread::finished, printThread, &PrintThread::deleteLater); if (!thread->isRunning()) { thread->start(); } } void AtCore::pushCommand(const QString &comm) { //Be sure our M112 is first in the queue. if (comm == GCode::toCommand(GCode::MCommands::M112)) { d->commandQueue.prepend(comm); } else { d->commandQueue.append(comm); } if (d->ready) { //The printer is ready for a command now so push one. processQueue(); } } void AtCore::closeConnection() { if (serialInitialized()) { if (AtCore::state() == AtCore::STATES::BUSY && !d->sdCardPrinting) { //We have to clean up the print job if printing from the host. //However disconnecting while printing from sd card should not affect the print job. setState(AtCore::STATES::STOP); } if (firmwarePluginLoaded()) { disconnect(firmwarePlugin(), &IFirmware::readyForCommand, this, &AtCore::processQueue); disconnect(d->serial, &SerialLayer::receivedCommand, this, &AtCore::newMessage); if (d->autoTemperatureReport) { blockSignals(true); setAutoTemperatureReport(false); blockSignals(false); } setTemperatureTimerInterval(0); //Attempt to unload the firmware plugin. QString name = firmwarePlugin()->name(); QString msg = d->pluginLoader.unload() ? QStringLiteral("closed.") : QStringLiteral("Failed to close."); qCDebug(ATCORE_CORE) << QStringLiteral("Firmware plugin %1 %2").arg(name, msg); d->firmwarePlugin = nullptr; } //Do not reset the connect on disconnect when closing this will cause a reset on connect for the next connection. disconnect(d->serial, &SerialLayer::serialError, this, &AtCore::handleSerialError); disconnect(d->serial, &SerialLayer::pushedCommand, this, &AtCore::newMessage); d->serial->close(); //Clear our copy of the sdcard filelist clearSdCardFileList(); setState(AtCore::STATES::DISCONNECTED); d->serialTimer.start(); } } AtCore::STATES AtCore::state() { return d->printerState; } void AtCore::setState(AtCore::STATES state) { if (state != d->printerState) { qCDebug(ATCORE_CORE) << QStringLiteral("Atcore state changed from [%1] to [%2]") .arg(QVariant::fromValue(d->printerState).toString(), QVariant::fromValue(state).toString()); d->printerState = state; if (state == AtCore::STATES::FINISHEDPRINT && d->sdCardPrinting) { //Clean up the sd card print d->sdCardPrinting = false; if (d->sdPrintProgressTimer.isActive()) { d->sdPrintProgressTimer.stop(); } } emit stateChanged(d->printerState); } } void AtCore::stop() { //Stop a print job setState(AtCore::STATES::STOP); d->commandQueue.clear(); if (d->sdCardPrinting) { stopSdPrint(); } setExtruderTemp(0, 0); setBedTemp(0); home(AtCore::AXES::X); } void AtCore::emergencyStop() { //Emergency Stop. Stops the machine //Clear the queue, and any print job //Before sending the command to ensure //Less chance of movement after the restart. d->commandQueue.clear(); if (AtCore::state() == AtCore::STATES::BUSY) { if (!d->sdCardPrinting) { //Stop our running print thread setState(AtCore::STATES::STOP); } } pushCommand(GCode::toCommand(GCode::MCommands::M112)); } void AtCore::stopSdPrint() { //Stop an SdCard Print. pushCommand(GCode::toCommand(GCode::MCommands::M25)); d->sdCardFileName = QString(); pushCommand(GCode::toCommand(GCode::MCommands::M23, d->sdCardFileName)); AtCore::setState(AtCore::STATES::FINISHEDPRINT); AtCore::setState(AtCore::STATES::IDLE); } void AtCore::requestFirmware() { if (serialInitialized()) { //ensure M115 is sent on cold connect. d->commandQueue.clear(); d->ready = true; qCDebug(ATCORE_CORE) << "Sending " << GCode::description(GCode::MCommands::M115); pushCommand(GCode::toCommand(GCode::MCommands::M115)); } else { qCDebug(ATCORE_CORE) << "There is no open device to send commands"; } } bool AtCore::firmwarePluginLoaded() const { return firmwarePlugin(); } QMap AtCore::findFirmwarePlugins(const QString &path) { QMap detectedPlugins; - QStringList files = QDir(path).entryList(QDir::Files); - for (const QString &f : files) { + for (const QString &f : QDir(path).entryList({AtCoreDirectories::pluginExtFilter}, QDir::Files)) { QString file = f; -#if defined(Q_OS_WIN) - if (file.endsWith(QStringLiteral(".dll"))) -#elif defined(Q_OS_MAC) - if (file.endsWith(QStringLiteral(".dylib"))) -#else - if (file.endsWith(QStringLiteral(".so"))) -#endif - file = file.split(QChar::fromLatin1('.')).at(0); - else { - continue; - } + file = file.split(QStringLiteral(".")).at(0).toLower().simplified(); if (file.startsWith(QStringLiteral("lib"))) { file = file.remove(QStringLiteral("lib")); } - file = file.toLower().simplified(); - QString pluginString = path; - pluginString.append(QChar::fromLatin1('/')); - pluginString.append(f); + QString pluginString = QStringLiteral("%1/%2").arg(path, f); detectedPlugins[file] = pluginString; qCDebug(ATCORE_PLUGIN) << QStringLiteral("Plugin:[%1]=%2").arg(file, pluginString); } return detectedPlugins; } QStringList AtCore::availableFirmwarePlugins() const { return d->plugins.keys(); } void AtCore::pause(const QString &pauseActions) { if (d->sdCardPrinting) { pushCommand(GCode::toCommand(GCode::MCommands::M25)); } //Push the command to request current coordinates. //This will be read by AtCore::newMessage and stored for use on resume. pushCommand(GCode::toCommand(GCode::MCommands::M114)); if (!pauseActions.isEmpty()) { QStringList temp = pauseActions.split(QChar::fromLatin1(',')); for (int i = 0; i < temp.length(); i++) { pushCommand(temp.at(i)); } } setState(AtCore::PAUSE); } void AtCore::resume() { if (d->sdCardPrinting) { pushCommand(GCode::toCommand(GCode::MCommands::M24)); } else { //Move back to previous coordinates. pushCommand(GCode::toCommand(GCode::GCommands::G0, QString::fromLatin1(d->posString))); } setState(AtCore::BUSY); } /*~~~~~Control Slots ~~~~~~~~*/ void AtCore::home() { pushCommand(GCode::toCommand(GCode::GCommands::G28)); } void AtCore::home(uchar axis) { QString args; if (axis & AtCore::AXES::X) { args.append(QStringLiteral("X0 ")); } if (axis & AtCore::AXES::Y) { args.append(QStringLiteral("Y0 ")); } if (axis & AtCore::AXES::Z) { args.append(QStringLiteral("Z0")); } pushCommand(GCode::toCommand(GCode::GCommands::G28, args)); } void AtCore::setExtruderTemp(uint temp, uint extruder, bool andWait) { temp = std::min(temp, 10000); extruder = std::min(extruder, 10000); if (andWait) { pushCommand(GCode::toCommand(GCode::MCommands::M109, QString::number(temp), QString::number(extruder))); } else { pushCommand(GCode::toCommand(GCode::MCommands::M104, QString::number(extruder), QString::number(temp))); } } void AtCore::setBedTemp(uint temp, bool andWait) { temp = std::min(temp, 10000); if (andWait) { pushCommand(GCode::toCommand(GCode::MCommands::M190, QString::number(temp))); } else { pushCommand(GCode::toCommand(GCode::MCommands::M140, QString::number(temp))); } } void AtCore::setFanSpeed(uint speed, uint fanNumber) { speed = std::min(speed, 10000); fanNumber = std::min(fanNumber, 10000); pushCommand(GCode::toCommand(GCode::MCommands::M106, QString::number(fanNumber), QString::number(speed))); } void AtCore::setPrinterSpeed(uint speed) { speed = std::min(speed, 10000); pushCommand(GCode::toCommand(GCode::MCommands::M220, QString::number(speed))); } void AtCore::setFlowRate(uint speed) { speed = std::min(speed, 10000); pushCommand(GCode::toCommand(GCode::MCommands::M221, QString::number(speed))); } void AtCore::move(AtCore::AXES axis, double arg) { const auto axisAsString = QMetaEnum::fromType().valueToKey(axis); move(QLatin1Char(axisAsString[0]), arg); } void AtCore::move(QLatin1Char axis, double arg) { //Using QString::number(double, format, precision) //f = 'format as [-]9.9' //3 = use 3 decimal precision pushCommand(GCode::toCommand(GCode::GCommands::G1, QStringLiteral("%1 %2").arg(axis).arg(QString::number(arg, 'f', 3)))); } int AtCore::extruderCount() const { return d->extruderCount; } void AtCore::setExtruderCount(int newCount) { if (d->extruderCount != newCount && newCount >= 1) { d->extruderCount = newCount; emit extruderCountChanged(newCount); qCDebug(ATCORE_CORE) << "Extruder Count:" << QString::number(extruderCount()); } } void AtCore::processQueue() { d->ready = true; if (d->commandQueue.isEmpty()) { return; } if (!serialInitialized()) { qCDebug(ATCORE_PLUGIN) << "Can't process queue ! Serial not initialized."; return; } d->lastCommand = d->commandQueue.takeAt(0); if (firmwarePluginLoaded()) { d->serial->pushCommand(firmwarePlugin()->translate(d->lastCommand)); } else { d->serial->pushCommand(d->lastCommand.toLocal8Bit()); } d->ready = false; } void AtCore::checkTemperature() { //One request for the temperature in the queue at a time. if (d->commandQueue.contains(GCode::toCommand(GCode::MCommands::M105))) { return; } pushCommand(GCode::toCommand(GCode::MCommands::M105)); } void AtCore::showMessage(const QString &message) { if (!message.isEmpty()) { pushCommand(GCode::toCommand((GCode::MCommands::M117), message)); } } void AtCore::setUnits(AtCore::UNITS units) { switch (units) { case AtCore::UNITS::METRIC: pushCommand(GCode::toCommand(GCode::GCommands::G21)); break; case AtCore::UNITS::IMPERIAL: pushCommand(GCode::toCommand(GCode::GCommands::G20)); break; } } QStringList AtCore::portSpeeds() const { return d->serial->validBaudRates(); } void AtCore::disableMotors(uint delay) { //Disables motors if (delay) { pushCommand(GCode::toCommand(GCode::MCommands::M84, QString::number(delay))); } else { pushCommand(GCode::toCommand(GCode::MCommands::M84)); } } //Most firmwares will not report if an sdcard is mounted on boot. bool AtCore::isSdMounted() const { return d->sdCardMounted; } void AtCore::setSdMounted(bool mounted) { if (mounted != isSdMounted()) { d->sdCardMounted = mounted; emit sdMountChanged(d->sdCardMounted); } } void AtCore::getSDFileList() { pushCommand(GCode::toCommand(GCode::MCommands::M20)); } QStringList AtCore::sdFileList() { if (!d->sdCardReadingFileList) { getSDFileList(); } return d->sdCardFileList; } void AtCore::appendSdCardFileList(const QString &fileName) { d->sdCardFileList.append(fileName); emit sdCardFileListChanged(d->sdCardFileList); } void AtCore::clearSdCardFileList() { d->sdCardFileList.clear(); emit sdCardFileListChanged(d->sdCardFileList); } void AtCore::sdDelete(const QString &fileName) { if (d->sdCardFileList.contains(fileName)) { pushCommand(GCode::toCommand(GCode::MCommands::M30, fileName)); getSDFileList(); } else { qCDebug(ATCORE_CORE) << "Delete failed file not found:" << fileName; } } void AtCore::mountSd(uint slot) { pushCommand(GCode::toCommand(GCode::MCommands::M21, QString::number(slot))); } void AtCore::umountSd(uint slot) { pushCommand(GCode::toCommand(GCode::MCommands::M22, QString::number(slot))); } bool AtCore::isReadingSdCardList() const { return d->sdCardReadingFileList; } void AtCore::setReadingSdCardList(bool readingList) { d->sdCardReadingFileList = readingList; } void AtCore::sdCardPrintStatus() { //One request for the Sd Job status in the queue at a time. if (d->commandQueue.contains(GCode::toCommand(GCode::MCommands::M27))) { return; } pushCommand(GCode::toCommand(GCode::MCommands::M27)); } void AtCore::disableResetOnConnect(const QString &port) { #if defined(Q_OS_UNIX) //should work on all unix' QProcess process(this); QStringList args({QStringLiteral("-F/dev/%1").arg(port), QStringLiteral("-hupcl")}); process.start(QStringLiteral("stty"), args); process.waitForFinished(500); connect(&process, &QProcess::errorOccurred, this, [&process] { qCDebug(ATCORE_CORE) << "Stty Error:" << process.errorString(); }); #elif defined(Q_OS_WIN) //TODO: Disable hangup on windows. #endif } void AtCore::handleSerialError(QSerialPort::SerialPortError error) { QString errorString; switch (error) { case (QSerialPort::SerialPortError::DeviceNotFoundError): errorString = tr("Device not found"); break; case (QSerialPort::SerialPortError::WriteError): errorString = tr("Unable to write to device"); break; case (QSerialPort::SerialPortError::ReadError): errorString = tr("Unable to read from device"); break; case (QSerialPort::SerialPortError::ResourceError): case (QSerialPort::SerialPortError::TimeoutError): errorString = tr("The device no longer available"); closeConnection(); break; case (QSerialPort::SerialPortError::UnsupportedOperationError): errorString = tr("Device does not support the operation"); break; case (QSerialPort::SerialPortError::UnknownError): errorString = tr("Unknown Error"); break; default: //Not Directly processed errors //QSerialPort::NoError, No error has happened //QSerialPort::PermissionError), Already handled. //QSerialPort::OpenError), Already handled. //QSerialPort::NotOpenError, SerialLayer destroyed if not connected. //QSerialPort::ParityError, Obsolete. Qt Docs "We strongly advise against using it in new code." //QSerialPort::FramingError, Obsolete. Qt Docs "We strongly advise against using it in new code." //QSerialPort::BreakConditionError, Obsolete. Qt Docs "We strongly advise against using it in new code." return; };//End of Switch qCDebug(ATCORE_CORE) << "SerialError:" << errorString; emit atcoreMessage(QStringLiteral("SerialError: %1").arg(errorString)); } std::shared_ptr AtCore::bedDeform() { return d->bedDeform; } diff --git a/src/core/atcore_default_folders.h.in b/src/core/atcore_default_folders.h.in index e5269e9..2c534a1 100644 --- a/src/core/atcore_default_folders.h.in +++ b/src/core/atcore_default_folders.h.in @@ -1,32 +1,40 @@ /* AtCore KDE Libary for 3D Printers Copyright (C) <2016> Authors: Tomaz Canabrava Chris Rizzitello Patrick José Pereira This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include namespace AtCoreDirectories { const QStringList pluginDir = { QStringLiteral("@CMAKE_INSTALL_PREFIX@/@KDE_INSTALL_PLUGINDIR@/AtCore") , QLibraryInfo::location(QLibraryInfo::PluginsPath) + QStringLiteral("/AtCore") , QStringLiteral("@KDE_INSTALL_PLUGINDIR@/AtCore") , QStringLiteral("@CMAKE_CURRENT_BINARY_DIR@/../plugins") }; + const QString pluginExtFilter = + #if defined(Q_OS_WIN) + QStringLiteral("*.dll"); + #elif defined(Q_OS_MAC) + QStringLiteral("*.dylib"); + #else + QStringLiteral("*.so"); + #endif } diff --git a/src/widgets/profilemanager.cpp b/src/widgets/profilemanager.cpp index 1ce7d17..77e59a9 100644 --- a/src/widgets/profilemanager.cpp +++ b/src/widgets/profilemanager.cpp @@ -1,339 +1,327 @@ /* AtCore - Widgets Copyright (C) <2019> Author: Chris Rizzitello - rizzitello@kde.org This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "profilemanager.h" #include "atcore_default_folders.h" #include "machineinfo.h" #include #include #include #include #include #include #include ProfileManager::ProfileManager(QWidget *parent) : QWidget(parent) { auto newLabel = new QLabel(tr("Profile:")); cbProfile = new QComboBox(); cbProfile->setEditable(true); cbProfile->addItems(MachineInfo::instance()->profileNames()); cbProfile->setCompleter(new QCompleter(MachineInfo::instance()->profileNames())); connect(MachineInfo::instance(), &MachineInfo::profilesChanged, this, [this] { int index = cbProfile->currentIndex(); cbProfile->clear(); cbProfile->completer()->deleteLater(); cbProfile->addItems(MachineInfo::instance()->profileNames()); cbProfile->setCompleter(new QCompleter(MachineInfo::instance()->profileNames())); cbProfile->setCurrentIndex(std::min(index, cbProfile->count() - 1)); }); connect(cbProfile, QOverload::of(&QComboBox::currentIndexChanged), this, [this] { if (MachineInfo::instance()->profileNames().contains(cbProfile->currentText())) { loadProfile(cbProfile->currentText()); } }); connect(cbProfile->lineEdit(), &QLineEdit::editingFinished, this, &ProfileManager::onCbProfileEditingFinished); auto newHLayout = new QHBoxLayout(); newHLayout->addWidget(newLabel); newHLayout->addWidget(cbProfile, 75); auto newButton = new QToolButton(); newButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete"), style()->standardIcon(QStyle::SP_DialogDiscardButton))); newButton->setToolTip(tr("Delete Current Profile")); newButton->setIconSize(QSize(fontMetrics().height(), fontMetrics().height())); connect(newButton, &QToolButton::clicked, this, [this] { if (!cbProfile->currentText().isEmpty()) { MachineInfo::instance()->removeProfile(cbProfile->currentText()); } }); auto mainLayout = new QVBoxLayout(); newHLayout->addWidget(newButton); mainLayout->addLayout(newHLayout); auto profileLayout = new QVBoxLayout(); auto boxLayout = new QVBoxLayout(); newLabel = new QLabel(tr("Name")); lineName = new QLineEdit(); connect(lineName, &QLineEdit::editingFinished, this, [this] { QString itemName = lineName->text(); if (MachineInfo::instance()->storeKey(cbProfile->currentText(), MachineInfo::KEY::NAME, itemName)) { loadProfile(itemName); } }); newHLayout = new QHBoxLayout; newHLayout->addWidget(newLabel); newHLayout->addWidget(lineName); profileLayout->addLayout(newHLayout); newLabel = new QLabel(tr("Printer Type:")); radioDelta = new QRadioButton(tr("Delta")); radioCartesian = new QRadioButton(tr("Cartesian")); radioCartesian->setChecked(true); connect(radioCartesian, &QRadioButton::toggled, this, &ProfileManager::onRadioCartesianToggled); newHLayout = new QHBoxLayout; newHLayout->addWidget(newLabel); newHLayout->addWidget(radioCartesian); newHLayout->addWidget(radioDelta); boxLayout->addLayout(newHLayout); lblX = new QLabel(tr("Maximum X")); sbMaxX = new QSpinBox(); sbMaxX->setMaximum(std::numeric_limits::max()); sbMaxX->setSuffix(QStringLiteral("mm")); connect(sbMaxX, QOverload::of(&QSpinBox::valueChanged), this, [this](int value) { MachineInfo::instance()->storeKey(cbProfile->currentText(), MachineInfo::KEY::XMAX, value); }); newHLayout = new QHBoxLayout; newHLayout->addWidget(lblX); newHLayout->addWidget(sbMaxX); boxLayout->addLayout(newHLayout); newLabel = new QLabel(tr("Maximum Y")); sbMaxY = new QSpinBox(); sbMaxY->setMaximum(std::numeric_limits::max()); sbMaxY->setSuffix(QStringLiteral("mm")); connect(sbMaxY, QOverload::of(&QSpinBox::valueChanged), this, [this](int value) { MachineInfo::instance()->storeKey(cbProfile->currentText(), MachineInfo::KEY::YMAX, value); }); newHLayout = new QHBoxLayout; newHLayout->setContentsMargins(0, 0, 0, 0); newHLayout->addWidget(newLabel); newHLayout->addWidget(sbMaxY); axisY = new QWidget(this); axisY->setLayout(newHLayout); boxLayout->addWidget(axisY); lblZ = new QLabel(tr("Maximum Z")); sbMaxZ = new QSpinBox(); sbMaxZ->setMaximum(std::numeric_limits::max()); sbMaxZ->setSuffix(QStringLiteral("mm")); connect(sbMaxZ, QOverload::of(&QSpinBox::valueChanged), this, [this](int value) { MachineInfo::instance()->storeKey(cbProfile->currentText(), MachineInfo::KEY::ZMAX, value); }); newHLayout = new QHBoxLayout; newHLayout->addWidget(lblZ); newHLayout->addWidget(sbMaxZ); boxLayout->addLayout(newHLayout); auto groupBox = new QGroupBox(tr("Mechanics")); groupBox->setLayout(boxLayout); profileLayout->addWidget(groupBox); boxLayout = new QVBoxLayout; newLabel = new QLabel(tr("Bed Maximum")); sbMaxBedTemp = new QSpinBox(); sbMaxBedTemp->setMaximum(999); sbMaxBedTemp->setSuffix(QStringLiteral(" ºC")); connect(sbMaxBedTemp, QOverload::of(&QSpinBox::valueChanged), this, [this](int value) { MachineInfo::instance()->storeKey(cbProfile->currentText(), MachineInfo::KEY::MAXBEDTEMP, value); }); newHLayout = new QHBoxLayout; newHLayout->addWidget(newLabel); newHLayout->addWidget(sbMaxBedTemp); boxLayout->addLayout(newHLayout); sbMaxExtTemp = new QSpinBox(); sbMaxExtTemp->setMaximum(999); newLabel = new QLabel(tr("Extruder Maximum")); sbMaxExtTemp->setSuffix(QStringLiteral(" ºC")); connect(sbMaxExtTemp, QOverload::of(&QSpinBox::valueChanged), this, [this](int value) { MachineInfo::instance()->storeKey(cbProfile->currentText(), MachineInfo::KEY::MAXEXTTEMP, value); }); newHLayout = new QHBoxLayout(); newHLayout->addWidget(newLabel); newHLayout->addWidget(sbMaxExtTemp); boxLayout->addLayout(newHLayout); checkAutoTempReport = new QCheckBox(tr("Auto Temperature Report")); checkAutoTempReport->setLayoutDirection(Qt::RightToLeft); connect(checkAutoTempReport, &QCheckBox::toggled, this, [this](bool checked) { MachineInfo::instance()->storeKey(cbProfile->currentText(), MachineInfo::KEY::AUTOTEMPREPORT, checked); }); boxLayout->addWidget(checkAutoTempReport, 0, Qt::AlignRight); groupBox = new QGroupBox(tr("Temperature")); groupBox->setLayout(boxLayout); profileLayout->addWidget(groupBox); boxLayout = new QVBoxLayout(); cbBaud = new QComboBox(); cbBaud->addItems(SERIAL::BAUDS); cbBaud->setCurrentText(QStringLiteral("115200")); connect(cbBaud, &QComboBox::currentTextChanged, this, [this](const QString & newText) { MachineInfo::instance()->storeKey(cbProfile->currentText(), MachineInfo::KEY::BAUDRATE, newText); }); newLabel = new QLabel(tr("Bit Rate")); newHLayout = new QHBoxLayout(); newHLayout->addWidget(newLabel); newHLayout->addWidget(cbBaud); boxLayout->addLayout(newHLayout); newLabel = new QLabel(tr("Firmware")); cbFirmware = new QComboBox(); cbFirmware->addItem(QStringLiteral("Auto-Detect")); cbFirmware->addItems(detectFWPlugins()); connect(cbFirmware, &QComboBox::currentTextChanged, this, [this](const QString & newText) { MachineInfo::instance()->storeKey(cbProfile->currentText(), MachineInfo::KEY::FIRMWARE, newText); }); newHLayout = new QHBoxLayout(); newHLayout->addWidget(newLabel); newHLayout->addWidget(cbFirmware); boxLayout->addLayout(newHLayout); linePostPause = new QLineEdit(); connect(linePostPause, &QLineEdit::editingFinished, this, [this] { MachineInfo::instance()->storeKey(cbProfile->currentText(), MachineInfo::KEY::POSTPAUSE, linePostPause->text()); }); newLabel = new QLabel(tr("PostPause")); newHLayout = new QHBoxLayout(); newHLayout->addWidget(newLabel); newHLayout->addWidget(linePostPause); boxLayout->addLayout(newHLayout); groupBox = new QGroupBox(tr("Advanced")); groupBox->setLayout(boxLayout); profileLayout->addWidget(groupBox); groupBox = new QGroupBox(tr("Profile")); groupBox->setLayout(profileLayout); mainLayout->addWidget(groupBox); setLayout(mainLayout); loadProfile(cbProfile->currentText()); } void ProfileManager::onCbProfileEditingFinished() { if (MachineInfo::instance()->profileNames().contains(cbProfile->currentText())) { loadProfile(cbProfile->currentText()); return; } QMap newProfile = { {MachineInfo::KEY::NAME, cbProfile->currentText()}, {MachineInfo::KEY::FIRMWARE, cbFirmware->currentText()}, {MachineInfo::KEY::BAUDRATE, cbBaud->currentText()}, {MachineInfo::KEY::POSTPAUSE, linePostPause->text()}, {MachineInfo::KEY::ISCARTESIAN, radioCartesian->isChecked()}, {MachineInfo::KEY::XMAX, sbMaxX->value()}, {MachineInfo::KEY::YMAX, sbMaxY->value()}, {MachineInfo::KEY::ZMAX, sbMaxZ->value()}, {MachineInfo::KEY::AUTOTEMPREPORT, checkAutoTempReport->isChecked()}, {MachineInfo::KEY::MAXBEDTEMP, sbMaxBedTemp->value()}, {MachineInfo::KEY::MAXEXTTEMP, sbMaxExtTemp->value()} }; MachineInfo::instance()->storeProfile(newProfile); loadProfile(newProfile[MachineInfo::KEY::NAME].toString()); } void ProfileManager::onRadioCartesianToggled(bool checked) { axisY->setVisible(checked); if (checked) { lblX->setText(tr("Maximum X")); lblZ->setText(tr("Maximum Z")); } else { lblX->setText(tr("Radius")); lblZ->setText(tr("Height")); } if (MachineInfo::instance()->profileNames().contains(cbProfile->currentText())) { MachineInfo::instance()->storeKey(cbProfile->currentText(), MachineInfo::KEY::ISCARTESIAN, checked); } } void ProfileManager::loadProfile(const QString &profileName) { if (profileName.isEmpty()) { return; } blockSignals(true); lineName->setText(MachineInfo::instance()->readKey(profileName, MachineInfo::KEY::NAME).toString()); radioCartesian->setChecked(MachineInfo::instance()->readKey(profileName, MachineInfo::KEY::ISCARTESIAN).toBool()); radioDelta->setChecked(!MachineInfo::instance()->readKey(profileName, MachineInfo::KEY::ISCARTESIAN).toBool()); sbMaxX->setValue(MachineInfo::instance()->readKey(profileName, MachineInfo::KEY::XMAX).toInt()); sbMaxY->setValue(MachineInfo::instance()->readKey(profileName, MachineInfo::KEY::YMAX).toInt()); sbMaxZ->setValue(MachineInfo::instance()->readKey(profileName, MachineInfo::KEY::ZMAX).toInt()); checkAutoTempReport->setChecked(MachineInfo::instance()->readKey(profileName, MachineInfo::KEY::AUTOTEMPREPORT).toBool()); sbMaxBedTemp->setValue(MachineInfo::instance()->readKey(profileName, MachineInfo::KEY::MAXBEDTEMP).toInt()); sbMaxExtTemp->setValue(MachineInfo::instance()->readKey(profileName, MachineInfo::KEY::MAXEXTTEMP).toInt()); cbFirmware->setCurrentText(MachineInfo::instance()->readKey(profileName, MachineInfo::KEY::FIRMWARE).toString()); cbBaud->setCurrentText(MachineInfo::instance()->readKey(profileName, MachineInfo::KEY::BAUDRATE).toString()); linePostPause->setText(MachineInfo::instance()->readKey(profileName, MachineInfo::KEY::POSTPAUSE).toString()); cbProfile->setCurrentText(profileName); cbProfile->setCurrentIndex(MachineInfo::instance()->profileNames().indexOf(profileName)); blockSignals(false); } QStringList ProfileManager::detectFWPlugins() { QStringList firmwares; QStringList paths = AtCoreDirectories::pluginDir; //Add our runtime paths const QString &p(qApp->applicationDirPath()); paths.prepend(p + QStringLiteral("/../Plugins/AtCore")); paths.prepend(p + QStringLiteral("/AtCore")); paths.prepend(p + QStringLiteral("/plugins")); for (const QString &path : qAsConst(paths)) { firmwares = firmwaresInPath(path); if (!firmwares.isEmpty()) { //use path where plugins were detected. break; } } return firmwares; } QStringList ProfileManager::firmwaresInPath(const QString &path) { QStringList firmwares; - QStringList files = QDir(path).entryList(QDir::Files); - for (QString file : files) { -#if defined(Q_OS_WIN) - if (file.endsWith(QStringLiteral(".dll"))) -#elif defined(Q_OS_MAC) - if (file.endsWith(QStringLiteral(".dylib"))) -#else - if (file.endsWith(QStringLiteral(".so"))) -#endif - file = file.split(QChar::fromLatin1('.')).at(0); - else { - continue; - } + for (QString file : QDir(path).entryList({AtCoreDirectories::pluginExtFilter}, QDir::Files)) { + file = file.split(QStringLiteral(".")).at(0).toLower().simplified(); if (file.startsWith(QStringLiteral("lib"))) { file = file.remove(QStringLiteral("lib")); } - file = file.toLower().simplified(); firmwares.append(file); } return firmwares; }