aboutsummaryrefslogtreecommitdiffstats

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

qt-cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug
cmake --build build --parallel

Produces four targets:

Target Binary Description
QtXpl2 libQtXpl2.a Static library + QML module (import Xpl2)
JettingProxy bin/JettingProxy TCP relay between one controller and N clients
JettingInterfaceDemo bin/JettingInterfaceDemo Interactive demo app
MockJettingController bin/MockJettingController Mock JI2 server for development

Quick start

# Terminal 1 — start the proxy
./build/bin/JettingProxy

# Terminal 2 — start the mock server (connects to proxy on 9110-9112)
./build/bin/MockJettingController

# Terminal 3 — run the demo (connects to proxy on 9210-9212)
./build/bin/JettingInterfaceDemo

Click Connect in the demo, then explore the Commands and Status tabs. Multiple demo instances can connect simultaneously.

CLI flags

Binary Flag Description
JettingInterfaceDemo --printheads N Number of printheads in the demo UI (default 10)
JettingProxy --controller-port P Base port for controller side (default 9110)
JettingProxy --client-port P Base port for client side (default 9210)
All --wire-debug Log raw wire bytes / forwarded frames

Architecture

Proxy architecture

A JettingProxy relay sits between the controller and N client instances:

MockController ──(9110/9111/9112)──→ JettingProxy ←──(9210/9211/9212)── Xpl2Client #1
                                                  ←──(9210/9211/9212)── Xpl2Client #2

The proxy broadcasts controller frames to all clients and forwards client frames to the controller. KA_PING is handled independently on both sides (never forwarded).

Three-port TCP protocol

The JI2 protocol uses three independent TCP channels:

Controller port Client port Name Purpose
9110 9210 Command GS_, CN_, CF_ commands and responses
9111 9211 Imaging m0-m6 image masks, m2/m4 start/stop, n replies
9112 9212 Status EV_ events, status messaging

Xpl2Client connects out to the proxy on the client ports 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:

{ "CN_JETTING_ALL_ON", { ResponseShape::JcSuccess, 2,
    [](auto *s, const auto &p) {
        emit s->jettingAllOnResult(p[0].toInt(), p[1].toInt() == 1);
    } } },

QML usage

import Xpl2

// Xpl2Client is a singleton — no instantiation needed
Button {
    text: "Connect"
    onClicked: {
        Xpl2Client.host = "192.168.1.100"
        Xpl2Client.commandPort = 9210
        Xpl2Client.connectToProxy()
    }
}

// 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:

  • JettingParamsResultOk (1), Failed (0), DutyCycle (-1) through NozzleDriveDutyCycle (-5)
  • SetterResultOk (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
  jetting-proxy/
    JettingProxy.h/cpp       # Transparent TCP relay (1 controller ↔ N clients)
    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

Jetting proxy

The proxy (JettingProxy) is a transparent TCP relay that enables multiple clients to share a single controller connection. It operates at the raw frame level — it reads LF-terminated lines and forwards them without parsing the protocol. KA_PING is handled independently on both sides to prevent N clients from each replying to the controller.

Mock server

The mock server responds to all 56 protocol tokens with canned data. It connects to the proxy (or directly to a listening client) on ports 9110-9112. 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