selphy_print/backend_dnpds40.c

1986 lines
48 KiB
C
Raw Normal View History

/*
* DNP DS40/DS80 Photo Printer CUPS backend -- libusb-1.0 version
*
2015-01-05 21:39:22 -05:00
* (c) 2013-2015 Solomon Peachy <pizza@shaftnet.org>
*
* Development of this backend was sponsored by:
*
* Marco Di Antonio and [ ilgruppodigitale.com ]
* LiveLink Technology [ www.livelinktechnology.net ]
*
* 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]
*
*/
2015-06-22 23:51:17 -04:00
//#define MATTE_STATE
//#define DNP_ONLY
#define MATTE_GLOSSY_2BUF
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#define BACKEND dnpds40_backend
#include "backend_common.h"
#define USB_VID_CITIZEN 0x1343
#define USB_PID_DNP_DS40 0x0003 // Also Citizen CX
#define USB_PID_DNP_DS80 0x0004 // Also Citizen CX-W, and Mitsubishi CP-3800DW
#define USB_PID_DNP_DSRX1 0x0005 // Also Citizen CY
#define USB_VID_DNP 0x1452
#define USB_PID_DNP_DS620 0x8b01
//#define USB_PID_DNP_DS80D XXXX
//#define USB_PID_CITIZEN_CW-02 XXXXX
//#define USB_PID_CITIZEN_OP900II XXXXX
/* Private data stucture */
struct dnpds40_ctx {
struct libusb_device_handle *dev;
uint8_t endp_up;
uint8_t endp_down;
int type;
char *serno;
char *version;
int buf_needed;
int last_matte;
int ver_major;
int ver_minor;
int media;
int duplex_media;
uint32_t multicut;
uint32_t last_multicut;
int fullcut;
int matte;
int cutter;
int can_rewind;
int manual_copies;
int supports_6x9;
int supports_2x6;
int supports_3x5x2;
int supports_matte;
int supports_fullcut;
int supports_rewind;
int supports_standby;
int supports_6x4_5;
int supports_mqty_default;
int supports_iserial;
int supports_square;
int supports_counterp;
int supports_adv_fullcut;
uint8_t *qty_offset;
uint8_t *buffctrl_offset;
uint8_t *multicut_offset;
uint8_t *databuf;
int datalen;
};
struct dnpds40_cmd {
uint8_t esc; /* Fixed at ascii ESC, aka 0x1B */
uint8_t p; /* Fixed at ascii 'P' aka 0x50 */
uint8_t arg1[6];
uint8_t arg2[16];
uint8_t arg3[8]; /* Decimal value of arg4's length, or empty */
uint8_t arg4[0]; /* Extra payload if arg3 is non-empty
Doesn't have to be sent in the same URB */
/* All unused elements are set to 0x20 (ie ascii space) */
};
#define min(__x, __y) ((__x) < (__y)) ? __x : __y
static void dnpds40_build_cmd(struct dnpds40_cmd *cmd, char *arg1, char *arg2, uint32_t arg3_len)
{
memset(cmd, 0x20, sizeof(*cmd));
cmd->esc = 0x1b;
cmd->p = 0x50;
memcpy(cmd->arg1, arg1, min(strlen(arg1), sizeof(cmd->arg1)));
memcpy(cmd->arg2, arg2, min(strlen(arg2), sizeof(cmd->arg2)));
if (arg3_len) {
char buf[9];
snprintf(buf, sizeof(buf), "%08u", arg3_len);
memcpy(cmd->arg3, buf, 8);
}
}
static void dnpds40_cleanup_string(char *start, int len)
{
char *ptr = strchr(start, 0x0d);
if (ptr && (ptr - start < len)) {
*ptr = 0x00; /* If there is a <CR>, terminate there */
len = ptr - start;
} else {
start[--len] = 0x00; /* force null-termination */
}
/* Trim trailing spaces */
while (len && start[len-1] == ' ') {
start[--len] = 0;
}
}
2015-06-22 23:55:48 -04:00
static char *dnpds40_media_types(int media)
{
2015-06-22 23:55:48 -04:00
switch (media) {
case 200: return "5x3.5 (L)";
case 210: return "5x7 (2L)";
case 300: return "6x4 (PC)";
case 310: return "6x8 (A5)";
case 400: return "6x9 (A5W)";
case 500: return "8x10";
case 510: return "8x12";
default:
break;
}
return "Unknown type";
}
static char *dnpds80_duplex_media_types(int media)
{
switch (media) {
case 100: return "8x10.75";
case 200: return "8x12";
default:
break;
}
return "Unknown type";
}
static char *dnpds80_duplex_statuses(int status)
{
switch (status) {
case 5000: return "No Error";
case 5500: return "Duplex Unit Not Connected";
case 5017: return "Paper Jam: Supply Sensor On";
case 5018: return "Paper Jam: Supply Sensor Off";
case 5019: return "Paper Jam: Slot Sensor On";
case 5020: return "Paper Jam: Slot Sensor Off";
case 5021: return "Paper Jam: Pass Sensor On";
case 5022: return "Paper Jam: Pass Sensor Off";
case 5023: return "Paper Jam: Shell Sensor 1 On";
case 5024: return "Paper Jam: Shell Sensor 1 Off";
case 5025: return "Paper Jam: Shell Sensor 2 On";
case 5026: return "Paper Jam: Shell Sensor 2 Off";
case 5027: return "Paper Jam: Eject Sensor On";
case 5028: return "Paper Jam: Eject Sensor Off";
case 5029: return "Paper Jam: Slot FG Sensor";
case 5030: return "Paper Jam: Shell FG Sensor";
case 5033: return "Paper Supply Sensor Off";
case 5034: return "Printer Feed Slot Sensor Off";
case 5035: return "Pinch Pass Sensor Off";
case 5036: return "Shell Pass Sensor 1 Off";
case 5037: return "Shell Pass Sensor 2 Off";
case 5038: return "Eject Sensor Off";
case 5049: return "Capstan Drive Control Error";
case 5065: return "Shell Roller Error";
case 5081: return "Pinch Open Error";
case 5082: return "Pinch Close Error";
case 5083: return "Pinch Init Error";
case 5084: return "Pinch Position Error";
case 5097: return "Pass Guide Supply Error";
case 5098: return "Pass Guide Shell Error";
case 5099: return "Pass Guide Eject Error";
case 5100: return "Pass Guide Init Error";
case 5101: return "Pass Guide Position Error";
case 5113: return "Side Guide Home Error";
case 5114: return "Side Guide Position Error";
case 5115: return "Side Guide Init Error";
case 5129: return "Act Guide Home Error";
case 5145: return "Shell Rotate Home Error";
case 5146: return "Shell Rotate Rev Error";
case 5161: return "Paper Feed Lever Down Error";
case 5162: return "Paper Feed Lever Lock Error";
case 5163: return "Paper Feed Lever Up Error";
case 5177: return "Cutter Home Error";
case 5178: return "Cutter Away Error";
case 5179: return "Cutter Init Error";
case 5180: return "Cutter Position Error";
case 5193: return "Paper Tray Removed";
case 5209: return "Cover Opened";
case 5241: return "System Error";
default:
break;
}
return "Unkown Duplexer Error";
}
static char *dnpds40_statuses(int status)
2013-12-03 22:32:08 -05:00
{
if (status >= 5000 && status <= 5999)
return dnpds80_duplex_statuses(status);
switch (status) {
2013-12-03 22:32:08 -05:00
case 0: return "Idle";
case 1: return "Printing";
case 500: return "Cooling Print Head";
case 510: return "Cooling Paper Motor";
case 900: return "Standby Mode";
2013-12-03 22:32:08 -05:00
case 1000: return "Cover Open";
case 1010: return "No Scrap Box";
case 1100: return "Paper End";
case 1200: return "Ribbon End";
case 1300: return "Paper Jam";
case 1400: return "Ribbon Error";
2013-12-03 22:32:08 -05:00
case 1500: return "Paper Definition Error";
case 1600: return "Data Error";
case 2000: return "Head Voltage Error";
case 2100: return "Head Position Error";
case 2200: return "Power Supply Fan Error";
case 2300: return "Cutter Error";
case 2400: return "Pinch Roller Error";
case 2500: return "Abnormal Head Temperature";
case 2600: return "Abnormal Media Temperature";
case 2610: return "Abnormal Paper Motor Temperature";
case 2700: return "Ribbon Tension Error";
case 2800: return "RF-ID Module Error";
case 3000: return "System Error";
default:
break;
}
return "Unknown Error";
2013-12-03 22:32:08 -05:00
}
static int dnpds40_do_cmd(struct dnpds40_ctx *ctx,
struct dnpds40_cmd *cmd,
uint8_t *data, int len)
{
int ret;
if ((ret = send_data(ctx->dev, ctx->endp_down,
(uint8_t*)cmd, sizeof(*cmd))))
return ret;
if (data && len)
if ((ret = send_data(ctx->dev, ctx->endp_down,
data, len)))
return ret;
return CUPS_BACKEND_OK;
}
static uint8_t * dnpds40_resp_cmd(struct dnpds40_ctx *ctx,
struct dnpds40_cmd *cmd,
int *len)
{
char tmp[9];
uint8_t *respbuf;
int ret, i, num = 0;
memset(tmp, 0, sizeof(tmp));
if ((ret = dnpds40_do_cmd(ctx, cmd, NULL, 0)))
return NULL;
/* Read in the response header */
ret = read_data(ctx->dev, ctx->endp_up,
(uint8_t*)tmp, 8, &num);
if (ret < 0)
return NULL;
if (num != 8) {
ERROR("Short read! (%d/%d)\n", num, 8);
return NULL;
}
i = atoi(tmp); /* Length of payload in bytes, possibly padded */
respbuf = malloc(i);
if (!respbuf) {
ERROR("Memory allocation failure (%d bytes)!\n", i);
return NULL;
}
/* Read in the actual response */
ret = read_data(ctx->dev, ctx->endp_up,
respbuf, i, &num);
if (ret < 0) {
free(respbuf);
return NULL;
}
if (num != i) {
ERROR("Short read! (%d/%d)\n", num, i);
free(respbuf);
return NULL;
}
*len = num;
return respbuf;
}
static int dnpds40_query_serno(struct libusb_device_handle *dev, uint8_t endp_up, uint8_t endp_down, char *buf, int buf_len)
{
struct dnpds40_cmd cmd;
uint8_t *resp;
int len = 0;
struct dnpds40_ctx ctx = {
.dev = dev,
.endp_up = endp_up,
.endp_down = endp_down,
};
/* Get Serial Number */
dnpds40_build_cmd(&cmd, "INFO", "SERIAL_NUMBER", 0);
resp = dnpds40_resp_cmd(&ctx, &cmd, &len);
if (!resp)
return CUPS_BACKEND_FAILED;
dnpds40_cleanup_string((char*)resp, len);
strncpy(buf, (char*)resp, buf_len);
buf[buf_len-1] = 0;
free(resp);
return CUPS_BACKEND_OK;
}
static void *dnpds40_init(void)
{
struct dnpds40_ctx *ctx = malloc(sizeof(struct dnpds40_ctx));
if (!ctx) {
ERROR("Memory allocation failure (%d bytes)!\n", (int)sizeof(struct dnpds40_ctx));
return NULL;
}
memset(ctx, 0, sizeof(struct dnpds40_ctx));
ctx->type = P_ANY;
ctx->last_matte = -1;
return ctx;
}
#define FW_VER_CHECK(__major, __minor) \
((ctx->ver_major > (__major)) || \
(ctx->ver_major == (__major) && ctx->ver_minor >= (__minor)))
static void dnpds40_attach(void *vctx, struct libusb_device_handle *dev,
uint8_t endp_up, uint8_t endp_down, uint8_t jobid)
{
struct dnpds40_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);
ctx->type = lookup_printer_type(&dnpds40_backend,
desc.idVendor, desc.idProduct);
{
/* Get Firmware Version */
struct dnpds40_cmd cmd;
uint8_t *resp;
int len = 0;
dnpds40_build_cmd(&cmd, "INFO", "FVER", 0);
resp = dnpds40_resp_cmd(ctx, &cmd, &len);
if (resp) {
char *ptr;
dnpds40_cleanup_string((char*)resp, len);
ctx->version = strdup((char*) resp);
/* Parse version */
/* ptr = */ strtok((char*)resp, " .");
ptr = strtok(NULL, ".");
ctx->ver_major = atoi(ptr);
ptr = strtok(NULL, ".");
ctx->ver_minor = atoi(ptr);
free(resp);
}
/* Get Serial Number */
dnpds40_build_cmd(&cmd, "INFO", "SERIAL_NUMBER", 0);
resp = dnpds40_resp_cmd(ctx, &cmd, &len);
if (resp) {
dnpds40_cleanup_string((char*)resp, len);
ctx->serno = (char*) resp;
/* Do NOT free resp! */
}
/* Query Media Info */
dnpds40_build_cmd(&cmd, "INFO", "MEDIA", 0);
resp = dnpds40_resp_cmd(ctx, &cmd, &len);
if (resp) {
char tmp[4];
dnpds40_cleanup_string((char*)resp, len);
memcpy(tmp, resp + 4, 3);
tmp[3] = 0;
ctx->media = atoi(tmp);
/* Subtract out the "mark" type */
if (ctx->media & 1)
ctx->media--;
free(resp);
}
}
if (ctx->type == P_DNP_DS80D) {
struct dnpds40_cmd cmd;
uint8_t *resp;
int len = 0;
/* Query Duplex Media Info */
dnpds40_build_cmd(&cmd, "INFO", "CUT_PAPER", 0);
resp = dnpds40_resp_cmd(ctx, &cmd, &len);
if (resp) {
char tmp[5];
dnpds40_cleanup_string((char*)resp, len);
memcpy(tmp, resp + 4, 4);
tmp[4] = 0;
ctx->duplex_media = atoi(tmp);
/* Subtract out the paper status */
if (ctx->duplex_media & 3)
ctx->duplex_media -= (ctx->duplex_media & 3);
free(resp);
}
}
#ifdef DNP_ONLY
/* Only allow DNP printers to work. Rebadged versions should not. */
{ /* Validate USB Vendor String is "Dai Nippon Printing" */
char buf[256];
buf[0] = 0;
libusb_get_string_descriptor_ascii(dev, desc->iManufacturer, (unsigned char*)buf, STR_LEN_MAX);
sanitize_string(buf);
if (strncmp(buf, "Dai", 3))
return 0;
}
#endif
/* Per-printer options */
switch (ctx->type) {
case P_DNP_DS40:
ctx->supports_6x9 = 1;
if (FW_VER_CHECK(1,04))
ctx->supports_counterp = 1;
if (FW_VER_CHECK(1,30))
ctx->supports_matte = 1;
if (FW_VER_CHECK(1,40))
ctx->supports_2x6 = 1;
if (FW_VER_CHECK(1,50))
ctx->supports_3x5x2 = 1;
if (FW_VER_CHECK(1,51))
ctx->supports_fullcut = 1;
break;
case P_DNP_DS80:
case P_DNP_DS80D:
if (FW_VER_CHECK(1,02))
ctx->supports_counterp = 1;
if (FW_VER_CHECK(1,30))
ctx->supports_matte = 1;
break;
case P_DNP_DSRX1:
ctx->supports_counterp = 1;
ctx->supports_matte = 1;
ctx->supports_mqty_default = 1; // 1.10 does. Maybe older too?
if (FW_VER_CHECK(1,10))
ctx->supports_2x6 = 1;
break;
case P_DNP_DS620:
ctx->supports_counterp = 1;
ctx->supports_matte = 1;
ctx->supports_2x6 = 1;
ctx->supports_fullcut = 1;
ctx->supports_mqty_default = 1;
ctx->supports_rewind = 1;
ctx->supports_standby = 1;
ctx->supports_iserial = 1;
ctx->supports_square = 1;
if (FW_VER_CHECK(0,30))
ctx->supports_3x5x2 = 1;
if (FW_VER_CHECK(1,10))
ctx->supports_6x9 = ctx->supports_6x4_5 = 1;
if (FW_VER_CHECK(1,20))
ctx->supports_adv_fullcut = 1;
break;
default:
ERROR("Unknown vid/pid %04x/%04x (%d)\n", desc.idVendor, desc.idProduct, ctx->type);
return;
}
}
static void dnpds40_teardown(void *vctx) {
struct dnpds40_ctx *ctx = vctx;
if (!ctx)
return;
if (ctx->type == P_DNP_DS80D) {
struct dnpds40_cmd cmd;
int ret;
/* Check to see if last print was the front side
of a duplex job, and if so, cancel things so we're done */
if (ctx->last_multicut >= 200 &&
ctx->last_multicut < 300) {
dnpds40_build_cmd(&cmd, "CNTRL", "DUPLEX_CANCEL", 0);
if ((ret = dnpds40_do_cmd(ctx, &cmd, NULL, 0)))
return;
}
}
if (ctx->databuf)
free(ctx->databuf);
if (ctx->serno)
free(ctx->serno);
if (ctx->version)
free(ctx->version);
free(ctx);
}
#define MAX_PRINTJOB_LEN (((2560*7536+1024+54))*3+1024) /* Worst-case */
static int dnpds40_read_parse(void *vctx, int data_fd) {
struct dnpds40_ctx *ctx = vctx;
int run = 1;
char buf[9] = { 0 };
uint32_t dpi;
if (!ctx)
return CUPS_BACKEND_FAILED;
if (ctx->databuf) {
free(ctx->databuf);
ctx->databuf = NULL;
}
/* There's no way to figure out the total job length in advance, we
have to parse the stream until we get to the image plane data,
and even then the stream can contain arbitrary commands later.
So instead, we allocate a buffer of the maximum possible length,
then parse the incoming stream until we hit the START command at
the end of the job.
*/
ctx->datalen = 0;
ctx->databuf = malloc(MAX_PRINTJOB_LEN);
if (!ctx->databuf) {
ERROR("Memory allocation failure!\n");
return CUPS_BACKEND_CANCEL;
}
/* Clear everything out */
dpi = 0;
ctx->matte = 0;
ctx->cutter = 0;
ctx->manual_copies = 0;
ctx->multicut = 0;
ctx->fullcut = 0;
ctx->can_rewind = 0;
ctx->buffctrl_offset = ctx->qty_offset = ctx->multicut_offset = 0;
while (run) {
int remain, i, j;
/* Read in command header */
i = read(data_fd, ctx->databuf + ctx->datalen,
sizeof(struct dnpds40_cmd));
if (i < 0)
return i;
if (i == 0)
break;
if (i < (int) sizeof(struct dnpds40_cmd))
return CUPS_BACKEND_CANCEL;
if (ctx->databuf[ctx->datalen + 0] != 0x1b ||
ctx->databuf[ctx->datalen + 1] != 0x50) {
ERROR("Unrecognized header data format @%d!\n", ctx->datalen);
return CUPS_BACKEND_CANCEL;
}
/* Parse out length of data chunk, if any */
memcpy(buf, ctx->databuf + ctx->datalen + 24, 8);
j = atoi(buf);
/* Read in data chunk as quickly as possible */
remain = j;
while (remain > 0) {
i = read(data_fd, ctx->databuf + ctx->datalen + sizeof(struct dnpds40_cmd),
remain);
if (i < 0) {
ERROR("Data Read Error: %d (%d/%d @%d)\n", i, remain, j, ctx->datalen);
return i;
}
if (i == 0)
return 1;
ctx->datalen += i;
remain -= i;
}
ctx->datalen -= j; /* Back it off */
/* Check for some offsets */
if(!memcmp("CNTRL QTY", ctx->databuf + ctx->datalen+2, 9)) {
ctx->qty_offset = ctx->databuf + ctx->datalen + 32;
}
if(!memcmp("CNTRL CUTTER", ctx->databuf + ctx->datalen+2, 12)) {
memcpy(buf, ctx->databuf + ctx->datalen + 32, 8);
ctx->cutter = atoi(buf);
}
if(!memcmp("CNTRL BUFFCNTRL", ctx->databuf + ctx->datalen+2, 15)) {
/* If the printer doesn't support matte, it doesn't
support buffcntrl. strip it from the stream */
if (ctx->supports_matte) {
ctx->buffctrl_offset = ctx->databuf + ctx->datalen + 32;
} else {
WARNING("Printer FW does not support BUFFCNTRL, please update\n");
continue;
}
}
if(!memcmp("CNTRL OVERCOAT", ctx->databuf + ctx->datalen+2, 14)) {
/* If the printer doesn't support matte, it doesn't
support buffcntrl. strip it from the stream */
if (ctx->supports_matte) {
memcpy(buf, ctx->databuf + ctx->datalen + 32, 8);
ctx->matte = atoi(buf);
} else {
WARNING("Printer FW does not support matte prints, using glossy mode\n");
continue;
}
}
if(!memcmp("IMAGE MULTICUT", ctx->databuf + ctx->datalen+2, 14)) {
ctx->multicut_offset = ctx->databuf + ctx->datalen + 32;
memcpy(buf, ctx->databuf + ctx->datalen + 32, 8);
ctx->multicut = atoi(buf);
2015-08-29 07:13:59 -04:00
/* Backend automatically handles rewind support, so
ignore application requests to use it. */
if (ctx->multicut > 400)
ctx->multicut -= 400;
}
if(!memcmp("CNTRL FULL_CUTTER_SET", ctx->databuf + ctx->datalen+2, 21)) {
if (!ctx->supports_fullcut) {
WARNING("Printer FW does not support full cutter control!\n");
continue;
}
ctx->fullcut = 1;