diff --git a/src/core/atcore.h b/src/core/atcore.h --- a/src/core/atcore.h +++ b/src/core/atcore.h @@ -338,6 +338,18 @@ */ void availableFirmwarePluginsChanged(); + /** + * @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: /** @@ -526,6 +538,13 @@ */ void sdCardPrintStatus(); + /** + * @brief Write a file to the machines SdCard + * @param fileName: Local file to send to machine + * @return True if successful + */ + Q_INVOKABLE 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 @@ -971,3 +971,101 @@ { return d->bedDeform; } + +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()) { + qCWarning(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(); + d->serial->waitForReadyRead(); + 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; + } + + //Process without thread. + auto file = new QFile(fileName); + if (!file->open(QFile::ReadOnly)) { + qCDebug(ATCORE_CORE) << "Sd Write Failed unable to read file"; + return false; + } + + bool restoreTimer = d->temperatureTimer.isActive(); + if (restoreTimer) { + d->temperatureTimer.stop(); + } + + const auto fname = QFileInfo(fileName).fileName(); + const auto filesize = file->size(); + auto txtStream = new QTextStream(file); + auto stillSize = filesize; + int progress = 0; + int lineNumber = -1; + int checksum = 0; + QString cline; + + setState(AtCore::BUSY); + emit sdWriteChanged(true); + pushCommand(GCode::toCommand(GCode::MCommands::M28, fname)); + d->serial->waitForReadyRead(); + + while (!txtStream->atEnd()) { + QCoreApplication::processEvents(); + if (state() == AtCore::STOP || state() == AtCore::DISCONNECTED) { + txtStream->setString(new QString()); + cline = QString(); + progress = 100; + } else { + cline = txtStream->readLine(); + stillSize -= cline.size() + 1; //remove read chars + progress = int((filesize - stillSize) * 100 / filesize); + } + if (!cline.startsWith(QStringLiteral(";"))) { + lineNumber++; + cline.prepend(QStringLiteral("N%1 ").arg(lineNumber)); + checksum = firmwarePlugin()->checksum(cline); + cline.insert(std::max(cline.indexOf(QStringLiteral(";")), cline.length()), QStringLiteral("*%1").arg(QString::number(checksum))); + } + if (!cline.isEmpty()) { + d->ready = true; + pushCommand(cline); + d->serial->waitForBytesWritten(); + d->serial->flush(); + } + emit sdWriteProgressChanged(progress); + qCDebug(ATCORE_CORE) << "Progress:" << progress << "Current Line:" << cline; + } + d->serial->waitForReadyRead(); + pushCommand(GCode::toCommand(GCode::MCommands::M29, fname)); + qDebug() << "Closed File"; + + file->close(); + emit sdWriteChanged(false); + getSDFileList(); + setState(AtCore::IDLE); + if (restoreTimer) { + d->temperatureTimer.start(); + } + return true; +} diff --git a/src/core/ifirmware.h b/src/core/ifirmware.h --- a/src/core/ifirmware.h +++ b/src/core/ifirmware.h @@ -46,6 +46,13 @@ void init(AtCore *parent); ~IFirmware() override = default; + /** + * @brief calculate the checksum of a line + * @param line: Line to be checksumed. + * @return checksum of the line. + */ + virtual uint8_t checksum(QString line); + /** * @brief Check for plugin support of sd cards. * @return True if firmware plugin supports sd cards. diff --git a/src/core/ifirmware.cpp b/src/core/ifirmware.cpp --- a/src/core/ifirmware.cpp +++ b/src/core/ifirmware.cpp @@ -71,3 +71,19 @@ { return command.toLocal8Bit(); } + +uint8_t IFirmware::checksum(QString line) +{ + uint8_t sum = 0; + if (line.contains(QStringLiteral("*"))) { + line.chop(line.indexOf(QStringLiteral("*"))); + } + if (line.contains(QStringLiteral(";"))) { + line.chop(line.indexOf(QStringLiteral(";"))); + } + int count = line.length(); + while (count) { + sum ^= line.at(--count).toLatin1(); + } + return sum; +} diff --git a/src/plugins/marlinplugin.h b/src/plugins/marlinplugin.h --- a/src/plugins/marlinplugin.h +++ b/src/plugins/marlinplugin.h @@ -56,9 +56,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,19 @@ } } } + +QString MarlinPlugin::shortName(const QString &fileName) +{ + //Generate an 8.3 Like FileName. + QString newName(QStringLiteral(".")); + QFileInfo info(fileName); + newName.prepend(info.baseName().toUpper()); + + if (newName.length() > 8) { + newName.resize(8); + } + + 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 @@ -59,6 +59,12 @@ */ void deleteSdFile(const QString &fileName); + /** + * @brief User has selected to write a file for the sd card. + * @param fileName: theFile the User wishes to Write + */ + void writeSdFile(const QString &fileName); + 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,23 @@ } }); + newButton = new QPushButton(tr("Send File"), this); + hBoxLayout->addWidget(newButton); + connect(newButton, &QPushButton::clicked, this, [this] { + 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()) + { + emit writeSdFile(fileName); + } + }); + newButton = new QPushButton(tr("Delete Selected"), this); 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 @@ -140,6 +140,11 @@ */ void initWidgets(); + /** + * @brief Handle WriteToSd + */ + void writeToSd(const QString &fileName); + //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 @@ -22,7 +22,6 @@ */ #include #include -#include #include #include #include @@ -56,6 +55,7 @@ connect(core, &AtCore::portsChanged, this, &MainWindow::locateSerialPort); connect(core, &AtCore::sdCardFileListChanged, sdWidget, &SdWidget::updateFilelist); connect(core, &AtCore::autoTemperatureReportChanged, this, &MainWindow::updateAutoTemperatureReport); + connect(sdWidget, &SdWidget::writeSdFile, this, &MainWindow::writeToSd); comboPort->setFocus(Qt::OtherFocusReason); @@ -687,3 +687,28 @@ }); } } + +void MainWindow::writeToSd(const QString &fileName) +{ + std::unique_ptr dialog(new QProgressDialog(tr("Writing file to SD Card..."), tr("Cancel"), 0, 100, this)); + + connect(core, &AtCore::sdWriteChanged, this, [&dialog](bool writing) { + if (writing) { + dialog->open(); + } else { + dialog->close(); + } + }); + + connect(core, &AtCore::sdWriteProgressChanged, dialog.get(), &QProgressDialog::setValue); + + connect(dialog.get(), &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); + } +}