selphy_print/backend_sonyupd.c
Solomon Peachy 4712d746e9 sonyupd: Add USB VID/PID for the Nidec Copal DPB-1500
No idea if it actually *works* but this is a necessary first step
2024-08-09 11:00:17 -04:00

1578 lines
38 KiB
C

/*
* Sony UP-D series Photo Printer CUPS backend
*
* (c) 2013-2024 Solomon Peachy <pizza@shaftnet.org>
*
* The latest version of this program can be found at:
*
* https://git.shaftnet.org/gitea/slp/selphy_print.git
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0+
*
*/
#define BACKEND sonyupd_backend
#include "backend_common.h"
/* Printer status
--> 1b e0 00 00 00 00 XX 00 [[ XX is 0xe on UPD895, 0xf on others ]]
<-- this struct
*/
struct sony_updsts {
uint8_t len; /* 0x0d/0x0e (ie number of bytes AFTER this one) */
uint8_t zero1; /* 0x00 */
uint8_t printing; /* UPD_PRINTING_* */
uint8_t remain; /* Number of remaining pages */
uint8_t sts0; /* UPD_STS0_* */
uint8_t sts1; /* UPD_STS1_* */
uint8_t sts2; /* UPD_STS2_* */
uint8_t sts3; /* UPD_STS3_* */
uint8_t ribbon; /* 0x04 = R206/6x8 or C48/4x8 */
uint8_t paper; /* 0x38 = EMPTY, 0xa8/0x90 = loaded */
uint16_t max_cols; /* BE */
uint16_t max_rows; /* BE */
uint8_t percent; /* 0-99, if job is printing (UP-D89x) */
} __attribute__((packed));
struct sony_prints {
uint8_t zero[4];
uint16_t remain; /* BE, remaining prints on media */
} __attribute((packed));
#define UPD_PRINTING_BW 0xe0 /* UPD-895/897 only */
#define UPD_PRINTING_Y 0x40
#define UPD_PRINTING_M 0x80
#define UPD_PRINTING_C 0xc0
#define UPD_PRINTING_O 0x20
#define UPD_PRINTING_IDLE 0x00
/* Confirmed on UP-DR200 */
#define UPD_STS0_OK 0x00
#define UPD_STS0_NORIBBON 0x10
#define UPD_STS0_NOPAPER 0x20
#define UPD_STS0_DOOROPEN 0x40
/* Confirmed on UP-D89x */
#define UPD_STS1_IDLE 0x00
#define UPD_STS1_DOOROPEN 0x08
#define UPD_STS1_NOPAPER 0x40
#define UPD_STS1_PRINTING 0x80
#define UPD_STS1_PRINTING2 0xC0
/* Confirmed on UP-D711 */
#define UPD7_STS0_IDLE 0x00
#define UPD7_STS0_NOPAPER 0x08
#define UPD7_STS0_DOOROPEN 0x40
//#define UPD7_STS0_PRINTING 0x80
//#define UPD7_STS0_PRINTING2 0xC0
#define UPD_RIBBON_R206 0x04
#define UPD_RIBBON_C48 0x04
#define NDC_RIBBON_D2T 0x02
/* Private data structures */
struct upd_printjob {
struct dyesub_job_common common;
uint8_t *databuf;
int datalen;
uint16_t rows;
uint16_t cols;
uint32_t imglen;
};
struct upd_ctx {
struct dyesub_connection *conn;
int native_bpp;
struct sony_updsts stsbuf;
struct sony_prints printbuf;
struct marker marker;
};
static const char *upd_ribbons(int type, uint8_t code)
{
if (type == P_SONY_UPD895 || type == P_SONY_UPD897) {
return "UP-110 Roll";
} else if (type == P_SONY_UPD711) {
return "UPP-84 Roll";
} else if (type == P_SONY_UPCR10) {
if (code == UPD_RIBBON_C48)
return "2UPC-C48 (4x8)";
} else if (type == P_SONY_UPDR150) {
/* DR200/DR150 */
if (code == UPD_RIBBON_R206)
return "R206 (8x6)";
} else if (type == P_NDC) {
if (code == NDC_RIBBON_D2T) {
return "RK-D2T (4x6)";
}
}
return "Unknown";
}
static int sonyupd_media_maxes(uint8_t type, uint8_t media)
{
if (type == P_SONY_UPDR150) {
if (media == UPD_RIBBON_R206)
return 350;
else
return 700; // XXX guess until we have more codes?
// XXX also differs for DR200 vs DR150?
} else if (type == P_SONY_UPCR10) {
if (media == UPD_RIBBON_C48)
return 150;
return 200; // XXX guess until we have more codes.
} else if (type == P_NDC) {
if (media == NDC_RIBBON_D2T) {
return 600;
}
}
return CUPS_MARKER_UNAVAILABLE;
}
// UP-DR200
// 2UPC-R203 3.5x5 (770)
// 2UPC-R204 4x6 (700)
// 2UPC-R205 5x7 (400)
// 2UPC-R206 6x8 (350)
// UP-DR150
// 2UPC-R153 (610)
// 2UPC-R154 (550)
// 2UPC-R155 (335)
// 2UPC-R156 (295)
// UP-CR10L & UP-CX1
// 2UPC-C13 (344)
// 2UPC-C14 (200)
// 2UPC-C15 (172)
// 2UPC-C48 (150)
// print order: ->YMCO->
// current prints (power on)
// total prints (lifetime)
// f/w version
static const char* upd895_statuses(uint8_t code)
{
switch (code) {
case UPD_STS1_IDLE:
return "Idle";
case UPD_STS1_DOOROPEN:
return "Door open";
case UPD_STS1_NOPAPER:
return "No paper";
case UPD_STS1_PRINTING:
case UPD_STS1_PRINTING2:
return "Printing";
default:
return "Unknown";
}
}
static const char* upd711_statuses(uint8_t code)
{
switch (code) {
case UPD7_STS0_IDLE:
return "Idle";
case UPD7_STS0_DOOROPEN:
return "Door open";
case UPD7_STS0_NOPAPER:
return "No paper";
#if 0
case UPD7_STS0_PRINTING:
case UPD7_STS0_PRINTING2:
return "Printing";
#endif
default:
return "Unknown";
}
}
static const char* updr200_statuses(uint8_t code)
{
switch (code) {
case UPD_STS0_OK:
return "OK";
case UPD_STS0_DOOROPEN:
return "Door open";
case UPD_STS0_NOPAPER:
return "No paper";
case UPD_STS0_NORIBBON:
return "No ribbon";
default:
return "Unknown";
}
}
/* Now for the code */
static int sony_get_status(struct upd_ctx *ctx, struct sony_updsts *buf)
{
int ret, num = 0;
uint8_t query[7] = { 0x1b, 0xe0, 0, 0, 0, 0x0f, 0 };
if (ctx->conn->type == P_SONY_UPD895 ||
ctx->conn->type == P_NDC)
query[5] = 0x0e;
if ((ret = send_data(ctx->conn,
query, sizeof(query))))
return CUPS_BACKEND_FAILED;
ret = read_data(ctx->conn, (uint8_t*) buf, sizeof(*buf),
&num);
if (ret < 0)
return CUPS_BACKEND_FAILED;
#if 0
if (ctx->conn->type == P_SONY_UPD895 && ret != 14)
return CUPS_BACKEND_FAILED;
else if (ret != 15)
return CUPS_BACKEND_FAILED;
#endif
buf->max_cols = be16_to_cpu(buf->max_cols);
buf->max_rows = be16_to_cpu(buf->max_rows);
return CUPS_BACKEND_OK;
}
static int sony_get_prints(struct upd_ctx *ctx, struct sony_prints *buf)
{
int ret, num = 0;
uint8_t query[7] = { 0x1b, 0xef, 0, 0, 0, 0x06, 0 };
if ((ret = send_data(ctx->conn,
query, sizeof(query))))
return CUPS_BACKEND_FAILED;
ret = read_data(ctx->conn, (uint8_t*) buf, sizeof(*buf),
&num);
if (ret < 0)
return CUPS_BACKEND_FAILED;
buf->remain = be16_to_cpu(buf->remain);
return CUPS_BACKEND_OK;
}
static void* upd_init(void)
{
struct upd_ctx *ctx = malloc(sizeof(struct upd_ctx));
if (!ctx) {
ERROR("Memory Allocation Failure!\n");
return NULL;
}
memset(ctx, 0, sizeof(struct upd_ctx));
return ctx;
}
static int upd_attach(void *vctx, struct dyesub_connection *conn, uint8_t jobid)
{
struct upd_ctx *ctx = vctx;
UNUSED(jobid);
ctx->conn = conn;
if (ctx->conn->type == P_SONY_UPD895 || ctx->conn->type == P_SONY_UPD897 || ctx->conn->type == P_SONY_UPD711) {
ctx->marker.color = "#000000"; /* Ie black! */
ctx->native_bpp = 1;
} else {
ctx->marker.color = "#00FFFF#FF00FF#FFFF00";
ctx->native_bpp = 3;
}
if (test_mode < TEST_MODE_NOATTACH) {
int ret;
if ((ret = sony_get_status(ctx, &ctx->stsbuf))) {
return ret;
}
if (ctx->native_bpp == 1 && (ret = sony_get_prints(ctx, &ctx->printbuf))) {
return ret;
}
}
if (test_mode >= TEST_MODE_NOATTACH && getenv("MEDIA_CODE")) {
ctx->marker.numtype = strtol(getenv("MEDIA_CODE"), NULL, 16);
} else {
ctx->marker.numtype = ctx->stsbuf.ribbon;
}
ctx->marker.name = upd_ribbons(ctx->conn->type, ctx->stsbuf.ribbon);
if (test_mode >= TEST_MODE_NOATTACH || ctx->native_bpp == 1) {
ctx->marker.levelmax = CUPS_MARKER_UNAVAILABLE;
ctx->marker.levelnow = CUPS_MARKER_UNKNOWN;
} else {
ctx->marker.levelmax = sonyupd_media_maxes(ctx->conn->type, ctx->stsbuf.ribbon);
ctx->marker.levelnow = ctx->printbuf.remain;
}
return CUPS_BACKEND_OK;
}
static void upd_cleanup_job(const void *vjob)
{
const struct upd_printjob *job = vjob;
if (job->databuf)
free(job->databuf);
free((void*)job);
}
//#define MAX_PRINTJOB_LEN (2048*2764*3 + 2048) /* Sony */
#define MAX_PRINTJOB_LEN (2444*3644*3 + 2048) /* DPB and ASK series */
static int ndc_read_parse(struct upd_ctx *ctx, struct upd_printjob *job, int data_fd, uint32_t *copies_offset)
{
int run = 1;
uint32_t param_offset = 0;
(void)ctx;
while(run) {
int i;
int remain = 0;
uint32_t cmdlen;
uint8_t cmdbuf[11];
/* Read the ESC and command */
i = read(data_fd, cmdbuf, 2);
if (i < 0) {
return CUPS_BACKEND_CANCEL;
}
if (i == 0)
break;
if (cmdbuf[0] != 0x1b) {
ERROR("Unexpected data in stream! (%02x)\n", cmdbuf[0]);
return CUPS_BACKEND_CANCEL;
}
/* Work out command specifics */
if (cmdbuf[1] == 0xea) { // Data transfer
cmdlen = 11;
} else { // everything else
cmdlen = 7;
}
if (job->datalen + cmdlen > MAX_PRINTJOB_LEN) {
ERROR("Buffer overflow when parsing printjob! (%d+%d)\n",
job->datalen, remain);
return CUPS_BACKEND_CANCEL;
}
/* Some special casing */
if (cmdbuf[1] == 0x0a)
run = 0;
else if (cmdbuf[1] == 0xee)
*copies_offset = job->datalen + 7 + sizeof(uint32_t)*2;
else if (cmdbuf[1] == 0xe1)
param_offset = job->datalen + 14 + sizeof(uint32_t)*2;
/* Read remainder of cmd */
i = read(data_fd, cmdbuf + 2, cmdlen - 2);
if (i != (int)(cmdlen - 2)) {
ERROR("Unexpected read length! (%d)\n", i);
return CUPS_BACKEND_CANCEL;
}
/* Move cmdbuf into job buffer */
memcpy(job->databuf + job->datalen, &cmdlen, sizeof(uint32_t));
job->datalen += sizeof(uint32_t);
memcpy(job->databuf + job->datalen, cmdbuf, cmdlen);
job->datalen += cmdlen;
/* Work out data transfer length */
if (cmdbuf[1] == 0xea) { /* Image data transfer */
uint32_t datalen;
memcpy(&datalen, cmdbuf + 6, sizeof(uint32_t));
datalen = be32_to_cpu(datalen);
memcpy(job->databuf + job->datalen, &datalen, sizeof(uint32_t));
job->datalen += sizeof(uint32_t);
remain = datalen;
job->imglen = datalen;
} else { // everything else
uint32_t datalen;
uint16_t len;
memcpy(&len, cmdbuf + 4, sizeof(uint16_t));
len = be16_to_cpu(len);
if (len) {
datalen = len;
memcpy(job->databuf + job->datalen, &datalen, sizeof(uint32_t));
job->datalen += sizeof(uint32_t);
}
remain = len;
}
if(dyesub_debug)
DEBUG("Data block (len %d)\n", remain);
/* Make sure we're not too large */
if (job->datalen + remain > MAX_PRINTJOB_LEN) {
ERROR("Buffer overflow when parsing printjob! (%d+%d)\n",
job->datalen, remain);
return CUPS_BACKEND_CANCEL;
}
/* Read in the data chunk */
while (remain > 0) {
i = read(data_fd, job->databuf + job->datalen, remain);
if (i < 0) {
return CUPS_BACKEND_CANCEL;
}
if (i == 0)
break;
remain -= i;
job->datalen += i;
}
}
/* Parse some other stuff */
if (param_offset) {
memcpy(&job->cols, job->databuf + param_offset, sizeof(uint16_t));
memcpy(&job->rows, job->databuf + param_offset + 2, sizeof(uint16_t));
job->cols = be16_to_cpu(job->cols);
job->rows = be16_to_cpu(job->rows);
}
return CUPS_BACKEND_OK;
}
static int sony_read_parse(struct upd_ctx *ctx, struct upd_printjob *job, int data_fd, uint32_t *copies_offset) {
int len, run = 1;
uint32_t param_offset = 0;
uint32_t data_offset = 0;
while(run) {
int i;
int keep = 0;
i = read(data_fd, job->databuf + job->datalen, 4);
if (i < 0) {
return CUPS_BACKEND_CANCEL;
}
if (i == 0)
break;
memcpy(&len, job->databuf + job->datalen, sizeof(len));
len = le32_to_cpu(len);
/* Filter out chunks we don't send to the printer */
if (len & 0xf0000000) {
switch (len) {
case 0xfffffff3:
if(dyesub_debug)
DEBUG("Block ID '%08x' (len %d)\n", len, 0);
len = 0;
if (ctx->conn->type == P_SONY_UPDR150)
run = 0;
break;
case 0xfffffff7:
if(dyesub_debug)
DEBUG("Block ID '%08x' (len %d)\n", len, 0);
len = 0;
if (ctx->conn->type == P_SONY_UPCR10)
run = 0;
break;
case 0xfffffff8: // 895
case 0xfffffff4: // 897/711
if(dyesub_debug)
DEBUG("Block ID '%08x' (len %d)\n", len, 0);
len = 0;
if (ctx->conn->type == P_SONY_UPD895 || ctx->conn->type == P_SONY_UPD897 || ctx->conn->type == P_SONY_UPD711)
run = 0;
break;
case 0xfffffff5:
if (ctx->conn->type == P_SONY_UPD711) {
if(dyesub_debug)
DEBUG("Block ID '%08x' (len %d)\n", len, 0);
len = 0;
} else {
if(dyesub_debug)
DEBUG("Block ID '%08x' (len %d)\n", len, 4);
len = 4;
}
break;
case 0xffffff97:
if(dyesub_debug)
DEBUG("Block ID '%08x' (len %d)\n", len, 12);
len = 12;
break;
case 0xffffffef:
if (ctx->conn->type == P_SONY_UPD895 || ctx->conn->type == P_SONY_UPD897) {
if(dyesub_debug)
DEBUG("Block ID '%08x' (len %d)\n", len, 0);
len = 0;
} else {
if(dyesub_debug)
DEBUG("Block ID '%08x' (len %d)\n", len, 4);
len = 4;
}
break;
case 0xffffffeb:
case 0xffffffee:
if(dyesub_debug)
DEBUG("Block ID '%08x' (len %d)\n", len, 4);
len = 4;
break;
case 0xffffffec:
if (ctx->conn->type == P_SONY_UPD897 || ctx->conn->type == P_SONY_UPD711) {
if(dyesub_debug)
DEBUG("Block ID '%08x' (len %d)\n", len, 4);
len = 4;
} else {
if(dyesub_debug)
DEBUG("Block ID '%08x' (len %d)\n", len, 0);
len = 0;
}
break;
default:
if(dyesub_debug)
DEBUG("Block ID '%08x' (len %d)\n", len, 0);
len = 0;
break;
}
} else {
/* Only keep these chunks */
if(dyesub_debug)
DEBUG("Data block (len %d)\n", len);
if (len > 0)
keep = 1;
}
if (keep)
job->datalen += sizeof(uint32_t);
/* Make sure we're not too large */
if (job->datalen + len > MAX_PRINTJOB_LEN) {
ERROR("Buffer overflow when parsing printjob! (%d+%d)\n",
job->datalen, len);
return CUPS_BACKEND_CANCEL;
}
/* Read in the data chunk */
while(len > 0) {
i = read(data_fd, job->databuf + job->datalen, len);
if (i < 0) {
return CUPS_BACKEND_CANCEL;
}
if (i == 0)
break;
/* Work out offset of copies command */
if (job->databuf[job->datalen] == 0x1b) {
int offset = 0;
if (i == 7)
offset = 4;
switch (job->databuf[job->datalen + 1]) {
case 0x15: /* Print dimensions */
param_offset = job->datalen + 16 + offset;
break;
// XXX case 0xc0:
// for param 03, take the value at offset 4 -- for (eg) 4x6 on 8x6 media, needs to be set to 0x02
case 0xee:
*copies_offset = job->datalen + 7 + offset;
break;
case 0xe1: /* Image dimensions */
param_offset = job->datalen + 14 + offset;
break;
case 0xea:
data_offset = job->datalen + 6 + offset;
break;
default:
break;
}
}
if (keep)
job->datalen += i;
len -= i;
}
}
if (!job->datalen) {
return CUPS_BACKEND_CANCEL;
}
/* Parse some other stuff */
if (param_offset) {
memcpy(&job->cols, job->databuf + param_offset, sizeof(uint16_t));
memcpy(&job->rows, job->databuf + param_offset + 2, sizeof(uint16_t));
job->cols = be16_to_cpu(job->cols);
job->rows = be16_to_cpu(job->rows);
}
if (data_offset) {
memcpy(&job->imglen, job->databuf + data_offset, sizeof(uint32_t));
job->imglen = be32_to_cpu(job->imglen);
}
return CUPS_BACKEND_OK;
}
static int upd_read_parse(void *vctx, const void **vjob, int data_fd, int copies) {
struct upd_ctx *ctx = vctx;
struct upd_printjob *job = NULL;
uint32_t copies_offset = 0;
int rval = CUPS_BACKEND_OK;
if (!ctx)
return CUPS_BACKEND_FAILED;
job = malloc(sizeof(*job));
if (!job) {
ERROR("Memory allocation failure!\n");
return CUPS_BACKEND_RETRY_CURRENT;
}
memset(job, 0, sizeof(*job));
job->common.jobsize = sizeof(*job);
job->common.copies = copies;
job->datalen = 0;
job->databuf = malloc(MAX_PRINTJOB_LEN);
if (!job->databuf) {
ERROR("Memory allocation failure!\n");
rval = CUPS_BACKEND_RETRY_CURRENT;
goto done;
}
if (ctx->conn->type == P_NDC) {
rval = ndc_read_parse(ctx, job, data_fd, &copies_offset);
} else {
rval = sony_read_parse(ctx, job, data_fd, &copies_offset);
}
/* Some models specify copies in the print job */
if (copies_offset) {
uint16_t tmp;
memcpy(&tmp, job->databuf + copies_offset, sizeof(tmp));
tmp = be16_to_cpu(tmp);
if (tmp < copies) { /* Use whichever one is larger */
tmp = cpu_to_be16(copies);
memcpy(job->databuf + copies_offset, &tmp, sizeof(tmp));
}
job->common.copies = 1;
}
if (!job->datalen) {
rval = CUPS_BACKEND_CANCEL;
goto done;
}
/* Sanity check job parameters */
if (job->imglen != (uint32_t)(job->rows * job->cols * ctx->native_bpp))
{
ERROR("Job data length mismatch (%u vs %d)!\n",
job->imglen, job->rows * job->cols * ctx->native_bpp);
rval = CUPS_BACKEND_CANCEL;
goto done;
}
#if 0
{
FILE *fp = fopen("/tmp/foo.prn", "wb");
fwrite(job->databuf, job->datalen, 1, fp);
fclose(fp);
}
#endif
done:
if (rval) {
upd_cleanup_job(job);
} else {
*vjob = job;
}
return rval;
}
static int upd_main_loop(void *vctx, const void *vjob, int wait_for_return) {
struct upd_ctx *ctx = vctx;
int i, ret;
int copies;
const struct upd_printjob *job = vjob;
if (!ctx)
return CUPS_BACKEND_FAILED;
if (!job)
return CUPS_BACKEND_FAILED;
copies = job->common.copies;
top:
/* Send Unknown CMD. Resets? */
if (ctx->conn->type == P_SONY_UPD897) {
const uint8_t cmdbuf[7] = { 0x1b, 0x1f, 0, 0, 0, 0, 0 };
ret = send_data(ctx->conn,
cmdbuf, sizeof(cmdbuf));
if (ret)
return CUPS_BACKEND_FAILED;
}
/* Query printer status */
ret = sony_get_status(ctx, &ctx->stsbuf);
if (ret)
return CUPS_BACKEND_FAILED;
/* Sanity check job parameters */
if (ctx->conn->type == P_SONY_UPD711) {
if (job->rows > ctx->stsbuf.max_cols ||
job->cols > ctx->stsbuf.max_rows) {
ERROR("Job dimensions (%u/%u) exceed printer max (%u/%u)\n",
job->cols, job->rows,
ctx->stsbuf.max_rows,
ctx->stsbuf.max_cols);
return CUPS_BACKEND_CANCEL;
}
} else {
if (job->rows > ctx->stsbuf.max_rows ||
job->cols > ctx->stsbuf.max_cols) {
ERROR("Job dimensions (%u/%u) exceed printer max (%u/%u)\n",
job->cols, job->rows,
ctx->stsbuf.max_cols,
ctx->stsbuf.max_rows);
return CUPS_BACKEND_CANCEL;
}
}
/* Check for idle */
if (ctx->conn->type == P_SONY_UPD711) {
if (ctx->stsbuf.sts0 != UPD7_STS0_IDLE) {
if (ctx->stsbuf.sts1 == UPD_STS1_PRINTING) { // XXX
INFO("Waiting for printer idle...\n");
sleep(1);
goto top;
} else {
ERROR("Printer error: %s (%02x)\n", upd711_statuses(ctx->stsbuf.sts0), ctx->stsbuf.sts0);
return CUPS_BACKEND_STOP;
}
}
} else {
if (ctx->stsbuf.sts0 != UPD7_STS0_IDLE) {
if (ctx->stsbuf.sts1 == UPD_STS1_PRINTING) {
INFO("Waiting for printer idle...\n");
sleep(1);
goto top;
} else {
if (ctx->conn->type == P_SONY_UPD895 || ctx->conn->type == P_SONY_UPD897) {
ERROR("Printer error: %s (%02x)\n", upd895_statuses(ctx->stsbuf.sts1), ctx->stsbuf.sts1);
} else {
ERROR("Printer error: %s (%02x)\n", updr200_statuses(ctx->stsbuf.sts0), ctx->stsbuf.sts0);
}
return CUPS_BACKEND_STOP;
}
}
}
/* Send RESET */
if (ctx->conn->type != P_SONY_UPD895) {
const uint8_t rstbuf[7] = { 0x1b, 0x16, 0, 0, 0, 0, 0 };
ret = send_data(ctx->conn,
rstbuf, sizeof(rstbuf));
if (ret)
return CUPS_BACKEND_FAILED;
}
#if 0 /* Unknown query */
if (ctx->conn->type == P_SONY_UPD897) {
// -> 1b e6 00 00 00 08 00
// <- ???
}
#endif
/* Send over job */
i = 0;
while (i < job->datalen) {
uint32_t len;
memcpy(&len, job->databuf + i, sizeof(len));
len = le32_to_cpu(len);
i += sizeof(uint32_t);
if ((ret = send_data(ctx->conn,
job->databuf + i, len)))
return CUPS_BACKEND_FAILED;
i += len;
}
// XXX generate and send copy cmd instead of using the offset.
// 1b ee 00 00 00 02 00 NN NN (BE)
/* Wait for completion! */
retry:
sleep(1);
/* Check for idle */
ret = sony_get_status(ctx, &ctx->stsbuf);
if (ret)
return ret;
switch (ctx->stsbuf.sts1) { // XXX UP-D711 too...
case UPD_STS1_IDLE:
goto done;
case UPD_STS1_PRINTING:
case UPD_STS1_PRINTING2:
break;
default:
if (ctx->conn->type == P_SONY_UPD895 || ctx->conn->type == P_SONY_UPD897) {
ERROR("Printer error: %s (%02x)\n", upd895_statuses(ctx->stsbuf.sts1), ctx->stsbuf.sts1);
} else if (ctx->conn->type == P_SONY_UPD711) {
ERROR("Printer error: %s (%02x)\n", upd711_statuses(ctx->stsbuf.sts0), ctx->stsbuf.sts0);
} else {
ERROR("Printer error: %s (%02x)\n", updr200_statuses(ctx->stsbuf.sts0), ctx->stsbuf.sts0);
}
return CUPS_BACKEND_STOP;
}
if (!wait_for_return && ctx->stsbuf.printing != UPD_PRINTING_IDLE) {
INFO("Fast return mode enabled.\n");
} else {
goto retry;
}
/* Clean up */
if (terminate)
copies = 1;
done:
INFO("Print complete (%d copies remaining)\n", copies - 1);
if (copies && --copies) {
goto top;
}
return CUPS_BACKEND_OK;
}
static int upd895_dump_status(struct upd_ctx *ctx)
{
int ret = sony_get_status(ctx, &ctx->stsbuf);
if (ret < 0)
return CUPS_BACKEND_FAILED;
if (ctx->conn->type == P_SONY_UPD895 || ctx->conn->type == P_SONY_UPD897) {
INFO("Printer status: %s (%02x)\n", upd895_statuses(ctx->stsbuf.sts1), ctx->stsbuf.sts1);
} else if (ctx->conn->type == P_SONY_UPD711) {
INFO("Printer status: %s (%02x)\n", upd711_statuses(ctx->stsbuf.sts0), ctx->stsbuf.sts0);
} else {
INFO("Printer status: %s (%02x)\n", updr200_statuses(ctx->stsbuf.sts0), ctx->stsbuf.sts0);
}
if (ctx->stsbuf.printing != UPD_PRINTING_IDLE &&
ctx->stsbuf.sts1 == UPD_STS1_PRINTING)
INFO("Remaining copies to print: %d\n", ctx->stsbuf.remain);
INFO("Media: %s (%02x)\n", upd_ribbons(ctx->conn->type, ctx->stsbuf.ribbon), ctx->stsbuf.ribbon);
if (ctx->native_bpp != 1) {
if ((ret = sony_get_prints(ctx, &ctx->printbuf)))
return ret;
INFO("Media remaining: %d/%d\n", ctx->printbuf.remain, sonyupd_media_maxes(ctx->conn->type, ctx->stsbuf.ribbon));
}
return CUPS_BACKEND_OK;
}
static void upd_cmdline(void)
{
DEBUG("\t\t[ -s ] # Query printer status\n");
}
static int upd_cmdline_arg(void *vctx, int argc, char **argv)
{
struct upd_ctx *ctx = vctx;
int i, j = 0;
if (!ctx)
return -1;
while ((i = getopt(argc, argv, GETOPT_LIST_GLOBAL "s")) >= 0) {
switch(i) {
GETOPT_PROCESS_GLOBAL
case 's':
j = upd895_dump_status(ctx);
break;
}
if (j) return j;
}
return CUPS_BACKEND_OK;
}
static int upd_query_markers(void *vctx, struct marker **markers, int *count)
{
struct upd_ctx *ctx = vctx;
int ret = sony_get_status(ctx, &ctx->stsbuf);
*markers = &ctx->marker;
*count = 1;
if (ret)
return CUPS_BACKEND_FAILED;
if (ctx->native_bpp != 1 && (ret = sony_get_prints(ctx, &ctx->printbuf))) {
return ret;
}
if (ctx->conn->type == P_SONY_UPD711) {
if (ctx->stsbuf.sts0 == UPD7_STS0_NOPAPER ||
ctx->stsbuf.sts0 == UPD7_STS0_DOOROPEN) {
ctx->marker.levelnow = 0;
} else {
ctx->marker.levelnow = CUPS_MARKER_UNKNOWN_OK;
}
} else {
if (ctx->stsbuf.sts1 == UPD_STS1_NOPAPER ||
ctx->stsbuf.sts1 == UPD_STS1_DOOROPEN) {
if (ctx->native_bpp == 1) {
ctx->marker.levelnow = 0;
} else {
ctx->marker.levelnow = ctx->printbuf.remain;
}
} else {
if (ctx->native_bpp == 1) {
ctx->marker.levelnow = CUPS_MARKER_UNKNOWN_OK;
} else {
ctx->marker.levelnow = ctx->printbuf.remain;
}
}
}
return CUPS_BACKEND_OK;
}
static const char *sonyupd_prefixes[] = {
"sonyupd", /* Family Name */
"dnp-sl10", // Unknown if shared with CR10L
// Backwards compatibility
"sonyupdr150", "sonyupdr200", "sonyupcr10",
NULL
};
static const struct device_id sonyupd_devices[] = {
{ 0x054c, 0x0049, P_SONY_UPD895, NULL, "sony-upd895"},
{ 0x054c, 0x01e7, P_SONY_UPD897, NULL, "sony-upd897"},
{ 0x054c, 0x01e8, P_SONY_UPDR150, NULL, "sony-updr150"},
{ 0x054c, 0x0226, P_SONY_UPCR10, NULL, "sony-upcr10l"},
{ 0x054c, 0x02d4, P_SONY_UPCR10, NULL, "sony-upcx1"},
{ 0x054c, 0x035f, P_SONY_UPDR150, NULL, "sony-updr200"},
{ 0x054c, 0x068c, P_SONY_UPD711, NULL, "sony-upd711"},
{ 0x07ce, 0xc005, P_NDC, NULL, "ndc-dpb-1500"},
{ 0x07ce, 0xc007, P_NDC, NULL, "ndc-dpb-4000"},
{ 0x07ce, 0xc007, P_NDC, NULL, "fujifilm-ask-4000"},
// DNP Q8?
{ 0x07ce, 0xc009, P_NDC, NULL, "ndc-dpb-6000"},
{ 0x07ce, 0xc009, P_NDC, NULL, "fujifilm-ask-2000"}, // duplicate, has different IEEE1284
{ 0x07ce, 0xc010, P_NDC, NULL, "ndc-dpb-7000"},
{ 0x07ce, 0xc011, P_NDC, NULL, "fujifilm-ask-2500"},
{ 0, 0, 0, NULL, NULL}
};
const struct dyesub_backend sonyupd_backend = {
.name = "Sony UP-D",
.version = "0.55",
.uri_prefixes = sonyupd_prefixes,
.devices = sonyupd_devices,
.cmdline_arg = upd_cmdline_arg,
.cmdline_usage = upd_cmdline,
.init = upd_init,
.attach = upd_attach,
.cleanup_job = upd_cleanup_job,
.read_parse = upd_read_parse,
.main_loop = upd_main_loop,
.query_markers = upd_query_markers,
};
/* Sony spool file format
The spool file is a series of 4-byte commands, followed by optional
arguments. The purpose of the commands is unknown, but they presumably
instruct the driver to perform certain things.
If you treat these 4 bytes as a 32-bit little-endian number, if any of the
least significant 4 bits are non-zero, the value is is to
be interpreted as a driver command. If the most significant bits are
zero, the value signifies that the following N bytes of data should be
sent to the printer as-is.
Known driver "commands":
97 ff ff ff
eb ff ff ff ?? 00 00 00
ec ff ff ff ?? 00 00 00
ed ff ff ff ?? 00 00 00
ee ff ff ff ?? 00 00 00
ef ff ff ff XX 00 00 00 # XX == print size (0x01/0x02/0x03/0x04)
ef ff ff ff # On UP-D895/897
f3 ff ff ff
f4 ff ff ff # End of job on UP-D897
f5 ff ff ff YY 00 00 00 # YY == ??? (seen 0x01)
f7 ff ff ff # End of job on UP-D895
All printer commands start with 0x1b, and are at least 7 bytes long.
General Command format:
1b XX ?? ?? LL LL 00 # XX is cmd, LL is data/response length, BE.
If least significant bit of XX is 1, it is a WRITE, and LL is number of bytes of the payload. If LSB is 0, it is a READ, and printer will return LL bytes.
UNKNOWN QUERY [possibly media?]
<- 1b 03 00 00 00 13 00
-> 70 00 00 00 00 00 00 0b 00 00 00 00 00 00 00 00
00 00 00
UNKNOWN CMD (UP-DR & SL10 & UP-D895 & UP-DP10 & DPB/ASK) [ possibly START? ]
<- 1b 0a 00 00 00 00 00
IMAGE DIMENSIONS
<- 1b 15 00 00 00 0d 00
<- 00 00 00 00 ZZ QQ QQ WW WW YY YY XX XX
QQ/WW/YY/XX are (origin_cols/origin_rows/cols/rows) in BE.
ZZ is 0x07 on UP-DR series, 0x01 on UP-D89x/711 series, 0x60 on Fujifilms
RESET
<- 1b 16 00 00 00 00 00
UNKNOWN CMD (UP-DR & SL & UP-D897, may be PRINT START?)
<- 1b 17 00 00 00 00 00
UNKNOWN CMD
<- 1b 1f 00 00 00 00 00
SET PARAM
<- 1b c0 00 NN LL LL 00 # LL is response length, NN is number.
<- [ LL bytes]
QUERY PARAM
<- 1b c1 00 NN LL LL 00 # LL is response length, NN is number.
-> [ LL bytes ]
PARAMS SEEN:
0c, len 5 [ 02 0c 00 01 XX ] XX == Long Feed Every XX pages (D711 only?)
0b, len 5 [ 02 0b 00 01 XX ] XX == Print Info (D711 only?)
0a, len 5 [ 02 0a 00 01 XX ] XX == Paper type (UP-D711 only?)
03, len 5 [ 02 03 00 01 XX ] (UPDR200, 00 = normal, 02 is multicut/div2 print, 01 seen at end of stream too..
02, len 06 [ 02 02 00 03 00 00 ]
01, len 10 [ 02 01 00 06 00 02 00 00 00 00 ] (UP-D897/711)
00, len 5 [ 02 01 00 01 XX ] XX == Gamma table (89x/711 only?)
STATUS QUERY
<- 1b e0 00 00 XX XX 00 # XX = 0x000e (UP-D895 or NDC), 0x000f (All others)
-> [14 or 15 bytes, see 'struct sony_updsts' ]
PRINT PARAMETERS
<- 1b e1 00 00 00 0b 00
<- 00 ZZ PP QQ QQ WW WW XX XX YY YY
QQ == origin_cols, WW == origin_rows
XX = cols, YY == rows
ZZ == 0x04 on UP-DP10/DPB-xxxx/ASK-xxxx, 08 on UPD89x/77x, otherwise 0x80
PP == 00/01 for fast/slow (UPD711), 00 glossy, 08 texture (UP-DP10 + UP-DR150), 0c matte, +0x10 for "nocorrection" on UP-DR200..
UNKNOWN
<- 1b e5 00 00 00 08 00
<- 00 00 00 00 00 00 00 XX 00 # Seen 01, 12, 0d, etc.
UNKNOWN (UP-D897)
<- 1b e6 00 00 00 08 00
-> 07 00 00 00 00 00 00 00
DATA TRANSFER
<- 1b ea 00 00 00 00 ZZ ZZ ZZ ZZ 00 # ZZ is BIG ENDIAN
<- [ ZZ ZZ ZZ ZZ bytes of data ]
UNKNOWN CMD (UP-DR and UP-D)
<- 1b ed 00 00 00 00 00
QUERY REMAINING PRINTS (UPDR series)
<- 1b ef 00 00 00 06 00
-> 05 00 00 00 NN NN # NN NN print count on media remaining
COPIES
<- 1b ee 00 00 00 02 00
<- NN NN # Number of copies (BE, 1-???)
UNKNOWN (UPDR series)
<- 1b f5 00 00 00 02 00
<- ?? ??
UNKNOWN (Fujifilm ASK2000 series)
<- 1b 23 00 00 00 04 00
<- ff ff ff ff
UNKNOWN (Fujifilm ASK2000/4000 series)
<- 1b 1e 00 00 00 0c 00
00 00 00 00 00 00 00 00 00 00 00 00
************************************************************************
The data stream sent to the printer consists of all the commands in the
spool file, plus a couple other ones that generate responses. It is
unknown if those additional commands are necessary. This is a typical
sequence:
[[ Sniff start of a UP-DR150 ]]
<- 1b e0 00 00 00 0f 00 [ STATUS QUERY ]
-> 0e 00 00 00 00 00 00 00 04 a8 08 00 0a a4 00
----- -----
MAX_C MAX_R
<- 1b 16 00 00 00 00 00
-> "reset" ??
[[ begin job ]]
<- 1b ef 00 00 00 06 00
-> 05 00 00 00 00 22
<- 1b e5 00 00 00 08 00 ** In spool file
<- 00 00 00 00 00 00 01 00
<- 1b c1 00 02 06 00 00 [ Query Param 2, length 6 ]
-> 02 02 00 03 00 00
<- 1b ee 00 00 00 02 00 ** In spool file
<- 00 01
<- 1b 15 00 00 00 0d 00 ** In spool file
<- 00 00 00 00 07 00 00 00 00 08 00 0a a4
<- 1b 03 00 00 00 13 00
-> 70 00 00 00 00 00 00 0b 00 00 00 00 00 00 00 00
00 00 00
<- 1b e1 00 00 00 0b 00 ** In spool file
<- 00 80 00 00 00 00 00 08 00 0a a4
<- 1b 03 00 00 00 13 00
-> 70 00 00 00 00 00 00 0b 00 00 00 00 00 00 00 00
00 00 00
<- 1b ea 00 00 00 00 00 ff 60 00 00 ** In spool file
<- [[ 0x0060ff00 bytes of data ]]
<- 1b e0 00 00 00 0f 00
-> 0e 00 00 00 00 00 00 00 04 a8 08 00 0a a4 00
<- 1b 0a 00 00 00 00 00 ** In spool file, marks end of file?
<- 1b 17 00 00 00 00 00 ** In spool file
[[fin]]
**************
Sony UP-CL10 / DNP SL-10 / UP-CX1 spool format:
60 ff ff ff
f8 ff ff ff
fd ff ff ff (or: f0 ff ff ff)
14 00 00 00
1b 15 00 00 00 0d 00 00 00 00 00 07 00 00 00 00 WW WW HH HH
fb ff ff ff
f4 ff ff ff
0b 00 00 00
1b ea 00 00 00 00 SH SH SH SH 00
SL SL SL SL
[[ Data, rows * cols * 3 bytes ]]
f3 ff ff ff
0f 00 00 00
1b e5 00 00 00 08 00 00 00 00 00 00 00 00 00
12 00 00 00
1b e1 00 00 00 0b 00 00 80 GG 00 00 00 00 WW WW HH HH
fa ff ff ff
09 00 00 00
1b ee 00 00 00 02 00 00 NN
07 00 00 00
1b 0a 00 00 00 00 00
f9 ff ff ff
fc ff ff ff
07 00 00 00
1b 17 00 00 00 00 00
f7 ff ff ff
WW WW == Columns, Big Endian
HH HH == Rows, Big Endian
SL SL SL SL == Plane size, Little Endian (Rows * Cols * 3)
SH SH SH SH == Plane size, Big Endian (Rows * Cols * 3)
NN == Copies
GG == Overcoat, 0x00 glossy, 0x0c matte (UP-CX1 only)
**************
Sony UP-D895 spool format:
XX XX == cols, BE (fixed at 1280/0x500)
YY YY == rows, BE (798/0x031e,1038/0x040e,1475/0x05c3, 2484/09b4) @ 960/1280/1920/3840+4096
SS SS SS SS == data len (rows * cols, LE)
S' S' S' S' == data len (rows * cols, BE)
NN == copies (1 -> ??)
GG GG == ??? 0000/0050/011b/04aa/05aa at each resolution.
G' == Gamma 01 (soft), 03 (hard), 02 (normal)
9c ff ff ff 97 ff ff ff 00 00 00 00 00 00 00 00 00 00 00 00 ff ff ff ff
14 00 00 00
1b 15 00 00 00 0d 00 00 00 00 00 01 GG GG 00 00 YY YY XX XX
0b 00 00 00
1b ea 00 00 00 00 S' S' S' S' 00
SS SS SS SS
...DATA... (rows * cols)
ff ff ff ff
09 00 00 00
1b ee 00 00 00 02 00 00 NN
0f 00 00 00
1b e5 00 00 00 08 00 00 00 00 00 00 00 00 00
0c 00 00 00
1b c0 00 00 00 05 00 02 00 00 01 G'
11 00 00 00
1b c0 00 01 00 0a 00 02 01 00 06 00 00 00 00 00 00
12 00 00 00
1b e1 00 00 00 0b 00 00 08 00 GG GG 00 00 YY YY XX XX
07 00 00 00
1b 0a 00 00 00 00 00
fd ff ff ff f7 ff ff ff f8 ff ff ff
**************
Sony UP-D897 spool format:
NN NN == copies (00 for printer selected)
XX XX == cols (fixed @ 1280)
YY YY == rows
GG == gamma -- Table 2 == 2, Table 1 == 3, Table 3 == 1, Table 4 == 4
DD == "dark" +- 64.
LL == "light" +- 64.
AA == "advanced" +- 32.
SS == Sharpness 0-14
ZZ ZZ ZZ ZZ == Data length (BE)
Z` Z` Z` Z` == Data length (LE)
83 ff ff ff fc ff ff ff
fb ff ff ff f5 ff ff ff
f1 ff ff ff f0 ff ff ff
ef ff ff ff
14 00 00 00
1b 15 00 00 00 0d 00 00 00 00 00 01 00 a2 00 00 YY YY XX XX
0b 00 00 00
1b ea 00 00 00 00 ZZ ZZ ZZ ZZ 00
Z` Z` Z` Z`
...DATA...
ea ff ff ff
09 00 00 00
1b ee 00 00 00 02 00 NN NN
ee ff ff ff 01 00 00 00
0e 00 00 00
1b e5 00 00 00 08 00 00 00 00 00 DD LL SS AA
eb ff ff ff ?? 00 00 00 <--- 02/05 5 at #3, 2 otherwise. Sharpness?
0c 00 00 00
1b c0 00 00 00 05 00 02 00 00 01 GG
ec ff ff ff ?? 00 00 00 <--- 01/00/02/01/01 Seen. Unknown.
11 00 00 00
1b c0 00 01 00 0a 00 02 01 00 06 00 00 00 00 00 00
ed ff ff ff 00 00 00 00
12 00 00 00
1b e1 00 00 00 0b 00 00 08 00 00 a2 00 00 YY YY XX XX
fa ff ff ff
07 00 00 00
1b 0a 00 00 00 00 00
fc ff ff ff fd ff ff ff ff ff ff ff
07 00 00 00
1b 17 00 00 00 00 00
f4 ff ff ff
****************
Sony UP-D711 spool format:
NN NN == copies (00 for printer selected)
XX XX == cols (fixed @ 896)
YY YY == rows
GG == gamma -- Table 2 == 2, Table 1 == 3, Table 3 == 1, Table 4 == 4
DD == "dark" +- 64.
LL == "light" +- 64.
AA == "advanced" +- 32.
SS == Sharpness 0-14
PP == Paper type (S = 00, HG = 01)
KK == Long Feed every KK prints, 00 disables.
RR == Speed (00 - Low, 01 = High)
PI == Print Info (01 - Yes, 00 - No)
ZZ ZZ ZZ ZZ == Data length (BE)
Z` Z` Z` Z` == Data length (LE)
83 ff ff ff fc ff ff ff
fb ff ff ff f5 ff ff ff
07 00 00 00
1b 15 00 00 00 0d 00
0d 00 00 00
00 00 00 00 01 01 43 00 00 XX XX YY YY
0b 00 00 00
1b ea 00 00 00 00 ZZ ZZ ZZ ZZ 00
Z` Z` Z` Z`
...DATA...
ea ff ff ff
07 00 00 00
1b ee 00 00 00 02 00
02 00 00 00
NN NN
ee ff ff ff 01 00 00 00
07 00 00 00
1b e5 00 00 00 08 00
08 00 00 00
00 00 00 00 DD LL SS AA
eb ff ff ff 02 00 00 00
07 00 00 00
1b c0 00 00 00 05 00
05 00 00 00
02 00 00 01 GG
ec ff ff ff PI 00 00 00
07 00 00 00
1b c0 00 01 00 0a 00
0a 00 00 00
02 01 00 06 00 00 00 00 00 00
ed ff ff ff 00 00 00 00
07 00 00 00
1b c0 00 0a 00 05 00
05 00 00 00
02 0a 00 01 PP
07 00 00 00
1b c0 00 0c 00 05 00
05 00 00 00
02 0c 00 01 KK
07 00 00 00
1b c0 00 0b 00 05 00
05 00 00 00
02 0b 00 01 PI
07 00 00 00
1b e1 00 00 00 0b 00
0b 00 00 00
00 08 RR 01 43 00 00 XX XX YY YY
fa ff ff ff
07 00 00 00
1b 0a 00 00 00 00 00
fc ff ff ff fd ff ff ff
ff ff ff ff
07 00 00 00
1b 17 00 00 00 00 00
f4 ff ff ff
****************
UP-D895 comms protocol:
<-- 1b e0 00 00 00 0e 00
--> 0d 00 XX YY 00 SS 00 ZZ 00 00 RR RR CC CC
RR : Max rows
CC : Max columns (fixed at 0x0500/1280)
XX : 0xe0 when printing, 0x00 otherwise.
YY : Number of remaining copies
SS : Status
0x00 = Idle
0x08 = Door open
0x40 = Paper empty
0x80 = Printing
?? = Cooling down
?? = Busy / Waiting
ZZ : Status2
0x01 = Print complete
0x02 = no prints yet
UP-D897 comms protocol:
<-- 1b e0 00 00 00 0f 00
--> 0e 00 XX YY 00 SS TT 01 02 02 RR RR CC CC PP
RR : Max rows
CC : Max columns (fixed at 0x0500/1280)
XX : 0xe0 when printing, 0x00 otherwise.
YY : Number of remaining copies
SS : Status
0x00 = Idle
?? 0x08 = Door open
0x40 = Paper empty
0x80 = Printing
?? = Cooling down
?? = Busy / Waiting
TT : Status 2
0x00 = Okay
0x08 = ?? Error state?
0x80 = Printing
PP : Percentage complete (0-99)
UP-D711 comms protocol:
<-- 1b e0 00 00 00 0f 00
--> 0e 00 XX YY SS 00 TT 01 02 02 RR RR CC CC PP
RR : Max rows
CC : Max columns (fixed at 0x380/896)
XX : 0xe0 when printing, 0x00 otherwise.
YY : Number of remaining copies
SS : Status
0x00 = Idle
0x08 = Paper empty
0x40 = Door open
?? 0x80 = Printing
?? = Cooling down
?? = Busy / Waiting
?? TT : Status 2
0x00 = Okay
0x08 = ?? Error state?
0x80 = Printing
PP : Percentage complete (0-99)
Other commands seen:
<-- 1b 16 00 00 00 00 00 -- Reset
<-- 1b c1 00 01 00 0a 00 -- Query ID 1, legth 10
--> 02 01 00 06 00 02 00 00 00 00
<-- 1b c1 00 00 00 05 00 -- Query id 0, length 5
--> 02 01 00 01 03
<-- 1b e6 00 00 00 08 00
--> 07 00 00 00 00 00 00 00
<-- 1b 17 00 00 00 00 00 -- Unknown?
CR10L: status progression when printing:
0e 00 00 00 00 00 00 00 04 a0 04 e0 07 38 00
0e 00 00 01 00 80 00 01 04 a0 04 e0 07 38 64
0e 00 40 01 00 80 00 01 04 a0 04 e0 07 38 64 <-- Y
0e 00 80 01 00 80 00 01 04 a0 04 e0 07 38 64 <-- M
0e 00 c0 01 00 80 00 01 04 a0 04 e0 07 38 64 <-- C
0e 00 20 01 00 80 00 01 04 a0 04 e0 07 38 64 <-- O
0e 00 20 01 00 80 00 01 04 a0 04 e0 07 38 00
0e 00 00 01 00 80 00 01 04 a0 04 e0 07 38 00
0e 00 00 00 00 00 00 00 04 a0 04 e0 07 38 00
UP-DR200 comms protocol:
<-- 1b e0 00 00 00 0f 00
--> 0e 00 XX NN E0 E1 00 E3 RT PT CC CC RR RR %%
XX : Print state (eg Y/M/C/O -- see UPD_PRINTING_* )
E0 : Error code0 (see UPD_STS0_*)
E1 : Error code1 (see UPD_STS1_*)
E3 : Unknown (00 or 01)
NN : Number of remaining copies (00 when complete/idle)
CC : Max Cols (0x0800 on 6" ribbon)
RR : Max Rows (0x0aa4 on 6x8)
RT : Ribbon type (04 = R206/8x6)
PT : Paper type (38 = empty, a8 = 6")
%% : Percentage complete (0-99)
Seen on UP-DR200:
0e 00 00 00 00 00 00 00 04 a8 08 00 0a a4 00 READY (R206 6x8 ribbon)
0e 00 00 00 40 00 00 00 04 a8 08 00 0a a4 00 DOOR OPEN
0e 00 00 00 20 00 00 00 04 38 08 00 0a a4 00 LOAD PAPER
0e 00 00 00 10 08 00 00 00 a8 08 00 0a a4 00 LOAD RIBBON
0e 00 00 01 00 c0 00 01 04 a8 08 00 0a a4 00 PRINT START
0e 00 40 01 00 c0 00 01 04 a8 08 00 0a a4 %% PRINT Y
0e 00 80 01 00 c0 00 01 04 a8 08 00 0a a4 %% PRINT M
0e 00 c0 01 00 c0 00 01 04 a8 08 00 0a a4 %% PRINT C
0e 00 20 01 00 c0 00 01 04 a8 08 00 0a a4 %% PRINT OC
0e 00 00 00 00 00 00 00 04 a8 08 00 0a a4 00 PRINT DONE / READY
*/