From 8bcf948b76c9564cb38d3611228ccaf73890a548 Mon Sep 17 00:00:00 2001 From: Thomas Vanbesien Date: Mon, 23 Mar 2026 16:48:32 +0100 Subject: Rename demo/ → jetting-interface/, mock-server/ → mock-jetting-controller/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Executables: QtXpl2Demo → JettingInterfaceDemo, Xpl2MockServer → MockJettingController. Dev log prefixes: "Demo" → "JI", "MockServer" → "MockJC". Window title → "Jetting Interface". --- CMakeLists.txt | 6 +- README.md | 12 +- demo/CMakeLists.txt | 23 -- demo/CommandsPage.qml | 213 ----------- demo/DebugConsole.qml | 34 -- demo/Main.qml | 173 --------- demo/StatusPage.qml | 620 --------------------------------- demo/main.cpp | 42 --- jetting-interface/CMakeLists.txt | 25 ++ jetting-interface/CommandsPage.qml | 213 +++++++++++ jetting-interface/DebugConsole.qml | 34 ++ jetting-interface/Main.qml | 173 +++++++++ jetting-interface/StatusPage.qml | 620 +++++++++++++++++++++++++++++++++ jetting-interface/main.cpp | 42 +++ mock-jetting-controller/CMakeLists.txt | 7 + mock-jetting-controller/MockServer.cpp | 541 ++++++++++++++++++++++++++++ mock-jetting-controller/MockServer.h | 90 +++++ mock-jetting-controller/main.cpp | 30 ++ mock-server/CMakeLists.txt | 6 - mock-server/MockServer.cpp | 541 ---------------------------- mock-server/MockServer.h | 90 ----- mock-server/main.cpp | 30 -- 22 files changed, 1784 insertions(+), 1781 deletions(-) delete mode 100644 demo/CMakeLists.txt delete mode 100644 demo/CommandsPage.qml delete mode 100644 demo/DebugConsole.qml delete mode 100644 demo/Main.qml delete mode 100644 demo/StatusPage.qml delete mode 100644 demo/main.cpp create mode 100644 jetting-interface/CMakeLists.txt create mode 100644 jetting-interface/CommandsPage.qml create mode 100644 jetting-interface/DebugConsole.qml create mode 100644 jetting-interface/Main.qml create mode 100644 jetting-interface/StatusPage.qml create mode 100644 jetting-interface/main.cpp create mode 100644 mock-jetting-controller/CMakeLists.txt create mode 100644 mock-jetting-controller/MockServer.cpp create mode 100644 mock-jetting-controller/MockServer.h create mode 100644 mock-jetting-controller/main.cpp delete mode 100644 mock-server/CMakeLists.txt delete mode 100644 mock-server/MockServer.cpp delete mode 100644 mock-server/MockServer.h delete mode 100644 mock-server/main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 8cb8618..a81bd5f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,13 +23,13 @@ if(PROJECT_IS_TOP_LEVEL) ON CACHE BOOL "") - # Let the demo executable find the library at run time. + # Let the jetting-interface executable find the library at run time. set(CMAKE_BUILD_RPATH "${PROJECT_BINARY_DIR}/qml/Xpl2") endif() add_subdirectory(src) if(PROJECT_IS_TOP_LEVEL) - add_subdirectory(demo) - add_subdirectory(mock-server) + add_subdirectory(jetting-interface) + add_subdirectory(mock-jetting-controller) endif() diff --git a/README.md b/README.md index f82cf68..d0f089c 100644 --- a/README.md +++ b/README.md @@ -20,17 +20,17 @@ Produces three targets: | Target | Binary | Description | |---|---|---| | `QtXpl2` | `libQtXpl2.a` | Static library + QML module (`import Xpl2`) | -| `QtXpl2Demo` | `bin/QtXpl2Demo` | Interactive demo app | -| `Xpl2MockServer` | `bin/Xpl2MockServer` | Mock JI2 server for development | +| `JettingInterfaceDemo` | `bin/JettingInterfaceDemo` | Interactive demo app | +| `MockJettingController` | `bin/MockJettingController` | Mock JI2 server for development | ## Quick start ```bash # Terminal 1 — start the mock server -./build/bin/Xpl2MockServer +./build/bin/MockJettingController # Terminal 2 — run the demo -./build/bin/QtXpl2Demo +./build/bin/JettingInterfaceDemo ``` The demo connects to `127.0.0.1` by default. Click **Connect**, then explore the **Commands** and **Status** tabs. @@ -139,14 +139,14 @@ QtXpl2/ Xpl2JcStatus.h/cpp # Q_GADGET for JC status (Appendix A) Xpl2PhStatus.h/cpp # Q_GADGET for PH status (Appendix B) CMakeLists.txt - demo/ + jetting-interface/ Main.qml # App shell: connection, tabs, console CommandsPage.qml # GS/CN/CF/imaging buttons, printhead list StatusPage.qml # Status messaging controls, live displays DebugConsole.qml # Dark log panel main.cpp CMakeLists.txt - mock-server/ + mock-jetting-controller/ MockServer.h/cpp # Canned responses, periodic status emission main.cpp CMakeLists.txt diff --git a/demo/CMakeLists.txt b/demo/CMakeLists.txt deleted file mode 100644 index aa8fd49..0000000 --- a/demo/CMakeLists.txt +++ /dev/null @@ -1,23 +0,0 @@ -qt_add_executable(QtXpl2Demo main.cpp) - -qt_add_qml_module( - QtXpl2Demo - URI - QtXpl2Demo - VERSION - 1.0 - QML_FILES - Main.qml - CommandsPage.qml - StatusPage.qml - DebugConsole.qml - # IMPORT_PATH sets QT_QML_IMPORT_PATH on the target, which qmlls reads to - # resolve imports like "import Xpl2" in .qmlls.ini. - IMPORT_PATH - "${PROJECT_BINARY_DIR}/qml") - -set_target_properties(QtXpl2Demo PROPERTIES RUNTIME_OUTPUT_DIRECTORY - "${PROJECT_BINARY_DIR}/bin") - -target_link_libraries(QtXpl2Demo PRIVATE Qt6::Quick QtXpl2plugin) -target_include_directories(QtXpl2Demo PRIVATE "${PROJECT_SOURCE_DIR}/src") diff --git a/demo/CommandsPage.qml b/demo/CommandsPage.qml deleted file mode 100644 index 5a084e6..0000000 --- a/demo/CommandsPage.qml +++ /dev/null @@ -1,213 +0,0 @@ -pragma ComponentBehavior: Bound - -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import Xpl2 - -ColumnLayout { - id: commandsPage - - required property int demoPhCount - required property ListModel phModel - - spacing: 12 - - // --- JC Version --- - GroupBox { - Layout.fillWidth: true - enabled: Xpl2Client.connected - title: "Jetting Controller" - - RowLayout { - anchors.fill: parent - - Button { - text: "Get JC Version" - - onClicked: Xpl2Client.getJcVersion() - } - - Label { - text: Xpl2Client.controllerId > 0 - ? "Controller: %1 | FW: %2 | HW: %3 | PHs: %4".arg( - Xpl2Client.controllerId).arg( - Xpl2Client.firmwareVersion).arg( - Xpl2Client.hardwareVersion).arg( - Xpl2Client.printheadCount) : "No version data" - } - } - } - - // --- Control --- - GroupBox { - Layout.fillWidth: true - enabled: Xpl2Client.connected - title: "Control" - - GridLayout { - anchors.fill: parent - columns: 4 - - Button { - text: "Jetting All On" - - onClicked: Xpl2Client.jettingAllOn() - } - - Button { - text: "Jetting Off" - - onClicked: Xpl2Client.jettingOff() - } - - Button { - text: "JC LED On" - - onClicked: Xpl2Client.jcIdLedOn() - } - - Button { - text: "JC LED Off" - - onClicked: Xpl2Client.jcIdLedOff() - } - - Button { - text: "JC Calibration" - - onClicked: Xpl2Client.jcCalibration() - } - - Button { - text: "Reset Fault Codes" - - onClicked: Xpl2Client.jcResetFaultCodes() - } - } - } - - // --- Configuration --- - GroupBox { - Layout.fillWidth: true - enabled: Xpl2Client.connected - title: "Configuration" - - GridLayout { - anchors.fill: parent - columns: 4 - - Button { - text: "Save All Settings" - - onClicked: Xpl2Client.jcSaveAllPrintheadSettings() - } - - Button { - text: "Reboot All PHs" - - onClicked: Xpl2Client.jcRebootAllPrintheads() - } - - Button { - text: "Restart JC" - - onClicked: Xpl2Client.jcRestart() - } - - Button { - text: "Shutdown JC" - - onClicked: Xpl2Client.jcShutdown() - } - } - } - - // --- Imaging --- - GroupBox { - Layout.fillWidth: true - enabled: Xpl2Client.connected - title: "Imaging" - - GridLayout { - anchors.fill: parent - columns: 4 - - Button { - text: "Start Imaging" - - onClicked: Xpl2Client.imagingStart(1.0) - } - - Button { - text: "Stop Imaging" - - onClicked: Xpl2Client.imagingStop() - } - - Button { - text: "Image Count" - - onClicked: Xpl2Client.imageCount() - } - } - } - - // --- Printheads --- - GroupBox { - Layout.fillHeight: true - Layout.fillWidth: true - enabled: Xpl2Client.connected - title: "Printheads (%1)".arg(commandsPage.demoPhCount) - - ColumnLayout { - anchors.fill: parent - - Button { - text: "Get All PH Versions" - - onClicked: { - for (let i = 0; i < commandsPage.phModel.count; ++i) - Xpl2Client.getPhVersion(commandsPage.phModel.get(i).phId); - } - } - - ScrollView { - Layout.fillHeight: true - Layout.fillWidth: true - - ListView { - model: commandsPage.phModel - spacing: 4 - - delegate: RowLayout { - id: phDelegate - - required property int phId - required property string versionInfo - - width: ListView.view.width - - Label { - Layout.preferredWidth: 50 - font.bold: true - text: "PH %1".arg(phDelegate.phId) - } - - Button { - text: "Version" - - onClicked: Xpl2Client.getPhVersion(phDelegate.phId) - } - - Label { - Layout.fillWidth: true - elide: Text.ElideRight - text: phDelegate.versionInfo || "—" - } - } - } - } - } - } -} diff --git a/demo/DebugConsole.qml b/demo/DebugConsole.qml deleted file mode 100644 index 9a5a9ff..0000000 --- a/demo/DebugConsole.qml +++ /dev/null @@ -1,34 +0,0 @@ -pragma ComponentBehavior: Bound - -import QtQuick -import QtQuick.Controls - -Rectangle { - id: debugRoot - - function appendLog(msg: string): void { - let ts = new Date().toLocaleTimeString(Qt.locale(), "HH:mm:ss.zzz"); - debugLog.text += "[" + ts + "] " + msg + "\n"; - debugLog.cursorPosition = debugLog.text.length; - } - - border.color: "#444" - color: "#1e1e1e" - radius: 4 - - ScrollView { - anchors.fill: parent - anchors.margins: 4 - - TextArea { - id: debugLog - - background: null - color: "#cccccc" - font.family: "monospace" - font.pointSize: 9 - readOnly: true - wrapMode: TextEdit.Wrap - } - } -} diff --git a/demo/Main.qml b/demo/Main.qml deleted file mode 100644 index 6aca201..0000000 --- a/demo/Main.qml +++ /dev/null @@ -1,173 +0,0 @@ -pragma ComponentBehavior: Bound - -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import Xpl2 - -ApplicationWindow { - id: root - - // Set from C++ via setInitialProperties (--printheads CLI arg, default 10). - required property int demoPhCount - - height: 1200 - title: "XPL2 Demo" - visible: true - width: 900 - - Component.onCompleted: { - for (let i = 1; i <= root.demoPhCount; ++i) - phModel.append({ - "phId": i, - "versionInfo": "" - }); - } - - ListModel { - id: phModel - - } - - Connections { - function onConnectedChanged() { - debugConsole.appendLog(Xpl2Client.connected - ? "Controller connected" : - "Controller disconnected"); - } - - function onErrorOccurred(error: string) { - debugConsole.appendLog("ERROR: " + error); - } - - function onJcStatusReceived(status) { - statusPage.lastJcStatus = status; - } - - function onListeningChanged() { - debugConsole.appendLog(Xpl2Client.listening - ? "Listening on ports 9110/9111/9112" : - "Stopped listening"); - } - - function onPhStatusReceived(status) { - statusPage.lastPhStatus = status; - } - - function onPhVersionReceived(controllerId: int, printheadId: int, - mcuFirmwareVersion: string, - mcuHardwareVersion: string, - mcuFirmwareVariant: string, - fpgaFirmwareVersion: string, - fpgaHardwareVersion: string, - bootloaderVersion: string) { - 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)); - break; - } - } - } - - function onShuttingDown() { - debugConsole.appendLog("SERVER SHUTTING DOWN"); - } - - function onStatusMessage(message: string) { - debugConsole.appendLog(message); - } - - target: Xpl2Client - } - - ColumnLayout { - anchors.fill: parent - spacing: 0 - - // --- Connection (always visible) --- - GroupBox { - Layout.bottomMargin: 0 - Layout.fillWidth: true - Layout.margins: 16 - title: "Connection" - - RowLayout { - anchors.fill: parent - - Label { - text: Xpl2Client.connected ? "Controller connected" : - "Waiting for controller…" - } - - Item { - Layout.fillWidth: true - } - - Button { - text: Xpl2Client.listening ? "Stop" : "Listen" - - onClicked: { - if (Xpl2Client.listening) - Xpl2Client.stopListening(); - else - Xpl2Client.startListening(); - } - } - } - } - - // --- Tab bar --- - TabBar { - id: tabBar - - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - TabButton { - text: "Commands" - } - - TabButton { - text: "Status" - } - } - - // --- Swipe view --- - SwipeView { - id: swipeView - - Layout.fillHeight: true - Layout.fillWidth: true - Layout.margins: 16 - currentIndex: tabBar.currentIndex - - onCurrentIndexChanged: tabBar.currentIndex = currentIndex - - CommandsPage { - demoPhCount: root.demoPhCount - phModel: phModel - } - - StatusPage { - id: statusPage - - } - } - - // --- Debug Console --- - DebugConsole { - id: debugConsole - - Layout.fillWidth: true - Layout.preferredHeight: 160 - } - } -} diff --git a/demo/StatusPage.qml b/demo/StatusPage.qml deleted file mode 100644 index 6a85bcc..0000000 --- a/demo/StatusPage.qml +++ /dev/null @@ -1,620 +0,0 @@ -pragma ComponentBehavior: Bound - -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import Xpl2 - -ColumnLayout { - id: statusPage - - property var lastJcStatus: null - property var lastPhStatus: null - - spacing: 12 - - // --- Status Messaging Controls --- - GroupBox { - Layout.fillWidth: true - enabled: Xpl2Client.connected - title: "Status Messaging" - - GridLayout { - anchors.fill: parent - columns: 2 - - Label { - text: "Level:" - } - - ComboBox { - id: statusLevelCombo - - Layout.fillWidth: true - model: ["1 — Basic", "2 — Extended"] - } - - Label { - text: "Interval (ms):" - } - - SpinBox { - id: statusIntervalSpin - - Layout.fillWidth: true - from: 100 - stepSize: 100 - to: 10000 - value: 1000 - } - - Button { - text: "Start JC Status" - - onClicked: Xpl2Client.jcStatusMessagingStart( - statusLevelCombo.currentIndex + 1, - statusIntervalSpin.value) - } - - Button { - text: "Stop JC Status" - - onClicked: Xpl2Client.jcStatusMessagingStop() - } - - Button { - text: "Start PH Status" - - onClicked: Xpl2Client.phStatusMessagingStart( - statusLevelCombo.currentIndex + 1, - statusIntervalSpin.value) - } - - Button { - text: "Stop PH Status" - - onClicked: Xpl2Client.phStatusMessagingStop() - } - } - } - - // --- JC Status Display --- - GroupBox { - Layout.fillWidth: true - title: "JC Status" - - RowLayout { - anchors.fill: parent - - GridLayout { - Layout.fillWidth: true - columns: 4 - - Label { - font.bold: true - text: "CPU:" - } - - Label { - text: statusPage.lastJcStatus - ? statusPage.lastJcStatus.cpuPercentageBusy.toFixed( - 1) + "%" : "—" - } - - Label { - font.bold: true - text: "Temp:" - } - - Label { - text: statusPage.lastJcStatus - ? statusPage.lastJcStatus.temperature.toFixed(1) - + "°C" : "—" - } - - Label { - font.bold: true - text: "Rail 5V:" - } - - Label { - text: statusPage.lastJcStatus - ? statusPage.lastJcStatus.rail5V.toFixed(2) + "V" : - "—" - } - - Label { - font.bold: true - text: "CAN 8V:" - } - - Label { - text: statusPage.lastJcStatus - ? statusPage.lastJcStatus.railCanBus8V.toFixed(2) - + "V" : "—" - } - - Label { - font.bold: true - text: "Humidity:" - } - - Label { - text: statusPage.lastJcStatus - ? statusPage.lastJcStatus.humidity.toFixed(1) + "%" : - "—" - } - - Label { - font.bold: true - text: "Bus I:" - } - - Label { - text: statusPage.lastJcStatus - ? statusPage.lastJcStatus.busCurrent.toFixed(3) + "A" : - "—" - } - - Label { - font.bold: true - text: "Uptime:" - } - - Label { - text: statusPage.lastJcStatus - ? statusPage.lastJcStatus.onTimeSeconds + "s" : "—" - } - } - - // --- Extended (Level 2) --- - GridLayout { - Layout.fillWidth: true - columns: 4 - visible: statusPage.lastJcStatus !== null - && statusPage.lastJcStatus.statusLevel >= 2 - - Label { - font.bold: true - text: "IP:" - } - - Label { - text: statusPage.lastJcStatus - ? statusPage.lastJcStatus.ipAddress : "—" - } - - Label { - font.bold: true - text: "eFuse V:" - } - - Label { - text: statusPage.lastJcStatus - ? statusPage.lastJcStatus.eFuseVoltage.toFixed(2) - + "V" : "—" - } - - Label { - font.bold: true - text: "eFuse Bus:" - } - - Label { - text: statusPage.lastJcStatus ? ( - statusPage.lastJcStatus.eFuseBusEnabled - ? "On" : "Off") : "—" - } - - Label { - font.bold: true - text: "Bus Power:" - } - - Label { - text: statusPage.lastJcStatus ? ( - statusPage.lastJcStatus.busPowerEnabled - ? "On" : "Off") : "—" - } - - Label { - font.bold: true - text: "Bus OK:" - } - - Label { - text: statusPage.lastJcStatus ? ( - statusPage.lastJcStatus.busPowerOk - ? "Yes" : "No") : "—" - } - - Label { - font.bold: true - text: "Switch:" - } - - Label { - text: statusPage.lastJcStatus ? String( - statusPage.lastJcStatus.switchValue) : - "—" - } - - Label { - font.bold: true - text: "FW:" - } - - Label { - text: statusPage.lastJcStatus - ? statusPage.lastJcStatus.firmwareVersion : "—" - } - - Label { - font.bold: true - text: "HW:" - } - - Label { - text: statusPage.lastJcStatus - ? statusPage.lastJcStatus.hardwareVersion : "—" - } - - Label { - font.bold: true - text: "Indicators:" - } - - Label { - Layout.columnSpan: 3 - text: statusPage.lastJcStatus - ? [statusPage.lastJcStatus.indicator0, - statusPage.lastJcStatus.indicator1, - statusPage.lastJcStatus.indicator2, - statusPage.lastJcStatus.indicator3, - statusPage.lastJcStatus.indicator4, - statusPage.lastJcStatus.indicator5].map(function ( - v) { - return v ? "1" : "0"; - }).join(" ") : "—" - } - } - } - } - - // --- PH Status Display --- - GroupBox { - Layout.fillHeight: true - Layout.fillWidth: true - title: "PH Status" - - RowLayout { - anchors.fill: parent - - GridLayout { - Layout.fillWidth: true - columns: 4 - - Label { - font.bold: true - text: "PH:" - } - - Label { - text: statusPage.lastPhStatus ? String( - statusPage.lastPhStatus.printheadId) : - "—" - } - - Label { - font.bold: true - text: "Temp:" - } - - Label { - text: statusPage.lastPhStatus - ? statusPage.lastPhStatus.temperature.toFixed(1) - + "°C" : "—" - } - - Label { - font.bold: true - text: "MCU Temp:" - } - - Label { - text: statusPage.lastPhStatus - ? statusPage.lastPhStatus.mcuTemperature.toFixed(1) - + "°C" : "—" - } - - Label { - font.bold: true - text: "Humidity:" - } - - Label { - text: statusPage.lastPhStatus - ? statusPage.lastPhStatus.humidity.toFixed(1) + "%" : - "—" - } - - Label { - font.bold: true - text: "PDS V:" - } - - Label { - text: statusPage.lastPhStatus - ? statusPage.lastPhStatus.pdsVoltage.toFixed(2) + "V" : - "—" - } - - Label { - font.bold: true - text: "MDS V:" - } - - Label { - text: statusPage.lastPhStatus - ? statusPage.lastPhStatus.mdsVoltage.toFixed(2) + "V" : - "—" - } - - Label { - font.bold: true - text: "Sys V:" - } - - Label { - text: statusPage.lastPhStatus - ? statusPage.lastPhStatus.systemVoltage.toFixed(2) - + "V" : "—" - } - - Label { - font.bold: true - text: "VDD:" - } - - Label { - text: statusPage.lastPhStatus - ? statusPage.lastPhStatus.vdd.toFixed(2) + "V" : "—" - } - - Label { - font.bold: true - text: "eFuse I:" - } - - Label { - text: statusPage.lastPhStatus - ? statusPage.lastPhStatus.eFuseCurrent.toFixed(3) - + "A" : "—" - } - - Label { - font.bold: true - text: "Nozzle I:" - } - - Label { - text: statusPage.lastPhStatus - ? statusPage.lastPhStatus.nozzleCurrent.toFixed(3) - + "A" : "—" - } - - Label { - font.bold: true - text: "Duty Cycle:" - } - - Label { - text: statusPage.lastPhStatus - ? statusPage.lastPhStatus.dutyCycle.toFixed(1) + "%" : - "—" - } - - Label { - font.bold: true - text: "Drive:" - } - - Label { - text: statusPage.lastPhStatus ? String( - statusPage.lastPhStatus.drive) : - "—" - } - - Label { - font.bold: true - text: "Uptime:" - } - - Label { - text: statusPage.lastPhStatus - ? statusPage.lastPhStatus.onTimeSeconds + "s" : "—" - } - } - - // --- Extended (Level 2) --- - GridLayout { - Layout.fillWidth: true - columns: 4 - visible: statusPage.lastPhStatus !== null - && statusPage.lastPhStatus.statusLevel >= 2 - - Label { - font.bold: true - text: "MCU FW:" - } - - Label { - text: statusPage.lastPhStatus - ? statusPage.lastPhStatus.mcuFirmwareVersion : "—" - } - - Label { - font.bold: true - text: "MCU HW:" - } - - Label { - text: statusPage.lastPhStatus - ? statusPage.lastPhStatus.mcuHardwareVersion : "—" - } - - Label { - font.bold: true - text: "MCU Variant:" - } - - Label { - text: statusPage.lastPhStatus - ? statusPage.lastPhStatus.mcuFirmwareVariant : "—" - } - - Label { - font.bold: true - text: "FPGA FW:" - } - - Label { - text: statusPage.lastPhStatus - ? statusPage.lastPhStatus.fpgaFirmwareVersion : "—" - } - - Label { - font.bold: true - text: "FPGA HW:" - } - - Label { - text: statusPage.lastPhStatus - ? statusPage.lastPhStatus.fpgaHardwareVersion : "—" - } - - Label { - font.bold: true - text: "Boot:" - } - - Label { - text: statusPage.lastPhStatus - ? statusPage.lastPhStatus.bootloaderVersion : "—" - } - - Label { - font.bold: true - text: "Max Temp:" - } - - Label { - text: statusPage.lastPhStatus - ? statusPage.lastPhStatus.maxAllowedTemperature.toFixed( - 1) + "°C" : "—" - } - - Label { - font.bold: true - text: "eFuse Max:" - } - - Label { - text: statusPage.lastPhStatus - ? statusPage.lastPhStatus.eFuseCurrentMax.toFixed(2) - + "A" : "—" - } - - Label { - font.bold: true - text: "PDS V Max:" - } - - Label { - text: statusPage.lastPhStatus - ? statusPage.lastPhStatus.pdsVoltageMax.toFixed(2) - + "V" : "—" - } - - Label { - font.bold: true - text: "PDS V Min:" - } - - Label { - text: statusPage.lastPhStatus - ? statusPage.lastPhStatus.pdsVoltageMin.toFixed(2) - + "V" : "—" - } - - Label { - font.bold: true - text: "MDS V Max:" - } - - Label { - text: statusPage.lastPhStatus - ? statusPage.lastPhStatus.mdsVoltageMax.toFixed(2) - + "V" : "—" - } - - Label { - font.bold: true - text: "MDS V Min:" - } - - Label { - text: statusPage.lastPhStatus - ? statusPage.lastPhStatus.mdsVoltageMin.toFixed(2) - + "V" : "—" - } - - Label { - font.bold: true - text: "Meas. HW:" - } - - Label { - text: statusPage.lastPhStatus - ? statusPage.lastPhStatus.measuredHardwareVersion : - "—" - } - - Label { - font.bold: true - text: "Accel ID:" - } - - Label { - text: statusPage.lastPhStatus ? String( - statusPage.lastPhStatus.accelerometerId) : - "—" - } - - Label { - font.bold: true - text: "Gyro:" - } - - Label { - text: statusPage.lastPhStatus - ? statusPage.lastPhStatus.gyroX + ", " - + statusPage.lastPhStatus.gyroY + ", " - + statusPage.lastPhStatus.gyroZ : "—" - } - - Label { - font.bold: true - text: "Accel:" - } - - Label { - text: statusPage.lastPhStatus - ? statusPage.lastPhStatus.accelerationX + ", " - + statusPage.lastPhStatus.accelerationY + ", " - + statusPage.lastPhStatus.accelerationZ : "—" - } - } - } - } -} diff --git a/demo/main.cpp b/demo/main.cpp deleted file mode 100644 index e0651d9..0000000 --- a/demo/main.cpp +++ /dev/null @@ -1,42 +0,0 @@ -/** - * @file main.cpp - * @brief Entry point for the QtXpl2 demo application. - */ -#include "Xpl2Client.h" - -#include -#include -#include - -#include -Q_IMPORT_QML_PLUGIN (Xpl2Plugin) - -int -main (int argc, char *argv[]) -{ - qSetMessagePattern ("Demo [%{time HH:mm:ss.zzz}] %{message}"); - - QGuiApplication app (argc, argv); - - QCommandLineParser parser; - parser.addOption ({ "wire-debug", "Log raw wire TX/RX to dev log" }); - parser.addOption ({ "printheads", - "Number of simulated printheads (default 10)", "N", - "10" }); - parser.addHelpOption (); - parser.process (app); - - if (parser.isSet ("wire-debug")) - Xpl2Client::enableWireDebug (); - - int phCount = qBound (1, parser.value ("printheads").toInt (), 99); - - QQmlApplicationEngine engine; - engine.setInitialProperties ({ { "demoPhCount", phCount } }); - QObject::connect ( - &engine, &QQmlApplicationEngine::objectCreationFailed, &app, - [] () { QCoreApplication::exit (1); }, Qt::QueuedConnection); - - engine.loadFromModule ("QtXpl2Demo", "Main"); - return app.exec (); -} diff --git a/jetting-interface/CMakeLists.txt b/jetting-interface/CMakeLists.txt new file mode 100644 index 0000000..4d46ba6 --- /dev/null +++ b/jetting-interface/CMakeLists.txt @@ -0,0 +1,25 @@ +qt_add_executable(JettingInterfaceDemo main.cpp) + +qt_add_qml_module( + JettingInterfaceDemo + URI + JettingInterfaceDemo + VERSION + 1.0 + QML_FILES + Main.qml + CommandsPage.qml + StatusPage.qml + DebugConsole.qml + # IMPORT_PATH sets QT_QML_IMPORT_PATH on the target, which qmlls reads to + # resolve imports like "import Xpl2" in .qmlls.ini. + IMPORT_PATH + "${PROJECT_BINARY_DIR}/qml") + +set_target_properties( + JettingInterfaceDemo PROPERTIES RUNTIME_OUTPUT_DIRECTORY + "${PROJECT_BINARY_DIR}/bin") + +target_link_libraries(JettingInterfaceDemo PRIVATE Qt6::Quick QtXpl2plugin) +target_include_directories(JettingInterfaceDemo + PRIVATE "${PROJECT_SOURCE_DIR}/src") diff --git a/jetting-interface/CommandsPage.qml b/jetting-interface/CommandsPage.qml new file mode 100644 index 0000000..5a084e6 --- /dev/null +++ b/jetting-interface/CommandsPage.qml @@ -0,0 +1,213 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Xpl2 + +ColumnLayout { + id: commandsPage + + required property int demoPhCount + required property ListModel phModel + + spacing: 12 + + // --- JC Version --- + GroupBox { + Layout.fillWidth: true + enabled: Xpl2Client.connected + title: "Jetting Controller" + + RowLayout { + anchors.fill: parent + + Button { + text: "Get JC Version" + + onClicked: Xpl2Client.getJcVersion() + } + + Label { + text: Xpl2Client.controllerId > 0 + ? "Controller: %1 | FW: %2 | HW: %3 | PHs: %4".arg( + Xpl2Client.controllerId).arg( + Xpl2Client.firmwareVersion).arg( + Xpl2Client.hardwareVersion).arg( + Xpl2Client.printheadCount) : "No version data" + } + } + } + + // --- Control --- + GroupBox { + Layout.fillWidth: true + enabled: Xpl2Client.connected + title: "Control" + + GridLayout { + anchors.fill: parent + columns: 4 + + Button { + text: "Jetting All On" + + onClicked: Xpl2Client.jettingAllOn() + } + + Button { + text: "Jetting Off" + + onClicked: Xpl2Client.jettingOff() + } + + Button { + text: "JC LED On" + + onClicked: Xpl2Client.jcIdLedOn() + } + + Button { + text: "JC LED Off" + + onClicked: Xpl2Client.jcIdLedOff() + } + + Button { + text: "JC Calibration" + + onClicked: Xpl2Client.jcCalibration() + } + + Button { + text: "Reset Fault Codes" + + onClicked: Xpl2Client.jcResetFaultCodes() + } + } + } + + // --- Configuration --- + GroupBox { + Layout.fillWidth: true + enabled: Xpl2Client.connected + title: "Configuration" + + GridLayout { + anchors.fill: parent + columns: 4 + + Button { + text: "Save All Settings" + + onClicked: Xpl2Client.jcSaveAllPrintheadSettings() + } + + Button { + text: "Reboot All PHs" + + onClicked: Xpl2Client.jcRebootAllPrintheads() + } + + Button { + text: "Restart JC" + + onClicked: Xpl2Client.jcRestart() + } + + Button { + text: "Shutdown JC" + + onClicked: Xpl2Client.jcShutdown() + } + } + } + + // --- Imaging --- + GroupBox { + Layout.fillWidth: true + enabled: Xpl2Client.connected + title: "Imaging" + + GridLayout { + anchors.fill: parent + columns: 4 + + Button { + text: "Start Imaging" + + onClicked: Xpl2Client.imagingStart(1.0) + } + + Button { + text: "Stop Imaging" + + onClicked: Xpl2Client.imagingStop() + } + + Button { + text: "Image Count" + + onClicked: Xpl2Client.imageCount() + } + } + } + + // --- Printheads --- + GroupBox { + Layout.fillHeight: true + Layout.fillWidth: true + enabled: Xpl2Client.connected + title: "Printheads (%1)".arg(commandsPage.demoPhCount) + + ColumnLayout { + anchors.fill: parent + + Button { + text: "Get All PH Versions" + + onClicked: { + for (let i = 0; i < commandsPage.phModel.count; ++i) + Xpl2Client.getPhVersion(commandsPage.phModel.get(i).phId); + } + } + + ScrollView { + Layout.fillHeight: true + Layout.fillWidth: true + + ListView { + model: commandsPage.phModel + spacing: 4 + + delegate: RowLayout { + id: phDelegate + + required property int phId + required property string versionInfo + + width: ListView.view.width + + Label { + Layout.preferredWidth: 50 + font.bold: true + text: "PH %1".arg(phDelegate.phId) + } + + Button { + text: "Version" + + onClicked: Xpl2Client.getPhVersion(phDelegate.phId) + } + + Label { + Layout.fillWidth: true + elide: Text.ElideRight + text: phDelegate.versionInfo || "—" + } + } + } + } + } + } +} diff --git a/jetting-interface/DebugConsole.qml b/jetting-interface/DebugConsole.qml new file mode 100644 index 0000000..9a5a9ff --- /dev/null +++ b/jetting-interface/DebugConsole.qml @@ -0,0 +1,34 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Controls + +Rectangle { + id: debugRoot + + function appendLog(msg: string): void { + let ts = new Date().toLocaleTimeString(Qt.locale(), "HH:mm:ss.zzz"); + debugLog.text += "[" + ts + "] " + msg + "\n"; + debugLog.cursorPosition = debugLog.text.length; + } + + border.color: "#444" + color: "#1e1e1e" + radius: 4 + + ScrollView { + anchors.fill: parent + anchors.margins: 4 + + TextArea { + id: debugLog + + background: null + color: "#cccccc" + font.family: "monospace" + font.pointSize: 9 + readOnly: true + wrapMode: TextEdit.Wrap + } + } +} diff --git a/jetting-interface/Main.qml b/jetting-interface/Main.qml new file mode 100644 index 0000000..1c7b553 --- /dev/null +++ b/jetting-interface/Main.qml @@ -0,0 +1,173 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Xpl2 + +ApplicationWindow { + id: root + + // Set from C++ via setInitialProperties (--printheads CLI arg, default 10). + required property int demoPhCount + + height: 1200 + title: "Jetting Interface" + visible: true + width: 900 + + Component.onCompleted: { + for (let i = 1; i <= root.demoPhCount; ++i) + phModel.append({ + "phId": i, + "versionInfo": "" + }); + } + + ListModel { + id: phModel + + } + + Connections { + function onConnectedChanged() { + debugConsole.appendLog(Xpl2Client.connected + ? "Controller connected" : + "Controller disconnected"); + } + + function onErrorOccurred(error: string) { + debugConsole.appendLog("ERROR: " + error); + } + + function onJcStatusReceived(status) { + statusPage.lastJcStatus = status; + } + + function onListeningChanged() { + debugConsole.appendLog(Xpl2Client.listening + ? "Listening on ports 9110/9111/9112" : + "Stopped listening"); + } + + function onPhStatusReceived(status) { + statusPage.lastPhStatus = status; + } + + function onPhVersionReceived(controllerId: int, printheadId: int, + mcuFirmwareVersion: string, + mcuHardwareVersion: string, + mcuFirmwareVariant: string, + fpgaFirmwareVersion: string, + fpgaHardwareVersion: string, + bootloaderVersion: string) { + 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)); + break; + } + } + } + + function onShuttingDown() { + debugConsole.appendLog("SERVER SHUTTING DOWN"); + } + + function onStatusMessage(message: string) { + debugConsole.appendLog(message); + } + + target: Xpl2Client + } + + ColumnLayout { + anchors.fill: parent + spacing: 0 + + // --- Connection (always visible) --- + GroupBox { + Layout.bottomMargin: 0 + Layout.fillWidth: true + Layout.margins: 16 + title: "Connection" + + RowLayout { + anchors.fill: parent + + Label { + text: Xpl2Client.connected ? "Controller connected" : + "Waiting for controller…" + } + + Item { + Layout.fillWidth: true + } + + Button { + text: Xpl2Client.listening ? "Stop" : "Listen" + + onClicked: { + if (Xpl2Client.listening) + Xpl2Client.stopListening(); + else + Xpl2Client.startListening(); + } + } + } + } + + // --- Tab bar --- + TabBar { + id: tabBar + + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + TabButton { + text: "Commands" + } + + TabButton { + text: "Status" + } + } + + // --- Swipe view --- + SwipeView { + id: swipeView + + Layout.fillHeight: true + Layout.fillWidth: true + Layout.margins: 16 + currentIndex: tabBar.currentIndex + + onCurrentIndexChanged: tabBar.currentIndex = currentIndex + + CommandsPage { + demoPhCount: root.demoPhCount + phModel: phModel + } + + StatusPage { + id: statusPage + + } + } + + // --- Debug Console --- + DebugConsole { + id: debugConsole + + Layout.fillWidth: true + Layout.preferredHeight: 160 + } + } +} diff --git a/jetting-interface/StatusPage.qml b/jetting-interface/StatusPage.qml new file mode 100644 index 0000000..6a85bcc --- /dev/null +++ b/jetting-interface/StatusPage.qml @@ -0,0 +1,620 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Xpl2 + +ColumnLayout { + id: statusPage + + property var lastJcStatus: null + property var lastPhStatus: null + + spacing: 12 + + // --- Status Messaging Controls --- + GroupBox { + Layout.fillWidth: true + enabled: Xpl2Client.connected + title: "Status Messaging" + + GridLayout { + anchors.fill: parent + columns: 2 + + Label { + text: "Level:" + } + + ComboBox { + id: statusLevelCombo + + Layout.fillWidth: true + model: ["1 — Basic", "2 — Extended"] + } + + Label { + text: "Interval (ms):" + } + + SpinBox { + id: statusIntervalSpin + + Layout.fillWidth: true + from: 100 + stepSize: 100 + to: 10000 + value: 1000 + } + + Button { + text: "Start JC Status" + + onClicked: Xpl2Client.jcStatusMessagingStart( + statusLevelCombo.currentIndex + 1, + statusIntervalSpin.value) + } + + Button { + text: "Stop JC Status" + + onClicked: Xpl2Client.jcStatusMessagingStop() + } + + Button { + text: "Start PH Status" + + onClicked: Xpl2Client.phStatusMessagingStart( + statusLevelCombo.currentIndex + 1, + statusIntervalSpin.value) + } + + Button { + text: "Stop PH Status" + + onClicked: Xpl2Client.phStatusMessagingStop() + } + } + } + + // --- JC Status Display --- + GroupBox { + Layout.fillWidth: true + title: "JC Status" + + RowLayout { + anchors.fill: parent + + GridLayout { + Layout.fillWidth: true + columns: 4 + + Label { + font.bold: true + text: "CPU:" + } + + Label { + text: statusPage.lastJcStatus + ? statusPage.lastJcStatus.cpuPercentageBusy.toFixed( + 1) + "%" : "—" + } + + Label { + font.bold: true + text: "Temp:" + } + + Label { + text: statusPage.lastJcStatus + ? statusPage.lastJcStatus.temperature.toFixed(1) + + "°C" : "—" + } + + Label { + font.bold: true + text: "Rail 5V:" + } + + Label { + text: statusPage.lastJcStatus + ? statusPage.lastJcStatus.rail5V.toFixed(2) + "V" : + "—" + } + + Label { + font.bold: true + text: "CAN 8V:" + } + + Label { + text: statusPage.lastJcStatus + ? statusPage.lastJcStatus.railCanBus8V.toFixed(2) + + "V" : "—" + } + + Label { + font.bold: true + text: "Humidity:" + } + + Label { + text: statusPage.lastJcStatus + ? statusPage.lastJcStatus.humidity.toFixed(1) + "%" : + "—" + } + + Label { + font.bold: true + text: "Bus I:" + } + + Label { + text: statusPage.lastJcStatus + ? statusPage.lastJcStatus.busCurrent.toFixed(3) + "A" : + "—" + } + + Label { + font.bold: true + text: "Uptime:" + } + + Label { + text: statusPage.lastJcStatus + ? statusPage.lastJcStatus.onTimeSeconds + "s" : "—" + } + } + + // --- Extended (Level 2) --- + GridLayout { + Layout.fillWidth: true + columns: 4 + visible: statusPage.lastJcStatus !== null + && statusPage.lastJcStatus.statusLevel >= 2 + + Label { + font.bold: true + text: "IP:" + } + + Label { + text: statusPage.lastJcStatus + ? statusPage.lastJcStatus.ipAddress : "—" + } + + Label { + font.bold: true + text: "eFuse V:" + } + + Label { + text: statusPage.lastJcStatus + ? statusPage.lastJcStatus.eFuseVoltage.toFixed(2) + + "V" : "—" + } + + Label { + font.bold: true + text: "eFuse Bus:" + } + + Label { + text: statusPage.lastJcStatus ? ( + statusPage.lastJcStatus.eFuseBusEnabled + ? "On" : "Off") : "—" + } + + Label { + font.bold: true + text: "Bus Power:" + } + + Label { + text: statusPage.lastJcStatus ? ( + statusPage.lastJcStatus.busPowerEnabled + ? "On" : "Off") : "—" + } + + Label { + font.bold: true + text: "Bus OK:" + } + + Label { + text: statusPage.lastJcStatus ? ( + statusPage.lastJcStatus.busPowerOk + ? "Yes" : "No") : "—" + } + + Label { + font.bold: true + text: "Switch:" + } + + Label { + text: statusPage.lastJcStatus ? String( + statusPage.lastJcStatus.switchValue) : + "—" + } + + Label { + font.bold: true + text: "FW:" + } + + Label { + text: statusPage.lastJcStatus + ? statusPage.lastJcStatus.firmwareVersion : "—" + } + + Label { + font.bold: true + text: "HW:" + } + + Label { + text: statusPage.lastJcStatus + ? statusPage.lastJcStatus.hardwareVersion : "—" + } + + Label { + font.bold: true + text: "Indicators:" + } + + Label { + Layout.columnSpan: 3 + text: statusPage.lastJcStatus + ? [statusPage.lastJcStatus.indicator0, + statusPage.lastJcStatus.indicator1, + statusPage.lastJcStatus.indicator2, + statusPage.lastJcStatus.indicator3, + statusPage.lastJcStatus.indicator4, + statusPage.lastJcStatus.indicator5].map(function ( + v) { + return v ? "1" : "0"; + }).join(" ") : "—" + } + } + } + } + + // --- PH Status Display --- + GroupBox { + Layout.fillHeight: true + Layout.fillWidth: true + title: "PH Status" + + RowLayout { + anchors.fill: parent + + GridLayout { + Layout.fillWidth: true + columns: 4 + + Label { + font.bold: true + text: "PH:" + } + + Label { + text: statusPage.lastPhStatus ? String( + statusPage.lastPhStatus.printheadId) : + "—" + } + + Label { + font.bold: true + text: "Temp:" + } + + Label { + text: statusPage.lastPhStatus + ? statusPage.lastPhStatus.temperature.toFixed(1) + + "°C" : "—" + } + + Label { + font.bold: true + text: "MCU Temp:" + } + + Label { + text: statusPage.lastPhStatus + ? statusPage.lastPhStatus.mcuTemperature.toFixed(1) + + "°C" : "—" + } + + Label { + font.bold: true + text: "Humidity:" + } + + Label { + text: statusPage.lastPhStatus + ? statusPage.lastPhStatus.humidity.toFixed(1) + "%" : + "—" + } + + Label { + font.bold: true + text: "PDS V:" + } + + Label { + text: statusPage.lastPhStatus + ? statusPage.lastPhStatus.pdsVoltage.toFixed(2) + "V" : + "—" + } + + Label { + font.bold: true + text: "MDS V:" + } + + Label { + text: statusPage.lastPhStatus + ? statusPage.lastPhStatus.mdsVoltage.toFixed(2) + "V" : + "—" + } + + Label { + font.bold: true + text: "Sys V:" + } + + Label { + text: statusPage.lastPhStatus + ? statusPage.lastPhStatus.systemVoltage.toFixed(2) + + "V" : "—" + } + + Label { + font.bold: true + text: "VDD:" + } + + Label { + text: statusPage.lastPhStatus + ? statusPage.lastPhStatus.vdd.toFixed(2) + "V" : "—" + } + + Label { + font.bold: true + text: "eFuse I:" + } + + Label { + text: statusPage.lastPhStatus + ? statusPage.lastPhStatus.eFuseCurrent.toFixed(3) + + "A" : "—" + } + + Label { + font.bold: true + text: "Nozzle I:" + } + + Label { + text: statusPage.lastPhStatus + ? statusPage.lastPhStatus.nozzleCurrent.toFixed(3) + + "A" : "—" + } + + Label { + font.bold: true + text: "Duty Cycle:" + } + + Label { + text: statusPage.lastPhStatus + ? statusPage.lastPhStatus.dutyCycle.toFixed(1) + "%" : + "—" + } + + Label { + font.bold: true + text: "Drive:" + } + + Label { + text: statusPage.lastPhStatus ? String( + statusPage.lastPhStatus.drive) : + "—" + } + + Label { + font.bold: true + text: "Uptime:" + } + + Label { + text: statusPage.lastPhStatus + ? statusPage.lastPhStatus.onTimeSeconds + "s" : "—" + } + } + + // --- Extended (Level 2) --- + GridLayout { + Layout.fillWidth: true + columns: 4 + visible: statusPage.lastPhStatus !== null + && statusPage.lastPhStatus.statusLevel >= 2 + + Label { + font.bold: true + text: "MCU FW:" + } + + Label { + text: statusPage.lastPhStatus + ? statusPage.lastPhStatus.mcuFirmwareVersion : "—" + } + + Label { + font.bold: true + text: "MCU HW:" + } + + Label { + text: statusPage.lastPhStatus + ? statusPage.lastPhStatus.mcuHardwareVersion : "—" + } + + Label { + font.bold: true + text: "MCU Variant:" + } + + Label { + text: statusPage.lastPhStatus + ? statusPage.lastPhStatus.mcuFirmwareVariant : "—" + } + + Label { + font.bold: true + text: "FPGA FW:" + } + + Label { + text: statusPage.lastPhStatus + ? statusPage.lastPhStatus.fpgaFirmwareVersion : "—" + } + + Label { + font.bold: true + text: "FPGA HW:" + } + + Label { + text: statusPage.lastPhStatus + ? statusPage.lastPhStatus.fpgaHardwareVersion : "—" + } + + Label { + font.bold: true + text: "Boot:" + } + + Label { + text: statusPage.lastPhStatus + ? statusPage.lastPhStatus.bootloaderVersion : "—" + } + + Label { + font.bold: true + text: "Max Temp:" + } + + Label { + text: statusPage.lastPhStatus + ? statusPage.lastPhStatus.maxAllowedTemperature.toFixed( + 1) + "°C" : "—" + } + + Label { + font.bold: true + text: "eFuse Max:" + } + + Label { + text: statusPage.lastPhStatus + ? statusPage.lastPhStatus.eFuseCurrentMax.toFixed(2) + + "A" : "—" + } + + Label { + font.bold: true + text: "PDS V Max:" + } + + Label { + text: statusPage.lastPhStatus + ? statusPage.lastPhStatus.pdsVoltageMax.toFixed(2) + + "V" : "—" + } + + Label { + font.bold: true + text: "PDS V Min:" + } + + Label { + text: statusPage.lastPhStatus + ? statusPage.lastPhStatus.pdsVoltageMin.toFixed(2) + + "V" : "—" + } + + Label { + font.bold: true + text: "MDS V Max:" + } + + Label { + text: statusPage.lastPhStatus + ? statusPage.lastPhStatus.mdsVoltageMax.toFixed(2) + + "V" : "—" + } + + Label { + font.bold: true + text: "MDS V Min:" + } + + Label { + text: statusPage.lastPhStatus + ? statusPage.lastPhStatus.mdsVoltageMin.toFixed(2) + + "V" : "—" + } + + Label { + font.bold: true + text: "Meas. HW:" + } + + Label { + text: statusPage.lastPhStatus + ? statusPage.lastPhStatus.measuredHardwareVersion : + "—" + } + + Label { + font.bold: true + text: "Accel ID:" + } + + Label { + text: statusPage.lastPhStatus ? String( + statusPage.lastPhStatus.accelerometerId) : + "—" + } + + Label { + font.bold: true + text: "Gyro:" + } + + Label { + text: statusPage.lastPhStatus + ? statusPage.lastPhStatus.gyroX + ", " + + statusPage.lastPhStatus.gyroY + ", " + + statusPage.lastPhStatus.gyroZ : "—" + } + + Label { + font.bold: true + text: "Accel:" + } + + Label { + text: statusPage.lastPhStatus + ? statusPage.lastPhStatus.accelerationX + ", " + + statusPage.lastPhStatus.accelerationY + ", " + + statusPage.lastPhStatus.accelerationZ : "—" + } + } + } + } +} diff --git a/jetting-interface/main.cpp b/jetting-interface/main.cpp new file mode 100644 index 0000000..b88bd37 --- /dev/null +++ b/jetting-interface/main.cpp @@ -0,0 +1,42 @@ +/** + * @file main.cpp + * @brief Entry point for the Jetting Interface demo application. + */ +#include "Xpl2Client.h" + +#include +#include +#include + +#include +Q_IMPORT_QML_PLUGIN (Xpl2Plugin) + +int +main (int argc, char *argv[]) +{ + qSetMessagePattern ("JI [%{time HH:mm:ss.zzz}] %{message}"); + + QGuiApplication app (argc, argv); + + QCommandLineParser parser; + parser.addOption ({ "wire-debug", "Log raw wire TX/RX to dev log" }); + parser.addOption ({ "printheads", + "Number of simulated printheads (default 10)", "N", + "10" }); + parser.addHelpOption (); + parser.process (app); + + if (parser.isSet ("wire-debug")) + Xpl2Client::enableWireDebug (); + + int phCount = qBound (1, parser.value ("printheads").toInt (), 99); + + QQmlApplicationEngine engine; + engine.setInitialProperties ({ { "demoPhCount", phCount } }); + QObject::connect ( + &engine, &QQmlApplicationEngine::objectCreationFailed, &app, + [] () { QCoreApplication::exit (1); }, Qt::QueuedConnection); + + engine.loadFromModule ("JettingInterfaceDemo", "Main"); + return app.exec (); +} diff --git a/mock-jetting-controller/CMakeLists.txt b/mock-jetting-controller/CMakeLists.txt new file mode 100644 index 0000000..87e518b --- /dev/null +++ b/mock-jetting-controller/CMakeLists.txt @@ -0,0 +1,7 @@ +qt_add_executable(MockJettingController main.cpp MockServer.h MockServer.cpp) + +set_target_properties( + MockJettingController PROPERTIES RUNTIME_OUTPUT_DIRECTORY + "${PROJECT_BINARY_DIR}/bin") + +target_link_libraries(MockJettingController PRIVATE Qt6::Core Qt6::Network) diff --git a/mock-jetting-controller/MockServer.cpp b/mock-jetting-controller/MockServer.cpp new file mode 100644 index 0000000..08eb116 --- /dev/null +++ b/mock-jetting-controller/MockServer.cpp @@ -0,0 +1,541 @@ +/** + * @file MockServer.cpp + * @brief Mock XPL2 server — connects out to the client on three protocol + * ports. + */ +#include "MockServer.h" + +bool MockServer::s_wireDebug = false; + +void +MockServer::enableWireDebug () +{ + s_wireDebug = true; +} + +MockServer::MockServer (const QString &host, QObject *parent) + : QObject (parent), m_host (host) +{ + setupPort (m_command, "Command", 9110); + setupPort (m_imaging, "Imaging", 9111); + setupPort (m_status, "Status", 9112); + + connect (&m_tickTimer, &QTimer::timeout, this, &MockServer::tick); + m_tickTimer.start (1000); + + connect (&m_jcStatusTimer, &QTimer::timeout, this, + &MockServer::sendJcStatusMsg); + connect (&m_phStatusTimer, &QTimer::timeout, this, + &MockServer::sendPhStatusMsg); + + connectAll (); +} + +void +MockServer::setupPort (Port &port, const char *name, quint16 number) +{ + port.name = name; + port.number = number; + + connect (&port.socket, &QTcpSocket::connected, this, + &MockServer::onSocketConnected); + connect (&port.socket, &QTcpSocket::disconnected, this, + &MockServer::onSocketDisconnected); + connect (&port.socket, &QTcpSocket::readyRead, this, + &MockServer::onSocketMessageReady); + connect (&port.socket, &QAbstractSocket::errorOccurred, this, + [this, &port] (QAbstractSocket::SocketError) + { + qDebug ("%s Connection error: %s", + qPrintable (logTag (&port.socket)), + qPrintable (port.socket.errorString ())); + }); +} + +void +MockServer::connectAll () +{ + for (auto *port : { &m_command, &m_imaging, &m_status }) + if (port->socket.state () == QAbstractSocket::UnconnectedState) + port->socket.connectToHost (m_host, port->number); +} + +QString +MockServer::logTag (const QTcpSocket *socket) const +{ + const char *name = "Unknown"; + quint16 number = 0; + if (socket == &m_command.socket) + { + name = m_command.name; + number = m_command.number; + } + else if (socket == &m_imaging.socket) + { + name = m_imaging.name; + number = m_imaging.number; + } + else if (socket == &m_status.socket) + { + name = m_status.name; + number = m_status.number; + } + + /* Fixed-width tag: "[Command:9110]" = 14 chars, left-padded to 15. */ + return QStringLiteral ("[%1:%2]").arg (name).arg (number).leftJustified (15); +} + +void +MockServer::onSocketConnected () +{ + auto *socket = qobject_cast (sender ()); + qInfo ("%s Connected to client", qPrintable (logTag (socket))); +} + +void +MockServer::onSocketDisconnected () +{ + auto *socket = qobject_cast (sender ()); + qInfo ("%s Disconnected from client", qPrintable (logTag (socket))); +} + +void +MockServer::onSocketMessageReady () +{ + auto *socket = qobject_cast (sender ()); + Port *port = nullptr; + if (socket == &m_command.socket) + port = &m_command; + else if (socket == &m_imaging.socket) + port = &m_imaging; + else + port = &m_status; + + while (socket->canReadLine ()) + { + QByteArray line = socket->readLine (); + handleCommand (*port, line); + } +} + +void +MockServer::tick () +{ + /* Send KA_PING on connected sockets, retry connection on disconnected. */ + for (auto *port : { &m_command, &m_imaging, &m_status }) + { + if (port->socket.state () == QAbstractSocket::ConnectedState) + sendReply (port->socket, "KA_PING\n"); + else if (port->socket.state () == QAbstractSocket::UnconnectedState) + port->socket.connectToHost (m_host, port->number); + } +} + +void +MockServer::handleCommand (Port &port, const QByteArray &line) +{ + QByteArray trimmed = line.trimmed (); + if (trimmed.isEmpty ()) + return; + + /* Split on first comma to get command token and remaining params. */ + int comma = trimmed.indexOf (','); + QByteArray cmd = (comma >= 0) ? trimmed.left (comma) : trimmed; + QByteArray params = (comma >= 0) ? trimmed.mid (comma + 1) : QByteArray (); + + if (cmd == "KA_PING") + handleKaPing (port, params); + else if (cmd == "GS_JC_VERSION") + handleGsJcVersion (port.socket); + else if (cmd == "GS_PH_VERSION") + handleGsPhVersion (port.socket, params); + /* CN_ JC success shape (no params needed) */ + else if (cmd == "CN_JETTING_ALL_ON" || cmd == "CN_JETTING_ON" + || cmd == "CN_JETTING_OFF" || cmd == "CN_JC_ID_LED_ON" + || cmd == "CN_JC_ID_LED_OFF" || cmd == "CN_JC_CALIBRATION" + || cmd == "CN_JC_RESET_FAULT_CODES") + handleCnJcSuccess (port.socket, cmd); + /* CN_ PH success shape (params carry printheadId) */ + else if (cmd == "CN_PH_JETTING_ON" || cmd == "CN_PH_JETTING_OFF" + || cmd == "CN_PH_ID_LED_ON" || cmd == "CN_PH_ID_LED_OFF" + || cmd == "CN_PH_CALIBRATION" || cmd == "CN_PH_RESET_FAULT_CODES" + || cmd == "CN_PH_NOZZLES_DISABLED") + handleCnPhSuccess (port.socket, cmd, params); + /* CN_ custom-shape commands */ + else if (cmd == "CN_PH_CALIBRATION_DATA") + handleCnPhCalibrationData (port.socket, params); + else if (cmd == "CN_PH_CALIBRATION_RAW_DATA") + handleCnPhCalibrationRawData (port.socket, params); + else if (cmd == "CN_PH_CALIBRATED_BASE_FREQUENCY") + handleCnPhCalibratedBaseFrequency (port.socket, params); + /* CN_ status messaging (arrives on status port) */ + else if (cmd == "CN_JC_STATUS_MESSAGING_START" + || cmd == "CN_PH_STATUS_MESSAGING_START") + handleCnStatusMessagingStart (port.socket, cmd, params); + else if (cmd == "CN_JC_STATUS_MESSAGING_STOP" + || cmd == "CN_PH_STATUS_MESSAGING_STOP") + handleCnStatusMessagingStop (port.socket, cmd); + /* CF_ JC success shape */ + else if (cmd == "CF_PH_DEASSIGN_ID" || cmd == "CF_JC_SAVE_CALIBRATION" + || cmd == "CF_JC_RESET_CALIBRATION" + || cmd == "CF_JC_SWITCH_OFF_PURGE" + || cmd == "CF_JC_RESET_SETTINGS_ALL_PRINTHEADS" + || cmd == "CF_JC_REBOOT_ALL_PRINTHEADS" + || cmd == "CF_JC_RESET_CONTROLLER_SOFTWARE" + || cmd == "CF_JC_RESTART" || cmd == "CF_JC_SHUTDOWN" + || cmd == "CF_JC_SAVE_ALL_PRINTHEAD_SETTINGS") + handleCnJcSuccess (port.socket, cmd); + /* CF_ PH success shape */ + else if (cmd == "CF_PH_SET_ID" || cmd == "CF_PH_SAVE_CALIBRATION" + || cmd == "CF_PH_RESET_CALIBRATION" + || cmd == "CF_PH_RESET_ALL_SETTINGS" || cmd == "CF_PH_REBOOT" + || cmd == "CF_PH_SAVE_SETTINGS") + handleCnPhSuccess (port.socket, cmd, params); + /* CF_ custom-shape commands */ + else if (cmd == "CF_JC_SET_PURGE_SETTINGS") + handleCfJcSetPurgeSettings (port.socket, params); + else if (cmd == "CF_JC_SET_JETTING_PARAMS") + handleCfJcSetJettingParams (port.socket, params); + else if (cmd == "CF_PH_SET_JETTING_PARAMS") + handleCfPhJettingParams (port.socket, cmd, params); + else if (cmd == "CF_PH_GET_JETTING_PARAMS") + handleCfPhJettingParams (port.socket, cmd, params); + else if (cmd == "CF_JC_SETTER") + handleCfJcSetter (port.socket, params); + else if (cmd == "CF_PH_SETTER") + handleCfPhSetter (port.socket, params); + else if (cmd == "CF_JC_GETTER") + handleCfJcGetter (port.socket, params); + else if (cmd == "CF_PH_GETTER") + handleCfPhGetter (port.socket, params); + /* Imaging commands (port 2) */ + else if (cmd == "m2") + handleImagingStart (port.socket, params); + else if (cmd == "m4") + handleImagingStop (port.socket); + else if (cmd == "m0" || cmd == "m5") + handleImagingMaskStart (port.socket, cmd, params); + else if (cmd == "m1" || cmd == "m6") + handleImagingMaskEnd (port.socket, cmd, params); + else if (cmd == "m3") + handleImageCount (port.socket); + else + qWarning ("%s Unknown command: %s", qPrintable (logTag (&port.socket)), + cmd.constData ()); +} + +void +MockServer::sendReply (QTcpSocket &socket, const QByteArray &data) +{ + socket.write (data); + QByteArray trimmed = data.trimmed (); + int comma = trimmed.indexOf (','); + QByteArray cmd = (comma >= 0) ? trimmed.left (comma) : trimmed; + QByteArray wire; + if (s_wireDebug) + wire = " >> " + trimmed; + qDebug ("%s TX %s%s", qPrintable (logTag (&socket)), cmd.constData (), + wire.constData ()); +} + +void +MockServer::handleKaPing (Port &port, const QByteArray ¶ms) +{ + QByteArray wire; + if (s_wireDebug) + wire = " << KA_PING," + params; + qDebug ("%s RX KA_PING ACK%s", qPrintable (logTag (&port.socket)), + wire.constData ()); +} + +void +MockServer::handleGsJcVersion (QTcpSocket &socket) +{ + qDebug ("%s RX GS_JC_VERSION", qPrintable (logTag (&socket))); + sendReply (socket, "GS_JC_VERSION,1,\"1.05\",\"2.00\",15\n"); +} + +void +MockServer::handleGsPhVersion (QTcpSocket &socket, const QByteArray ¶ms) +{ + qDebug ("%s RX GS_PH_VERSION,%s", qPrintable (logTag (&socket)), + params.constData ()); + /* Echo back canned version info for whatever printhead ID was requested. */ + sendReply (socket, QByteArray ("GS_PH_VERSION,1,") + params + + ",\"3.10\",\"1.00\",\"Standard\"," + "\"2.05\",\"1.02\",\"0.9.1\"\n"); +} + +void +MockServer::handleCnJcSuccess (QTcpSocket &socket, const QByteArray &cmd) +{ + qDebug ("%s RX %s", qPrintable (logTag (&socket)), cmd.constData ()); + sendReply (socket, cmd + ",1,1\n"); +} + +void +MockServer::handleCnPhSuccess (QTcpSocket &socket, const QByteArray &cmd, + const QByteArray ¶ms) +{ + qDebug ("%s RX %s,%s", qPrintable (logTag (&socket)), cmd.constData (), + params.constData ()); + /* Extract printhead ID (first field before any comma). */ + int comma = params.indexOf (','); + QByteArray phId = (comma >= 0) ? params.left (comma) : params; + sendReply (socket, cmd + ",1," + phId + ",1\n"); +} + +void +MockServer::handleCnPhCalibrationData (QTcpSocket &socket, + const QByteArray ¶ms) +{ + qDebug ("%s RX CN_PH_CALIBRATION_DATA,%s", qPrintable (logTag (&socket)), + params.constData ()); + /* Reply: controllerId, phId, then 48 floats (-1 = uncalibrated). */ + QByteArray reply = "CN_PH_CALIBRATION_DATA,1," + params; + for (int i = 0; i < 48; ++i) + reply += ",-1"; + reply += '\n'; + sendReply (socket, reply); +} + +void +MockServer::handleCnPhCalibrationRawData (QTcpSocket &socket, + const QByteArray ¶ms) +{ + qDebug ("%s RX CN_PH_CALIBRATION_RAW_DATA,%s", qPrintable (logTag (&socket)), + params.constData ()); + QByteArray reply = "CN_PH_CALIBRATION_RAW_DATA,1," + params; + for (int i = 0; i < 48; ++i) + reply += ",-1"; + reply += '\n'; + sendReply (socket, reply); +} + +void +MockServer::handleCnPhCalibratedBaseFrequency (QTcpSocket &socket, + const QByteArray ¶ms) +{ + qDebug ("%s RX CN_PH_CALIBRATED_BASE_FREQUENCY,%s", + qPrintable (logTag (&socket)), params.constData ()); + sendReply (socket, + "CN_PH_CALIBRATED_BASE_FREQUENCY,1," + params + ",10.0,10.0\n"); +} + +void +MockServer::handleCnStatusMessagingStart (QTcpSocket &socket, + const QByteArray &cmd, + const QByteArray ¶ms) +{ + qDebug ("%s RX %s,%s", qPrintable (logTag (&socket)), cmd.constData (), + params.constData ()); + + /* Parse level and interval from params: "level,interval" */ + QList parts = params.split (','); + int level = parts.size () > 0 ? parts[0].toInt () : 1; + int interval = parts.size () > 1 ? parts[1].toInt () : 1000; + + if (cmd == "CN_JC_STATUS_MESSAGING_START") + { + m_jcStatusLevel = level; + m_jcStatusTimer.start (interval); + } + else + { + m_phStatusLevel = level; + m_phStatusTimer.start (interval); + } + + sendReply (socket, cmd + ",1," + params + ",1\n"); +} + +void +MockServer::handleCnStatusMessagingStop (QTcpSocket &socket, + const QByteArray &cmd) +{ + qDebug ("%s RX %s", qPrintable (logTag (&socket)), cmd.constData ()); + + if (cmd == "CN_JC_STATUS_MESSAGING_STOP") + m_jcStatusTimer.stop (); + else + m_phStatusTimer.stop (); + + sendReply (socket, cmd + ",1,1\n"); +} + +void +MockServer::handleCfJcSetPurgeSettings (QTcpSocket &socket, + const QByteArray ¶ms) +{ + qDebug ("%s RX CF_JC_SET_PURGE_SETTINGS,%s", qPrintable (logTag (&socket)), + params.constData ()); + sendReply (socket, "CF_JC_SET_PURGE_SETTINGS,1," + params + ",1\n"); +} + +void +MockServer::handleCfJcSetJettingParams (QTcpSocket &socket, + const QByteArray ¶ms) +{ + qDebug ("%s RX CF_JC_SET_JETTING_PARAMS,%s", qPrintable (logTag (&socket)), + params.constData ()); + sendReply (socket, "CF_JC_SET_JETTING_PARAMS,1," + params + ",1\n"); +} + +void +MockServer::handleCfPhJettingParams (QTcpSocket &socket, const QByteArray &cmd, + const QByteArray ¶ms) +{ + qDebug ("%s RX %s,%s", qPrintable (logTag (&socket)), cmd.constData (), + params.constData ()); + if (cmd == "CF_PH_GET_JETTING_PARAMS") + sendReply (socket, cmd + ",1," + params + ",0,0,0,0,0,1\n"); + else + sendReply (socket, cmd + ",1," + params + ",1\n"); +} + +void +MockServer::handleCfJcSetter (QTcpSocket &socket, const QByteArray ¶ms) +{ + qDebug ("%s RX CF_JC_SETTER,%s", qPrintable (logTag (&socket)), + params.constData ()); + sendReply (socket, "CF_JC_SETTER,1," + params + ",1\n"); +} + +void +MockServer::handleCfPhSetter (QTcpSocket &socket, const QByteArray ¶ms) +{ + qDebug ("%s RX CF_PH_SETTER,%s", qPrintable (logTag (&socket)), + params.constData ()); + sendReply (socket, "CF_PH_SETTER,1," + params + ",1\n"); +} + +void +MockServer::handleCfJcGetter (QTcpSocket &socket, const QByteArray ¶ms) +{ + qDebug ("%s RX CF_JC_GETTER,%s", qPrintable (logTag (&socket)), + params.constData ()); + sendReply (socket, "CF_JC_GETTER,1," + params + ",\"0\",1\n"); +} + +void +MockServer::handleCfPhGetter (QTcpSocket &socket, const QByteArray ¶ms) +{ + qDebug ("%s RX CF_PH_GETTER,%s", qPrintable (logTag (&socket)), + params.constData ()); + sendReply (socket, "CF_PH_GETTER,1," + params + ",\"0\",1\n"); +} + +void +MockServer::sendJcStatusMsg () +{ + if (m_status.socket.state () != QAbstractSocket::ConnectedState) + return; + + static int tick = 0; + ++tick; + bool odd = (tick % 2) != 0; + + /* Alternate between two value sets so the demo visually updates. + Level 1: cid, level, cpu, rail5v, canBus8v, temp, humidity, busCurrent, + onTimeSeconds */ + QByteArray level = QByteArray::number (m_jcStatusLevel); + QByteArray base = "EV_STATUS_MSG_JC,1," + level; + if (odd) + base += ",42.5,4.98,7.95,35.2,55.0,0.12,3600"; + else + base += ",58.3,5.02,8.05,37.8,48.5,0.15,3660"; + + if (m_jcStatusLevel >= 2) + { + if (odd) + base += ",\"192.168.1.100\",23.5,1,1,1,3,\"1.05\",\"2.00\"" + ",0,0,0,0,0,0"; + else + base += ",\"192.168.1.100\",24.1,1,1,1,3,\"1.05\",\"2.00\"" + ",1,0,1,0,1,0"; + } + base += '\n'; + + sendReply (m_status.socket, base); +} + +void +MockServer::sendPhStatusMsg () +{ + if (m_status.socket.state () != QAbstractSocket::ConnectedState) + return; + + static int tick = 0; + ++tick; + bool odd = (tick % 2) != 0; + + /* Alternate between two value sets. + Level 1: cid, level, phId, temp, humidity, mcuTemp, pdsV, mdsV, sysV, + eFuseI, nozzleI, vdd, 13×trip bools, dutyCycle, pwmFreq, drive, + nozzleDriveFreq, nozzleDriveDutyCycle, onTimeSeconds */ + QByteArray level = QByteArray::number (m_phStatusLevel); + QByteArray base = "EV_STATUS_MSG_PH,1," + level + ",1"; + if (odd) + base += ",38.5,45.0,40.2,36.0,24.0,3.3,0.05,0.02,3.3" + ",0,0,0,0,0,0,0,0,0,0,0,0,0" + ",50.0,10000.0,3,8000.0,50.0,1800"; + else + base += ",41.2,42.0,43.1,35.5,23.8,3.28,0.06,0.03,3.31" + ",0,0,0,0,0,0,0,0,0,0,0,0,0" + ",55.0,10500.0,4,8200.0,52.0,1860"; + + if (m_phStatusLevel >= 2) + { + if (odd) + base += ",0,\"MCU001\",\"FLASH001\",12345" + ",\"1.00\",\"3.10\",\"Standard\",\"1.00\",\"2.05\",\"0.9.1\"" + ",100.0,45.0,20.0,36.0,30.0,18.0,2.0,\"1.00\"" + ",0,0,0,0,0,0,0,0,0,0,0,0,0,0"; + else + base += ",0,\"MCU001\",\"FLASH001\",12345" + ",\"1.00\",\"3.10\",\"Standard\",\"1.00\",\"2.05\",\"0.9.1\"" + ",100.0,45.0,20.0,35.5,30.0,18.0,2.0,\"1.00\"" + ",1,-1,2,0,0,0,0,0,0,0,0,0,0,0"; + } + base += '\n'; + + sendReply (m_status.socket, base); +} + +void +MockServer::handleImagingStart (QTcpSocket &socket, const QByteArray ¶ms) +{ + qDebug ("%s RX m2,%s", qPrintable (logTag (&socket)), params.constData ()); + sendReply (socket, "n,\"A\"\n"); +} + +void +MockServer::handleImagingStop (QTcpSocket &socket) +{ + qDebug ("%s RX m4", qPrintable (logTag (&socket))); + sendReply (socket, "m4,1\n"); +} + +void +MockServer::handleImagingMaskStart (QTcpSocket &socket, const QByteArray &cmd, + const QByteArray ¶ms) +{ + qDebug ("%s RX %s,%s", qPrintable (logTag (&socket)), cmd.constData (), + params.constData ()); +} + +void +MockServer::handleImagingMaskEnd (QTcpSocket &socket, const QByteArray &cmd, + const QByteArray ¶ms) +{ + qDebug ("%s RX %s,%s", qPrintable (logTag (&socket)), cmd.constData (), + params.constData ()); + sendReply (socket, "n,\"A\"\n"); +} + +void +MockServer::handleImageCount (QTcpSocket &socket) +{ + qDebug ("%s RX m3", qPrintable (logTag (&socket))); + sendReply (socket, "n,\"0\"\n"); +} diff --git a/mock-jetting-controller/MockServer.h b/mock-jetting-controller/MockServer.h new file mode 100644 index 0000000..242cb32 --- /dev/null +++ b/mock-jetting-controller/MockServer.h @@ -0,0 +1,90 @@ +/** + * @file MockServer.h + * @brief Mock XPL2 server — connects out to the client on three protocol + * ports. + */ +#pragma once + +#include +#include +#include + +class MockServer : public QObject +{ + Q_OBJECT + +public: + explicit MockServer (const QString &host, QObject *parent = nullptr); + static void enableWireDebug (); + +private slots: + void onSocketConnected (); + void onSocketDisconnected (); + void onSocketMessageReady (); + /* Send KA_PING on connected sockets, retry connection on disconnected. */ + void tick (); + void sendJcStatusMsg (); + void sendPhStatusMsg (); + +private: + struct Port + { + QTcpSocket socket; + const char *name = nullptr; + quint16 number = 0; + }; + + void setupPort (Port &port, const char *name, quint16 number); + void connectAll (); + /* Return a fixed-width "[Name:port]" tag for log lines. */ + QString logTag (const QTcpSocket *socket) const; + /* Parse incoming line, log RX, and dispatch to the matching handler. */ + void handleCommand (Port &port, const QByteArray &line); + void sendReply (QTcpSocket &socket, const QByteArray &data); + void handleKaPing (Port &port, const QByteArray ¶ms); + void handleGsJcVersion (QTcpSocket &socket); + void handleGsPhVersion (QTcpSocket &socket, const QByteArray ¶ms); + /* CN_ control command handlers */ + void handleCnJcSuccess (QTcpSocket &socket, const QByteArray &cmd); + void handleCnPhSuccess (QTcpSocket &socket, const QByteArray &cmd, + const QByteArray ¶ms); + void handleCnPhCalibrationData (QTcpSocket &socket, + const QByteArray ¶ms); + void handleCnPhCalibrationRawData (QTcpSocket &socket, + const QByteArray ¶ms); + void handleCnPhCalibratedBaseFrequency (QTcpSocket &socket, + const QByteArray ¶ms); + void handleCnStatusMessagingStart (QTcpSocket &socket, const QByteArray &cmd, + const QByteArray ¶ms); + void handleCnStatusMessagingStop (QTcpSocket &socket, const QByteArray &cmd); + /* CF_ configuration command handlers */ + void handleCfJcSetPurgeSettings (QTcpSocket &socket, + const QByteArray ¶ms); + void handleCfJcSetJettingParams (QTcpSocket &socket, + const QByteArray ¶ms); + void handleCfPhJettingParams (QTcpSocket &socket, const QByteArray &cmd, + const QByteArray ¶ms); + void handleCfJcSetter (QTcpSocket &socket, const QByteArray ¶ms); + void handleCfPhSetter (QTcpSocket &socket, const QByteArray ¶ms); + void handleCfJcGetter (QTcpSocket &socket, const QByteArray ¶ms); + void handleCfPhGetter (QTcpSocket &socket, const QByteArray ¶ms); + /* Imaging command handlers */ + void handleImagingStart (QTcpSocket &socket, const QByteArray ¶ms); + void handleImagingStop (QTcpSocket &socket); + void handleImagingMaskStart (QTcpSocket &socket, const QByteArray &cmd, + const QByteArray ¶ms); + void handleImagingMaskEnd (QTcpSocket &socket, const QByteArray &cmd, + const QByteArray ¶ms); + void handleImageCount (QTcpSocket &socket); + + QString m_host; + Port m_command; + Port m_imaging; + Port m_status; + QTimer m_tickTimer; + QTimer m_jcStatusTimer; + QTimer m_phStatusTimer; + int m_jcStatusLevel = 1; + int m_phStatusLevel = 1; + static bool s_wireDebug; +}; diff --git a/mock-jetting-controller/main.cpp b/mock-jetting-controller/main.cpp new file mode 100644 index 0000000..e5677fa --- /dev/null +++ b/mock-jetting-controller/main.cpp @@ -0,0 +1,30 @@ +/** + * @file main.cpp + * @brief Mock jetting controller on three protocol ports. + */ +#include "MockServer.h" + +#include +#include + +int +main (int argc, char *argv[]) +{ + qSetMessagePattern ("MockJC [%{time HH:mm:ss.zzz}] %{message}"); + + QCoreApplication app (argc, argv); + + QCommandLineParser parser; + parser.addOption ({ "wire-debug", "Log raw wire TX/RX to dev log" }); + parser.addOption ({ "host", "Target host to connect to (default 127.0.0.1)", + "address", "127.0.0.1" }); + parser.addHelpOption (); + parser.process (app); + + if (parser.isSet ("wire-debug")) + MockServer::enableWireDebug (); + + new MockServer (parser.value ("host"), &app); + + return app.exec (); +} diff --git a/mock-server/CMakeLists.txt b/mock-server/CMakeLists.txt deleted file mode 100644 index abcb65c..0000000 --- a/mock-server/CMakeLists.txt +++ /dev/null @@ -1,6 +0,0 @@ -qt_add_executable(Xpl2MockServer main.cpp MockServer.h MockServer.cpp) - -set_target_properties(Xpl2MockServer PROPERTIES RUNTIME_OUTPUT_DIRECTORY - "${PROJECT_BINARY_DIR}/bin") - -target_link_libraries(Xpl2MockServer PRIVATE Qt6::Core Qt6::Network) diff --git a/mock-server/MockServer.cpp b/mock-server/MockServer.cpp deleted file mode 100644 index 08eb116..0000000 --- a/mock-server/MockServer.cpp +++ /dev/null @@ -1,541 +0,0 @@ -/** - * @file MockServer.cpp - * @brief Mock XPL2 server — connects out to the client on three protocol - * ports. - */ -#include "MockServer.h" - -bool MockServer::s_wireDebug = false; - -void -MockServer::enableWireDebug () -{ - s_wireDebug = true; -} - -MockServer::MockServer (const QString &host, QObject *parent) - : QObject (parent), m_host (host) -{ - setupPort (m_command, "Command", 9110); - setupPort (m_imaging, "Imaging", 9111); - setupPort (m_status, "Status", 9112); - - connect (&m_tickTimer, &QTimer::timeout, this, &MockServer::tick); - m_tickTimer.start (1000); - - connect (&m_jcStatusTimer, &QTimer::timeout, this, - &MockServer::sendJcStatusMsg); - connect (&m_phStatusTimer, &QTimer::timeout, this, - &MockServer::sendPhStatusMsg); - - connectAll (); -} - -void -MockServer::setupPort (Port &port, const char *name, quint16 number) -{ - port.name = name; - port.number = number; - - connect (&port.socket, &QTcpSocket::connected, this, - &MockServer::onSocketConnected); - connect (&port.socket, &QTcpSocket::disconnected, this, - &MockServer::onSocketDisconnected); - connect (&port.socket, &QTcpSocket::readyRead, this, - &MockServer::onSocketMessageReady); - connect (&port.socket, &QAbstractSocket::errorOccurred, this, - [this, &port] (QAbstractSocket::SocketError) - { - qDebug ("%s Connection error: %s", - qPrintable (logTag (&port.socket)), - qPrintable (port.socket.errorString ())); - }); -} - -void -MockServer::connectAll () -{ - for (auto *port : { &m_command, &m_imaging, &m_status }) - if (port->socket.state () == QAbstractSocket::UnconnectedState) - port->socket.connectToHost (m_host, port->number); -} - -QString -MockServer::logTag (const QTcpSocket *socket) const -{ - const char *name = "Unknown"; - quint16 number = 0; - if (socket == &m_command.socket) - { - name = m_command.name; - number = m_command.number; - } - else if (socket == &m_imaging.socket) - { - name = m_imaging.name; - number = m_imaging.number; - } - else if (socket == &m_status.socket) - { - name = m_status.name; - number = m_status.number; - } - - /* Fixed-width tag: "[Command:9110]" = 14 chars, left-padded to 15. */ - return QStringLiteral ("[%1:%2]").arg (name).arg (number).leftJustified (15); -} - -void -MockServer::onSocketConnected () -{ - auto *socket = qobject_cast (sender ()); - qInfo ("%s Connected to client", qPrintable (logTag (socket))); -} - -void -MockServer::onSocketDisconnected () -{ - auto *socket = qobject_cast (sender ()); - qInfo ("%s Disconnected from client", qPrintable (logTag (socket))); -} - -void -MockServer::onSocketMessageReady () -{ - auto *socket = qobject_cast (sender ()); - Port *port = nullptr; - if (socket == &m_command.socket) - port = &m_command; - else if (socket == &m_imaging.socket) - port = &m_imaging; - else - port = &m_status; - - while (socket->canReadLine ()) - { - QByteArray line = socket->readLine (); - handleCommand (*port, line); - } -} - -void -MockServer::tick () -{ - /* Send KA_PING on connected sockets, retry connection on disconnected. */ - for (auto *port : { &m_command, &m_imaging, &m_status }) - { - if (port->socket.state () == QAbstractSocket::ConnectedState) - sendReply (port->socket, "KA_PING\n"); - else if (port->socket.state () == QAbstractSocket::UnconnectedState) - port->socket.connectToHost (m_host, port->number); - } -} - -void -MockServer::handleCommand (Port &port, const QByteArray &line) -{ - QByteArray trimmed = line.trimmed (); - if (trimmed.isEmpty ()) - return; - - /* Split on first comma to get command token and remaining params. */ - int comma = trimmed.indexOf (','); - QByteArray cmd = (comma >= 0) ? trimmed.left (comma) : trimmed; - QByteArray params = (comma >= 0) ? trimmed.mid (comma + 1) : QByteArray (); - - if (cmd == "KA_PING") - handleKaPing (port, params); - else if (cmd == "GS_JC_VERSION") - handleGsJcVersion (port.socket); - else if (cmd == "GS_PH_VERSION") - handleGsPhVersion (port.socket, params); - /* CN_ JC success shape (no params needed) */ - else if (cmd == "CN_JETTING_ALL_ON" || cmd == "CN_JETTING_ON" - || cmd == "CN_JETTING_OFF" || cmd == "CN_JC_ID_LED_ON" - || cmd == "CN_JC_ID_LED_OFF" || cmd == "CN_JC_CALIBRATION" - || cmd == "CN_JC_RESET_FAULT_CODES") - handleCnJcSuccess (port.socket, cmd); - /* CN_ PH success shape (params carry printheadId) */ - else if (cmd == "CN_PH_JETTING_ON" || cmd == "CN_PH_JETTING_OFF" - || cmd == "CN_PH_ID_LED_ON" || cmd == "CN_PH_ID_LED_OFF" - || cmd == "CN_PH_CALIBRATION" || cmd == "CN_PH_RESET_FAULT_CODES" - || cmd == "CN_PH_NOZZLES_DISABLED") - handleCnPhSuccess (port.socket, cmd, params); - /* CN_ custom-shape commands */ - else if (cmd == "CN_PH_CALIBRATION_DATA") - handleCnPhCalibrationData (port.socket, params); - else if (cmd == "CN_PH_CALIBRATION_RAW_DATA") - handleCnPhCalibrationRawData (port.socket, params); - else if (cmd == "CN_PH_CALIBRATED_BASE_FREQUENCY") - handleCnPhCalibratedBaseFrequency (port.socket, params); - /* CN_ status messaging (arrives on status port) */ - else if (cmd == "CN_JC_STATUS_MESSAGING_START" - || cmd == "CN_PH_STATUS_MESSAGING_START") - handleCnStatusMessagingStart (port.socket, cmd, params); - else if (cmd == "CN_JC_STATUS_MESSAGING_STOP" - || cmd == "CN_PH_STATUS_MESSAGING_STOP") - handleCnStatusMessagingStop (port.socket, cmd); - /* CF_ JC success shape */ - else if (cmd == "CF_PH_DEASSIGN_ID" || cmd == "CF_JC_SAVE_CALIBRATION" - || cmd == "CF_JC_RESET_CALIBRATION" - || cmd == "CF_JC_SWITCH_OFF_PURGE" - || cmd == "CF_JC_RESET_SETTINGS_ALL_PRINTHEADS" - || cmd == "CF_JC_REBOOT_ALL_PRINTHEADS" - || cmd == "CF_JC_RESET_CONTROLLER_SOFTWARE" - || cmd == "CF_JC_RESTART" || cmd == "CF_JC_SHUTDOWN" - || cmd == "CF_JC_SAVE_ALL_PRINTHEAD_SETTINGS") - handleCnJcSuccess (port.socket, cmd); - /* CF_ PH success shape */ - else if (cmd == "CF_PH_SET_ID" || cmd == "CF_PH_SAVE_CALIBRATION" - || cmd == "CF_PH_RESET_CALIBRATION" - || cmd == "CF_PH_RESET_ALL_SETTINGS" || cmd == "CF_PH_REBOOT" - || cmd == "CF_PH_SAVE_SETTINGS") - handleCnPhSuccess (port.socket, cmd, params); - /* CF_ custom-shape commands */ - else if (cmd == "CF_JC_SET_PURGE_SETTINGS") - handleCfJcSetPurgeSettings (port.socket, params); - else if (cmd == "CF_JC_SET_JETTING_PARAMS") - handleCfJcSetJettingParams (port.socket, params); - else if (cmd == "CF_PH_SET_JETTING_PARAMS") - handleCfPhJettingParams (port.socket, cmd, params); - else if (cmd == "CF_PH_GET_JETTING_PARAMS") - handleCfPhJettingParams (port.socket, cmd, params); - else if (cmd == "CF_JC_SETTER") - handleCfJcSetter (port.socket, params); - else if (cmd == "CF_PH_SETTER") - handleCfPhSetter (port.socket, params); - else if (cmd == "CF_JC_GETTER") - handleCfJcGetter (port.socket, params); - else if (cmd == "CF_PH_GETTER") - handleCfPhGetter (port.socket, params); - /* Imaging commands (port 2) */ - else if (cmd == "m2") - handleImagingStart (port.socket, params); - else if (cmd == "m4") - handleImagingStop (port.socket); - else if (cmd == "m0" || cmd == "m5") - handleImagingMaskStart (port.socket, cmd, params); - else if (cmd == "m1" || cmd == "m6") - handleImagingMaskEnd (port.socket, cmd, params); - else if (cmd == "m3") - handleImageCount (port.socket); - else - qWarning ("%s Unknown command: %s", qPrintable (logTag (&port.socket)), - cmd.constData ()); -} - -void -MockServer::sendReply (QTcpSocket &socket, const QByteArray &data) -{ - socket.write (data); - QByteArray trimmed = data.trimmed (); - int comma = trimmed.indexOf (','); - QByteArray cmd = (comma >= 0) ? trimmed.left (comma) : trimmed; - QByteArray wire; - if (s_wireDebug) - wire = " >> " + trimmed; - qDebug ("%s TX %s%s", qPrintable (logTag (&socket)), cmd.constData (), - wire.constData ()); -} - -void -MockServer::handleKaPing (Port &port, const QByteArray ¶ms) -{ - QByteArray wire; - if (s_wireDebug) - wire = " << KA_PING," + params; - qDebug ("%s RX KA_PING ACK%s", qPrintable (logTag (&port.socket)), - wire.constData ()); -} - -void -MockServer::handleGsJcVersion (QTcpSocket &socket) -{ - qDebug ("%s RX GS_JC_VERSION", qPrintable (logTag (&socket))); - sendReply (socket, "GS_JC_VERSION,1,\"1.05\",\"2.00\",15\n"); -} - -void -MockServer::handleGsPhVersion (QTcpSocket &socket, const QByteArray ¶ms) -{ - qDebug ("%s RX GS_PH_VERSION,%s", qPrintable (logTag (&socket)), - params.constData ()); - /* Echo back canned version info for whatever printhead ID was requested. */ - sendReply (socket, QByteArray ("GS_PH_VERSION,1,") + params - + ",\"3.10\",\"1.00\",\"Standard\"," - "\"2.05\",\"1.02\",\"0.9.1\"\n"); -} - -void -MockServer::handleCnJcSuccess (QTcpSocket &socket, const QByteArray &cmd) -{ - qDebug ("%s RX %s", qPrintable (logTag (&socket)), cmd.constData ()); - sendReply (socket, cmd + ",1,1\n"); -} - -void -MockServer::handleCnPhSuccess (QTcpSocket &socket, const QByteArray &cmd, - const QByteArray ¶ms) -{ - qDebug ("%s RX %s,%s", qPrintable (logTag (&socket)), cmd.constData (), - params.constData ()); - /* Extract printhead ID (first field before any comma). */ - int comma = params.indexOf (','); - QByteArray phId = (comma >= 0) ? params.left (comma) : params; - sendReply (socket, cmd + ",1," + phId + ",1\n"); -} - -void -MockServer::handleCnPhCalibrationData (QTcpSocket &socket, - const QByteArray ¶ms) -{ - qDebug ("%s RX CN_PH_CALIBRATION_DATA,%s", qPrintable (logTag (&socket)), - params.constData ()); - /* Reply: controllerId, phId, then 48 floats (-1 = uncalibrated). */ - QByteArray reply = "CN_PH_CALIBRATION_DATA,1," + params; - for (int i = 0; i < 48; ++i) - reply += ",-1"; - reply += '\n'; - sendReply (socket, reply); -} - -void -MockServer::handleCnPhCalibrationRawData (QTcpSocket &socket, - const QByteArray ¶ms) -{ - qDebug ("%s RX CN_PH_CALIBRATION_RAW_DATA,%s", qPrintable (logTag (&socket)), - params.constData ()); - QByteArray reply = "CN_PH_CALIBRATION_RAW_DATA,1," + params; - for (int i = 0; i < 48; ++i) - reply += ",-1"; - reply += '\n'; - sendReply (socket, reply); -} - -void -MockServer::handleCnPhCalibratedBaseFrequency (QTcpSocket &socket, - const QByteArray ¶ms) -{ - qDebug ("%s RX CN_PH_CALIBRATED_BASE_FREQUENCY,%s", - qPrintable (logTag (&socket)), params.constData ()); - sendReply (socket, - "CN_PH_CALIBRATED_BASE_FREQUENCY,1," + params + ",10.0,10.0\n"); -} - -void -MockServer::handleCnStatusMessagingStart (QTcpSocket &socket, - const QByteArray &cmd, - const QByteArray ¶ms) -{ - qDebug ("%s RX %s,%s", qPrintable (logTag (&socket)), cmd.constData (), - params.constData ()); - - /* Parse level and interval from params: "level,interval" */ - QList parts = params.split (','); - int level = parts.size () > 0 ? parts[0].toInt () : 1; - int interval = parts.size () > 1 ? parts[1].toInt () : 1000; - - if (cmd == "CN_JC_STATUS_MESSAGING_START") - { - m_jcStatusLevel = level; - m_jcStatusTimer.start (interval); - } - else - { - m_phStatusLevel = level; - m_phStatusTimer.start (interval); - } - - sendReply (socket, cmd + ",1," + params + ",1\n"); -} - -void -MockServer::handleCnStatusMessagingStop (QTcpSocket &socket, - const QByteArray &cmd) -{ - qDebug ("%s RX %s", qPrintable (logTag (&socket)), cmd.constData ()); - - if (cmd == "CN_JC_STATUS_MESSAGING_STOP") - m_jcStatusTimer.stop (); - else - m_phStatusTimer.stop (); - - sendReply (socket, cmd + ",1,1\n"); -} - -void -MockServer::handleCfJcSetPurgeSettings (QTcpSocket &socket, - const QByteArray ¶ms) -{ - qDebug ("%s RX CF_JC_SET_PURGE_SETTINGS,%s", qPrintable (logTag (&socket)), - params.constData ()); - sendReply (socket, "CF_JC_SET_PURGE_SETTINGS,1," + params + ",1\n"); -} - -void -MockServer::handleCfJcSetJettingParams (QTcpSocket &socket, - const QByteArray ¶ms) -{ - qDebug ("%s RX CF_JC_SET_JETTING_PARAMS,%s", qPrintable (logTag (&socket)), - params.constData ()); - sendReply (socket, "CF_JC_SET_JETTING_PARAMS,1," + params + ",1\n"); -} - -void -MockServer::handleCfPhJettingParams (QTcpSocket &socket, const QByteArray &cmd, - const QByteArray ¶ms) -{ - qDebug ("%s RX %s,%s", qPrintable (logTag (&socket)), cmd.constData (), - params.constData ()); - if (cmd == "CF_PH_GET_JETTING_PARAMS") - sendReply (socket, cmd + ",1," + params + ",0,0,0,0,0,1\n"); - else - sendReply (socket, cmd + ",1," + params + ",1\n"); -} - -void -MockServer::handleCfJcSetter (QTcpSocket &socket, const QByteArray ¶ms) -{ - qDebug ("%s RX CF_JC_SETTER,%s", qPrintable (logTag (&socket)), - params.constData ()); - sendReply (socket, "CF_JC_SETTER,1," + params + ",1\n"); -} - -void -MockServer::handleCfPhSetter (QTcpSocket &socket, const QByteArray ¶ms) -{ - qDebug ("%s RX CF_PH_SETTER,%s", qPrintable (logTag (&socket)), - params.constData ()); - sendReply (socket, "CF_PH_SETTER,1," + params + ",1\n"); -} - -void -MockServer::handleCfJcGetter (QTcpSocket &socket, const QByteArray ¶ms) -{ - qDebug ("%s RX CF_JC_GETTER,%s", qPrintable (logTag (&socket)), - params.constData ()); - sendReply (socket, "CF_JC_GETTER,1," + params + ",\"0\",1\n"); -} - -void -MockServer::handleCfPhGetter (QTcpSocket &socket, const QByteArray ¶ms) -{ - qDebug ("%s RX CF_PH_GETTER,%s", qPrintable (logTag (&socket)), - params.constData ()); - sendReply (socket, "CF_PH_GETTER,1," + params + ",\"0\",1\n"); -} - -void -MockServer::sendJcStatusMsg () -{ - if (m_status.socket.state () != QAbstractSocket::ConnectedState) - return; - - static int tick = 0; - ++tick; - bool odd = (tick % 2) != 0; - - /* Alternate between two value sets so the demo visually updates. - Level 1: cid, level, cpu, rail5v, canBus8v, temp, humidity, busCurrent, - onTimeSeconds */ - QByteArray level = QByteArray::number (m_jcStatusLevel); - QByteArray base = "EV_STATUS_MSG_JC,1," + level; - if (odd) - base += ",42.5,4.98,7.95,35.2,55.0,0.12,3600"; - else - base += ",58.3,5.02,8.05,37.8,48.5,0.15,3660"; - - if (m_jcStatusLevel >= 2) - { - if (odd) - base += ",\"192.168.1.100\",23.5,1,1,1,3,\"1.05\",\"2.00\"" - ",0,0,0,0,0,0"; - else - base += ",\"192.168.1.100\",24.1,1,1,1,3,\"1.05\",\"2.00\"" - ",1,0,1,0,1,0"; - } - base += '\n'; - - sendReply (m_status.socket, base); -} - -void -MockServer::sendPhStatusMsg () -{ - if (m_status.socket.state () != QAbstractSocket::ConnectedState) - return; - - static int tick = 0; - ++tick; - bool odd = (tick % 2) != 0; - - /* Alternate between two value sets. - Level 1: cid, level, phId, temp, humidity, mcuTemp, pdsV, mdsV, sysV, - eFuseI, nozzleI, vdd, 13×trip bools, dutyCycle, pwmFreq, drive, - nozzleDriveFreq, nozzleDriveDutyCycle, onTimeSeconds */ - QByteArray level = QByteArray::number (m_phStatusLevel); - QByteArray base = "EV_STATUS_MSG_PH,1," + level + ",1"; - if (odd) - base += ",38.5,45.0,40.2,36.0,24.0,3.3,0.05,0.02,3.3" - ",0,0,0,0,0,0,0,0,0,0,0,0,0" - ",50.0,10000.0,3,8000.0,50.0,1800"; - else - base += ",41.2,42.0,43.1,35.5,23.8,3.28,0.06,0.03,3.31" - ",0,0,0,0,0,0,0,0,0,0,0,0,0" - ",55.0,10500.0,4,8200.0,52.0,1860"; - - if (m_phStatusLevel >= 2) - { - if (odd) - base += ",0,\"MCU001\",\"FLASH001\",12345" - ",\"1.00\",\"3.10\",\"Standard\",\"1.00\",\"2.05\",\"0.9.1\"" - ",100.0,45.0,20.0,36.0,30.0,18.0,2.0,\"1.00\"" - ",0,0,0,0,0,0,0,0,0,0,0,0,0,0"; - else - base += ",0,\"MCU001\",\"FLASH001\",12345" - ",\"1.00\",\"3.10\",\"Standard\",\"1.00\",\"2.05\",\"0.9.1\"" - ",100.0,45.0,20.0,35.5,30.0,18.0,2.0,\"1.00\"" - ",1,-1,2,0,0,0,0,0,0,0,0,0,0,0"; - } - base += '\n'; - - sendReply (m_status.socket, base); -} - -void -MockServer::handleImagingStart (QTcpSocket &socket, const QByteArray ¶ms) -{ - qDebug ("%s RX m2,%s", qPrintable (logTag (&socket)), params.constData ()); - sendReply (socket, "n,\"A\"\n"); -} - -void -MockServer::handleImagingStop (QTcpSocket &socket) -{ - qDebug ("%s RX m4", qPrintable (logTag (&socket))); - sendReply (socket, "m4,1\n"); -} - -void -MockServer::handleImagingMaskStart (QTcpSocket &socket, const QByteArray &cmd, - const QByteArray ¶ms) -{ - qDebug ("%s RX %s,%s", qPrintable (logTag (&socket)), cmd.constData (), - params.constData ()); -} - -void -MockServer::handleImagingMaskEnd (QTcpSocket &socket, const QByteArray &cmd, - const QByteArray ¶ms) -{ - qDebug ("%s RX %s,%s", qPrintable (logTag (&socket)), cmd.constData (), - params.constData ()); - sendReply (socket, "n,\"A\"\n"); -} - -void -MockServer::handleImageCount (QTcpSocket &socket) -{ - qDebug ("%s RX m3", qPrintable (logTag (&socket))); - sendReply (socket, "n,\"0\"\n"); -} diff --git a/mock-server/MockServer.h b/mock-server/MockServer.h deleted file mode 100644 index 242cb32..0000000 --- a/mock-server/MockServer.h +++ /dev/null @@ -1,90 +0,0 @@ -/** - * @file MockServer.h - * @brief Mock XPL2 server — connects out to the client on three protocol - * ports. - */ -#pragma once - -#include -#include -#include - -class MockServer : public QObject -{ - Q_OBJECT - -public: - explicit MockServer (const QString &host, QObject *parent = nullptr); - static void enableWireDebug (); - -private slots: - void onSocketConnected (); - void onSocketDisconnected (); - void onSocketMessageReady (); - /* Send KA_PING on connected sockets, retry connection on disconnected. */ - void tick (); - void sendJcStatusMsg (); - void sendPhStatusMsg (); - -private: - struct Port - { - QTcpSocket socket; - const char *name = nullptr; - quint16 number = 0; - }; - - void setupPort (Port &port, const char *name, quint16 number); - void connectAll (); - /* Return a fixed-width "[Name:port]" tag for log lines. */ - QString logTag (const QTcpSocket *socket) const; - /* Parse incoming line, log RX, and dispatch to the matching handler. */ - void handleCommand (Port &port, const QByteArray &line); - void sendReply (QTcpSocket &socket, const QByteArray &data); - void handleKaPing (Port &port, const QByteArray ¶ms); - void handleGsJcVersion (QTcpSocket &socket); - void handleGsPhVersion (QTcpSocket &socket, const QByteArray ¶ms); - /* CN_ control command handlers */ - void handleCnJcSuccess (QTcpSocket &socket, const QByteArray &cmd); - void handleCnPhSuccess (QTcpSocket &socket, const QByteArray &cmd, - const QByteArray ¶ms); - void handleCnPhCalibrationData (QTcpSocket &socket, - const QByteArray ¶ms); - void handleCnPhCalibrationRawData (QTcpSocket &socket, - const QByteArray ¶ms); - void handleCnPhCalibratedBaseFrequency (QTcpSocket &socket, - const QByteArray ¶ms); - void handleCnStatusMessagingStart (QTcpSocket &socket, const QByteArray &cmd, - const QByteArray ¶ms); - void handleCnStatusMessagingStop (QTcpSocket &socket, const QByteArray &cmd); - /* CF_ configuration command handlers */ - void handleCfJcSetPurgeSettings (QTcpSocket &socket, - const QByteArray ¶ms); - void handleCfJcSetJettingParams (QTcpSocket &socket, - const QByteArray ¶ms); - void handleCfPhJettingParams (QTcpSocket &socket, const QByteArray &cmd, - const QByteArray ¶ms); - void handleCfJcSetter (QTcpSocket &socket, const QByteArray ¶ms); - void handleCfPhSetter (QTcpSocket &socket, const QByteArray ¶ms); - void handleCfJcGetter (QTcpSocket &socket, const QByteArray ¶ms); - void handleCfPhGetter (QTcpSocket &socket, const QByteArray ¶ms); - /* Imaging command handlers */ - void handleImagingStart (QTcpSocket &socket, const QByteArray ¶ms); - void handleImagingStop (QTcpSocket &socket); - void handleImagingMaskStart (QTcpSocket &socket, const QByteArray &cmd, - const QByteArray ¶ms); - void handleImagingMaskEnd (QTcpSocket &socket, const QByteArray &cmd, - const QByteArray ¶ms); - void handleImageCount (QTcpSocket &socket); - - QString m_host; - Port m_command; - Port m_imaging; - Port m_status; - QTimer m_tickTimer; - QTimer m_jcStatusTimer; - QTimer m_phStatusTimer; - int m_jcStatusLevel = 1; - int m_phStatusLevel = 1; - static bool s_wireDebug; -}; diff --git a/mock-server/main.cpp b/mock-server/main.cpp deleted file mode 100644 index 9a55463..0000000 --- a/mock-server/main.cpp +++ /dev/null @@ -1,30 +0,0 @@ -/** - * @file main.cpp - * @brief Mock XPL2 server on three protocol ports. - */ -#include "MockServer.h" - -#include -#include - -int -main (int argc, char *argv[]) -{ - qSetMessagePattern ("MockServer [%{time HH:mm:ss.zzz}] %{message}"); - - QCoreApplication app (argc, argv); - - QCommandLineParser parser; - parser.addOption ({ "wire-debug", "Log raw wire TX/RX to dev log" }); - parser.addOption ({ "host", "Target host to connect to (default 127.0.0.1)", - "address", "127.0.0.1" }); - parser.addHelpOption (); - parser.process (app); - - if (parser.isSet ("wire-debug")) - MockServer::enableWireDebug (); - - new MockServer (parser.value ("host"), &app); - - return app.exec (); -} -- cgit v1.2.3