diff options
| author | Thomas Vanbesien <tvanbesi@proton.me> | 2026-06-16 17:03:26 +0200 |
|---|---|---|
| committer | Thomas Vanbesien <tvanbesi@proton.me> | 2026-06-16 17:03:26 +0200 |
| commit | e90b28d8ccb9eedc19c23ebeb0129308a74e2865 (patch) | |
| tree | 0cfdd646986c8ea8b2ecf325ac96fa909db5069a | |
| parent | bf525d35301dcf0c612598f4394e4357b8378746 (diff) | |
| download | QtXpl2-e90b28d8ccb9eedc19c23ebeb0129308a74e2865.tar.gz QtXpl2-e90b28d8ccb9eedc19c23ebeb0129308a74e2865.zip | |
fix: add workaround for turning all printheads on/offreality-check
| -rw-r--r-- | jetting-interface/CommandsPage.qml | 44 | ||||
| -rw-r--r-- | jetting-interface/Main.qml | 27 | ||||
| -rw-r--r-- | src/Xpl2Client.cpp | 136 | ||||
| -rw-r--r-- | src/Xpl2Client.h | 79 |
4 files changed, 269 insertions, 17 deletions
diff --git a/jetting-interface/CommandsPage.qml b/jetting-interface/CommandsPage.qml index 5a084e6..5b466f0 100644 --- a/jetting-interface/CommandsPage.qml +++ b/jetting-interface/CommandsPage.qml @@ -163,12 +163,31 @@ ColumnLayout { ColumnLayout { anchors.fill: parent - Button { - text: "Get All PH Versions" + RowLayout { + Layout.fillWidth: true + + Button { + text: "Get All PH Versions" + + onClicked: { + for (let i = 0; i < commandsPage.phModel.count; ++i) + Xpl2Client.getPhVersion(commandsPage.phModel.get(i).phId); + } + } + + // Working replacement for the broken Jetting All On/Off: the + // library self-probes versions then jets each available head by + // ID, even non-contiguous (see docs/DISCREPANCIES.md, D6). + Button { + text: "Jet On All Available" + + onClicked: Xpl2Client.jettingAvailableOn() + } - onClicked: { - for (let i = 0; i < commandsPage.phModel.count; ++i) - Xpl2Client.getPhVersion(commandsPage.phModel.get(i).phId); + Button { + text: "Jet Off All Available" + + onClicked: Xpl2Client.jettingAvailableOff() } } @@ -185,6 +204,7 @@ ColumnLayout { required property int phId required property string versionInfo + required property bool valid width: ListView.view.width @@ -200,6 +220,20 @@ ColumnLayout { onClicked: Xpl2Client.getPhVersion(phDelegate.phId) } + Button { + text: "Jet On" + enabled: phDelegate.valid + + onClicked: Xpl2Client.phJettingOn(phDelegate.phId, "FFFFFFFFFFFF") + } + + Button { + text: "Jet Off" + enabled: phDelegate.valid + + onClicked: Xpl2Client.phJettingOff(phDelegate.phId) + } + Label { Layout.fillWidth: true elide: Text.ElideRight diff --git a/jetting-interface/Main.qml b/jetting-interface/Main.qml index cd69ed8..cd239bb 100644 --- a/jetting-interface/Main.qml +++ b/jetting-interface/Main.qml @@ -20,7 +20,8 @@ ApplicationWindow { for (let i = 1; i <= root.demoPhCount; ++i) phModel.append({ "phId": i, - "versionInfo": "" + "versionInfo": "", + "valid": false }); } @@ -54,16 +55,24 @@ ApplicationWindow { fpgaFirmwareVersion: string, fpgaHardwareVersion: string, bootloaderVersion: string) { + // A printhead is present only if it reports a non-zero version. + // Absent slots reply with all zeros; mcuHardwareVersion and + // mcuFirmwareVariant are "00" even on present heads, so they are + // excluded from the check (see docs/DISCREPANCIES.md). + let isZero = v => parseFloat(v) === 0 || v === ""; + let valid = !(isZero(mcuFirmwareVersion) && isZero(fpgaFirmwareVersion) + && isZero(fpgaHardwareVersion) && isZero(bootloaderVersion)); + let info = valid ? "MCU %1/%2 (%3) | FPGA %4/%5 | Boot %6".arg( + mcuFirmwareVersion).arg( + mcuHardwareVersion).arg( + mcuFirmwareVariant).arg( + fpgaFirmwareVersion).arg( + fpgaHardwareVersion).arg( + bootloaderVersion) : "(Printhead unavailable)"; for (let i = 0; i < phModel.count; ++i) { if (phModel.get(i).phId === printheadId) { - phModel.setProperty(i, "versionInfo", - "MCU %1/%2 (%3) | FPGA %4/%5 | Boot %6".arg( - mcuFirmwareVersion).arg( - mcuHardwareVersion).arg( - mcuFirmwareVariant).arg( - fpgaFirmwareVersion).arg( - fpgaHardwareVersion).arg( - bootloaderVersion)); + phModel.setProperty(i, "versionInfo", info); + phModel.setProperty(i, "valid", valid); break; } } diff --git a/src/Xpl2Client.cpp b/src/Xpl2Client.cpp index 102970e..40118b1 100644 --- a/src/Xpl2Client.cpp +++ b/src/Xpl2Client.cpp @@ -6,6 +6,8 @@ #include <QDebug> +#include <algorithm> + bool Xpl2Client::s_wireDebug = false; /* ------------------------------------------------------------------ */ @@ -163,6 +165,10 @@ Xpl2Client::Xpl2Client (QObject *parent) : QObject (parent) connect (&m_retryTimer, &QTimer::timeout, this, &Xpl2Client::retryConnection); + + m_jettingScanTimer.setSingleShot (true); + connect (&m_jettingScanTimer, &QTimer::timeout, this, + &Xpl2Client::onJettingScanTimeout); } /* ------------------------------------------------------------------ */ @@ -301,6 +307,12 @@ Xpl2Client::disconnectFromProxy () void Xpl2Client::getJcVersion () { + qWarning ("%s getJcVersion() is unreliable on this controller (wrong " + "printhead count) — do not use. See docs/DISCREPANCIES.md (D3).", + qPrintable (logTag (&m_commandSocket))); + emit errorOccurred ( + QStringLiteral ("getJcVersion() is unreliable on this controller (wrong " + "printhead count) — do not use it.")); sendCommand (&m_commandSocket, "GS_JC_VERSION"); } @@ -317,6 +329,14 @@ Xpl2Client::getPhVersion (int printheadId) void Xpl2Client::jettingAllOn () { + qWarning ( + "%s jettingAllOn() is broken on this controller (only drives heads " + "before the first ID gap) — use jettingAvailableOn(). See " + "docs/DISCREPANCIES.md (D6).", + qPrintable (logTag (&m_commandSocket))); + emit errorOccurred (QStringLiteral ( + "jettingAllOn() is broken on this controller — use jettingAvailableOn() " + "instead.")); sendCommand (&m_commandSocket, "CN_JETTING_ALL_ON"); } @@ -329,6 +349,13 @@ Xpl2Client::jettingOn (const QString &jettingMask) void Xpl2Client::jettingOff () { + qWarning ("%s jettingOff() mirrors the jettingAllOn() defect on this " + "controller — use jettingAvailableOff(). See " + "docs/DISCREPANCIES.md (D6).", + qPrintable (logTag (&m_commandSocket))); + emit errorOccurred ( + QStringLiteral ("jettingOff() is unreliable on this controller — use " + "jettingAvailableOff() instead.")); sendCommand (&m_commandSocket, "CN_JETTING_OFF"); } @@ -346,6 +373,106 @@ Xpl2Client::phJettingOff (int printheadId) } void +Xpl2Client::jettingAvailableOn () +{ + startJettingScan (true); +} + +void +Xpl2Client::jettingAvailableOff () +{ + startJettingScan (false); +} + +bool +Xpl2Client::phVersionIsAvailable (const QString &mcuFirmwareVersion, + const QString &fpgaFirmwareVersion, + const QString &fpgaHardwareVersion, + const QString &bootloaderVersion) +{ + // Absent printheads reply with all-zero versions. mcuHardwareVersion and + // mcuFirmwareVariant read "00" even on present heads, so they are not part + // of the test (see docs/DISCREPANCIES.md). + return mcuFirmwareVersion.toDouble () != 0.0 + || fpgaFirmwareVersion.toDouble () != 0.0 + || fpgaHardwareVersion.toDouble () != 0.0 + || bootloaderVersion.toDouble () != 0.0; +} + +void +Xpl2Client::startJettingScan (bool turnOn) +{ + if (!m_connected) + { + emit errorOccurred ( + QStringLiteral ("Not connected for jettingAvailable%1") + .arg (turnOn ? QStringLiteral ("On") : QStringLiteral ("Off"))); + return; + } + if (m_jettingScanActive) + { + qWarning ("%s jettingAvailable: a scan is already in progress, ignoring", + qPrintable (logTag (&m_commandSocket))); + return; + } + + m_jettingScanActive = true; + m_jettingScanTurnOn = turnOn; + m_jettingScanAvailable.clear (); + m_jettingScanPending.clear (); + for (int id = 1; id <= MaxProbedPrintheadId; ++id) + { + m_jettingScanPending.insert (id); + getPhVersion (id); + } + m_jettingScanTimer.start (JettingScanTimeoutMs); + qDebug ("%s jettingAvailable%s: probing %d printheads", + qPrintable (logTag (&m_commandSocket)), turnOn ? "On" : "Off", + MaxProbedPrintheadId); +} + +void +Xpl2Client::finishJettingScan () +{ + m_jettingScanTimer.stop (); + m_jettingScanActive = false; + m_jettingScanPending.clear (); + std::sort (m_jettingScanAvailable.begin (), m_jettingScanAvailable.end ()); + + if (m_connected) + for (int id : m_jettingScanAvailable) + { + if (m_jettingScanTurnOn) + phJettingOn (id, QString::fromLatin1 (AllNozzlesOnMask)); + else + phJettingOff (id); + } + qDebug ("%s jettingAvailable%s: jetted %lld available printhead(s)", + qPrintable (logTag (&m_commandSocket)), + m_jettingScanTurnOn ? "On" : "Off", + static_cast<long long> (m_jettingScanAvailable.size ())); + emit jettingAvailableComplete (m_jettingScanTurnOn, m_jettingScanAvailable); +} + +void +Xpl2Client::onJettingScanTimeout () +{ + if (!m_jettingScanActive) + return; + qWarning ( + "%s jettingAvailable: timed out waiting for %lld version reply(ies);" + " proceeding with %lld available", + qPrintable (logTag (&m_commandSocket)), + static_cast<long long> (m_jettingScanPending.size ()), + static_cast<long long> (m_jettingScanAvailable.size ())); + emit errorOccurred ( + QStringLiteral ( + "jettingAvailable: %1 printhead(s) did not respond in time") + .arg (m_jettingScanPending.size ())); + finishJettingScan (); +} + +void Xpl2Client::jcIdLedOn () { sendCommand (&m_commandSocket, "CN_JC_ID_LED_ON"); @@ -789,6 +916,15 @@ Xpl2Client::handleGsPhVersion (const QVariantList ¶ms) .arg (bootVer)); emit phVersionReceived (cid, phId, mcuFw, mcuHw, mcuFwVar, fpgaFw, fpgaHw, bootVer); + + // Feed an in-progress jettingAvailableOn()/Off() scan. + if (m_jettingScanActive && m_jettingScanPending.remove (phId)) + { + if (phVersionIsAvailable (mcuFw, fpgaFw, fpgaHw, bootVer)) + m_jettingScanAvailable.append (phId); + if (m_jettingScanPending.isEmpty ()) + finishJettingScan (); + } } bool diff --git a/src/Xpl2Client.h b/src/Xpl2Client.h index 49fbec5..cce2a47 100644 --- a/src/Xpl2Client.h +++ b/src/Xpl2Client.h @@ -9,8 +9,10 @@ #include "Xpl2Protocol.h" #include <QHash> +#include <QList> #include <QObject> #include <QQmlEngine> +#include <QSet> #include <QTcpSocket> #include <QTimer> @@ -53,24 +55,64 @@ public: Q_INVOKABLE void connectToProxy (); /** Disconnect from the proxy. */ Q_INVOKABLE void disconnectFromProxy (); - /** Get the just connected Jetting Controller ID and Software Version. */ + /** + * Get the just connected Jetting Controller ID and Software Version. + * + * @deprecated Unreliable on real controllers: the printhead-count field is + * wrong (reports the heads before the first ID gap, not the true count). See + * docs/DISCREPANCIES.md (D3). Enumerate printheads via getPhVersion() and + * the jettingAvailable* helpers instead. Calling this emits errorOccurred(). + */ Q_INVOKABLE void getJcVersion (); /** Query the specified printhead's version info. */ Q_INVOKABLE void getPhVersion (int printheadId); /* -- CN_ Control commands ----------------------------------------- */ - /** Switch jetting on for all printheads. */ + /** + * Switch jetting on for all printheads (CN_JETTING_ALL_ON). + * + * @deprecated Broken on real controllers: only drives the printheads before + * the first gap in the ID sequence, even though it replies success. See + * docs/DISCREPANCIES.md (D6). Use jettingAvailableOn() instead. Calling this + * emits errorOccurred(). + */ Q_INVOKABLE void jettingAllOn (); /** Switch jetting on for all printheads with a per-nozzle mask (180 chars). */ Q_INVOKABLE void jettingOn (const QString &jettingMask); - /** Switch jetting off for all printheads. */ + /** + * Switch jetting off for all printheads (CN_JETTING_OFF). + * + * @deprecated Mirrors the jettingAllOn() defect on real controllers (only + * affects the heads before the first ID gap). See docs/DISCREPANCIES.md + * (D6). Use jettingAvailableOff() instead. Calling this emits + * errorOccurred(). + */ Q_INVOKABLE void jettingOff (); /** Switch jetting on for one printhead with a per-nozzle mask (12 chars). */ Q_INVOKABLE void phJettingOn (int printheadId, const QString &jettingMask); /** Switch jetting off for one printhead. */ Q_INVOKABLE void phJettingOff (int printheadId); + + /** + * Switch jetting ON for every *available* printhead — the working + * replacement for the broken jettingAllOn(). + * + * Probes every printhead's version (getPhVersion for IDs 1..15), then sends + * a per-head CN_PH_JETTING_ON (all-nozzles mask) to each printhead that + * reports a valid, non-zero version. Handles non-contiguous printhead IDs + * correctly (see docs/DISCREPANCIES.md, D2/D6). Asynchronous: emits + * jettingAvailableComplete() once the per-head commands have been sent. A + * second call while a scan is in progress is ignored. + */ + Q_INVOKABLE void jettingAvailableOn (); + /** + * Switch jetting OFF for every available printhead — the working replacement + * for the broken jettingOff(). Counterpart to jettingAvailableOn(); same + * probe-then-per-head behaviour with CN_PH_JETTING_OFF. + */ + Q_INVOKABLE void jettingAvailableOff (); /** Switch the JC identification LED on. */ Q_INVOKABLE void jcIdLedOn (); /** Switch the JC identification LED off. */ @@ -203,6 +245,11 @@ signals: void jettingOffResult (int controllerId, bool success); void phJettingOnResult (int controllerId, int printheadId, bool success); void phJettingOffResult (int controllerId, int printheadId, bool success); + /** Emitted when jettingAvailableOn()/Off() has finished probing versions and + * has sent the per-head jetting commands. @p printheadIds are the available + * heads that were jetted (sorted ascending). */ + void jettingAvailableComplete (bool turnedOn, + const QList<int> &printheadIds); void jcIdLedOnResult (int controllerId, bool success); void jcIdLedOffResult (int controllerId, bool success); void phIdLedOnResult (int controllerId, int printheadId, bool success); @@ -298,6 +345,7 @@ private slots: void onSocketMessageReady (); void onSocketError (QAbstractSocket::SocketError error); void retryConnection (); + void onJettingScanTimeout (); private: void setupSocket (QTcpSocket &socket); @@ -343,6 +391,25 @@ private: void updateConnectedState (); QString logTag (const QTcpSocket *socket) const; + /* Working "jet all available" workaround for the broken CN_JETTING_ALL_ON / + CN_JETTING_OFF (see docs/DISCREPANCIES.md, D6): probe all printhead + versions, then jet every head that reports a valid version. */ + void startJettingScan (bool turnOn); + void finishJettingScan (); + static bool phVersionIsAvailable (const QString &mcuFirmwareVersion, + const QString &fpgaFirmwareVersion, + const QString &fpgaHardwareVersion, + const QString &bootloaderVersion); + + /* Highest printhead ID to probe (protocol allows up to 15: a 180-char + jetting mask is 12 chars x 15 heads). */ + static constexpr int MaxProbedPrintheadId = 15; + /* Give up waiting for version replies after this long and jet whatever + responded. */ + static constexpr int JettingScanTimeoutMs = 2000; + /* 12-char all-nozzles-on mask for one printhead. */ + static constexpr char AllNozzlesOnMask[] = "FFFFFFFFFFFF"; + QString m_host = QStringLiteral ("127.0.0.1"); quint16 m_commandPort = 9210; QTcpSocket m_commandSocket; @@ -355,4 +422,10 @@ private: QString m_firmwareVersion; QString m_hardwareVersion; int m_printheadCount = 0; + + QTimer m_jettingScanTimer; + bool m_jettingScanActive = false; + bool m_jettingScanTurnOn = false; + QSet<int> m_jettingScanPending; + QList<int> m_jettingScanAvailable; }; |
