A .NET 8 client library for the Domino A200+ inkjet printer Codenet protocol, shipped with a mock simulator, an automated test harness, and a console demo — so the whole thing can be evaluated end to end without a physical printer.
┌──────────────┐ TCP 7000 ┌──────────────────┐
│ Your app │ ────────────► │ A200+ printer │
│ + this SDK │ ◄──────────── │ (or the mock) │
└──────────────┘ 0x06/0x32 └──────────────────┘
用途限制 / Usage restriction 本仓库全部源代码仅供学习与技术验证,帮助理解 A200+ Codenet 字节级协议的集成与测试方法。 严禁用于任何商业用途、商业产品集成或生产环境。 如需商业部署,请获取设备厂商官方 SDK。 完整合规说明见下方「合规说明」一节。
This is NOT the official Domino SDK. It is not affiliated with, endorsed by, or supported by Domino Printing Sciences plc.
This demo is only for technical demonstration, not for commercial sale of Domino-related SDK.
核心约束 / Core constraint — 源代码仅限学习与技术验证 本仓库所有源代码仅供个人学习、技术研究与原型验证使用,明确禁止用于任何商业用途 (包括但不限于商业产品集成、对外提供服务、生产环境部署)。此约束为本仓库对使用者的 明示许可条件;超出此范围的任何使用均不在授权之内,相关法律责任由使用者自行承担。
Core constraint: all source code in this repository is provided for personal learning, technical study and prototyping only. Any commercial use — including integration into a commercial product, offering it as a service, or deployment in a production environment — is expressly prohibited. This is a condition of the permission granted by this repository; use outside it is not licensed, and the user bears all resulting legal responsibility.
本协议是通过网络抓包(packet capture)逆向工程(reverse engineering)反向分析得到的, 本项目仅用于学习和技术验证,不得用于商业用途。
The protocol described and implemented here was obtained solely by reverse engineering captured network traffic (packet sniffing of our own integration environment). No vendor manual, SDK, firmware image or confidential documentation was consulted, reproduced or distributed.
This project exists for learning and technical verification only: studying how a byte-level device protocol works, and validating that study in code. It must not be used for commercial purposes, nor relied upon for production integrations.
The code here is an independent interoperability study. It is published for educational and portfolio purposes so that others can see how a byte-level device protocol integration is structured and tested.
If you need a production integration, obtain the official vendor SDK from the manufacturer. This repository grants no rights to any Domino intellectual property, trademark or documentation — see LICENSE for the full statement.
| Project | Type | Purpose |
|---|---|---|
DominoA200Sdk |
Class library | The client you reference. Hides the transport (TCP or RS232 serial), framing, ACK/NAK handling, timeouts, FIFO mirroring and reconnect behind a small async API. No project dependencies, so it stands alone — see DominoA200Sdk/README.md for how to reference and use it. |
DominoMockServer |
Console app | Simulates an A200+ over TCP. No printer required. Logs all traffic as hex. |
DemoConsoleApp |
Console app | A short end-to-end demonstration of the SDK against the mock. |
DominoSdk.Harness |
xUnit project | Automated tests that start the mock themselves and assert on protocol behaviour. |
InkPrinterCode |
WinForms app | The host application this SDK work originated from. Independent of the library: neither project references the other. |
Only DominoA200Sdk is reusable elsewhere; the other four exist to demonstrate, test
and host it. InkPrinterCode carries its own copy of the protocol/transport layer, so
the SDK and the host can be evolved (or shipped) separately.
dotnet run --project DominoMockServer==============================================================
Domino A200+ Codenet Mock Simulator
--------------------------------------------------------------
Starting mock printer on 127.0.0.1:7000 ...
Emulated limits: FIFO capacity 3, print time 1500 ms.
dotnet run --project DemoConsoleAppYou will see the connection handshake, two batches of three jobs submitted and
acknowledged, a deliberate queue overflow (a fourth job) rejected with 0x15, and
the 0x32 completion events arriving asynchronously — with every byte printed as
raw hexadecimal:
[08:14:22.310] [TX ] 1B 49 31 32 04
[08:14:22.318] [RX ] 06
[08:14:22.325] [TX ] 1B 4F 45 30 30 30 30 30 04
[08:14:22.334] [RX ] 06
...
[08:14:22.511] [TX ] 1B 4F 45 30 30 32 30 32 30 32 36 2D 30 39 2D 31 35 20 41 42 43 30 30 30 30 30 31 04
[08:14:22.520] [RX ] 06
-> Submitted ABC000001 as JOB-000001 | Pending in FIFO: 1
...
[08:14:24.401] [RX ] 32
[event] Job finished, JobId:JOB-000001 Code:2026-09-15 ABC000001
dotnet testThe harness starts its own simulator on a free port, runs the cases, and shuts it down — nothing to start manually.
Add a project or package reference to DominoA200Sdk, then:
var printer = new DominoA200Client("127.0.0.1", 7000);
await printer.ConnectAsync();
// Subscribe before submitting, so a fast completion is not missed.
printer.OnJobCompleted += (sender, args) =>
{
Console.WriteLine($"Job finished, JobId:{args.JobId}");
};
var job = new PrintJob("2026-09-15", "ABC123456");
string jobId = await printer.SendPrintJobAsync(job);
int pending = await printer.GetFifoQueueCountAsync();
Console.WriteLine($"Pending jobs in FIFO: {pending}");
await printer.DisconnectAsync();The same code works over a serial link — swap the client and the constructor:
// COM3, 9600 baud, 8 data bits / 1 stop bit / no parity.
var printer = new DominoA200SerialClient("COM3", baudRate: 9600);
await printer.ConnectAsync();
// ... identical API from here ...
await printer.DisconnectAsync();- TCP packet sticking — coalesced and split reads are reassembled into frames.
- Frame encoding and framing — headers, terminators and the 4-digit length field.
- Length-aware parsing — an
OEframe's declared length is cross-checked against the actual terminator, so a corrupt length cannot silently truncate a code. - Payload validation — the code is checked before it reaches the wire (ASCII only,
at most 9999 characters, and free of the
0x04terminator). - Response handling — waits for
0x06, with a configurable timeout. - Rejection handling — a
0x15becomes aPrinterNackException. - Unsolicited completion events —
0x32is watched for continuously, independent of any request, and surfaced throughOnJobCompleted. - Disconnect detection and reconnect — optional, configurable background recovery.
| Type | Namespace | Role |
|---|---|---|
DominoA200Client |
DominoA200Sdk |
The entry point (TCP). |
DominoA200SerialClient |
DominoA200Sdk |
The entry point (RS232), same API. |
PrintJob |
DominoA200Sdk.Models |
A job to print. |
PrinterStatus |
DominoA200Sdk.Models |
A status snapshot. |
PrinterEventArgs |
DominoA200Sdk.Models |
Completion-event payload. |
PrinterNackException |
DominoA200Sdk.Exceptions |
Raised on 0x15. |
PrinterTimeoutException |
DominoA200Sdk.Exceptions |
Raised on no response. |
PrinterState / PrinterStateMachine |
DominoA200Sdk.Core |
State tracking. |
CodenetFrame / JobQueue |
DominoA200Sdk.Core |
Framing (with ValidatePrintJobPayload) and FIFO mirror. |
Full detail: Docs/ApiReference.md
A print job is the only frame that carries a variable-length payload, and its length field is the easiest thing to get wrong:
1B 4F 45 30 30 32 30 32 30 32 36 2D 30 39 2D 31 37 20 41 42 43 30 30 30 30 30 31 04
│ │ └──────────── 20 bytes of code text ────────────┘ │
│ └─ "0020" = 20, the count of the code text ONLY │
└─ 1B 4F 45 = ESC 'O' 'E' └─ 04
The declared length counts the code text alone — not the four length digits, not the
terminator. So a 20-character code yields a 28-byte frame (3 + 4 + 20 + 1).
Because the terminator is a plain byte with no escape mechanism, a code containing 0x04
would be cut short by any receiver that scans for the first terminator. The SDK refuses
such a payload up front instead of losing codes on the line. The same goes for non-ASCII
text and anything over 9999 characters. Full rules:
DominoA200Sdk/README.md
A short version of what was observed on the wire. Full notes, including byte-level examples: Docs/ProtocolNotes.md
| Response | Meaning |
|---|---|
0x06 |
Command accepted. For a job, it is now queued. |
0x15 |
Command refused. For a job, the queue is full. |
0x32 |
A print finished — pushed unsolicited, with no job id attached. |
Three behaviours are worth calling out, because they are what makes this integration non-trivial:
- The FIFO holds only three jobs. The fourth submission is refused with
0x15until one completes. Refusal is routine at production line rates, not an error. - Completion events are unsolicited and unidentified. A bare
0x32arrives whenever a print finishes, carrying no job id, so it must be correlated against a locally maintained queue. - The printer must be told to report completion. The setup frame
1B 49 31 32 04has to be sent first, or the printer prints silently and no completion event ever arrives.
InkPrinterCode/ ← repository root = solution root
├── README.md ← this file
├── LICENSE ← MIT, with the non-official-SDK notice
├── InkPrinterCode.slnx ← the solution (5 projects)
├── Docs
│ ├── ApiReference.md ← full API documentation
│ └── ProtocolNotes.md ← observed wire behaviour and byte examples
├── InkPrinterCode/ ← WinForms host application
├── DominoA200Sdk/ ← the client library
│ ├── README.md ← standalone usage guide (reference it, use it)
│ ├── Core
│ │ ├── CodenetFrame.cs
│ │ ├── PrinterStateMachine.cs
│ │ └── JobQueue.cs
│ ├── Models
│ │ ├── PrintJob.cs
│ │ ├── PrinterStatus.cs
│ │ └── PrinterEventArgs.cs
│ ├── Exceptions
│ │ ├── PrinterNackException.cs
│ │ └── PrinterTimeoutException.cs
│ ├── DominoA200Client.cs ← TCP/IP transport
│ └── DominoA200SerialClient.cs ← RS232 serial transport
├── DominoMockServer/ ← TCP simulator
│ ├── MockPrinter.cs
│ ├── MockFifoQueue.cs
│ ├── CodenetHandler.cs
│ └── Program.cs
├── DemoConsoleApp/ ← demonstration
│ └── Program.cs
└── DominoSdk.Harness/ ← automated tests (xUnit)
├── TestFixture
│ └── MockServerFixture.cs
└── TestCases
├── ConnectTests.cs
├── SendJobTests.cs
├── JobCompleteEventTests.cs
├── DisconnectReconnectTests.cs
└── FramingTests.cs ← pure byte-level framing (no mock needed)
The harness is the part that distinguishes this from a hand-written demo: rather than a human clicking buttons, the whole protocol path is asserted in code.
| Area | Cases |
|---|---|
| Connect | Session establishes and completes the handshake; handshake commands are actually issued; a dead port fails rather than hangs. |
| Send job | A job is acknowledged and queued; the fourth job is refused with PrinterNackException; empty code values are rejected client-side; payload formatting is correct. |
| Completion events | OnJobCompleted fires after the print delay with the correlated job id; the FIFO count drops as events are consumed. |
| Disconnect / reconnect | Disconnect raises its event once and updates state; repeated disconnect is idempotent; a fresh session works after disconnect and resets the mirror; the FIFO mirror reports capacity and clears correctly. |
| Framing (no mock needed) | The length field counts the code text only; 0x04 in a payload is rejected while 0x1B is allowed; non-ASCII and over-length codes throw; every frame type round-trips; a frame cut at any byte offset reassembles; a lying length field is refused rather than guessed; coalesced frames split in order; leading garbage is resynchronised. |
dotnet test- .NET 8 SDK (the library, mock, demo, tests and the
InkPrinterCodeWinForms host all target .NET 8; the host targetsnet8.0-windowsand needs the .NET 8 Desktop Runtime to run) - The SDK's only package is
System.IO.Ports(suppliesSerialPortfor the RS232 transport); the TCP client needs nothing beyond the base class library - Tests require the xUnit runner packages, restored automatically
This repository demonstrates technique: how to structure a device-protocol client, how to simulate the device so it can be tested, and how to prove the integration works automatically. It is deliberately published as a work sample rather than as a product.
For commercial deployment, use the manufacturer's official SDK, or implement against a protocol you hold the necessary rights to.