diff --git a/src/core/atcore.h b/src/core/atcore.h --- a/src/core/atcore.h +++ b/src/core/atcore.h @@ -242,6 +242,12 @@ */ bool isSdMounted() const; + /** + * @brief isSdWriting + * @return true if writing to sd card + */ + bool isSdWriting() const; + signals: /** @@ -303,6 +309,18 @@ */ void sdCardFileListChanged(const QStringList &fileList); + /** + * @brief WriteToSD precentage changed. + * @param newProgress. + */ + void sdWriteProgressChanged(float newProgress); + + /** + * @brief sdWriteChanged + * @param writing: true if writing to file. + */ + void sdWriteChanged(bool writing); + public slots: /** @@ -473,6 +491,13 @@ */ void sdCardPrintStatus(); + /** + * @brief Write a file to the machines SdCard + * @param fileName: Local file to send to machine + * @return True if successful + */ + bool toSd(const QString &fileName); + private slots: /** * @brief processQueue send commands from the queue. diff --git a/src/core/atcore.cpp b/src/core/atcore.cpp --- a/src/core/atcore.cpp +++ b/src/core/atcore.cpp @@ -66,6 +66,7 @@ bool sdCardMounted = false; //!< @param sdCardMounted: True if Sd Card is mounted. bool sdCardReadingFileList = false; //!< @param sdCardReadingFileList: True while getting file names from sd card bool sdCardPrinting = false; //!< @param sdCardPrinting: True if currently printing from sd card. + bool sdWriting = false; //!< @param sdWriting: True if writing to sd card. QString sdCardFileName; //!< @param sdCardFileName: name of file being used from sd card. QStringList sdCardFileList; //!< @param sdCardFileList: List of files on sd card. }; @@ -765,6 +766,11 @@ return d->sdCardReadingFileList; } +bool AtCore::isSdWriting() const +{ + return d->sdWriting; +} + void AtCore::setReadingSdCardList(bool readingList) { d->sdCardReadingFileList = readingList; @@ -836,3 +842,85 @@ qCDebug(ATCORE_CORE) << "SerialError:" << errorString; emit atcoreMessage(QStringLiteral("SerialError: %1").arg(errorString)); } + +bool AtCore::toSd(const QString &fileName) +{ + //Do a few sanity checks before attempting to write to SD. + //Can't write an empty fileName. + if (fileName.isEmpty()) { + qCDebug(ATCORE_CORE) << "Sd Write Failed: Empty fileName."; + return false; + } + //Can not write if plugin doesn't have Sd Support. + if (!d->firmwarePlugin->isSdSupported()) { + qCDebug(ATCORE_CORE) << "Sd Write Failed: Firmware plugin does not support Sd functions."; + return false; + } + + //If the card is not mounted try to read the file list and mount it. + if (!d->sdCardMounted) { + getSDFileList(); + if (!d->sdCardMounted) { + qCDebug(ATCORE_CORE) << "Sd Write Failed unable to mount Sd Card"; + return false; + } + } + // Be sure the printer is IDLE + if (d->printerState == AtCore::DISCONNECTED) { + qCDebug(ATCORE_CORE) << "No Device Connected."; + return false; + } else if (d->printerState != AtCore::IDLE) { + qCDebug(ATCORE_CORE) << "Sd Write Failed: Device is Busy"; + return false; + } + + bool restoreTimer = d->tempTimer->isActive(); + int timerTime = 0; + if (restoreTimer) { + timerTime = d->tempTimer->interval(); + d->tempTimer->stop(); + } + + //Process without thread. + QString fname = QFileInfo(fileName).fileName(); + QFile *file = new QFile(fileName); + file->open(QFile::ReadOnly); + QTextStream *txtStream = new QTextStream(file); + const qint64 filesize = file->size(); + float progress = 0; + qint64 stillSize = filesize; + QString cline; + + setState(AtCore::BUSY); + d->sdWriting = true; + emit sdWriteChanged(d->sdWriting); + pushCommand(GCode::toCommand(GCode::M28, fname)); + + while (!txtStream->atEnd()) { + if (state() == AtCore::STOP) { + txtStream->setString(new QString()); + progress = 100; + } else { + cline = txtStream->readLine(); + stillSize -= cline.size() + 1; //remove read chars + progress = float(filesize - stillSize) * 100 / float(filesize); + } + pushCommand(cline); + processQueue(); + emit sdWriteProgressChanged(progress); + qDebug() << "Progress:" << progress << "Current Line:" << cline; + } + + pushCommand(GCode::toCommand(GCode::M29, fname)); + + file->close(); + setState(AtCore::IDLE); + getSDFileList(); + + d->sdWriting = false; + if (restoreTimer) { + d->tempTimer->start(timerTime); + } + emit sdWriteChanged(d->sdWriting); + return true; +} diff --git a/src/plugins/marlinplugin.h b/src/plugins/marlinplugin.h --- a/src/plugins/marlinplugin.h +++ b/src/plugins/marlinplugin.h @@ -55,9 +55,26 @@ */ QString name() const override; + /** + * @brief Translate common commands to firmware specific command. + * @param command: command to translate + * @return firmware specific translated command + */ + QByteArray translate(const QString &command) override; + /** * @brief validateCommand to filter commands from messages * @param lastMessage: last Message from printer */ void validateCommand(const QString &lastMessage) override; + +private: + /** + * @brief Convert a filename to 8.3 Because Marlin will only accept those, Generates a 8.3 like Filename. + * This is Close to but not a 8.3 filename for instance we do not check the Filesystem so ~# will not be added if files with similar names are on the sd card + * Marlin will overwrite files with the samename. + * @param fileName: to convert to 8.3 format. + * @return Return a 8.3 Format name + */ + QString shortName(const QString &fileName); }; diff --git a/src/plugins/marlinplugin.cpp b/src/plugins/marlinplugin.cpp --- a/src/plugins/marlinplugin.cpp +++ b/src/plugins/marlinplugin.cpp @@ -22,7 +22,9 @@ 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 "marlinplugin.h" @@ -45,6 +47,33 @@ qCDebug(MARLIN_PLUGIN) << name() << " plugin loaded!"; } +QByteArray MarlinPlugin::translate(const QString &command) +{ + //Capture M28 + static const auto marlin_M28RegEx = QRegularExpression(QStringLiteral("M28 \\S+")); + //Capture both M29 and M29 but not M290 + static const auto marlin_M29RegEx = QRegularExpression(QStringLiteral("M29( \\S*)?[^0]")); + + QString temp = command; + if (marlin_M28RegEx.match(command).hasMatch()) { + QString filename = temp.mid(4, temp.length()); + filename = shortName(filename); + temp = QStringLiteral("M28 %1").arg(filename); + + } else if (marlin_M29RegEx.match(command).hasMatch()) { + if (temp.length() > 3) { + //command contains file name + QString filename = temp.mid(4, temp.length()); + filename = shortName(filename); + temp = QStringLiteral("M29 %1").arg(filename); + } else { + temp = QStringLiteral("M29"); + } + } + + return temp.toLocal8Bit(); +} + void MarlinPlugin::validateCommand(const QString &lastMessage) { if (lastMessage.contains(QStringLiteral("End file list"))) { @@ -96,3 +125,20 @@ } } } + +QString MarlinPlugin::shortName(const QString &fileName) +{ + //Generate an 8.3 Like FileName. + QString newName; + QFileInfo info(fileName); + newName = info.baseName().toUpper(); + + if (newName.length() > 8) { + newName.resize(8); + } + + newName.append(QStringLiteral(".")); + newName.append(info.suffix().toUpper()); + newName.replace(QStringLiteral(".GCODE"), QStringLiteral(".GCO")); + return newName; +} diff --git a/src/widgets/sdwidget.h b/src/widgets/sdwidget.h --- a/src/widgets/sdwidget.h +++ b/src/widgets/sdwidget.h @@ -58,6 +58,11 @@ */ void deleteSdFile(const QString &fileName); + /** + * @brief User has selected to write a file for the sd card. + */ + void writeSdFile(); + private: QListWidget *listSdFiles = nullptr; }; diff --git a/src/widgets/sdwidget.cpp b/src/widgets/sdwidget.cpp --- a/src/widgets/sdwidget.cpp +++ b/src/widgets/sdwidget.cpp @@ -15,6 +15,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ +#include #include #include #include @@ -42,6 +43,12 @@ } }); + newButton = new QPushButton(tr("Send File")); + hBoxLayout->addWidget(newButton); + connect(newButton, &QPushButton::clicked, this, [this] { + emit writeSdFile(); + }); + newButton = new QPushButton(tr("Delete Selected")); hBoxLayout->addWidget(newButton); connect(newButton, &QPushButton::clicked, this, [this] { diff --git a/testclient/mainwindow.h b/testclient/mainwindow.h --- a/testclient/mainwindow.h +++ b/testclient/mainwindow.h @@ -136,6 +136,11 @@ */ void initWidgets(); + /** + * @brief Handle WriteToSd + */ + void writeToSd(); + //Private GUI Items //menuView is global to allow for docks to be added / removed. QMenu *menuView = nullptr; diff --git a/testclient/mainwindow.cpp b/testclient/mainwindow.cpp --- a/testclient/mainwindow.cpp +++ b/testclient/mainwindow.cpp @@ -54,6 +54,7 @@ connect(core, &AtCore::stateChanged, this, &MainWindow::printerStateChanged); connect(core, &AtCore::portsChanged, this, &MainWindow::locateSerialPort); connect(core, &AtCore::sdCardFileListChanged, sdWidget, &SdWidget::updateFilelist); + connect(sdWidget, &SdWidget::writeSdFile, this, &MainWindow::writeToSd); } void MainWindow::initMenu() @@ -584,3 +585,39 @@ statusWidget->showPrintArea(false); } } + +void MainWindow::writeToSd() +{ + static QString filter = tr("GCode Files(*.gco *.gcode)"); + QString fileName = QFileDialog::getOpenFileName( + this + , tr("Write file to SD") + , QDir::homePath() + , tr("All Files(*.*);;GCode Files(*.gco *.gcode)") + , &filter + ); + + if (!fileName.isEmpty() && QFileInfo(fileName).isReadable()) { + QProgressDialog *dialog = new QProgressDialog(tr("Writing file to SD Card..."), tr("Cancel"), 0, 100, this); + + connect(core, &AtCore::sdWriteChanged, this, [dialog](bool writing) { + if (writing) { + QApplication::processEvents(); + dialog->open(); + } else { + dialog->close(); + } + }); + connect(core, &AtCore::sdWriteProgressChanged, dialog, &QProgressDialog::setValue); + + connect(dialog, &QProgressDialog::canceled, this, [this] { + core->setState(AtCore::STOP); + }); + + if (!core->toSd(fileName)) { + QMessageBox::warning(this, tr("Write to Sd"), tr("Writing FAILED")); + dialog->close(); + core->setState(AtCore::IDLE); + } + } +}