# QtXpl2 Qt 6 static library and QML module for communicating with **Alchemie Jetting Interface 2** (JI2) controllers over TCP. Implements the full XPL2 printhead remote protocol (56 command tokens) with a typed, signal-based API designed for Qt Quick applications. ## Requirements - Qt 6.10.2 (Core, Network, Qml, Quick) - CMake 3.16+ - Ninja (recommended) ## Build ```bash qt-cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug cmake --build build --parallel ``` Produces three targets: | Target | Binary | Description | |---|---|---| | `QtXpl2` | `libQtXpl2.a` | Static library + QML module (`import Xpl2`) | | `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/MockJettingController # Terminal 2 — run the demo ./build/bin/JettingInterfaceDemo ``` The demo connects to `127.0.0.1` by default. Click **Connect**, then explore the **Commands** and **Status** tabs. ### CLI flags | Flag | Description | |---|---| | `--printheads N` | Number of printheads in the demo UI (default 10) | | `--wire-debug` | Log raw wire bytes for both client and mock server | ## Architecture ### Three-port TCP client The JI2 protocol uses three independent TCP sockets: | Port | Name | Purpose | |---|---|---| | 9110 | Command | GS_, CN_, CF_ commands and responses | | 9111 | Imaging | m0-m6 image masks, m2/m4 start/stop, n replies | | 9112 | Status | KA_PING keepalive, EV_ events, status messaging | `Xpl2Client` manages all three connections as a QML singleton. ### Protocol implementation All 56 protocol tokens are implemented across 6 categories: | Category | Count | Direction | Examples | |---|---|---|---| | GS_ (Status) | 2 | Request/reply | `getJcVersion()`, `getPhVersion()` | | CN_ (Control) | 17 | Request/reply | `jettingAllOn()`, `jcCalibration()`, `phIdLedOn()` | | CF_ (Config) | 20 | Request/reply | `jcSetter()`, `phGetJettingParams()`, `jcShutdown()` | | Imaging (m/n) | 8 | Request/reply | `imagingStart()`, `imageMaskEnd()`, `imageCount()` | | KA_ (Keepalive) | 1 | Automatic | Invisible to QML, handled internally | | EV_ (Events) | 8 | Server-push | `jcStatusReceived()`, `phErrorCode()`, `shuttingDown()` | ### Data-driven dispatch Response handling uses a static dispatch table (`QHash`) mapping each command token to its response shape, minimum parameter count, and signal emitter. Adding a new command is one table entry: ```cpp { "CN_JETTING_ALL_ON", { ResponseShape::JcSuccess, 2, [](auto *s, const auto &p) { emit s->jettingAllOnResult(p[0].toInt(), p[1].toInt() == 1); } } }, ``` ## QML usage ```qml import Xpl2 // Xpl2Client is a singleton — no instantiation needed Button { text: "Connect" onClicked: { Xpl2Client.host = "192.168.1.100" Xpl2Client.connectToServer() } } // Bind to properties Label { text: "FW: " + Xpl2Client.firmwareVersion } // React to signals Connections { target: Xpl2Client function onJcStatusReceived(status) { // status is an Xpl2JcStatus Q_GADGET tempLabel.text = status.temperature.toFixed(1) + "°C" cpuLabel.text = status.cpuPercentageBusy.toFixed(1) + "%" } function onPhErrorCode(controllerId, printheadId, errorCode, params) { console.log("PH", printheadId, "error:", errorCode) } } ``` ### Status gadgets Periodic status messages are parsed into structured types with named properties: - **`Xpl2JcStatus`** — 21 fields (7 at level 1, 14 more at level 2): CPU%, voltages, temperature, humidity, indicators, firmware versions - **`Xpl2PhStatus`** — 59 fields (28 at level 1, 31 more at level 2): temperatures, voltages, trip flags, jetting params, gyro/accelerometer, purge state ### Extended result enums Configuration commands that can fail on specific parameters return typed enums visible in QML: - **`JettingParamsResult`** — `Ok` (1), `Failed` (0), `DutyCycle` (-1) through `NozzleDriveDutyCycle` (-5) - **`SetterResult`** — `Ok` (1), `Failed` (0), `IncorrectNewValue` (-1) ## Project structure ``` QtXpl2/ src/ Xpl2Client.h/cpp # QML singleton — typed API, dispatch table Xpl2Protocol.h/cpp # Wire framing, enums (Q_NAMESPACE) Xpl2JcStatus.h/cpp # Q_GADGET for JC status (Appendix A) Xpl2PhStatus.h/cpp # Q_GADGET for PH status (Appendix B) CMakeLists.txt 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-jetting-controller/ MockServer.h/cpp # Canned responses, periodic status emission main.cpp CMakeLists.txt docs/ protocol.pdf # Alchemie JI2-JC protocol specification ``` ## Mock server The mock server responds to all 56 protocol tokens with canned data. When status messaging is started (`CN_JC_STATUS_MESSAGING_START` / `CN_PH_STATUS_MESSAGING_START`), it emits periodic `EV_STATUS_MSG_JC` / `EV_STATUS_MSG_PH` events at the requested interval, alternating between two value sets so updates are visually apparent in the demo. ## Conventions - C++17, GNU code style (`.clang-format`) - QML: `.qmlformat.ini`, `pragma ComponentBehavior: Bound`, no unqualified access - Doc-comments on declarations (headers), not definitions (.cpp) - No external dependencies beyond Qt 6.10.2 - Prefer named functions/slots over lambdas