Fix PT-P710BT raster protocol handling and add regression coverage.

Align print command payload generation with model-specific protocol requirements, add verbose CLI diagnostics and robust status retries, and introduce protocol regression tests to prevent future GF/ESC i z regressions.

Made-with: Cursor
This commit is contained in:
knb
2026-04-17 03:24:00 +09:00
parent e92273a747
commit 42a785f086
8 changed files with 219 additions and 39 deletions

View File

@@ -25,6 +25,7 @@ configure_file(
set(LIBPTOUCH_SOURCES
src/lib/libptouch_core.c
src/lib/libptouch_usb.c
src/lib/libptouch_protocol.c
src/lib/libptouch_layout.c
src/lib/libptouch_media_info.c
src/lib/libptouch_trim.c
@@ -74,6 +75,19 @@ if(NOT MSVC)
target_compile_options(ptouch-print PRIVATE -Wall -Wextra -Wpedantic)
endif()
enable_testing()
add_executable(ptouch-protocol-regression-test
tests/protocol_regression_test.c
src/lib/libptouch_protocol.c
)
target_include_directories(ptouch-protocol-regression-test PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/src/lib"
)
if(NOT MSVC)
target_compile_options(ptouch-protocol-regression-test PRIVATE -Wall -Wextra -Wpedantic)
endif()
add_test(NAME protocol_regression_test COMMAND ptouch-protocol-regression-test)
install(TARGETS ptouch ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}")
install(TARGETS ptouch_shared LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}")

View File

@@ -8,8 +8,9 @@ Brother P-touch シリーズ向けのラベル印刷用 **C コアライブラ
## レイアウト
| パス | 内容 |
|------|------|
| ----------------------- | --------------------------------------------------------------------------- |
| `include/libptouch.h` | 公開 API |
| `src/lib/libptouch_*.c` | ライブラリ本体core / usb / print / status / png / svg |
| `src/cli/main.c` | `ptouch-print` エントリ |
@@ -17,6 +18,7 @@ Brother P-touch シリーズ向けのラベル印刷用 **C コアライブラ
| `ruby/` | Ruby FFI gem`libptouch`)・コマンド `ptouch-print-png`PNG のみ)— `ruby/README.md` |
| `reference/` | 仕様・参考資料(例: ラスター PDF |
## ビルド
依存: **CMake 3.16+**、**libusb-1.0**、**libpng**、**librsvg-2.0**(開発パッケージ例: `libusb-1.0-0-dev``libpng-dev``librsvg2-dev`)。
@@ -24,6 +26,7 @@ Brother P-touch シリーズ向けのラベル印刷用 **C コアライブラ
```bash
cmake -S . -B build
cmake --build build
ctest --test-dir build --output-on-failure
```
成果物(`build/` 以下):
@@ -96,7 +99,7 @@ cmake --build build
- 接続は **libusb-1.0** のみ。機種ごとに **VID/PID**`lsusb` 等)を調べ、`libptouch_open_usb_vid_pid` に渡すか、既定の PT-P900W なら `libptouch_open_usb` を使います。PT-P750W は **04f9:2062**、PT-P710BT は **04f9:20af**`include/libptouch.h` の定数参照)。
- PT-P750W / PT-P710BT ではラスター幅方向は **180 dpi**P900W は 360 dpi。PNG から印刷する場合は解像度に合わせて画像を用意してください。
- ラスターコマンドの詳細は **`reference/` の PDF** および Brother 公開資料に沿って `src/lib/libptouch_*.c` に実装してください。
- ラスターコマンドの詳細は `**reference/` の PDF** および Brother 公開資料に沿って `src/lib/libptouch_*.c` に実装してください。
### Ubuntu で sudo なしで USB を開くudev

View File

@@ -24,6 +24,7 @@ static void usage(const char *argv0)
" -t, --threshold N しきい値 0255既定 %u、PNG/SVG\n"
" --trim-right[=DOTS] 右側空白を削減(省略時は左余白、失敗時 0\n"
" -n, --dry-run 読み込みと check_raster のみUSB なし)\n"
" -v, --verbose 印刷前情報と印刷後ステータスを標準出力\n"
" -p, --pid HEX USB 製品 ID既定: P900W の 0x2085。例: P750W 0x2062、P710BT 0x20af\n"
" -S, --status ステータスを表示して終了\n"
" -V, --version バージョンを表示して終了\n"
@@ -101,6 +102,53 @@ static int read_file(const char *path, uint8_t **out, size_t *out_len)
return 0;
}
static void verbose_print_pre_print_info(libptouch_ctx *ctx,
const libptouch_raster_params_t *params,
size_t data_len)
{
if (!ctx || !params)
return;
printf("=== verbose: pre-print info ===\n");
printf("raster bytes: %zu\n", data_len);
printf("raster size: %ux%u dots (length x width)\n",
(unsigned)params->width_dots, (unsigned)params->height_dots);
printf("margin_mm: %u\n", (unsigned)params->margin_mm);
libptouch_media_info_t mi;
if (libptouch_get_current_media_info(ctx, &mi) == LIBPTOUCH_OK) {
printf("media width code: 0x%02X\n", (unsigned)mi.media_width_code);
printf("media kind code: 0x%02X\n", (unsigned)mi.media_kind_code);
printf("tape width: %.1f mm\n", mi.tape_width_mm);
printf("printable dots: %u\n", (unsigned)mi.printable_dots);
printf("left/right margins: %u/%u dots\n",
(unsigned)mi.left_margin_dots, (unsigned)mi.right_margin_dots);
printf("print/feed dpi: %.1f/%.1f\n", mi.print_dpi, mi.feed_dpi);
} else {
printf("media info: unavailable (%s)\n", libptouch_strerror(ctx));
}
uint8_t st[LIBPTOUCH_STATUS_LENGTH];
if (libptouch_get_status(ctx, st) == LIBPTOUCH_OK) {
libptouch_status_fprint(stdout, st);
} else {
printf("status: unavailable (%s)\n", libptouch_strerror(ctx));
}
}
static void verbose_print_post_print_status(libptouch_ctx *ctx, const char *label)
{
if (!ctx)
return;
printf("=== verbose: post-print status (%s) ===\n", label);
uint8_t st[LIBPTOUCH_STATUS_LENGTH];
if (libptouch_get_status(ctx, st) == LIBPTOUCH_OK) {
libptouch_status_fprint(stdout, st);
} else {
printf("status: unavailable (%s)\n", libptouch_strerror(ctx));
}
}
int main(int argc, char **argv)
{
const char *file = NULL;
@@ -108,6 +156,7 @@ int main(int argc, char **argv)
unsigned threshold = LIBPTOUCH_PNG_DEFAULT_THRESHOLD;
unsigned usb_pid_arg = 0;
int dry_run = 0;
int verbose = 0;
int trim_right_enabled = 0;
int trim_right_auto = 0;
unsigned trim_right_dots = 0;
@@ -120,6 +169,7 @@ int main(int argc, char **argv)
{ "threshold", required_argument, NULL, 't' },
{ "trim-right", optional_argument, NULL, 'r' },
{ "dry-run", no_argument, NULL, 'n' },
{ "verbose", no_argument, NULL, 'v' },
{ "pid", required_argument, NULL, 'p' },
{ "status", no_argument, NULL, 'S' },
{ "version", no_argument, NULL, 'V' },
@@ -128,7 +178,7 @@ int main(int argc, char **argv)
};
int c;
while ((c = getopt_long(argc, argv, "w:H:f:t:r::p:nhSV", longopts, NULL)) !=
while ((c = getopt_long(argc, argv, "w:H:f:t:r::p:nvhSV", longopts, NULL)) !=
-1) {
switch (c) {
case 'w':
@@ -151,6 +201,9 @@ int main(int argc, char **argv)
case 'n':
dry_run = 1;
break;
case 'v':
verbose = 1;
break;
case 'r':
trim_right_enabled = 1;
if (!optarg) {
@@ -397,8 +450,13 @@ int main(int argc, char **argv)
}
}
if (verbose)
verbose_print_pre_print_info(ctx, &params, data_len);
e = libptouch_print_raster(ctx, data, data_len, &params);
if (e != LIBPTOUCH_OK) {
if (verbose)
verbose_print_post_print_status(ctx, "print failed");
fprintf(stderr, "print_raster: %s\n",
libptouch_strerror(ctx));
libptouch_close(ctx);
@@ -410,6 +468,9 @@ int main(int argc, char **argv)
return 1;
}
if (verbose)
verbose_print_post_print_status(ctx, "print success");
libptouch_close(ctx);
libptouch_destroy(ctx);
if (data_from_lib)

View File

@@ -7,6 +7,7 @@
#include "libptouch_internal.h"
#include "libptouch_layout.h"
#include "libptouch_protocol.h"
#include <libusb.h>
#include <stdio.h>
@@ -102,7 +103,7 @@ libptouch_err_t libptouch_print_raster(libptouch_ctx *ctx,
(void)right_dots;
unsigned head_dots = prof->head_width_dots;
size_t line_payload = (size_t)((head_dots + 7u) / 8u);
size_t line_payload = ptouch_line_payload_bytes(head_dots);
size_t gf_packet = 3u + line_payload;
uint32_t wd = params->width_dots;
@@ -139,21 +140,6 @@ libptouch_err_t libptouch_print_raster(libptouch_ctx *ctx,
}
uint32_t lines = ht;
uint8_t n5 = (uint8_t)(lines & 0xFFu);
uint8_t n6 = (uint8_t)((lines >> 8) & 0xFFu);
uint8_t n7 = (uint8_t)((lines >> 16) & 0xFFu);
uint8_t n8 = (uint8_t)((lines >> 24) & 0xFFu);
uint8_t n2_paper = 0x09u;
if (media_kind == 0x03u)
n2_paper = 0x00u;
else if (media_kind == 0x11u)
n2_paper = 0x11u;
else if (media_kind == 0x17u)
n2_paper = 0x17u;
else if (media_kind == 0x13u)
n2_paper = 0x13u;
uint8_t head[256];
size_t pos = 0;
memset(head + pos, 0, 200);
@@ -165,18 +151,28 @@ libptouch_err_t libptouch_print_raster(libptouch_ctx *ctx,
memcpy(head + pos, raster_mode, sizeof(raster_mode));
pos += sizeof(raster_mode);
uint8_t esc_iz[] = { 0x1B, 0x69, 0x7A, 0x0Eu, n2_paper, media_w,
0x00u, n5, n6, n7, n8, 0x02u, 0x00u };
uint8_t esc_iz[13];
ptouch_fill_esc_iz(esc_iz, media_kind, media_w, lines);
memcpy(head + pos, esc_iz, sizeof(esc_iz));
pos += sizeof(esc_iz);
static const uint8_t esc_im[] = { 0x1B, 0x69, 0x4D, 0x40 };
memcpy(head + pos, esc_im, sizeof(esc_im));
pos += sizeof(esc_im);
/*
* ESC i A ("cut each n labels") is not supported on PT-P710BT class
* devices. Sending it can trigger a communication error (red LED blink).
* Keep it only for 560-dot family.
*/
if (prof->head_width_dots > 128u) {
static const uint8_t esc_ia[] = { 0x1B, 0x69, 0x41, 0x01 };
memcpy(head + pos, esc_ia, sizeof(esc_ia));
pos += sizeof(esc_ia);
static const uint8_t esc_ik[] = { 0x1B, 0x69, 0x4B, 0x0C };
}
/* Chain printing off; safer default for 128-dot family as well. */
static const uint8_t esc_ik[] = { 0x1B, 0x69, 0x4B, 0x08 };
memcpy(head + pos, esc_ik, sizeof(esc_ik));
pos += sizeof(esc_ik);
@@ -199,7 +195,6 @@ libptouch_err_t libptouch_print_raster(libptouch_ctx *ctx,
}
size_t row_b = ((size_t)wd + 7u) / 8u;
static const uint8_t g_hdr[] = { 0x47, 0x46, 0x00 };
uint8_t *gbuf = (uint8_t *)malloc(gf_packet);
if (!gbuf) {
@@ -213,7 +208,7 @@ libptouch_err_t libptouch_print_raster(libptouch_ctx *ctx,
const uint8_t *row = src + (size_t)y * row_b;
pack_line(gbuf + 3, line_payload, head_dots, row, wd, left_dots,
print_dots);
memcpy(gbuf, g_hdr, sizeof(g_hdr));
ptouch_fill_gf_header(gbuf, line_payload);
v = ptouch_bulk_send_job(ctx, gbuf, gf_packet, "raster line");
if (v != LIBPTOUCH_OK) {
free(gbuf);

View File

@@ -0,0 +1,41 @@
#include "libptouch_protocol.h"
size_t ptouch_line_payload_bytes(unsigned head_dots)
{
return (size_t)((head_dots + 7u) / 8u);
}
void ptouch_fill_gf_header(uint8_t out[3], size_t line_payload_bytes)
{
out[0] = 0x47u;
out[1] = (uint8_t)(line_payload_bytes & 0xFFu);
out[2] = (uint8_t)((line_payload_bytes >> 8) & 0xFFu);
}
void ptouch_fill_esc_iz(uint8_t out[13], uint8_t media_kind, uint8_t media_width,
uint32_t raster_lines)
{
uint8_t n2_paper = 0x09u;
if (media_kind == 0x03u)
n2_paper = 0x00u;
else if (media_kind == 0x11u)
n2_paper = 0x11u;
else if (media_kind == 0x17u)
n2_paper = 0x17u;
else if (media_kind == 0x13u)
n2_paper = 0x13u;
out[0] = 0x1Bu;
out[1] = 0x69u;
out[2] = 0x7Au;
out[3] = 0x0Eu;
out[4] = n2_paper;
out[5] = media_width;
out[6] = 0x00u;
out[7] = (uint8_t)(raster_lines & 0xFFu);
out[8] = (uint8_t)((raster_lines >> 8) & 0xFFu);
out[9] = (uint8_t)((raster_lines >> 16) & 0xFFu);
out[10] = (uint8_t)((raster_lines >> 24) & 0xFFu);
out[11] = 0x00u; /* first page */
out[12] = 0x00u; /* fixed */
}

View File

@@ -0,0 +1,12 @@
#ifndef LIBPTOUCH_PROTOCOL_H
#define LIBPTOUCH_PROTOCOL_H
#include <stddef.h>
#include <stdint.h>
size_t ptouch_line_payload_bytes(unsigned head_dots);
void ptouch_fill_gf_header(uint8_t out[3], size_t line_payload_bytes);
void ptouch_fill_esc_iz(uint8_t out[13], uint8_t media_kind, uint8_t media_width,
uint32_t raster_lines);
#endif /* LIBPTOUCH_PROTOCOL_H */

View File

@@ -9,6 +9,7 @@
#include <stdio.h>
#include <string.h>
#include <unistd.h>
libptouch_err_t libptouch_get_status(libptouch_ctx *ctx, uint8_t *status)
{
@@ -28,18 +29,26 @@ libptouch_err_t libptouch_get_status(libptouch_ctx *ctx, uint8_t *status)
static const uint8_t req[] = { 0x1B, 0x69, 0x53 };
int r = LIBUSB_ERROR_OTHER;
for (int attempt = 0; attempt < 2; attempt++) {
if (attempt > 0) {
for (int attempt = 0; attempt < 5; attempt++) {
/* First status read after reconnect can fail on some P-touch units. */
(void)libusb_clear_halt(h, ctx->bulk_in_ep);
(void)libusb_clear_halt(h, ctx->bulk_out_ep);
if (attempt > 0) {
usleep(120000); /* 120ms backoff */
}
r = ptouch_bulk_out(h, ctx->bulk_out_ep, init, 2, 5000u);
if (r != 0) {
if (r == LIBUSB_ERROR_IO || r == LIBUSB_ERROR_PIPE ||
r == LIBUSB_ERROR_TIMEOUT)
continue;
ptouch_set_error_usb(ctx, r, "bulk OUT ESC @");
return LIBPTOUCH_ERR_USB;
}
r = ptouch_bulk_out(h, ctx->bulk_out_ep, req, 3, 5000u);
if (r != 0) {
if (r == LIBUSB_ERROR_IO || r == LIBUSB_ERROR_PIPE ||
r == LIBUSB_ERROR_TIMEOUT)
continue;
ptouch_set_error_usb(ctx, r, "bulk OUT ESC i S");
return LIBPTOUCH_ERR_USB;
}
@@ -47,7 +56,8 @@ libptouch_err_t libptouch_get_status(libptouch_ctx *ctx, uint8_t *status)
(int)LIBPTOUCH_STATUS_LENGTH, 5000u);
if (r == 0)
break;
if (r != LIBUSB_ERROR_IO && r != LIBUSB_ERROR_PIPE)
if (r != LIBUSB_ERROR_IO && r != LIBUSB_ERROR_PIPE &&
r != LIBUSB_ERROR_TIMEOUT)
break;
}
if (r != 0) {

View File

@@ -0,0 +1,44 @@
#include "libptouch_protocol.h"
#include <stdio.h>
#include <stdlib.h>
static int expect_int(const char *name, int got, int want)
{
if (got == want)
return 0;
fprintf(stderr, "%s mismatch: got=%d want=%d\n", name, got, want);
return 1;
}
int main(void)
{
int fail = 0;
fail |= expect_int("line_payload_128", (int)ptouch_line_payload_bytes(128u), 16);
fail |= expect_int("line_payload_560", (int)ptouch_line_payload_bytes(560u), 70);
uint8_t gf[3];
ptouch_fill_gf_header(gf, 16u);
fail |= expect_int("gf16_cmd", gf[0], 0x47);
fail |= expect_int("gf16_n1", gf[1], 0x10);
fail |= expect_int("gf16_n2", gf[2], 0x00);
ptouch_fill_gf_header(gf, 70u);
fail |= expect_int("gf70_cmd", gf[0], 0x47);
fail |= expect_int("gf70_n1", gf[1], 0x46);
fail |= expect_int("gf70_n2", gf[2], 0x00);
uint8_t iz[13];
ptouch_fill_esc_iz(iz, 0x01u, 0x0Cu, 70u);
fail |= expect_int("iz_cmd_0", iz[0], 0x1B);
fail |= expect_int("iz_cmd_1", iz[1], 0x69);
fail |= expect_int("iz_cmd_2", iz[2], 0x7A);
fail |= expect_int("iz_media_kind_map", iz[4], 0x09);
fail |= expect_int("iz_media_width", iz[5], 0x0C);
fail |= expect_int("iz_lines_lsb", iz[7], 70);
fail |= expect_int("iz_page_index", iz[11], 0x00);
fail |= expect_int("iz_last_fixed", iz[12], 0x00);
return fail ? EXIT_FAILURE : EXIT_SUCCESS;
}