File indexing completed on 2024-05-12 04:41:09

0001 /* AtCore KDE Libary for 3D Printers
0002     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0003     SPDX-FileCopyrightText: 2016, 2018 Tomaz Canabrava <tcanabrava@kde.org>
0004     SPDX-FileCopyrightText: 2016-2019, 2021 Chris Rizzitello <rizzitello@kde.org>
0005     SPDX-FileCopyrightText: 2016-2019 Patrick José Pereira <patrickjp@kde.org>
0006     SPDX-FileCopyrightText: 2016, 2019 Lays Rodrigues <lays.rodrigues@kde.org>
0007     SPDX-FileCopyrightText: 2018 Leandro Santiago <leandrosansilva@gmail.com>
0008     SPDX-FileCopyrightText: 2018 Caio Jordão Carvalho <caiojcarvalho@gmail.com>
0009 */
0010 
0011 #include <QCoreApplication>
0012 #include <QDir>
0013 #include <QLoggingCategory>
0014 #include <QMetaEnum>
0015 #include <QPluginLoader>
0016 #include <QProcess>
0017 #include <QSerialPortInfo>
0018 #include <QThread>
0019 #include <QTime>
0020 #include <QTimer>
0021 #include <QVariant>
0022 
0023 #include "atcore.h"
0024 #include "atcore_default_folders.h"
0025 #include "atcore_version.h"
0026 #include "gcodecommands.h"
0027 #include "printthread.h"
0028 #include "seriallayer.h"
0029 
0030 Q_LOGGING_CATEGORY(ATCORE_PLUGIN, "org.kde.atelier.core.plugin")
0031 Q_LOGGING_CATEGORY(ATCORE_CORE, "org.kde.atelier.core")
0032 /**
0033  * @brief The AtCorePrivate struct
0034  * Provides a private data set for atcore.
0035  */
0036 struct AtCore::AtCorePrivate {
0037     /** firmwarePlugin: pointer to firmware plugin */
0038     IFirmware *firmwarePlugin = nullptr;
0039     /** serial: pointer to the serial layer */
0040     SerialLayer *serial = nullptr;
0041     /** pluginLoader: QPluginLoader */
0042     QPluginLoader pluginLoader;
0043     /** plugins: Map of plugins name / path */
0044     QMap<QString, QString> plugins;
0045     /** lastMessage: lastMessage from the printer */
0046     QByteArray lastMessage;
0047     /** lastCommand: the last command sent to the printer */
0048     QString lastCommand;
0049     /** extruderCount: extruder count */
0050     int extruderCount = 1;
0051     /** temperature: Temperature object */
0052     std::shared_ptr<Temperature> temperature = nullptr;
0053     /** autoTemperatureReport: True if using auto Temperature Reporting*/
0054     bool autoTemperatureReport = false;
0055     /** bedDeform: BedDeform object */
0056     std::shared_ptr<BedDeform> bedDeform = nullptr;
0057     /** commandQueue: the list of commands to send to the printer */
0058     QStringList commandQueue;
0059     /** ready: True if printer is ready for a command */
0060     bool ready = false;
0061     /** temperatureTimer: timer connected to the checkTemperature function */
0062     QTimer temperatureTimer;
0063     /** sdPrintProgressTimer: timer used to check in on sdJobs */
0064     QTimer sdPrintProgressTimer;
0065     /** percentage: print job percent */
0066     float percentage = 0.0;
0067     /** posString: stored string from last M114 return */
0068     QByteArray posString;
0069     /** printerState: State of the Printer */
0070     AtCore::STATES printerState = AtCore::DISCONNECTED;
0071     /** seralPorts: Detected serial Ports */
0072     QStringList serialPorts;
0073     /** serialTimer: Timer connected to locateSerialPorts */
0074     QTimer serialTimer;
0075     /** sdCardMounted: True if Sd Card is mounted. */
0076     bool sdCardMounted = false;
0077     /** sdCardReadingFileList: True while getting file names from sd card */
0078     bool sdCardReadingFileList = false;
0079     /** sdCardPrinting: True if currently printing from sd card. */
0080     bool sdCardPrinting = false;
0081     /** sdCardFileName: name of file being used from sd card. */
0082     QString sdCardFileName;
0083     /** sdCardFileList: List of files on sd card. */
0084     QStringList sdCardFileList;
0085     /** tempMultiString: Hold temp returns for multiline returns when needed */
0086     QStringList tempMultiString;
0087 };
0088 
0089 AtCore::AtCore(QObject *parent)
0090     : QObject(parent)
0091     , d(new AtCorePrivate)
0092 {
0093     d->temperature.reset(new Temperature);
0094     d->bedDeform.reset(new BedDeform);
0095     // Register MetaTypes
0096     qRegisterMetaType<AtCore::STATES>("AtCore::STATES");
0097     setState(AtCore::STATES::DISCONNECTED);
0098     // Connect our Timers
0099     connect(&d->sdPrintProgressTimer, &QTimer::timeout, this, &AtCore::sdCardPrintStatus);
0100     connect(&d->serialTimer, &QTimer::timeout, this, &AtCore::locateSerialPort);
0101     connect(&d->temperatureTimer, &QTimer::timeout, this, &AtCore::checkTemperature);
0102 
0103     updateFWPlugins();
0104     setState(AtCore::STATES::DISCONNECTED);
0105 }
0106 
0107 AtCore::~AtCore()
0108 {
0109     /*Needed Allow for Unique AtCorePrivate Pointer*/
0110 }
0111 
0112 QString AtCore::version() const
0113 {
0114     QString versionString = QString::fromLatin1(ATCORE_VERSION_STRING);
0115 #if defined GIT_REVISION
0116     if (!QStringLiteral(GIT_REVISION).isEmpty()) {
0117         versionString.append(QString::fromLatin1("-%1").arg(QStringLiteral(GIT_REVISION)));
0118     }
0119 #endif
0120     return versionString;
0121 }
0122 
0123 IFirmware *AtCore::firmwarePlugin() const
0124 {
0125     return d->firmwarePlugin;
0126 }
0127 
0128 void AtCore::close()
0129 {
0130     exit(0);
0131 }
0132 
0133 Temperature *AtCore::temperature()
0134 {
0135     return d->temperature.get();
0136 }
0137 
0138 void AtCore::findFirmware(const QByteArray &message)
0139 {
0140     Q_EMIT receivedMessage(message);
0141     Q_EMIT atcoreMessage(tr("Waiting for firmware detect."));
0142     qCDebug(ATCORE_CORE) << "Find Firmware: " << message;
0143     if (!message.contains("FIRMWARE_NAME:")) {
0144         qCDebug(ATCORE_CORE) << "No firmware yet.";
0145         return;
0146     }
0147 
0148     qCDebug(ATCORE_CORE) << "Found firmware string, Looking for Firmware Name.";
0149 
0150     QString fwName = QString::fromLocal8Bit(message);
0151     fwName = fwName.split(QChar::fromLatin1(':')).at(1);
0152     if (fwName.indexOf(QChar::fromLatin1(' ')) == 0) {
0153         // remove leading space
0154         fwName.remove(0, 1);
0155     }
0156     if (fwName.contains(QChar::fromLatin1(' '))) {
0157         // check there is a space or dont' resize
0158         fwName.resize(fwName.indexOf(QChar::fromLatin1(' ')));
0159     }
0160     fwName = fwName.toLower().simplified();
0161     if (fwName.contains(QChar::fromLatin1('_'))) {
0162         fwName.resize(fwName.indexOf(QChar::fromLatin1('_')));
0163     }
0164     qCDebug(ATCORE_CORE) << "Firmware Name:" << fwName;
0165 
0166     if (message.contains("EXTRUDER_COUNT:")) {
0167         // this code is broken if more then 9 extruders are detected. since only one char is returned
0168         setExtruderCount(message.at(message.indexOf("EXTRUDER_COUNT:") + 15) - '0');
0169     }
0170     loadFirmwarePlugin(fwName);
0171 }
0172 
0173 void AtCore::loadFirmwarePlugin(const QString &fwName)
0174 {
0175     qCDebug(ATCORE_CORE) << "Loading plugin: " << d->plugins[fwName];
0176     if (d->plugins.contains(fwName)) {
0177         d->pluginLoader.setFileName(d->plugins[fwName]);
0178         if (!d->pluginLoader.load()) {
0179             // Plugin was not loaded, Provide some debug info.
0180             qCDebug(ATCORE_CORE) << "Plugin Loading: Failed.";
0181             qCDebug(ATCORE_CORE) << d->pluginLoader.errorString();
0182             setState(AtCore::STATES::CONNECTING);
0183         } else {
0184             // Plugin was loaded successfully.
0185             d->firmwarePlugin = qobject_cast<IFirmware *>(d->pluginLoader.instance());
0186             firmwarePlugin()->init(this);
0187             disconnect(d->serial, &SerialLayer::receivedCommand, this, nullptr);
0188             connect(d->serial, &SerialLayer::receivedCommand, this, &AtCore::newMessage);
0189             connect(firmwarePlugin(), &IFirmware::readyForCommand, this, &AtCore::processQueue);
0190             d->ready = true; // ready on new firmware load
0191             if (firmwarePlugin()->name() != QStringLiteral("Grbl")) {
0192                 setTemperatureTimerInterval(5000);
0193             }
0194             setState(IDLE);
0195             Q_EMIT atcoreMessage(tr("Connected to printer using %1 plugin").arg(d->firmwarePlugin->name()));
0196         }
0197     } else {
0198         qCDebug(ATCORE_CORE) << "Plugin:" << fwName << ": Not found.";
0199         Q_EMIT atcoreMessage(tr("No plugin found for %1.").arg(fwName));
0200     }
0201 }
0202 
0203 void AtCore::waitForPrinterReboot(const QByteArray &message, const QString &fwName)
0204 {
0205     Q_EMIT receivedMessage(message);
0206     if (message.isEmpty()) {
0207         return;
0208     }
0209 
0210     if (message.contains("Grbl")) {
0211         loadFirmwarePlugin(QString::fromLatin1("grbl"));
0212     } else if (message.contains("Smoothie")) {
0213         loadFirmwarePlugin(QString::fromLatin1("smoothie"));
0214     } else if (message.contains("start")) {
0215         if (!d->plugins.contains(fwName)) {
0216             disconnect(d->serial, &SerialLayer::receivedCommand, this, {});
0217             connect(d->serial, &SerialLayer::receivedCommand, this, &AtCore::findFirmware);
0218             QTimer::singleShot(500, this, &AtCore::requestFirmware);
0219         } else {
0220             loadFirmwarePlugin(fwName);
0221         }
0222     }
0223 }
0224 
0225 bool AtCore::newConnection(const QString &port, int baud, const QString &fwName, bool disableROC)
0226 {
0227     if (disableROC) {
0228         disableResetOnConnect(port);
0229     }
0230 
0231     d->serial = new SerialLayer(port, baud, this);
0232     connect(d->serial, &SerialLayer::serialError, this, &AtCore::handleSerialError);
0233     if (serialInitialized() && d->serial->isWritable()) {
0234         setState(AtCore::STATES::CONNECTING);
0235         connect(d->serial, &SerialLayer::pushedCommand, this, &AtCore::newCommand);
0236 
0237         if (!disableROC) {
0238             Q_EMIT atcoreMessage(tr("Waiting for machine restart"));
0239             connect(d->serial, &SerialLayer::receivedCommand, this, [this, fwName](const QByteArray &message) { waitForPrinterReboot(message, fwName); });
0240         } else {
0241             loadFirmwarePlugin(fwName);
0242         }
0243         if (d->serialTimer.isActive()) {
0244             d->serialTimer.stop();
0245         }
0246         return true;
0247     }
0248     qCDebug(ATCORE_CORE) << "Failed to open device for Read / Write.";
0249     Q_EMIT atcoreMessage(tr("Failed to open device in read/write mode."));
0250     return false;
0251 }
0252 
0253 bool AtCore::serialInitialized() const
0254 {
0255     if (!d->serial) {
0256         return false;
0257     }
0258     return d->serial->isOpen();
0259 }
0260 
0261 QString AtCore::connectedPort() const
0262 {
0263     return d->serial->portName();
0264 }
0265 
0266 QStringList AtCore::serialPorts() const
0267 {
0268     QStringList ports;
0269     const QList<QSerialPortInfo> serialPortInfoList = QSerialPortInfo::availablePorts();
0270     if (!serialPortInfoList.isEmpty()) {
0271         for (const QSerialPortInfo &serialPortInfo : serialPortInfoList) {
0272 #ifdef Q_OS_MAC
0273             // Mac OS has callout serial ports starting with cu these devices are read only.
0274             // It is necessary to filter them out to help prevent user error.
0275             if (!serialPortInfo.portName().startsWith(QStringLiteral("cu."), Qt::CaseInsensitive)) {
0276                 ports.append(serialPortInfo.portName());
0277             }
0278 #else
0279             ports.append(serialPortInfo.portName());
0280 #endif
0281         }
0282     }
0283     return ports;
0284 }
0285 
0286 void AtCore::locateSerialPort()
0287 {
0288     QStringList ports = serialPorts();
0289     if (d->serialPorts != ports) {
0290         d->serialPorts = ports;
0291         Q_EMIT portsChanged(d->serialPorts);
0292     }
0293 }
0294 
0295 int AtCore::serialTimerInterval() const
0296 {
0297     return d->serialTimer.interval();
0298 }
0299 
0300 void AtCore::setSerialTimerInterval(int newTime)
0301 {
0302     newTime = std::max(newTime, 0);
0303     if (newTime != d->serialTimer.interval()) {
0304         d->serialTimer.setInterval(newTime);
0305         Q_EMIT serialTimerIntervalChanged(newTime);
0306     }
0307     if (newTime == 0 && d->serialTimer.isActive()) {
0308         d->serialTimer.stop();
0309     } else {
0310         d->serialTimer.start(newTime);
0311     }
0312 }
0313 
0314 int AtCore::temperatureTimerInterval() const
0315 {
0316     return d->temperatureTimer.interval();
0317 }
0318 
0319 void AtCore::setTemperatureTimerInterval(int newTime)
0320 {
0321     newTime = std::max(newTime, 0);
0322     if (newTime != d->temperatureTimer.interval()) {
0323         d->temperatureTimer.setInterval(newTime);
0324         Q_EMIT temperatureTimerIntervalChanged(newTime);
0325     }
0326     if (newTime == 0 && d->temperatureTimer.isActive()) {
0327         d->temperatureTimer.stop();
0328     } else {
0329         d->temperatureTimer.start(newTime);
0330     }
0331 }
0332 
0333 void AtCore::setAutoTemperatureReport(bool autoReport)
0334 {
0335     if (autoReport == d->autoTemperatureReport) {
0336         return;
0337     }
0338 
0339     d->autoTemperatureReport = autoReport;
0340     Q_EMIT autoTemperatureReportChanged(autoReport);
0341 
0342     if (autoReport) {
0343         setTemperatureTimerInterval(0);
0344         d->commandQueue.removeAll(GCode::toCommand(GCode::M105));
0345         setAutoCheckTemperatureInterval(5);
0346     } else {
0347         setAutoCheckTemperatureInterval(0);
0348         setTemperatureTimerInterval(5000);
0349     }
0350 }
0351 
0352 void AtCore::setAutoCheckTemperatureInterval(int newTime)
0353 {
0354     if (state() >= 2 && state() != AtCore::ERRORSTATE) {
0355         pushCommand(GCode::toCommand(GCode::M155, QString::number(newTime)));
0356     }
0357     Q_EMIT autoCheckTemperatureIntervalChanged(newTime);
0358 }
0359 
0360 bool AtCore::autoTemperatureReport() const
0361 {
0362     return d->autoTemperatureReport;
0363 }
0364 
0365 void AtCore::newMessage(const QByteArray &message)
0366 {
0367     d->lastMessage = message;
0368     if (d->lastMessage.contains(QString::fromLatin1("Cap:AUTOREPORT_TEMP:1").toLocal8Bit())) {
0369         setAutoTemperatureReport(true);
0370     }
0371 
0372     if (d->lastCommand.startsWith(GCode::toCommand(GCode::GCode::G29))) {
0373         if (d->lastMessage.contains("ok")) {
0374             d->bedDeform.get()->decodeDeform(d->tempMultiString);
0375         }
0376         d->tempMultiString.append(QString::fromLatin1(d->lastMessage));
0377     }
0378     // Check if the message has current coordinates.
0379     if (d->lastCommand.startsWith(GCode::toCommand(GCode::MCommands::M114)) && d->lastMessage.startsWith(QString::fromLatin1("X:").toLocal8Bit())) {
0380         d->posString = message;
0381         d->posString.resize(d->posString.indexOf('E'));
0382         d->posString.replace(':', "");
0383     }
0384 
0385     // Check if have temperature info and decode it
0386     if (d->lastMessage.contains("T:") || d->lastMessage.contains("B:")) {
0387         temperature()->decodeTemp(d->lastMessage);
0388     }
0389     Q_EMIT receivedMessage(d->lastMessage);
0390 }
0391 
0392 void AtCore::newCommand(const QByteArray &command)
0393 {
0394     Q_EMIT pushedCommand(command);
0395 }
0396 
0397 void AtCore::setRelativePosition()
0398 {
0399     pushCommand(GCode::toCommand(GCode::GCommands::G91));
0400 }
0401 
0402 void AtCore::setAbsolutePosition()
0403 {
0404     pushCommand(GCode::toCommand(GCode::GCommands::G90));
0405 }
0406 
0407 float AtCore::percentagePrinted() const
0408 {
0409     return d->percentage;
0410 }
0411 
0412 void AtCore::print(const QString &fileName, bool sdPrint)
0413 {
0414     if (state() == AtCore::STATES::CONNECTING) {
0415         qCDebug(ATCORE_CORE) << "Load a firmware plugin to print.";
0416         return;
0417     }
0418     // Start a print job.
0419     setState(AtCore::STATES::STARTPRINT);
0420     // Only try to print from Sd if the firmware has support for sd cards
0421     if (firmwarePlugin()->isSdSupported()) {
0422         if (sdPrint) {
0423             // Printing from the sd card requires us to send some M commands.
0424             pushCommand(GCode::toCommand(GCode::MCommands::M23, fileName));
0425             d->sdCardFileName = fileName;
0426             pushCommand(GCode::toCommand(GCode::MCommands::M24));
0427             setState(AtCore::STATES::BUSY);
0428             d->sdCardPrinting = true;
0429             d->sdPrintProgressTimer.start(5000);
0430             return;
0431         }
0432     }
0433     // Process the gcode with a printThread.
0434     // The Thread processes the gcode without freezing the libary.
0435     // Only sends a command back when the printer is ready, avoiding buffer overflow in the printer.
0436     auto thread = new QThread(this);
0437     auto printThread = new PrintThread(this, fileName);
0438     printThread->moveToThread(thread);
0439 
0440     connect(printThread, &PrintThread::printProgressChanged, this, &AtCore::printProgressChanged, Qt::QueuedConnection);
0441     connect(thread, &QThread::started, printThread, &PrintThread::start);
0442     connect(printThread, &PrintThread::finished, thread, &QThread::quit);
0443     connect(thread, &QThread::finished, printThread, &PrintThread::deleteLater);
0444     if (!thread->isRunning()) {
0445         thread->start();
0446     }
0447 }
0448 
0449 void AtCore::pushCommand(const QString &comm)
0450 {
0451     // Be sure our M112 is first in the queue.
0452     if (comm == GCode::toCommand(GCode::MCommands::M112)) {
0453         d->commandQueue.prepend(comm);
0454     } else {
0455         d->commandQueue.append(comm);
0456     }
0457     if (d->ready) {
0458         // The printer is ready for a command now so push one.
0459         processQueue();
0460     }
0461 }
0462 
0463 void AtCore::closeConnection()
0464 {
0465     if (serialInitialized()) {
0466         if (AtCore::state() == AtCore::STATES::BUSY && !d->sdCardPrinting) {
0467             // We have to clean up the print job if printing from the host.
0468             // However disconnecting while printing from sd card should not affect the print job.
0469             setState(AtCore::STATES::STOP);
0470         }
0471         if (firmwarePluginLoaded()) {
0472             disconnect(firmwarePlugin(), &IFirmware::readyForCommand, this, &AtCore::processQueue);
0473             disconnect(d->serial, &SerialLayer::receivedCommand, this, &AtCore::newMessage);
0474             if (d->autoTemperatureReport) {
0475                 blockSignals(true);
0476                 setAutoTemperatureReport(false);
0477                 blockSignals(false);
0478             }
0479             setTemperatureTimerInterval(0);
0480             // Attempt to unload the firmware plugin.
0481             QString name = firmwarePlugin()->name();
0482             QString msg = d->pluginLoader.unload() ? QStringLiteral("closed.") : QStringLiteral("Failed to close.");
0483             qCDebug(ATCORE_CORE) << QStringLiteral("Firmware plugin %1 %2").arg(name, msg);
0484             d->firmwarePlugin = nullptr;
0485         }
0486         // Do not reset the connect on disconnect when closing this will cause a reset on connect for the next connection.
0487         disconnect(d->serial, &SerialLayer::serialError, this, &AtCore::handleSerialError);
0488         disconnect(d->serial, &SerialLayer::pushedCommand, this, &AtCore::newMessage);
0489         d->serial->close();
0490         // Clear our copy of the sdcard filelist
0491         clearSdCardFileList();
0492         setState(AtCore::STATES::DISCONNECTED);
0493         d->serialTimer.start();
0494     }
0495 }
0496 
0497 AtCore::STATES AtCore::state()
0498 {
0499     return d->printerState;
0500 }
0501 
0502 void AtCore::setState(AtCore::STATES state)
0503 {
0504     if (state != d->printerState) {
0505         qCDebug(ATCORE_CORE) << QStringLiteral("Atcore state changed from [%1] to [%2]").arg(QVariant::fromValue(d->printerState).toString(), QVariant::fromValue(state).toString());
0506         d->printerState = state;
0507         if (state == AtCore::STATES::FINISHEDPRINT && d->sdCardPrinting) {
0508             // Clean up the sd card print
0509             d->sdCardPrinting = false;
0510             if (d->sdPrintProgressTimer.isActive()) {
0511                 d->sdPrintProgressTimer.stop();
0512             }
0513         }
0514         Q_EMIT stateChanged(d->printerState);
0515     }
0516 }
0517 
0518 void AtCore::stop()
0519 {
0520     // Stop a print job
0521     setState(AtCore::STATES::STOP);
0522     d->commandQueue.clear();
0523     if (d->sdCardPrinting) {
0524         stopSdPrint();
0525     }
0526     setExtruderTemp(0, 0);
0527     setBedTemp(0);
0528     home(AtCore::AXES::X);
0529 }
0530 
0531 void AtCore::emergencyStop()
0532 {
0533     // Emergency Stop. Stops the machine
0534     // Clear the queue, and any print job
0535     // Before sending the command to ensure
0536     // Less chance of movement after the restart.
0537     d->commandQueue.clear();
0538     if (AtCore::state() == AtCore::STATES::BUSY) {
0539         if (!d->sdCardPrinting) {
0540             // Stop our running print thread
0541             setState(AtCore::STATES::STOP);
0542         }
0543     }
0544     pushCommand(GCode::toCommand(GCode::MCommands::M112));
0545 }
0546 
0547 void AtCore::stopSdPrint()
0548 {
0549     // Stop an SdCard Print.
0550     pushCommand(GCode::toCommand(GCode::MCommands::M25));
0551     d->sdCardFileName = QString();
0552     pushCommand(GCode::toCommand(GCode::MCommands::M23, d->sdCardFileName));
0553     AtCore::setState(AtCore::STATES::FINISHEDPRINT);
0554     AtCore::setState(AtCore::STATES::IDLE);
0555 }
0556 
0557 void AtCore::requestFirmware()
0558 {
0559     if (serialInitialized()) {
0560         // ensure M115 is sent on cold connect.
0561         d->commandQueue.clear();
0562         d->ready = true;
0563         qCDebug(ATCORE_CORE) << "Sending " << GCode::description(GCode::MCommands::M115);
0564         pushCommand(GCode::toCommand(GCode::MCommands::M115));
0565     } else {
0566         qCDebug(ATCORE_CORE) << "There is no open device to send commands";
0567     }
0568 }
0569 
0570 bool AtCore::firmwarePluginLoaded() const
0571 {
0572     return firmwarePlugin();
0573 }
0574 
0575 QStringList AtCore::availableFirmwarePlugins() const
0576 {
0577     return d->plugins.keys();
0578 }
0579 
0580 void AtCore::pause(const QString &pauseActions)
0581 {
0582     if (d->sdCardPrinting) {
0583         pushCommand(GCode::toCommand(GCode::MCommands::M25));
0584     }
0585     // Push the command to request current coordinates.
0586     // This will be read by AtCore::newMessage and stored for use on resume.
0587     pushCommand(GCode::toCommand(GCode::MCommands::M114));
0588     if (!pauseActions.isEmpty()) {
0589         QStringList temp = pauseActions.split(QChar::fromLatin1(','));
0590         for (int i = 0; i < temp.length(); i++) {
0591             pushCommand(temp.at(i));
0592         }
0593     }
0594     setState(AtCore::PAUSE);
0595 }
0596 
0597 void AtCore::resume()
0598 {
0599     if (d->sdCardPrinting) {
0600         pushCommand(GCode::toCommand(GCode::MCommands::M24));
0601     } else {
0602         // Move back to previous coordinates.
0603         pushCommand(GCode::toCommand(GCode::GCommands::G0, QString::fromLatin1(d->posString)));
0604     }
0605     setState(AtCore::BUSY);
0606 }
0607 
0608 /*~~~~~Control Slots ~~~~~~~~*/
0609 
0610 void AtCore::home()
0611 {
0612     pushCommand(GCode::toCommand(GCode::GCommands::G28));
0613 }
0614 
0615 void AtCore::home(uchar axis)
0616 {
0617     QString args;
0618 
0619     if (axis & AtCore::AXES::X) {
0620         args.append(QStringLiteral("X0 "));
0621     }
0622 
0623     if (axis & AtCore::AXES::Y) {
0624         args.append(QStringLiteral("Y0 "));
0625     }
0626 
0627     if (axis & AtCore::AXES::Z) {
0628         args.append(QStringLiteral("Z0"));
0629     }
0630     pushCommand(GCode::toCommand(GCode::GCommands::G28, args));
0631 }
0632 
0633 void AtCore::setExtruderTemp(uint temp, uint extruder, bool andWait)
0634 {
0635     temp = std::min<uint>(temp, 10000);
0636     extruder = std::min<uint>(extruder, 10000);
0637 
0638     if (andWait) {
0639         pushCommand(GCode::toCommand(GCode::MCommands::M109, QString::number(temp), QString::number(extruder)));
0640     } else {
0641         pushCommand(GCode::toCommand(GCode::MCommands::M104, QString::number(extruder), QString::number(temp)));
0642     }
0643 }
0644 
0645 void AtCore::setBedTemp(uint temp, bool andWait)
0646 {
0647     temp = std::min<uint>(temp, 10000);
0648 
0649     if (andWait) {
0650         pushCommand(GCode::toCommand(GCode::MCommands::M190, QString::number(temp)));
0651     } else {
0652         pushCommand(GCode::toCommand(GCode::MCommands::M140, QString::number(temp)));
0653     }
0654 }
0655 
0656 void AtCore::setFanSpeed(uint speed, uint fanNumber)
0657 {
0658     speed = std::max<uint>(0, std::min<uint>(speed, 10000));
0659     fanNumber = std::min<uint>(fanNumber, 10000);
0660 
0661     pushCommand(GCode::toCommand(GCode::MCommands::M106, QString::number(fanNumber), QString::number(speed)));
0662 }
0663 
0664 void AtCore::setPrinterSpeed(uint speed)
0665 {
0666     speed = std::max<uint>(0, std::min<uint>(speed, 10000));
0667     pushCommand(GCode::toCommand(GCode::MCommands::M220, QString::number(speed)));
0668 }
0669 
0670 void AtCore::setFlowRate(uint speed)
0671 {
0672     speed = std::max<uint>(0, std::min<uint>(speed, 10000));
0673     pushCommand(GCode::toCommand(GCode::MCommands::M221, QString::number(speed)));
0674 }
0675 
0676 void AtCore::move(AtCore::AXES axis, double arg)
0677 {
0678     const auto axisAsString = QMetaEnum::fromType<AtCore::AXES>().valueToKey(axis);
0679     move(QLatin1Char(axisAsString[0]), arg);
0680 }
0681 
0682 void AtCore::move(QLatin1Char axis, double arg)
0683 {
0684     // Using QString::number(double, format, precision)
0685     // f = 'format as [-]9.9'
0686     // 3 = use 3 decimal precision
0687     pushCommand(GCode::toCommand(GCode::GCommands::G1, QStringLiteral("%1 %2").arg(axis).arg(QString::number(arg, 'f', 3))));
0688 }
0689 
0690 int AtCore::extruderCount() const
0691 {
0692     return d->extruderCount;
0693 }
0694 
0695 void AtCore::setExtruderCount(int newCount)
0696 {
0697     if (d->extruderCount != newCount && newCount >= 1) {
0698         d->extruderCount = newCount;
0699         Q_EMIT extruderCountChanged(newCount);
0700         qCDebug(ATCORE_CORE) << "Extruder Count:" << QString::number(extruderCount());
0701     }
0702 }
0703 void AtCore::processQueue()
0704 {
0705     d->ready = true;
0706 
0707     if (d->commandQueue.isEmpty()) {
0708         return;
0709     }
0710 
0711     if (!serialInitialized()) {
0712         qCDebug(ATCORE_PLUGIN) << "Can't process queue ! Serial not initialized.";
0713         return;
0714     }
0715 
0716     d->lastCommand = d->commandQueue.takeAt(0);
0717 
0718     if (firmwarePluginLoaded()) {
0719         d->serial->pushCommand(firmwarePlugin()->translate(d->lastCommand));
0720     } else {
0721         d->serial->pushCommand(d->lastCommand.toLocal8Bit());
0722     }
0723     d->ready = false;
0724 }
0725 
0726 void AtCore::checkTemperature()
0727 {
0728     // One request for the temperature in the queue at a time.
0729     if (d->commandQueue.contains(GCode::toCommand(GCode::MCommands::M105))) {
0730         return;
0731     }
0732     pushCommand(GCode::toCommand(GCode::MCommands::M105));
0733 }
0734 
0735 void AtCore::showMessage(const QString &message)
0736 {
0737     if (!message.isEmpty()) {
0738         pushCommand(GCode::toCommand((GCode::MCommands::M117), message));
0739     }
0740 }
0741 
0742 void AtCore::setUnits(AtCore::UNITS units)
0743 {
0744     switch (units) {
0745     case AtCore::UNITS::METRIC:
0746         pushCommand(GCode::toCommand(GCode::GCommands::G21));
0747         break;
0748     case AtCore::UNITS::IMPERIAL:
0749         pushCommand(GCode::toCommand(GCode::GCommands::G20));
0750         break;
0751     }
0752 }
0753 
0754 QStringList AtCore::portSpeeds() const
0755 {
0756     return d->serial->validBaudRates();
0757 }
0758 
0759 void AtCore::disableMotors(uint delay)
0760 {
0761     // Disables motors
0762     if (delay) {
0763         pushCommand(GCode::toCommand(GCode::MCommands::M84, QString::number(delay)));
0764     } else {
0765         pushCommand(GCode::toCommand(GCode::MCommands::M84));
0766     }
0767 }
0768 // Most firmwares will not report if an sdcard is mounted on boot.
0769 bool AtCore::isSdMounted() const
0770 {
0771     return d->sdCardMounted;
0772 }
0773 
0774 void AtCore::setSdMounted(bool mounted)
0775 {
0776     if (mounted != isSdMounted()) {
0777         d->sdCardMounted = mounted;
0778         Q_EMIT sdMountChanged(d->sdCardMounted);
0779     }
0780 }
0781 
0782 void AtCore::getSDFileList()
0783 {
0784     pushCommand(GCode::toCommand(GCode::MCommands::M20));
0785 }
0786 
0787 QStringList AtCore::sdFileList()
0788 {
0789     if (!d->sdCardReadingFileList) {
0790         getSDFileList();
0791     }
0792     return d->sdCardFileList;
0793 }
0794 
0795 void AtCore::appendSdCardFileList(const QString &fileName)
0796 {
0797     d->sdCardFileList.append(fileName);
0798     Q_EMIT sdCardFileListChanged(d->sdCardFileList);
0799 }
0800 
0801 void AtCore::clearSdCardFileList()
0802 {
0803     d->sdCardFileList.clear();
0804     Q_EMIT sdCardFileListChanged(d->sdCardFileList);
0805 }
0806 
0807 void AtCore::sdDelete(const QString &fileName)
0808 {
0809     if (d->sdCardFileList.contains(fileName)) {
0810         pushCommand(GCode::toCommand(GCode::MCommands::M30, fileName));
0811         getSDFileList();
0812     } else {
0813         qCDebug(ATCORE_CORE) << "Delete failed file not found:" << fileName;
0814     }
0815 }
0816 
0817 void AtCore::mountSd(uint slot)
0818 {
0819     pushCommand(GCode::toCommand(GCode::MCommands::M21, QString::number(slot)));
0820 }
0821 
0822 void AtCore::umountSd(uint slot)
0823 {
0824     pushCommand(GCode::toCommand(GCode::MCommands::M22, QString::number(slot)));
0825 }
0826 
0827 bool AtCore::isReadingSdCardList() const
0828 {
0829     return d->sdCardReadingFileList;
0830 }
0831 
0832 void AtCore::setReadingSdCardList(bool readingList)
0833 {
0834     d->sdCardReadingFileList = readingList;
0835 }
0836 
0837 void AtCore::sdCardPrintStatus()
0838 {
0839     // One request for the Sd Job status in the queue at a time.
0840     if (d->commandQueue.contains(GCode::toCommand(GCode::MCommands::M27))) {
0841         return;
0842     }
0843     pushCommand(GCode::toCommand(GCode::MCommands::M27));
0844 }
0845 
0846 void AtCore::disableResetOnConnect(const QString &port)
0847 {
0848 #if defined(Q_OS_UNIX)
0849     // should work on all unix'
0850     QProcess process(this);
0851     QStringList args({QStringLiteral("-F/dev/%1").arg(port), QStringLiteral("-hupcl")});
0852     process.start(QStringLiteral("stty"), args);
0853     process.waitForFinished(500);
0854 
0855     connect(&process, &QProcess::errorOccurred, this, [&process] { qCDebug(ATCORE_CORE) << "Stty Error:" << process.errorString(); });
0856 
0857 #elif defined(Q_OS_WIN)
0858     // TODO: Disable hangup on windows.
0859 #endif
0860 }
0861 
0862 void AtCore::handleSerialError(QSerialPort::SerialPortError error)
0863 {
0864     QString errorString;
0865 
0866     switch (error) {
0867     case (QSerialPort::SerialPortError::DeviceNotFoundError):
0868         errorString = tr("Device not found");
0869         break;
0870     case (QSerialPort::SerialPortError::WriteError):
0871         errorString = tr("Unable to write to device");
0872         break;
0873     case (QSerialPort::SerialPortError::ReadError):
0874         errorString = tr("Unable to read from device");
0875         break;
0876     case (QSerialPort::SerialPortError::ResourceError):
0877     case (QSerialPort::SerialPortError::TimeoutError):
0878         errorString = tr("The device no longer available");
0879         closeConnection();
0880         break;
0881     case (QSerialPort::SerialPortError::UnsupportedOperationError):
0882         errorString = tr("Device does not support the operation");
0883         break;
0884     case (QSerialPort::SerialPortError::UnknownError):
0885         errorString = tr("Unknown Error");
0886         break;
0887     default:
0888         // Not Directly processed errors
0889         // QSerialPort::NoError, No error has happened
0890         // QSerialPort::PermissionError), Already handled.
0891         // QSerialPort::OpenError), Already handled.
0892         // QSerialPort::NotOpenError, SerialLayer destroyed if not connected.
0893         // QSerialPort::ParityError, Obsolete. Qt Docs "We strongly advise against using it in new code."
0894         // QSerialPort::FramingError, Obsolete. Qt Docs "We strongly advise against using it in new code."
0895         // QSerialPort::BreakConditionError, Obsolete. Qt Docs "We strongly advise against using it in new code."
0896         return;
0897     }; // End of Switch
0898     qCDebug(ATCORE_CORE) << "SerialError:" << errorString;
0899     Q_EMIT atcoreMessage(QStringLiteral("SerialError: %1").arg(errorString));
0900 }
0901 
0902 void AtCore::updateFWPlugins()
0903 {
0904     // Attempt to find our plugins
0905     qCDebug(ATCORE_PLUGIN) << "Detecting Plugin path";
0906     QStringList paths = AtCoreDirectories::pluginDir;
0907     qCDebug(ATCORE_PLUGIN) << "Paths" << paths;
0908     for (const auto &path : AtCoreDirectories::pluginDir) {
0909         qCDebug(ATCORE_PLUGIN) << "Checking: " << path;
0910         QMap<QString, QString> detectedPlugins;
0911         const auto pluginList = QDir(path).entryList({AtCoreDirectories::pluginExtFilter}, QDir::Files);
0912         for (const QString &f : pluginList) {
0913             QString file = f;
0914             file = file.split(QStringLiteral(".")).at(0).toLower().simplified();
0915             if (file.startsWith(QStringLiteral("lib")))
0916                 file = file.remove(QStringLiteral("lib"));
0917             QString pluginString = QStringLiteral("%1/%2").arg(path, f);
0918             detectedPlugins.insert(file, pluginString);
0919             qCDebug(ATCORE_PLUGIN) << QStringLiteral("Plugin:[%1]=%2").arg(file, pluginString);
0920         }
0921         if (!detectedPlugins.isEmpty()) {
0922             d->plugins = detectedPlugins;
0923             Q_EMIT availableFirmwarePluginsChanged();
0924             return;
0925         }
0926     }
0927 }
0928 
0929 std::shared_ptr<BedDeform> AtCore::bedDeform()
0930 {
0931     return d->bedDeform;
0932 }