Fix print completion flow for PT-P710BT and PT-P900W.

Align raster protocol bytes and print-end sequencing by printer family, add safer status polling/retry behavior, and document the changes with regression coverage to prevent protocol regressions.

Made-with: Cursor
This commit is contained in:
knb
2026-04-17 05:17:22 +09:00
parent 779a50747d
commit d2fd6cc1f9
8 changed files with 98 additions and 28 deletions

19
CHANGELOG.md Normal file
View File

@@ -0,0 +1,19 @@
# Changelog
## Unreleased
- Fix PT-P710BT/PT-P900W print completion flow and protocol bytes.
- Correct `G` raster transfer header length (`n1/n2`) from per-model payload size.
- Fix `ESC i z` page index (`n9`) and adjust print-information flags/media kind handling.
- Keep `ESC i A` disabled on 128-dot family and split `ESC i K` by head width.
- Use `0x0C -> 0x1A` end sequence on 560-dot family to complete feed/cut reliably.
- Improve status handling around print completion.
- Retry status reads with backoff and safer command ordering.
- Add verbose post-print short polling until print-end status is observed.
- Add small wait before `--status` command retrieval.
- Add protocol regression safeguards.
- Introduce `libptouch_protocol` helpers for shared command-byte generation.
- Add `protocol_regression_test` and wire it into CTest.
- Update documentation.
- Add `ctest` step to build instructions.
- Link to changelog from README.

View File

@@ -121,3 +121,7 @@ sudo usermod -aG plugdev "$USER"
## ライセンス ## ライセンス
[MIT License](LICENSE)`LICENSE` ファイルを参照)。 [MIT License](LICENSE)`LICENSE` ファイルを参照)。
## 変更履歴
リリース間の変更点は `CHANGELOG.md` を参照してください。

View File

@@ -13,6 +13,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <strings.h> #include <strings.h>
#include <unistd.h>
static void usage(const char *argv0) static void usage(const char *argv0)
{ {
@@ -141,13 +142,29 @@ static void verbose_print_post_print_status(libptouch_ctx *ctx, const char *labe
if (!ctx) if (!ctx)
return; return;
printf("=== verbose: post-print status (%s) ===\n", label); printf("=== verbose: post-print status (%s) ===\n", label);
/*
* 印刷直後は phase change (status[18] != 0x00) が続くことがあるため、
* 短時間ポーリングで最終状態まで追跡する。
*/
const int max_polls = 12; /* ~2.4s */
const useconds_t poll_interval_us = 200000;
for (int i = 0; i < max_polls; i++) {
uint8_t st[LIBPTOUCH_STATUS_LENGTH]; uint8_t st[LIBPTOUCH_STATUS_LENGTH];
if (libptouch_get_status(ctx, st) == LIBPTOUCH_OK) { if (libptouch_get_status(ctx, st) != LIBPTOUCH_OK) {
libptouch_status_fprint(stdout, st); printf("status poll %d/%d: unavailable (%s)\n", i + 1,
max_polls, libptouch_strerror(ctx));
} else { } else {
printf("status: unavailable (%s)\n", libptouch_strerror(ctx)); printf("status poll %d/%d:\n", i + 1, max_polls);
libptouch_status_fprint(stdout, st);
if (st[18] == 0x00u) {
printf("post-print polling settled: print end.\n");
return;
} }
} }
usleep(poll_interval_us);
}
printf("post-print polling timeout: phase did not settle.\n");
}
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
@@ -261,6 +278,10 @@ int main(int argc, char **argv)
libptouch_destroy(sctx); libptouch_destroy(sctx);
return 1; return 1;
} }
/* 印刷直後などは内部処理中で最初のステータス応答が不安定なことがあるため、
* 少し待ってからステータスを取りに行く。 */
usleep(300000); /* 300ms */
uint8_t st[LIBPTOUCH_STATUS_LENGTH]; uint8_t st[LIBPTOUCH_STATUS_LENGTH];
se = libptouch_get_status(sctx, st); se = libptouch_get_status(sctx, st);
if (se != LIBPTOUCH_OK) { if (se != LIBPTOUCH_OK) {

View File

@@ -171,8 +171,7 @@ libptouch_err_t libptouch_print_raster(libptouch_ctx *ctx,
pos += sizeof(esc_ia); pos += sizeof(esc_ia);
} }
/* Chain printing off; safer default for 128-dot family as well. */ uint8_t esc_ik[] = { 0x1B, 0x69, 0x4B, ptouch_esc_ik_value(head_dots) };
static const uint8_t esc_ik[] = { 0x1B, 0x69, 0x4B, 0x08 };
memcpy(head + pos, esc_ik, sizeof(esc_ik)); memcpy(head + pos, esc_ik, sizeof(esc_ik));
pos += sizeof(esc_ik); pos += sizeof(esc_ik);
@@ -218,6 +217,21 @@ libptouch_err_t libptouch_print_raster(libptouch_ctx *ctx,
} }
free(gbuf); free(gbuf);
/*
* Some 560-dot family devices are sensitive to end-of-page sequencing.
* Send FF (0x0C) then Control-Z (0x1A) on that family to explicitly
* terminate page and final feed/cut.
*/
if (head_dots > 128u) {
static const uint8_t page_end[] = { 0x0C };
v = ptouch_bulk_send_job(ctx, page_end, sizeof(page_end),
"print page end");
if (v != LIBPTOUCH_OK) {
free(transposed);
return v;
}
}
static const uint8_t print_end[] = { 0x1A }; static const uint8_t print_end[] = { 0x1A };
v = ptouch_bulk_send_job(ctx, print_end, sizeof(print_end), "print end"); v = ptouch_bulk_send_job(ctx, print_end, sizeof(print_end), "print end");
if (v != LIBPTOUCH_OK) { if (v != LIBPTOUCH_OK) {

View File

@@ -15,21 +15,13 @@ 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, void ptouch_fill_esc_iz(uint8_t out[13], uint8_t media_kind, uint8_t media_width,
uint32_t raster_lines) 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[0] = 0x1Bu;
out[1] = 0x69u; out[1] = 0x69u;
out[2] = 0x7Au; out[2] = 0x7Au;
out[3] = 0x0Eu; /* PI flags: enable media/width/length with quality bit for broad compatibility. */
out[4] = n2_paper; out[3] = 0x8Eu;
/* Use status media-kind byte directly (e.g., 0x01 laminated, 0x03 non-laminate). */
out[4] = media_kind;
out[5] = media_width; out[5] = media_width;
out[6] = 0x00u; out[6] = 0x00u;
out[7] = (uint8_t)(raster_lines & 0xFFu); out[7] = (uint8_t)(raster_lines & 0xFFu);
@@ -39,3 +31,9 @@ void ptouch_fill_esc_iz(uint8_t out[13], uint8_t media_kind, uint8_t media_width
out[11] = 0x00u; /* first page */ out[11] = 0x00u; /* first page */
out[12] = 0x00u; /* fixed */ out[12] = 0x00u; /* fixed */
} }
uint8_t ptouch_esc_ik_value(unsigned head_dots)
{
/* Preserve legacy 560-dot behaviour (0x0C), 128-dot uses 0x08. */
return head_dots > 128u ? 0x0Cu : 0x08u;
}

View File

@@ -8,5 +8,6 @@ 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_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, void ptouch_fill_esc_iz(uint8_t out[13], uint8_t media_kind, uint8_t media_width,
uint32_t raster_lines); uint32_t raster_lines);
uint8_t ptouch_esc_ik_value(unsigned head_dots);
#endif /* LIBPTOUCH_PROTOCOL_H */ #endif /* LIBPTOUCH_PROTOCOL_H */

View File

@@ -36,6 +36,13 @@ libptouch_err_t libptouch_get_status(libptouch_ctx *ctx, uint8_t *status)
if (attempt > 0) { if (attempt > 0) {
usleep(120000); /* 120ms backoff */ usleep(120000); /* 120ms backoff */
} }
/*
* Avoid unconditional ESC @ here:
* some models can be in phase-change right after print end, and
* re-initializing there may disturb cutter/feed completion.
* Try plain ESC i S first, use ESC @ only on retry paths.
*/
if (attempt > 0) {
r = ptouch_bulk_out(h, ctx->bulk_out_ep, init, 2, 5000u); r = ptouch_bulk_out(h, ctx->bulk_out_ep, init, 2, 5000u);
if (r != 0) { if (r != 0) {
if (r == LIBUSB_ERROR_IO || r == LIBUSB_ERROR_PIPE || if (r == LIBUSB_ERROR_IO || r == LIBUSB_ERROR_PIPE ||
@@ -44,6 +51,7 @@ libptouch_err_t libptouch_get_status(libptouch_ctx *ctx, uint8_t *status)
ptouch_set_error_usb(ctx, r, "bulk OUT ESC @"); ptouch_set_error_usb(ctx, r, "bulk OUT ESC @");
return LIBPTOUCH_ERR_USB; return LIBPTOUCH_ERR_USB;
} }
}
r = ptouch_bulk_out(h, ctx->bulk_out_ep, req, 3, 5000u); r = ptouch_bulk_out(h, ctx->bulk_out_ep, req, 3, 5000u);
if (r != 0) { if (r != 0) {
if (r == LIBUSB_ERROR_IO || r == LIBUSB_ERROR_PIPE || if (r == LIBUSB_ERROR_IO || r == LIBUSB_ERROR_PIPE ||

View File

@@ -34,11 +34,16 @@ int main(void)
fail |= expect_int("iz_cmd_0", iz[0], 0x1B); fail |= expect_int("iz_cmd_0", iz[0], 0x1B);
fail |= expect_int("iz_cmd_1", iz[1], 0x69); fail |= expect_int("iz_cmd_1", iz[1], 0x69);
fail |= expect_int("iz_cmd_2", iz[2], 0x7A); fail |= expect_int("iz_cmd_2", iz[2], 0x7A);
fail |= expect_int("iz_media_kind_map", iz[4], 0x09); fail |= expect_int("iz_n1_flags", iz[3], 0x8E);
fail |= expect_int("iz_media_kind_passthrough", iz[4], 0x01);
fail |= expect_int("iz_media_width", iz[5], 0x0C); fail |= expect_int("iz_media_width", iz[5], 0x0C);
fail |= expect_int("iz_lines_lsb", iz[7], 70); fail |= expect_int("iz_lines_lsb", iz[7], 70);
fail |= expect_int("iz_page_index", iz[11], 0x00); fail |= expect_int("iz_page_index", iz[11], 0x00);
fail |= expect_int("iz_last_fixed", iz[12], 0x00); fail |= expect_int("iz_last_fixed", iz[12], 0x00);
/* ESC i K mode byte: 128-dot vs 560-dot families */
fail |= expect_int("esc_ik_128", ptouch_esc_ik_value(128u), 0x08);
fail |= expect_int("esc_ik_560", ptouch_esc_ik_value(560u), 0x0C);
return fail ? EXIT_FAILURE : EXIT_SUCCESS; return fail ? EXIT_FAILURE : EXIT_SUCCESS;
} }