From 1aebe612534f33f8d51061a84350a4a0cfcb6d9f Mon Sep 17 00:00:00 2001 From: Solomon Peachy Date: Sat, 3 Dec 2016 23:21:59 -0500 Subject: [PATCH] mitsup95d: Add in support for the Mitsubishi P95D. --- .gitignore | 1 + Makefile | 2 +- README | 10 + backend_common.c | 2 + backend_common.h | 1 + backend_mitsup95d.c | 535 ++++++++++++++++++++++++++++++++++++++++++++ blacklist | 3 + 7 files changed, 553 insertions(+), 1 deletion(-) create mode 100644 backend_mitsup95d.c diff --git a/.gitignore b/.gitignore index ae7736b..289a26e 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ dnpds40 citizencw01 dyesub_backend mitsu9550 +mitsup95d diff --git a/Makefile b/Makefile index ff38323..76cb7d0 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,7 @@ CPPFLAGS += -DURI_PREFIX=\"$(BACKEND_NAME)\" CFLAGS += -funit-at-a-time # List of backends -BACKENDS = sonyupdr150 kodak6800 kodak1400 shinkos2145 shinkos1245 canonselphy mitsu70x kodak605 dnpds40 citizencw01 mitsu9550 shinkos6245 shinkos6145 canonselphyneo +BACKENDS = sonyupdr150 kodak6800 kodak1400 shinkos2145 shinkos1245 canonselphy mitsu70x kodak605 dnpds40 citizencw01 mitsu9550 shinkos6245 shinkos6145 canonselphyneo mitsup95d # For the s6145 and mitsu70x backends CPPFLAGS += -DUSE_DLOPEN diff --git a/README b/README index 942e647..dcde95e 100644 --- a/README +++ b/README @@ -44,6 +44,7 @@ Mitsubishi CP-K60DW-S Mitsubishi CP-D70DW Mitsubishi CP-D80DW + Mitsubishi P95DW Shinko CHC-S6145 (aka Sinfonia CS2) Ciaat Brava 21 @@ -657,6 +658,15 @@ instead of being bundled separately, further complicating the process of figuring out how everything works. Progress is limited. + *************************************************************************** + BACKEND=mitsup95d + + Verified supported printers: + + Mitsubishi P95DW + + This backend does not support additional commands. + *************************************************************************** BACKEND=dnpds40 diff --git a/backend_common.c b/backend_common.c index c1ec2e7..a3beeb3 100644 --- a/backend_common.c +++ b/backend_common.c @@ -565,6 +565,7 @@ extern struct dyesub_backend canonselphy_backend; extern struct dyesub_backend canonselphyneo_backend; extern struct dyesub_backend mitsu70x_backend; extern struct dyesub_backend mitsu9550_backend; +extern struct dyesub_backend mitsup95d_backend; extern struct dyesub_backend dnpds40_backend; extern struct dyesub_backend cw01_backend; @@ -581,6 +582,7 @@ static struct dyesub_backend *backends[] = { &updr150_backend, &mitsu70x_backend, &mitsu9550_backend, + &mitsup95d_backend, &dnpds40_backend, &cw01_backend, NULL, diff --git a/backend_common.h b/backend_common.h index 15c6ff7..3b69128 100644 --- a/backend_common.h +++ b/backend_common.h @@ -117,6 +117,7 @@ enum { P_MITSU_9800, P_MITSU_9800S, P_MITSU_9810, + P_MITSU_P95D, P_DNP_DS40, P_DNP_DS80, P_DNP_DS80D, diff --git a/backend_mitsup95d.c b/backend_mitsup95d.c new file mode 100644 index 0000000..d074092 --- /dev/null +++ b/backend_mitsup95d.c @@ -0,0 +1,535 @@ +/* + * Mitsubishi P95D Monochrome Thermal Photo Printer CUPS backend + * + * (c) 2016 Solomon Peachy + * + * The latest version of this program can be found at: + * + * http://git.shaftnet.org/cgit/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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * [http://www.gnu.org/licenses/gpl-3.0.html] + * + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#define BACKEND mitsup95d_backend + +#include "backend_common.h" + +#define USB_VID_MITSU 0x06D3 +#define USB_PID_MITSU_P95D 0x3b10 + +/* Private data stucture */ +struct mitsup95d_ctx { + struct libusb_device_handle *dev; + uint8_t endp_up; + uint8_t endp_down; + + uint8_t mem_clr[4]; // 1b 5a 43 00 + int mem_clr_present; + + uint8_t hdr[2]; // 1b 51 + + uint8_t hdr1[50]; // 1b 57 20 2e ... + uint8_t hdr2[50]; // 1b 57 21 2e ... + uint8_t hdr3[50]; // 1b 57 22 2e ... + + uint8_t hdr4[36]; // 1b 58 ... + uint8_t plane[12]; // 1b 5a 74 00 ... + + uint8_t *databuf; + uint32_t datalen; + + uint8_t ftr[2]; +}; + + +static void *mitsup95d_init(void) +{ + struct mitsup95d_ctx *ctx = malloc(sizeof(struct mitsup95d_ctx)); + if (!ctx) { + ERROR("Memory Allocation Failure!\n"); + return NULL; + } + memset(ctx, 0, sizeof(struct mitsup95d_ctx)); + + return ctx; +} + +static void mitsup95d_attach(void *vctx, struct libusb_device_handle *dev, + uint8_t endp_up, uint8_t endp_down, uint8_t jobid) +{ + struct mitsup95d_ctx *ctx = vctx; + struct libusb_device *device; + struct libusb_device_descriptor desc; + + UNUSED(jobid); + + ctx->dev = dev; + ctx->endp_up = endp_up; + ctx->endp_down = endp_down; + + device = libusb_get_device(dev); + libusb_get_device_descriptor(device, &desc); + +} + +static void mitsup95d_teardown(void *vctx) { + struct mitsup95d_ctx *ctx = vctx; + + if (!ctx) + return; + + if (ctx->databuf) + free(ctx->databuf); + free(ctx); +} + +static int mitsup95d_read_parse(void *vctx, int data_fd) { + struct mitsup95d_ctx *ctx = vctx; + uint8_t buf[2]; /* Enough to read in any header */ + uint8_t tmphdr[50]; + uint8_t *ptr; + int i; + int remain; + int ptr_offset; + + if (!ctx) + return CUPS_BACKEND_FAILED; + + if (ctx->databuf) { + free(ctx->databuf); + ctx->databuf = NULL; + } + ctx->mem_clr_present = 0; + +top: + i = read(data_fd, buf, sizeof(buf)); + if (i == 0) + return CUPS_BACKEND_CANCEL; + if (i < 0) + return CUPS_BACKEND_CANCEL; + + if (buf[0] != 0x1b) { + ERROR("malformed data stream\n"); + return CUPS_BACKEND_CANCEL; + } + + switch (buf[1]) { + case 0x43: /* Memory Clear */ + remain = 4; + ptr = ctx->mem_clr; + ctx->mem_clr_present = 1; + break; + case 0x50: /* Footer */ + remain = 2; + ptr = ctx->ftr; + break; + case 0x51: /* Job Header */ + remain = 2; + ptr = ctx->hdr; + break; + case 0x57: /* Geeneral headers */ + remain = sizeof(tmphdr); + ptr = tmphdr; + break; + case 0x58: /* User Comment */ + remain = 36; + ptr = ctx->hdr4; + break; + case 0x5a: /* Plane header */ + remain = 12; + ptr = ctx->plane; + break; + default: + ERROR("Unrecognized command! (%02x %02x)\n", buf[0], buf[1]); + return CUPS_BACKEND_CANCEL; + } + + memcpy(ptr, buf, sizeof(buf)); + remain -= sizeof(buf); + ptr_offset = sizeof(buf); + + while (remain) { + i = read(data_fd, ptr + ptr_offset, remain); + if (i == 0) + return CUPS_BACKEND_CANCEL; + if (i < 0) + return CUPS_BACKEND_CANCEL; + remain -= i; + ptr_offset += i; + } + + if (ptr == tmphdr) { + if (tmphdr[3] != 46) { + ERROR("Unexpected header chunk: %02x %02x %02x %02x\n", + tmphdr[0], tmphdr[1], tmphdr[2], tmphdr[3]); + return CUPS_BACKEND_CANCEL; + } + switch (tmphdr[2]) { + case 0x20: + ptr = ctx->hdr1; + break; + case 0x21: + ptr = ctx->hdr2; + break; + case 0x22: + ptr = ctx->hdr3; + break; + default: + ERROR("Unexpected header chunk: %02x %02x %02x %02x\n", + tmphdr[0], tmphdr[1], tmphdr[2], tmphdr[3]); + } + memcpy(ptr, tmphdr, sizeof(tmphdr)); + } else if (ptr == ctx->plane) { + uint16_t rows = ctx->plane[10] << 8 | ctx->plane[11]; + uint16_t cols = ctx->plane[8] << 8 | ctx->plane[9]; + + remain = rows * cols; + + /* Allocate buffer for the payload */ + ctx->datalen = 0; + ctx->databuf = malloc(remain); + if (!ctx->databuf) { + ERROR("Memory allocation failure!\n"); + return CUPS_BACKEND_FAILED; + } + + /* Read it in */ + while (remain) { + i = read(data_fd, ctx->databuf + ctx->datalen, remain); + if (i == 0) + return CUPS_BACKEND_CANCEL; + if (i < 0) + return CUPS_BACKEND_CANCEL; + remain -= i; + ctx->datalen += i; + } + } else if (ptr == ctx->ftr) { + return CUPS_BACKEND_OK; + } + + goto top; +} + +static int mitsup95d_main_loop(void *vctx, int copies) { + struct mitsup95d_ctx *ctx = vctx; + uint8_t querycmd[4] = { 0x1b, 0x72, 0x00, 0x00 }; + uint8_t queryresp[9]; + + int ret; + int num; + + if (!ctx) + return CUPS_BACKEND_FAILED; + + /* Update printjob header to reflect number of requested copies */ + if (ctx->hdr2[13] != 0xff) + ctx->hdr2[13] = copies; + + /* XXX Update unknown header field to match sniffs */ + if (ctx->hdr1[18] == 0x00) + ctx->hdr1[18] = 0x01; + + INFO("Waiting for printer idle\n"); + + /* Query Status to make sure printer is idle */ + do { + if ((ret = send_data(ctx->dev, ctx->endp_down, + querycmd, sizeof(querycmd)))) + return CUPS_BACKEND_FAILED; + ret = read_data(ctx->dev, ctx->endp_up, + queryresp, sizeof(queryresp), &num); + if (num != sizeof(queryresp) || ret < 0) { + return CUPS_BACKEND_FAILED; + } + + if (queryresp[5] & 0x40) { + ERROR("Printer error %02x\n", queryresp[5]); // XXX decode + return CUPS_BACKEND_STOP; + } + if (queryresp[5] == 0x00) + break; + + sleep(1); + } while (1); + + /* Send over Memory Clear, if present */ + if (ctx->mem_clr_present) { + if ((ret = send_data(ctx->dev, ctx->endp_down, + ctx->mem_clr, sizeof(ctx->mem_clr)))) + return CUPS_BACKEND_FAILED; + } + + /* Send Job Start */ + if ((ret = send_data(ctx->dev, ctx->endp_down, + ctx->hdr, sizeof(ctx->hdr)))) + return CUPS_BACKEND_FAILED; + + /* Send over headers */ + if ((ret = send_data(ctx->dev, ctx->endp_down, + ctx->hdr1, sizeof(ctx->hdr1)))) + return CUPS_BACKEND_FAILED; + if ((ret = send_data(ctx->dev, ctx->endp_down, + ctx->hdr2, sizeof(ctx->hdr2)))) + return CUPS_BACKEND_FAILED; + if ((ret = send_data(ctx->dev, ctx->endp_down, + ctx->hdr3, sizeof(ctx->hdr3)))) + return CUPS_BACKEND_FAILED; + if ((ret = send_data(ctx->dev, ctx->endp_down, + ctx->hdr4, sizeof(ctx->hdr4)))) + return CUPS_BACKEND_FAILED; + + /* Send plane header and image data */ + if ((ret = send_data(ctx->dev, ctx->endp_down, + ctx->plane, sizeof(ctx->plane)))) + return CUPS_BACKEND_FAILED; + if ((ret = send_data(ctx->dev, ctx->endp_down, + ctx->databuf, ctx->datalen))) + return CUPS_BACKEND_FAILED; + + /* Query Status to sanity-check job */ + if ((ret = send_data(ctx->dev, ctx->endp_down, + querycmd, sizeof(querycmd)))) + return CUPS_BACKEND_FAILED; + ret = read_data(ctx->dev, ctx->endp_up, + queryresp, sizeof(queryresp), &num); + if (num != sizeof(queryresp) || ret < 0) { + return CUPS_BACKEND_FAILED; + } + if (queryresp[5] & 0x40) { + ERROR("Printer error %02x\n", queryresp[5]); // XXX decode + return CUPS_BACKEND_STOP; + } + if (queryresp[5] != 0x00) { + ERROR("Printer not ready (%02x)!\n", queryresp[5]); + return CUPS_BACKEND_CANCEL; + } + + /* Send over Footer */ + if ((ret = send_data(ctx->dev, ctx->endp_down, + ctx->ftr, sizeof(ctx->ftr)))) + return CUPS_BACKEND_FAILED; + + /* Query status until we're done.. */ + do { + sleep(1); + + /* Query Status */ + if ((ret = send_data(ctx->dev, ctx->endp_down, + querycmd, sizeof(querycmd)))) + return CUPS_BACKEND_FAILED; + ret = read_data(ctx->dev, ctx->endp_up, + queryresp, sizeof(queryresp), &num); + if (num != sizeof(queryresp) || ret < 0) { + return CUPS_BACKEND_FAILED; + } + + if (queryresp[5] & 0x40) { + ERROR("Printer error %02x\n", queryresp[5]); // XXX decode + return CUPS_BACKEND_STOP; + } + if (queryresp[5] == 0 && queryresp[7] == 0) + break; + + if (queryresp[7] > 0) { + if (fast_return) { + INFO("Fast return mode enabled.\n"); + break; + } + } + } while(1); + + INFO("Print complete\n"); + return CUPS_BACKEND_OK; +} + +static int mitsup95d_cmdline_arg(void *vctx, int argc, char **argv) +{ + struct canonselphy_ctx *ctx = vctx; + int i, j = 0; + + if (!ctx) + return -1; + + while ((i = getopt(argc, argv, GETOPT_LIST_GLOBAL)) >= 0) { + switch(i) { + GETOPT_PROCESS_GLOBAL + } + + if (j) return j; + } + + return 0; +} + +/* Exported */ +struct dyesub_backend mitsup95d_backend = { + .name = "Mitsubishi P95D", + .version = "0.01", + .uri_prefix = "mitsup95d", + .cmdline_arg = mitsup95d_cmdline_arg, + .init = mitsup95d_init, + .attach = mitsup95d_attach, + .teardown = mitsup95d_teardown, + .read_parse = mitsup95d_read_parse, + .main_loop = mitsup95d_main_loop, + .devices = { + { USB_VID_MITSU, USB_PID_MITSU_P95D, P_MITSU_P95D, ""}, + { 0, 0, 0, ""} + } +}; + +/***************************************************** + + Mitsubishi P95D Spool Format + + ...All fields are BIG ENDIAN. + + MEMORY_CLEAR (optional) + + 1b 5a 43 00 + + JOB_HDR + + 1b 51 + + PRINT_SETUP + + 1b 57 20 2e 00 0a 00 02 00 00 00 00 00 00 CC CC + RR RR XX 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 + + XX == 01 seen in sniffs, 00 seen in dumps. Unknown! + + CC CC = columns, RR RR = rows (print dimensions) + + PRINT_OPTIONS + + + 1b 57 21 2e 00 4a aa 00 20 TT 00 00 64 NN 00 MM + [[ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 ]] 00 00 00 02 00 00 00 00 00 00 00 00 00 00 + 00 XY + + NN = copies + 1..200 + 0xff (continuous print) + MM = comment type + 00 = None + 01 = Printer Setting + 02 = Date + 03 = DateTime + [[ .. ]] = actual comment (18 bytes), see below. + TT = media type + 00 = Standard + 01 = High Density + 02 = High Glossy + 03 = High Glossy (K95HG) + X = media cut length + 4..8 (mm) + Y = flags + 0x04 = Paper save + 0x03 = Buzzer (3 = high, 2 = low, 0 = off) + + + GAMMA ???? + + 1b 57 22 2e 00 15 TT 00 00 00 00 00 LL BB CC 00 + [[ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 ]] + + LL = Gamma table + 00 = Printer Setting + 01..05 Gamma table 1..5 + 10 = Use LUT + BB = Brightness (signed 8-bit) + CC = Contrast (signed 8-bit) + TT = Table present + 00 = No + 01 = Yes + [[ .. ]] = Gamma table, loaded from LUT on disk. (skip first 16 bytes) + + USER_COMMENT + + 1b 58 [[ 20 20 20 20 20 20 20 20 20 20 20 20 20 + 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 + 20 20 20 ]] + + [[ .. ]] = Actual comment. 34 bytes payload, 0x20 -> 0x7e + (Null terminated?) + + IMAGE_DATA + + 1b 5a 74 00 00 00 YY YY CC CC RR RR + [[ .. data ... ]] + + CC CC = columns + RR CC = rows + YY YY = row offset + + Followed by C*R bytes of monochrome data, 0xff = white, 0x00 = black + + PRINT_START + + 1b 50 + + ********************************* + + Printer Comms: + + STATUS query + + -> 1b 72 00 00 + <- e4 72 00 00 04 XX 00 YY 00 + + YY == remaining copies + XX == Status? + 00 == Idle + 02 == Printing + 43 == Door open + 44 == No Paper + 4? == "Button" + 4? == "Gear Lock" + 4? == Head Up + ^ + \--- 0x40 appears to be a flag that indicates error. + + **************************** + +UNKNOWNS: + + * How multiple images are stacked for printing on a single page + (col offset too? write four, then tell PRINT?) Is this the mystery 0x01? + * How to adjust printer sharpness? + * Serial number query (iSerial appears bogus) + * What "custom gamma" table does to spool file? + +*/ + + diff --git a/blacklist b/blacklist index a8c0380..a3cb817 100644 --- a/blacklist +++ b/blacklist @@ -175,6 +175,9 @@ # Mitsubishi CP-9810D/DW 0x06d3 0x3b21 blacklist +# Mitsubishi P95D +0x06d3 0x3b10 blacklist + # Citizen CW-01 0x1343 0x0002 blacklist