/* * DNP DS40/DS80 Photo Printer CUPS backend -- libusb-1.0 version * * (c) 2013 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 #include "backend_common.h" #define USB_VID_DNP 0x1343 #define USB_PID_DNP_DS40 0x0003 #define USB_PID_DNP_DS80 0x0004 /* Private data stucture */ struct dnpds40_ctx { struct libusb_device_handle *dev; uint8_t endp_up; uint8_t endp_down; int type; uint8_t *databuf; int datalen; }; struct dnpds40_cmd { uint8_t esc; uint8_t p; uint8_t arg1[6]; uint8_t arg2[16]; uint8_t arg3[8]; /* Decimal value of arg4's length, or empty */ /* All unused elements are set to ' ' (ie ascii space) */ }; #define DS40_CMD_LEN 32 #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) { uint i; 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) snprintf((char*)cmd->arg3, 8, "%08d", arg3_len); DEBUG("command: '%s' ", (char*)cmd); for (i = 0 ; i < sizeof(*cmd); i++) { DEBUG2("%02x ", *(((uint8_t*)cmd)+i)); } DEBUG2("\n"); } static void dnpds40_cleanup_string(char *start, int len) { char *ptr = strchr(start, 0x0d); if (ptr) *ptr = 0x00; /* If there is a , terminate there */ else *(start + len - 1) = 0x00; /* force null-termination */ } static char *dnpds40_media_types(char *str) { char tmp[4]; int i; memcpy(tmp, str + 4, 3); tmp[3] = 0; i = atoi(tmp); switch (i) { 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 *dnpds40_statuses(char *str) { char tmp[6]; int i; memcpy(tmp, str, 5); tmp[5] = 0; i = atoi(tmp); switch (i) { case 0: return "Idle"; case 1: return "Printing"; case 500: return "Cooling Print Head"; case 510: return "Cooling Paper Motor"; 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 "Unkown type"; } static void *dnpds40_init(void) { struct dnpds40_ctx *ctx = malloc(sizeof(struct dnpds40_ctx)); if (!ctx) return NULL; memset(ctx, 0, sizeof(struct dnpds40_ctx)); ctx->type = P_ANY; return ctx; } 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); /* Map out device type */ if (desc.idProduct == USB_PID_DNP_DS40) ctx->type = P_DNP_DS40; else ctx->type = P_DNP_DS80; } static void dnpds40_teardown(void *vctx) { struct dnpds40_ctx *ctx = vctx; if (!ctx) return; if (ctx->databuf) free(ctx->databuf); free(ctx); } #define MAX_PRINTJOB_LEN (27927714+1024) // Add a little bit of padding #define READBACK_LEN 1024 static int dnpds40_read_parse(void *vctx, int data_fd) { struct dnpds40_ctx *ctx = vctx; int i; if (!ctx) return 1; ctx->databuf = malloc(MAX_PRINTJOB_LEN); if (!ctx->databuf) { ERROR("Memory allocation failure!\n"); return 2; } while((i = read(data_fd, ctx->databuf + ctx->datalen, 4096)) > 0) { ctx->datalen += i; } return 0; } static int dnpds40_main_loop(void *vctx, int copies) { struct dnpds40_ctx *ctx = vctx; int ret; if (!ctx) return 1; // XXX printer probably supports making copies. while (copies--) { DEBUG("Sending %d bytes to printer\n", ctx->datalen); if ((ret = send_data(ctx->dev, ctx->endp_down, ctx->databuf, ctx->datalen))) return ret; INFO("Print complete (%d remaining)\n", copies); } /* Just dump the whole damn thing over */ return 0; } static int dnpds40_get_info(struct dnpds40_ctx *ctx) { struct dnpds40_cmd cmd; uint8_t rdbuf[READBACK_LEN]; char tmp[9]; int ret, i, num = 0; /* Get Firmware Version */ dnpds40_build_cmd(&cmd, "INFO", "FVER", 0); if ((ret = send_data(ctx->dev, ctx->endp_down, (uint8_t*)&cmd, sizeof(cmd)))) return ret; /* Read in the response */ memset(rdbuf, 0, sizeof(rdbuf)); ret = libusb_bulk_transfer(ctx->dev, ctx->endp_up, rdbuf, READBACK_LEN, &num, 5000); if (ret < 0 || num < 8) { ERROR("Failure to receive data from printer (libusb error %d: (%d/%d from 0x%02x))\n", ret, num, (int)sizeof(rdbuf), ctx->endp_up); if (ret < 0) return ret; return 4; } memcpy(tmp, rdbuf, 8); tmp[8] = 0; i = atoi(tmp); /* Length of payload in bytes, possibly padded */ DEBUG("readback: '%s' len %d/%d\n", (char*) rdbuf, i, num); dnpds40_cleanup_string((char*)rdbuf + 8, i); INFO("Firmware Version: '%s'\n", (char*)rdbuf + 8); /* *************************** */ /* Get Sensor Info */ dnpds40_build_cmd(&cmd, "INFO", "SENSOR", 0); if ((ret = send_data(ctx->dev, ctx->endp_down, (uint8_t*)&cmd, sizeof(cmd)))) return ret; /* Read in the response */ memset(rdbuf, 0, sizeof(rdbuf)); ret = libusb_bulk_transfer(ctx->dev, ctx->endp_up, rdbuf, READBACK_LEN, &num, 5000); if (ret < 0 || num < 8) { ERROR("Failure to receive data from printer (libusb error %d: (%d/%d from 0x%02x))\n", ret, num, (int)sizeof(rdbuf), ctx->endp_up); if (ret < 0) return ret; return 4; } memcpy(tmp, rdbuf, 8); tmp[8] = 0; i = atoi(tmp); /* Length of payload in bytes, possibly padded */ DEBUG("readback: '%s' len %d/%d\n", (char*) rdbuf, i, num); dnpds40_cleanup_string((char*)rdbuf + 8, i); INFO("Sensor Info: '%s'\n", (char*)rdbuf + 8); /* *************************** */ /* Get Media Info */ dnpds40_build_cmd(&cmd, "INFO", "MEDIA", 0); if ((ret = send_data(ctx->dev, ctx->endp_down, (uint8_t*)&cmd, sizeof(cmd)))) return ret; /* Read in the response */ memset(rdbuf, 0, sizeof(rdbuf)); ret = libusb_bulk_transfer(ctx->dev, ctx->endp_up, rdbuf, READBACK_LEN, &num, 5000); if (ret < 0 || num < 8) { ERROR("Failure to receive data from printer (libusb error %d: (%d/%d from 0x%02x))\n", ret, num, (int)sizeof(rdbuf), ctx->endp_up); if (ret < 0) return ret; return 4; } memcpy(tmp, rdbuf, 8); tmp[8] = 0; i = atoi(tmp); /* Length of payload in bytes, possibly padded */ DEBUG("readback: '%s' len %d/%d\n", (char*) rdbuf, i, num); dnpds40_cleanup_string((char*)rdbuf + 8, i); INFO("Media Type: '%s'\n", (char*)rdbuf + 8); INFO(" %s\n", dnpds40_media_types((char*)rdbuf+8)); switch (*(rdbuf+8+4)) { case '1': INFO(" Stickier paper\n"); break; case '0': INFO(" Standard paper\n"); break; default: INFO(" Unknown paper(%c)\n", *(rdbuf+8+4)); break; } switch (*(rdbuf+8+7)) { case '1': INFO(" With mark\n"); break; case '0': INFO(" Without mark\n"); break; default: INFO(" Unknown mark(%c)\n", *(rdbuf+8+7)); break; } /* Get Media remaining */ dnpds40_build_cmd(&cmd, "INFO", "MQTY", 0); if ((ret = send_data(ctx->dev, ctx->endp_down, (uint8_t*)&cmd, sizeof(cmd)))) return ret; /* Read in the response */ memset(rdbuf, 0, sizeof(rdbuf)); ret = libusb_bulk_transfer(ctx->dev, ctx->endp_up, rdbuf, READBACK_LEN, &num, 5000); if (ret < 0 || num < 8) { ERROR("Failure to receive data from printer (libusb error %d: (%d/%d from 0x%02x))\n", ret, num, (int)sizeof(rdbuf), ctx->endp_up); if (ret < 0) return ret; return 4; } memcpy(tmp, rdbuf, 8); tmp[8] = 0; i = atoi(tmp); /* Length of payload in bytes, possibly padded */ DEBUG("readback: '%s' len %d/%d\n", (char*) rdbuf, i, num); dnpds40_cleanup_string((char*)rdbuf + 8, i); INFO("Prints remaining: '%s'\n", (char*)rdbuf + 8 + 4); return 0; } static int dnpds40_get_status(struct dnpds40_ctx *ctx) { struct dnpds40_cmd cmd; uint8_t rdbuf[READBACK_LEN]; char tmp[9]; int ret, i, num = 0; /* Get Firmware Version */ dnpds40_build_cmd(&cmd, "STATUS", "", 0); if ((ret = send_data(ctx->dev, ctx->endp_down, (uint8_t*)&cmd, sizeof(cmd)))) return ret; /* Read in the response */ memset(rdbuf, 0, sizeof(rdbuf)); ret = libusb_bulk_transfer(ctx->dev, ctx->endp_up, rdbuf, READBACK_LEN, &num, 5000); if (ret < 0 || num < 8) { ERROR("Failure to receive data from printer (libusb error %d: (%d/%d from 0x%02x))\n", ret, num, (int)sizeof(rdbuf), ctx->endp_up); if (ret < 0) return ret; return 4; } memcpy(tmp, rdbuf, 8); tmp[8] = 0; i = atoi(tmp); /* Length of payload in bytes, possibly padded */ DEBUG("readback: '%s' len %d/%d\n", (char*) rdbuf, i, num); dnpds40_cleanup_string((char*)rdbuf + 8, i); INFO("Printer Status: %s => %s\n", (char*)rdbuf + 8, dnpds40_statuses((char*)rdbuf+8)); return 0; } static void dnpds40_cmdline(char *caller) { DEBUG("\t\t%s [ -qs | -qi ]\n", caller); } static int dnpds40_cmdline_arg(void *vctx, int run, char *arg1, char *arg2) { struct dnpds40_ctx *ctx = vctx; UNUSED(arg2); if (!run || !ctx) return (!strcmp("-qs", arg1) || !strcmp("-qi", arg1)); if (!strcmp("-qs", arg1)) return dnpds40_get_status(ctx); if (!strcmp("-qi", arg1)) return dnpds40_get_info(ctx); return -1; } /* Exported */ struct dyesub_backend dnpds40_backend = { .name = "DNP DS40/DS80", .version = "0.03", .uri_prefix = "dnpds40", .cmdline_usage = dnpds40_cmdline, .cmdline_arg = dnpds40_cmdline_arg, .init = dnpds40_init, .attach = dnpds40_attach, .teardown = dnpds40_teardown, .read_parse = dnpds40_read_parse, .main_loop = dnpds40_main_loop, .devices = { { USB_VID_DNP, USB_PID_DNP_DS40, P_DNP_DS40, ""}, { USB_VID_DNP, USB_PID_DNP_DS80, P_DNP_DS80, ""}, { 0, 0, 0, ""} } }; /* DNP DS40 Windows Driver printer spool format: NOTE: This backend (and gutenprint) do *NOT* use this format. UNKNOWN variables/offsets: - 300 vs 600dpi selection - number of copies - lamination type - media type 4x6, 300dpi, 1 copy, 0 sharpen, glossy Page header: 01 00 01 00 <- page setup? 28 58 24 00 <- Total plane len == 40 + x*y + 1024 (2381864) 00 00 00 00 <- ?? Plane header (ie one for each plane) 28 00 00 00 80 07 00 00 <- X res (1920) ( = 6.4" @ 300dpi) d8 04 00 00 <- Y res (1240) ( = 4.13" @ 300dpi) 01 00 08 00 00 00 00 00 00 00 00 00 20 2e 00 00 <- 11808 = ?? 20 2e 00 00 <- 11808 00 01 00 00 00 00 00 00 [ folowed by 256 entries of color mapping starting with 0xff -> ff ff ff 00 ] [ followed by x*y bytes of plane data ] 5x7, "600x600dpi", 2 copies, 0 sharpen, matte Page header: 02 02 02 00 <- page setup ?? 28 4a 7d 00 <- Total plane len == 40 + x*y + 1024 02 00 00 00 <- ?? Plane header (ie one for each plane) 28 00 00 00 80 07 00 00 <- X res (1920) ( = 6.4" @ 300dpi) b4 10 00 00 <- Y res (4276) ( =~ 7.13" @ 600 dpi ) 01 00 08 00 00 00 00 00 00 00 00 00 40 5c 00 00 <- 23615 = ? 40 5c 00 00 <- 23615 00 01 00 00 00 00 00 00 [ folowed by 256 entries of color mapping starting with 0xff -> ff ff ff 00 ] [ followed by x*y bytes of plane data ] */