diff options
Diffstat (limited to 'README.md')
| -rw-r--r-- | README.md | 167 |
1 files changed, 167 insertions, 0 deletions
diff --git a/README.md b/README.md new file mode 100644 index 0000000..f82cf68 --- /dev/null +++ b/README.md @@ -0,0 +1,167 @@ +# 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`) | +| `QtXpl2Demo` | `bin/QtXpl2Demo` | Interactive demo app | +| `Xpl2MockServer` | `bin/Xpl2MockServer` | Mock JI2 server for development | + +## Quick start + +```bash +# Terminal 1 — start the mock server +./build/bin/Xpl2MockServer + +# Terminal 2 — run the demo +./build/bin/QtXpl2Demo +``` + +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<QByteArray, ResponseEntry>`) 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 + demo/ + 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/ + 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 |
