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);
+ }
+}