diff --git a/src/atcore.h b/src/atcore.h --- a/src/atcore.h +++ b/src/atcore.h @@ -59,6 +59,8 @@ Q_PROPERTY(QStringList portSpeeds READ portSpeeds) Q_PROPERTY(QString connectedPort READ connectedPort) Q_PROPERTY(AtCore::STATES state READ state WRITE setState NOTIFY stateChanged) + Q_PROPERTY(bool sdMount READ isSdMounted NOTIFY sdMountChanged) + Q_PROPERTY(QStringList sdFileList READ sdFileList NOTIFY sdCardFileListChanged) public: /** * @brief STATES enum Possible states the printer can be in @@ -200,6 +202,18 @@ */ quint16 serialTimerInterval() const; + /** + * @brief Attempt to Mount an sd card + * @param slot: Sd card Slot on machine (0 is default) + */ + void mountSd(int slot = 0); + + /** + * @brief Attempt to Unmount an sd card + * @param slot: Sd card Slot on machine (0 is default) + */ + void umountSd(int slot = 0); + signals: /** @@ -227,6 +241,16 @@ */ void portsChanged(QStringList); + /** + * @brief Sd Card Mount Changed + */ + void sdMountChanged(bool); + + /** + * @brief get a list of the files on the sd card + */ + void sdCardFileListChanged(QStringList); + public slots: /** @@ -246,8 +270,9 @@ /** * @brief Public Interface for printing a file * @param fileName: the gcode file to print. + * @param sdPrint: set true to print fileName from Sd card */ - void print(const QString &fileName); + void print(const QString &fileName, bool sdPrint); /** * @brief Stop the Printer by empting the queue and aborting the print job (if running) @@ -266,6 +291,7 @@ * * Sends M114 on pause to store the location where the head stoped. * This is known to cause problems on fake printers + * Post Pause moves are not sent for sd card jobs firmware has it own post pause moves * @param pauseActions: Gcode to run after pausing commands are ',' separated * @sa resume(),stop(),emergencyStop() */ @@ -378,6 +404,23 @@ */ void setSerialTimerInterval(const quint16 &newTime); + /** + * @brief sdFileList + * @return List of files on the sd card. + */ + QStringList sdFileList(); + + /** + * @brief Check if an sd card is mounted on the printer + * @return True if card mounted + */ + bool isSdMounted(); + + /** + * @brief delete file from sd card + */ + void sdDelete(const QString &fileName); + private slots: /** * @brief processQueue send commands from the queue. @@ -407,6 +450,11 @@ */ void locateSerialPort(); + /** + * @brief Send request to the printer for the sd card file list. + */ + void getSDFileList(); + private: /** * @brief True if a firmware plugin is loaded diff --git a/src/atcore.cpp b/src/atcore.cpp --- a/src/atcore.cpp +++ b/src/atcore.cpp @@ -61,6 +61,11 @@ AtCore::STATES printerState; //!< @param printerState: State of the Printer QStringList serialPorts; //!< @param seralPorts: Detected serial Ports QTimer *serialTimer = nullptr; //!< @param serialTimer: Timer connected to locateSerialPorts + bool sdCardMounted = false; //!< @param sdCardMounted: True if Sd Card is mounted. + bool sdCardReadingFileList = false; //!< @param sdCardReadingFileList: True while getting file names from sd card + QString sdCardFileName; //!< @param sdCardFileName: name of file being used from sd card. + bool sdCardPrinting = false; //!< @param sdCardPrinting: True if currently printing from sd card. + QStringList sdCardFileList; //!< @param sdCardFileList: List of files on sd card. }; AtCore::AtCore(QObject *parent) : @@ -297,15 +302,40 @@ void AtCore::newMessage(const QByteArray &message) { d->lastMessage = message; - if (message.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(message); + if (message.contains("End file list")) { + d->sdCardReadingFileList = false; + emit(sdCardFileListChanged(d->sdCardFileList)); + } else if (d->sdCardReadingFileList) { + // Below is to not add directories + if (!message.endsWith('/')) { + QString fileName = QString::fromLocal8Bit(message); + fileName.chop(fileName.length() - fileName.lastIndexOf(QChar::fromLatin1(' '))); + d->sdCardFileList.append(fileName); + qCDebug(ATCORE_CORE) << "Adding " << fileName << " to file list"; + } + } else { + if (message.startsWith(QString::fromLatin1("X:").toLocal8Bit())) { + d->posString = message; + d->posString.resize(d->posString.indexOf('E')); + d->posString.replace(':', ""); + } else if (message.contains("SD card")) { + if (message.contains("inserted")) { + d->sdCardMounted = true; + } else if (message.contains("removed")) { + d->sdCardMounted = false; + } + emit sdMountChanged(d->sdCardMounted); + } else if (message.contains("Begin file list")) { + if (d->sdCardMounted != true) { + d->sdCardMounted = true; + emit sdMountChanged(true); + } + d->sdCardReadingFileList = true; + d->sdCardFileList.clear(); + } else if (d->lastMessage.contains("T:") || d->lastMessage.contains("B:")) { + temperature().decodeTemp(message); + } } emit(receivedMessage(d->lastMessage)); } @@ -322,27 +352,43 @@ float AtCore::percentagePrinted() const { + /* + if(d->sdCardPrinting) { + pushCommand(GCode::toCommand(GCode::M27)); + } else { + return d->percentage; + } + */ return d->percentage; } -void AtCore::print(const QString &fileName) +void AtCore::print(const QString &fileName, bool sdPrint) { - if (state() == AtCore::CONNECTING) { - qCDebug(ATCORE_CORE) << "Load a firmware plugin to print."; - return; - } - //START A THREAD AND CONNECT TO IT - setState(AtCore::STARTPRINT); - QThread *thread = new QThread(); - PrintThread *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(); + if (sdPrint) { + pushCommand(GCode::toCommand(GCode::M23, fileName)); + d->sdCardFileName = fileName; + pushCommand(GCode::toCommand(GCode::M24)); + setState(AtCore::STARTPRINT); + setState(AtCore::BUSY); + d->sdCardPrinting = true; + } else { + if (state() == AtCore::CONNECTING) { + qCDebug(ATCORE_CORE) << "Load a firmware plugin to print."; + return; + } + //START A THREAD AND CONNECT TO IT + setState(AtCore::STARTPRINT); + QThread *thread = new QThread(); + PrintThread *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(); + } } } @@ -391,8 +437,14 @@ void AtCore::stop() { setState(AtCore::STOP); - d->commandQueue.clear(); + if (d->sdCardPrinting) { + pushCommand(GCode::toCommand(GCode::M25)); + d->sdCardFileName = QStringLiteral(""); + pushCommand(GCode::toCommand(GCode::M23, d->sdCardFileName)); + d->sdCardPrinting = false; + } setExtruderTemp(0, 0); + d->commandQueue.clear(); setBedTemp(0); home(AtCore::X); } @@ -403,6 +455,9 @@ setState(AtCore::STOP); } d->commandQueue.clear(); + if (d->sdCardPrinting) { + stop(); + } serial()->pushCommand(GCode::toCommand(GCode::M112).toLocal8Bit()); } @@ -468,19 +523,27 @@ void AtCore::pause(const QString &pauseActions) { - pushCommand(GCode::toCommand(GCode::M114)); - setState(AtCore::PAUSE); - if (!pauseActions.isEmpty()) { - QStringList temp = pauseActions.split(QChar::fromLatin1(',')); - for (int i = 0; i < temp.length(); i++) { - pushCommand(temp.at(i)); + if (d->sdCardPrinting) { + pushCommand(GCode::toCommand(GCode::M25)); + } else { + pushCommand(GCode::toCommand(GCode::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() { - pushCommand(GCode::toCommand(GCode::G0, QString::fromLatin1(d->posString))); + if (d->sdCardPrinting) { + pushCommand(GCode::toCommand(GCode::M24)); + } else { + pushCommand(GCode::toCommand(GCode::G0, QString::fromLatin1(d->posString))); + } setState(AtCore::BUSY); } @@ -624,3 +687,37 @@ pushCommand(GCode::toCommand(GCode::M84)); } } + +bool AtCore::isSdMounted() +{ + return d->sdCardMounted; +} + +void AtCore::getSDFileList() +{ + pushCommand(GCode::toCommand(GCode::M20)); +} + +QStringList AtCore::sdFileList() +{ + if (d->sdCardFileList.isEmpty()) { + getSDFileList(); + } + return d->sdCardFileList; +} + +void AtCore::sdDelete(const QString &fileName) +{ + pushCommand(GCode::toCommand(GCode::M30, fileName)); + getSDFileList(); +} + +void AtCore::mountSd(int slot) +{ + pushCommand(GCode::toCommand(GCode::M21, QString::number(slot))); +} + +void AtCore::umountSd(int slot) +{ + pushCommand(GCode::toCommand(GCode::M22, QString::number(slot))); +} diff --git a/src/gcodecommands.cpp b/src/gcodecommands.cpp --- a/src/gcodecommands.cpp +++ b/src/gcodecommands.cpp @@ -139,7 +139,7 @@ return QObject::tr("M21: Initialize SDCard"); case M22://Teacup - Sprinter - Marlin - Repetier - RepRap Firmware return QObject::tr("M22: Release SDCard"); - case M23:////Teacup - Sprinter - Marlin - Repetier - Smoothie - RepRap Firmware + case M23://Teacup - Sprinter - Marlin - Repetier - Smoothie - RepRap Firmware return QObject::tr("M23: Select SD file"); case M24://Teacup - Sprinter - Marlin - Repetier - Smoothie - RepRap Firmware return QObject::tr("M24: Start/resume SD print"); @@ -299,7 +299,7 @@ return QObject::tr("M231: Set OPS parameter"); case M232://Repetier return QObject::tr("M232: Read and reset max. advance values"); - case M240: //Marlin + case M240://Marlin return QObject::tr("M240: Trigger camera"); case M250://Marlin return QObject::tr("M250: Set LCD contrast"); @@ -504,6 +504,67 @@ QString GCode::toCommand(MCommands gcode, const QString &value1, const QString &value2) { switch (gcode) { + case M20: { + return QStringLiteral("M20"); + } + case M21: { + if (!value1.isEmpty()) { + return QStringLiteral("M21 P%1").arg(value1); + } + return QStringLiteral("M21"); + } + case M22: { + if (!value1.isEmpty()) { + return QStringLiteral("M22 P%1").arg(value1); + } + return QStringLiteral("M22"); + } + case M23: { + if (!value1.isEmpty()) { + return QStringLiteral("M23 %1").arg(value1); + } + return QObject::tr("ERROR! M23: It's obligatory to have an argument"); + } + case M24: { + return QStringLiteral("M24"); + } + case M25: { + return QStringLiteral("M25"); + } + /// For M26 values that end with %. AtCore will send the percentage verison of the command (optional in firmwares) + /// For all values not ending in % it will start on that byte. This is the standard Sd resume supported by all reprap based firmware. + case M26: { + if (!value1.isEmpty()) { + if (value1.endsWith(QStringLiteral("%"))) { + QString temp = value1; + temp.replace(QStringLiteral("%"), QStringLiteral("")); + return QStringLiteral("M26 P%1").arg(temp.toDouble() / 100); + } + return QStringLiteral("M26 S%1").arg(value1); + } + return QObject::tr("ERROR! M26: It's obligatory to have an argument"); + } + case M27: { + return QStringLiteral("M27"); + } + case M28: { + if (!value1.isEmpty()) { + return QStringLiteral("M28 %1").arg(value1); + } + return QObject::tr("ERROR! M28: It's obligatory to have an argument"); + } + case M29: { + if (!value1.isEmpty()) { + return QStringLiteral("M29 %1").arg(value1); + } + return QObject::tr("ERROR! M29: It's obligatory to have an argument"); + } + case M30: { + if (!value1.isEmpty()) { + return QStringLiteral("M30 %1").arg(value1); + } + return QObject::tr("ERROR! M30: It's obligatory to have an argument"); + } case M84: { if (!value1.isEmpty()) { return QStringLiteral("M84 S%1").arg(value1); diff --git a/testclient/mainwindow.h b/testclient/mainwindow.h --- a/testclient/mainwindow.h +++ b/testclient/mainwindow.h @@ -131,6 +131,10 @@ */ void printPBClicked(); + /** + * @brief Print Button for Sd Prints clicked. + */ + void pb_printSDClicked(); /** * @brief Save the log file. */ @@ -176,12 +180,24 @@ * @brief Show the about dialog */ void about(); + + /** + * @brief List Files on the sd card. + */ + void getSdList(); + + /** + * @brief Sd Card Delete file clicked + */ + void pb_sdDelClicked(); + signals: /** * @brief printFile emit ready to print a file to atcore * @param fileName : the file to print + * @param sdPrint : True if file is on printers Sd Card */ - void printFile(const QString &fileName); + void printFile(const QString &fileName, bool sdPrint = false); private: Ui::MainWindow *ui; @@ -269,4 +285,9 @@ * @brief Populate comboboxes */ void populateCBs(); + + /** + * @brief Gui Changes for when sd card mount status has changed. + */ + void sdChanged(bool mounted); }; diff --git a/testclient/mainwindow.cpp b/testclient/mainwindow.cpp --- a/testclient/mainwindow.cpp +++ b/testclient/mainwindow.cpp @@ -90,18 +90,27 @@ connect(ui->mvAxisPB, &QPushButton::clicked, this, &MainWindow::mvAxisPBClicked); connect(ui->fanSpeedPB, &QPushButton::clicked, this, &MainWindow::fanSpeedPBClicked); connect(ui->printPB, &QPushButton::clicked, this, &MainWindow::printPBClicked); + connect(ui->pb_printSD, &QPushButton::clicked, this, &MainWindow::pb_printSDClicked); + connect(ui->pb_sdDel, &QPushButton::clicked, this, &MainWindow::pb_sdDelClicked); connect(ui->printerSpeedPB, &QPushButton::clicked, this, &MainWindow::printerSpeedPBClicked); connect(ui->flowRatePB, &QPushButton::clicked, this, &MainWindow::flowRatePBClicked); connect(ui->showMessagePB, &QPushButton::clicked, this, &MainWindow::showMessage); connect(ui->pluginCB, &QComboBox::currentTextChanged, this, &MainWindow::pluginCBChanged); connect(ui->disableMotorsPB, &QPushButton::clicked, this, &MainWindow::disableMotorsPBClicked); + connect(ui->pb_sdList, &QPushButton::clicked, this, &MainWindow::getSdList); connect(core, &AtCore::stateChanged, this, &MainWindow::printerStateChanged); connect(this, &MainWindow::printFile, core, &AtCore::print); connect(ui->stopPB, &QPushButton::clicked, core, &AtCore::stop); connect(ui->emergencyStopPB, &QPushButton::clicked, core, &AtCore::emergencyStop); connect(axisControl, &AxisControl::clicked, this, &MainWindow::axisControlClicked); connect(core, &AtCore::portsChanged, this, &MainWindow::locateSerialPort); connect(core, &AtCore::printProgressChanged, this, &MainWindow::printProgressChanged); + connect(core, &AtCore::sdMountChanged, this, &MainWindow::sdChanged); + + connect(core, &AtCore::sdCardFileListChanged, [this](QStringList fileList) { + ui->list_sdFileView->clear(); + ui->list_sdFileView->addItems(fileList); + }); connect(&core->temperature(), &Temperature::bedTemperatureChanged, [ = ](float temp) { checkTemperature(0x00, 0, temp); @@ -135,11 +144,13 @@ ui->menuView->insertAction(nullptr, ui->moveDock->toggleViewAction()); ui->menuView->insertAction(nullptr, ui->tempTimelineDock->toggleViewAction()); ui->menuView->insertAction(nullptr, ui->logDock->toggleViewAction()); + ui->menuView->insertAction(nullptr, ui->sdDock->toggleViewAction()); //more dock stuff. setTabPosition(Qt::LeftDockWidgetArea, QTabWidget::North); setTabPosition(Qt::RightDockWidgetArea, QTabWidget::North); tabifyDockWidget(ui->moveDock, ui->tempControlsDock); + tabifyDockWidget(ui->moveDock, ui->sdDock); ui->moveDock->raise(); tabifyDockWidget(ui->connectDock, ui->printDock); @@ -487,14 +498,16 @@ ui->moveDock->setDisabled(true); ui->tempControlsDock->setDisabled(true); ui->printDock->setDisabled(true); + ui->sdDock->setDisabled(true); break; case AtCore::CONNECTING: stateString = QStringLiteral("Connecting"); ui->commandDock->setDisabled(false); ui->moveDock->setDisabled(false); ui->tempControlsDock->setDisabled(false); ui->printDock->setDisabled(false); + ui->sdDock->setDisabled(false); break; case AtCore::STOP: @@ -552,14 +565,16 @@ delete ui->moveDock->titleBarWidget(); delete ui->tempControlsDock->titleBarWidget(); delete ui->printDock->titleBarWidget(); + delete ui->sdDock->titleBarWidget(); } else { ui->connectDock->setTitleBarWidget(new QWidget()); ui->logDock->setTitleBarWidget(new QWidget()); ui->tempTimelineDock->setTitleBarWidget(new QWidget()); ui->commandDock->setTitleBarWidget(new QWidget()); ui->moveDock->setTitleBarWidget(new QWidget()); ui->tempControlsDock->setTitleBarWidget(new QWidget()); ui->printDock->setTitleBarWidget(new QWidget()); + ui->sdDock->setTitleBarWidget(new QWidget()); } } @@ -580,3 +595,36 @@ { core->setIdleHold(0); } + +void MainWindow::sdChanged(bool mounted) +{ + if (mounted) { + ui->lbl_sd->setText(QStringLiteral("SD")); + } else { + ui->lbl_sd->setText(QStringLiteral("")); + } +} + +void MainWindow::getSdList() +{ + core->sdFileList(); +} + +void MainWindow::pb_printSDClicked() +{ + if (ui->list_sdFileView->currentRow() == -1) { + QMessageBox::information(this, QStringLiteral("Print Error"), QStringLiteral("You must Select a file from the list")); + } else { + core->print(ui->list_sdFileView->currentItem()->text(), true); + } +} + +void MainWindow::pb_sdDelClicked() +{ + if (ui->list_sdFileView->currentRow() == -1) { + QMessageBox::information(this, QStringLiteral("Print Error"), QStringLiteral("You must Select a file from the list")); + } else { + core->sdDelete(ui->list_sdFileView->currentItem()->text()); + ui->list_sdFileView->setCurrentRow(-1); + } +} diff --git a/testclient/mainwindow.ui b/testclient/mainwindow.ui --- a/testclient/mainwindow.ui +++ b/testclient/mainwindow.ui @@ -56,6 +56,41 @@ + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 10 + 20 + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + + + + + + @@ -380,6 +415,71 @@ + + + false + + + + 0 + 42 + 515 + 317 + + + + S&d Card + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + Get List + + + + + + + Print Selected + + + + + + + Delete Selected + + + + + + + + + + Files On Sd Card. + + + + + + + + + + + diff --git a/unittests/gcodetests.h b/unittests/gcodetests.h --- a/unittests/gcodetests.h +++ b/unittests/gcodetests.h @@ -59,6 +59,17 @@ void string_G161(); void string_G162(); + void command_M20(); + void command_M21(); + void command_M22(); + void command_M23(); + void command_M24(); + void command_M25(); + void command_M26(); + void command_M27(); + void command_M28(); + void command_M29(); + void command_M30(); void command_M84(); void command_M104(); void command_M105(); diff --git a/unittests/gcodetests.cpp b/unittests/gcodetests.cpp --- a/unittests/gcodetests.cpp +++ b/unittests/gcodetests.cpp @@ -183,6 +183,70 @@ QVERIFY(GCode::toString(GCode::G162) == QObject::tr("G162: Home axis to maximum")); } +void GCodeTests::command_M20() +{ + QVERIFY(GCode::toCommand(GCode::M20) == QStringLiteral("M20")); +} + +void GCodeTests::command_M21() +{ + QVERIFY(GCode::toCommand(GCode::M21) == QStringLiteral("M21")); + QVERIFY(GCode::toCommand(GCode::M21, QStringLiteral("2")) == QStringLiteral("M21 P2")); +} + +void GCodeTests::command_M22() +{ + QVERIFY(GCode::toCommand(GCode::M22) == QStringLiteral("M22")); + QVERIFY(GCode::toCommand(GCode::M22, QStringLiteral("5")) == QStringLiteral("M22 P5")); +} + +void GCodeTests::command_M23() +{ + QVERIFY(GCode::toCommand(GCode::M23) == QStringLiteral("ERROR! M23: It's obligatory to have an argument")); + QVERIFY(GCode::toCommand(GCode::M23, QStringLiteral("FileName")) == QStringLiteral("M23 FileName")); +} + +void GCodeTests::command_M24() +{ + QVERIFY(GCode::toCommand(GCode::M24) == QStringLiteral("M24")); +} + +void GCodeTests::command_M25() +{ + QVERIFY(GCode::toCommand(GCode::M25) == QStringLiteral("M25")); +} + +void GCodeTests::command_M26() +{ + QVERIFY(GCode::toCommand(GCode::M26) == QStringLiteral("ERROR! M26: It's obligatory to have an argument")); + QVERIFY(GCode::toCommand(GCode::M26, QStringLiteral("15%")) == QStringLiteral("M26 P0.15")); + QVERIFY(GCode::toCommand(GCode::M26, QStringLiteral("15")) == QStringLiteral("M26 S15")); + +} + +void GCodeTests::command_M27() +{ + QVERIFY(GCode::toCommand(GCode::M27) == QStringLiteral("M27")); +} + +void GCodeTests::command_M28() +{ + QVERIFY(GCode::toCommand(GCode::M28) == QStringLiteral("ERROR! M28: It's obligatory to have an argument")); + QVERIFY(GCode::toCommand(GCode::M28, QStringLiteral("FileName")) == QStringLiteral("M28 FileName")); +} + +void GCodeTests::command_M29() +{ + QVERIFY(GCode::toCommand(GCode::M29) == QStringLiteral("ERROR! M29: It's obligatory to have an argument")); + QVERIFY(GCode::toCommand(GCode::M29, QStringLiteral("FileName")) == QStringLiteral("M29 FileName")); +} + +void GCodeTests::command_M30() +{ + QVERIFY(GCode::toCommand(GCode::M30) == QStringLiteral("ERROR! M30: It's obligatory to have an argument")); + QVERIFY(GCode::toCommand(GCode::M30, QStringLiteral("FileName")) == QStringLiteral("M30 FileName")); +} + void GCodeTests::command_M84() { QVERIFY(GCode::toCommand(GCode::M84) == QStringLiteral("M84"));