You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
selphy_print/backend_dnpds40.c

2509 lines
62 KiB

/*
* DNP DS40/DS80 Photo Printer CUPS backend -- libusb-1.0 version
*
* (c) 2013-2016 Solomon Peachy <pizza@shaftnet.org>
*
* Development of this backend was sponsored by:
*
* Marco Di Antonio and [ ilgruppodigitale.com ]
* LiveLink Technology [ www.livelinktechnology.net ]
* A generous benefactor who wishes to remain anonymous
*
* 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]
*
*/
//#define DNP_ONLY
/* Enables caching of last print type to speed up
job pipelining. Without this we always have to
assume the worst */
//#define STATE_DIR "/tmp"
#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_PID_CITIZEN_CW02 0x0006
#define USB_PID_DNP_DS80D 0x0007
#define USB_PID_DNP_DS620_OLD 0x0008
#define USB_VID_DNP 0x1452
#define USB_PID_DNP_DS620 0x8b01
#define USB_PID_DNP_DS820 0x9001
/* 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 ver_major;
int ver_minor;
uint32_t media;
uint32_t duplex_media;
uint16_t media_count_new;
uint32_t multicut;
uint32_t last_multicut;
int last_matte;
int fullcut;
int matte;
int cutter;
int can_rewind;
int printspeed;
int mediaoffset;
int manual_copies;
int correct_count;
uint32_t native_width;
int supports_6x9;
int supports_2x6;
int supports_3x5x2;
int supports_matte;
int supports_finematte;
int supports_luster;
int supports_advmatte;
int supports_fullcut;
int supports_rewind;
int supports_standby;
int supports_6x4_5;
int supports_mqty_default;
int supports_iserial;
int supports_6x6;
int supports_5x5;
int supports_counterp;
int supports_adv_fullcut;
int supports_mediaoffset;
int supports_media_ext;
int supports_printspeed;
int supports_lowspeed;
int supports_highdensity;
int supports_gamma;
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 MULTICUT_5x3_5 1
#define MULTICUT_6x4 2
#define MULTICUT_5x7 3
#define MULTICUT_6x8 4
#define MULTICUT_6x9 5
#define MULTICUT_8x10 6
#define MULTICUT_8x12 7
#define MULTICUT_8x4 8
#define MULTICUT_8x5 9
#define MULTICUT_8x6 10
#define MULTICUT_8x8 11
#define MULTICUT_6x4X2 12
#define MULTICUT_8x4X2 13
#define MULTICUT_8x5X2 14
#define MULTICUT_8x6X2 15
#define MULTICUT_8x5_8x4 16
#define MULTICUT_8x6_8x4 17
#define MULTICUT_8x6_8x5 18
#define MULTICUT_8x8_8x4 19
#define MULTICUT_8x4X3 20
#define MULTICUT_8xA4LEN 21
#define MULTICUT_5x3_5X2 22
#define MULTICUT_6x6 27
#define MULTICUT_5x5 29
#define MULTICUT_6x4_5 30
#define MULTICUT_6x4_5X2 31
#define MULTICUT_8x7 32
#define MULTICUT_8x9 33
#define MULTICUT_A5 34
#define MULTICUT_A5X2 35
#define MULTICUT_A4x4 36
#define MULTICUT_A4x5 37
#define MULTICUT_A4x6 38
#define MULTICUT_A4x8 39
#define MULTICUT_A4x10 40
#define MULTICUT_A4 41
#define MULTICUT_A4x5X2 43
#define MULTICUT_S_SIMPLEX 100
#define MULTICUT_S_FRONT 200
#define MULTICUT_S_BACK 300
#define MULTICUT_S_8x10 6
#define MULTICUT_S_8x12 7
#define MULTICUT_S_8x4 8
#define MULTICUT_S_8x5 9
#define MULTICUT_S_8x6 10
#define MULTICUT_S_8x8 11
#define MULTICUT_S_8x4X2 13
#define MULTICUT_S_8x5X2 14
#define MULTICUT_S_8x6X2 15
#define MULTICUT_S_8x10_5 25
#define MULTICUT_S_8x10_75 26
#define MULTICUT_S_8x4X3 28 // different than roll type.
#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;
}
}
static char *dnpds40_printer_type(int type)
{
switch(type) {
case P_DNP_DS40: return "DS40";
case P_DNP_DS80: return "DS80";
case P_DNP_DS80D: return "DS80DX";
case P_DNP_DSRX1: return "DSRX1";
case P_DNP_DS620: return "DS620";
case P_DNP_DS820: return "DS820";
default: break;
}
return "Unknown";
}
static char *dnpds40_media_types(int media)
{
switch (media) {
case 100: return "UNKNOWN100"; // seen in driver dumps
case 110: return "UNKNOWN110"; // seen in driver dumps
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";
case 600: return "A4";
default:
break;
}
return "Unknown type";
}
static char *dnpds620_media_extension_code(int media)
{
switch (media) {
case 00: return "Normal Paper";
case 01: return "Sticky Paper";
case 99: return "Unknown Paper";
default:
break;
}
return "Unknown type";
}
static char *dnpds820_media_subtypes(int media)
{
switch (media) {
case 0001: return "SD";
case 0003: return "PP";
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)
{
if (status >= 5000 && status <= 5999)
return dnpds80_duplex_statuses(status);
switch (status) {
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";
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";
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";
}
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_cmd2(struct dnpds40_ctx *ctx,
struct dnpds40_cmd *cmd,
int *len,
uint8_t *buf, uint32_t buf_len)
{
char tmp[9];
uint8_t *respbuf;
int ret, i, num = 0;
memset(tmp, 0, sizeof(tmp));
if ((ret = dnpds40_do_cmd(ctx, cmd, buf, buf_len)))
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;
}
#define dnpds40_resp_cmd(__ctx, __cmd, __len) dnpds40_resp_cmd2(__ctx, __cmd, __len, NULL, 0)
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;
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->native_width = 1920;
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;
break;
case P_DNP_DS80:
case P_DNP_DS80D:
ctx->native_width = 2560;
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->native_width = 1920;
ctx->supports_counterp = 1;
ctx->supports_matte = 1;
if (FW_VER_CHECK(1,10))
ctx->supports_2x6 = ctx->supports_mqty_default = 1;
if (FW_VER_CHECK(1,20))
ctx->supports_3x5x2 = 1;
if (FW_VER_CHECK(2,00)) { /* AKA RX1HS */
ctx->supports_mediaoffset = 1;
ctx->supports_iserial = 1;
}
break;
case P_DNP_DS620:
ctx->native_width = 1920;
ctx->correct_count = 1;
ctx->supports_counterp = 1;
ctx->supports_matte = 1;
ctx->supports_2x6 = 1;
ctx->supports_fullcut = 1;
ctx->supports_mqty_default = 1;
if (strchr(ctx->version, 'A'))
ctx->supports_rewind = 0;
else
ctx->supports_rewind = 1;
ctx->supports_standby = 1;
ctx->supports_iserial = 1;
ctx->supports_6x6 = 1;
ctx->supports_5x5 = 1;
ctx->supports_lowspeed = 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 = ctx->supports_advmatte = 1;
if (FW_VER_CHECK(1,30))
ctx->supports_luster = ctx->supports_finematte = 1;
if (FW_VER_CHECK(1,33))
ctx->supports_media_ext = 1;
break;
case P_DNP_DS820:
ctx->native_width = 2560;
ctx->correct_count = 1;
ctx->supports_counterp = 1;
ctx->supports_matte = 1;
ctx->supports_fullcut = 1;
ctx->supports_mqty_default = 1;
if (strchr(ctx->version, 'A'))
ctx->supports_rewind = 0;
else
ctx->supports_rewind = 1;
ctx->supports_standby = 1;
ctx->supports_iserial = 1;
ctx->supports_adv_fullcut = 1;
ctx->supports_advmatte = 1;
ctx->supports_luster = 1;
ctx->supports_finematte = 1;
ctx->supports_printspeed = 1;
ctx->supports_lowspeed = 1;
ctx->supports_highdensity = 1;
if (FW_VER_CHECK(0,50))
ctx->supports_gamma = 1;
break;
default:
ERROR("Unknown vid/pid %04x/%04x (%d)\n", desc.idVendor, desc.idProduct, ctx->type);
return;
}
ctx->last_matte = -1;
#ifdef STATE_DIR
/* Check our current job's lamination vs previous job. */
{
/* Load last matte status from file */
char buf[64];
FILE *f;
snprintf(buf, sizeof(buf), STATE_DIR "/%s-last", ctx->serno);
f = fopen(buf, "r");
if (f) {
fscanf(f, "%d", &ctx->last_matte);
fclose(f);
}
}
#endif
if (ctx->supports_mediaoffset) {
/* Get Media Offset */
struct dnpds40_cmd cmd;
uint8_t *resp;
int len = 0;
dnpds40_build_cmd(&cmd, "INFO", "MEDIA_OFFSET", 0);
resp = dnpds40_resp_cmd(ctx, &cmd, &len);
if (resp) {
ctx->mediaoffset = atoi((char*)resp+4);
free(resp);
}
} else if (!ctx->correct_count) {
ctx->mediaoffset = 50;
}
if (ctx->supports_mqty_default) {
struct dnpds40_cmd cmd;
uint8_t *resp;
int len = 0;
dnpds40_build_cmd(&cmd, "INFO", "MQTY_DEFAULT", 0);
resp = dnpds40_resp_cmd(ctx, &cmd, &len);
if (resp) {
dnpds40_cleanup_string((char*)resp, len);
ctx->media_count_new = atoi((char*)resp+4);
free(resp);
ctx->media_count_new -= ctx->mediaoffset;
}
} else {
/* Look it up for legacy models & FW */
switch (ctx->type) {
case P_DNP_DS40:
switch (ctx->media) {
case 200: // L
ctx->media_count_new = 460;
break;
case 210: // 2L
ctx->media_count_new = 230;
break;
case 300: // PC
ctx->media_count_new = 400;
break;
case 310: // A5
ctx->media_count_new = 200;
break;
case 400: // A5W
ctx->media_count_new = 180;
break;
default:
ctx->media_count_new = 999; // non-zero
break;
}
break;
case P_DNP_DSRX1:
switch (ctx->media) {
case 300: // PC
ctx->media_count_new = 700;
break;
case 310: // A5
ctx->media_count_new = 350;
break;
default:
ctx->media_count_new = 999; // non-zero