/*
KL-G2 Printer Utility
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 .
*/
#include
#include
#include
#include
#include
#include
#include
#include "config.h"
/* USB constants */
const uint16_t KLG2_VID = 0x07CF;
const uint16_t KLG2_PID = 0x4204;
const uint8_t KLG2_IFACE = 0;
const uint8_t KLG2_EPOUT = 0x01;
const uint8_t KLG2_EPIN = 0x82;
const uint8_t KLG2_EPSIZE = 0x40;
/* Valid output endpoint transfer sizes */
enum EPSIZE_T {
EPSIZE_1 = 1,
EPSIZE_16 = 16,
EPSIZE_64 = 64
};
/* Tape codes */
enum TAPECODE_T {
TAPECODE_NOTAPE = 0x0000,
TAPECODE_6MM = 0x8100,
TAPECODE_9MM = 0x8500,
TAPECODE_12MM = 0x8303,
TAPECODE_18MM = 0x8703,
TAPECODE_24MM = 0x8603
};
/* Margin/feed codes */
enum MARGINCODE_T {
MARGINCODE_SMALL = 0x40,
MARGINCODE_MEDIUM = 0x80,
MARGINCODE_LARGE = 0x02,
MARGINCODE_NOFEED = 0x01
};
/* Print density codes */
enum DENSITYCODE_T {
DENSITYCODE_1 = 0xFE,
DENSITYCODE_2 = 0xFF,
DENSITYCODE_3 = 0x00,
DENSITYCODE_4 = 0x01,
DENSITYCODE_5 = 0x02
};
/* Cutter mode codes */
enum CUTTERCODE_T {
CUTTERCODE_FULLCUT = 0x00,
CUTTERCODE_HALFCUT = 01,
CUTTERCODE_NOCUT = 0xFF
};
/* Options */
_Bool dump_comm = false;
enum DENSITYCODE_T opt_density = DENSITYCODE_3;
enum MARGINCODE_T opt_margin = MARGINCODE_SMALL;
enum TAPECODE_T opt_tape = TAPECODE_12MM;
enum CUTTERCODE_T opt_cutter = CUTTERCODE_HALFCUT;
enum OPERMODE_T {
OPERATION_PRINT,
OPERATION_FEED,
OPERATION_CUT,
OPERATION_HALFCUT
} opt_operation = OPERATION_PRINT;
#define PRINTER_ACK 0x06
#define PRINTER_NAK 0x1E
#define PRINTER_STX 0x02
/* Printer handle */
libusb_device_handle *devhnd;
/* Image buffer */
#define IMAGE_ROWS 32
unsigned image_w;
uint8_t *image_stripes[IMAGE_ROWS];
/* Print pattern */
unsigned pattern_size;
uint8_t *pattern;
/*======================================================================
Debug dump
*/
void debug_dump(char marker, const uint8_t *frame, unsigned flen)
{
if (dump_comm) {
putc(marker, stderr);
int i;
for (i = 0; i < flen; ++i) {
fprintf(stderr, "%02X ", frame[i]);
}
putc('\n', stderr);
}
}
/*======================================================================
Receive a frame from the printer
*/
int recv_from_printer(uint8_t *d)
{
uint8_t in[KLG2_EPSIZE];
memset(in, 0, KLG2_EPSIZE);
int rxcnt = 0;
/* Endpoint buffer is 64 bytes */
int rc = libusb_bulk_transfer(devhnd, KLG2_EPIN, in, KLG2_EPSIZE,
&rxcnt, 0);
if (rc) {
fprintf(stderr, "Error receiving frame (%d)\n", rc);
abort();
}
memcpy(d, in, rxcnt);
debug_dump('<', in, rxcnt);
return rxcnt;
}
/*======================================================================
Send a frame to the printer
*/
int send_to_printer(const uint8_t *d, uint8_t cnt, enum EPSIZE_T epsize)
{
uint8_t out[KLG2_EPSIZE];
/* Important: keep padded with zeros */
memset(out, 0, KLG2_EPSIZE);
/* Important (2): it only accepts transfers of 1, 16 or 64 bytes
depending on the command, not on the amount of data transferred.
EXAMPLE: an incomplete raster transfer must be of 64 bytes even
if it fits in 16 */
if (cnt > epsize) {
fprintf(stderr, "Shouldn't happen: output too large (%d)\n",
cnt);
abort();
}
memcpy(out, d, cnt);
int txcnt = 0;
debug_dump('>', out, cnt);
int rc = libusb_bulk_transfer(devhnd, KLG2_EPOUT, out, epsize,
&txcnt, 0);
if (rc) {
fprintf(stderr, "Error sending frame (%d)\n", rc);
abort();
}
if (txcnt != epsize) {
fprintf(stderr, "Incomplete transfer (%d/%d)\n",
txcnt, epsize);
abort();
}
return txcnt;
}
/*======================================================================
Check printer readiness (can be slow)
*/
int printer_check_status(void)
{
uint8_t rsp[KLG2_EPSIZE];
static const uint8_t idcheck[] = {
PRINTER_STX, 0x1D
};
send_to_printer(idcheck, 2, EPSIZE_16);
int rc = recv_from_printer(rsp);
if (rc != 6) {
fprintf(stderr, "Unexpected status response length (%d)\n", rc);
return 1;
}
if (rsp[0] != PRINTER_STX || rsp[1] != 0x80 || rsp[2] != 0x02 ||
rsp[3] != 0x00 || rsp[4] != 0x00 || rsp[5] != 0xc8) {
fputs("Status response mismatch\n", stderr);
return 1;
}
return 0;
}
/*======================================================================
Common case for receive PRINTER_ACK
*/
static int printer_recv_ack(const char *msg)
{
uint8_t rsp[KLG2_EPSIZE];
int rc = recv_from_printer(rsp);
if (rc != 1 || rsp[0] != PRINTER_ACK) {
fputs(msg, stderr);
return 1;
}
return 0;
}
/*======================================================================
Printer reset
*/
int printer_reset(void)
{
static const uint8_t reset[] = {
0x02, 0x01
};
send_to_printer(reset, 2, EPSIZE_16);
return printer_recv_ack("Printer reset failed\n");
}
/*======================================================================
Tape cut
*/
int printer_tape_cut(void)
{
static const uint8_t tapecut[] = {
0x08
};
send_to_printer(tapecut, 1, EPSIZE_1);
return printer_recv_ack("Tape cut failed\n");
}
/*======================================================================
Tape halfcut
*/
int printer_tape_halfcut(void)
{
static const uint8_t tapecut[] = {
0x09
};
send_to_printer(tapecut, 1, EPSIZE_1);
return printer_recv_ack("Tape half cut failed\n");
}
/*======================================================================
Tape feed
*/
int printer_tape_feed(void)
{
static const uint8_t tapefeed[] = {
0x0A
};
send_to_printer(tapefeed, 1, EPSIZE_1);
return printer_recv_ack("Tape feed failed\n");
}
/*======================================================================
Cancel job
*/
int printer_cancel_job(void)
{
static const uint8_t canceljob[] = {
0x18
};
send_to_printer(canceljob, 1, EPSIZE_1);
/* No answer expected */
return 0;
}
/*======================================================================
Printer Speed Adjust
*/
int printer_set_speed(void)
{
static const uint8_t psa[] = {
PRINTER_STX, 0x1C, 0x01, 0x00, 0x01
};
send_to_printer(psa, 5, EPSIZE_16);
return printer_recv_ack("Speed adjust failed\n");
}
/*======================================================================
Tape check
*/
int printer_check_tape(enum TAPECODE_T tapeid)
{
/* For some reason there is an extra byte after the tape code proper
(either 0 or 3), no idea of what it means */
static uint8_t mtc[] = {
PRINTER_STX, 0x17, 0x02, 0x00, 0x00, 0x00
};
mtc[4] = tapeid >> 8;
mtc[5] = tapeid & 0xFF;
send_to_printer(mtc, 6, EPSIZE_16);
return printer_recv_ack("Tape check failed\n");
}
/*======================================================================
Margin select
*/
int printer_set_margin(enum MARGINCODE_T marginid)
{
static uint8_t afs[] = {
PRINTER_STX, 0x0d, 0x01, 0x00, 0x40
};
afs[4] = marginid;
send_to_printer(afs, 5, EPSIZE_16);
return printer_recv_ack("Margin select failed\n");
}
/*======================================================================
Density select (deployment mode select)
*/
int printer_set_density(enum DENSITYCODE_T densityid)
{
static uint8_t dms[] = {
PRINTER_STX, 0x09, 0x07, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00
};
dms[8] = densityid; // ?
send_to_printer(dms, 11, EPSIZE_16);
return printer_recv_ack("Print density select failed\n");
}
/*======================================================================
Cutter mode
*/
int printer_set_cutter(enum CUTTERCODE_T cutterid)
{
static uint8_t cms[] = {
PRINTER_STX, 0x19, 0x01, 0x00, 0x00
};
cms[4] = cutterid;
send_to_printer(cms, 5, EPSIZE_16);
return printer_recv_ack("Cutter mode select failed\n");
}
/*======================================================================
Get the mounted tape
*/
int printer_get_tape(enum TAPECODE_T *tapeid)
{
uint8_t rsp[KLG2_EPSIZE];
static const uint8_t idcheck[] = {
PRINTER_STX, 0x1A
};
send_to_printer(idcheck, 2, EPSIZE_16);
int rc = recv_from_printer(rsp);
*tapeid = TAPECODE_NOTAPE;
if (rc == 5) {
switch (rsp[4]) {
case 0x81:
*tapeid = TAPECODE_6MM;
break;
case 0x85:
*tapeid = TAPECODE_9MM;
break;
case 0x83:
*tapeid = TAPECODE_12MM;
break;
case 0x87:
*tapeid = TAPECODE_18MM;
break;
case 0x86:
*tapeid = TAPECODE_24MM;
}
return 0;
} else {
return 1;
}
}
/*======================================================================
Pre feed the tape
*/
int printer_prefeed_tape(uint8_t amount)
{
static uint8_t feed[] = {
PRINTER_STX, 0x1B, 0x01, 0x00, 0x00
};
feed[4] = amount;
send_to_printer(feed, 5, EPSIZE_16);
return printer_recv_ack("Prefeed failed\n");
}
/*======================================================================
Raster end
*/
int printer_raster_end(void)
{
static const uint8_t raster_end[] = {
PRINTER_STX, 0x04
};
send_to_printer(raster_end, 2, EPSIZE_16);
return printer_recv_ack("Raster end failed\n");
}
/*======================================================================
Print page
*/
int printer_print_page(void)
{
static const uint8_t print_page[] = {
0x0C
};
send_to_printer(print_page, 1, EPSIZE_1);
return printer_recv_ack("Print page failed\n");
}
/*======================================================================
Send raster block
*/
int printer_raster_block(uint8_t *blk, uint8_t blksize)
{
blk[0] = PRINTER_STX;
blk[1] = 0xFE;
blk[2] = blksize;
blk[3] = 0;
send_to_printer(blk, blksize+4, EPSIZE_64);
return printer_recv_ack("Raster block failed\n");
}
/*======================================================================
Send raster data
The printhead on the KL-G2 gives 8 points/mm (standard thermal 200dpi)
*/
int printer_send_raster(const uint8_t *raw, unsigned rawsize)
{
unsigned sent_size = 0;
unsigned page_size = 0;
unsigned block_size = 0;
uint8_t block[64];
do {
block[4 + block_size] = raw[sent_size];
++block_size;
++sent_size;
++page_size;
if (block_size == 60
|| page_size == 8192
|| sent_size == rawsize) {
if (printer_raster_block(block, block_size)) {
return 1;
}
block_size = 0;
if (sent_size == rawsize) {
if (printer_raster_end()) {
return 1;
}
}
if (page_size == 8192 || sent_size == rawsize) {
if (printer_print_page()) {
return 1;
}
page_size = 0;
}
}
} while (sent_size < rawsize);
return 0;
}
/*======================================================================
PBM Loader
*/
int load_image(FILE *fin)
{
/* Check signature */
if (getc(fin) != 'P' ||
getc(fin) != '4' ||
getc(fin) != '\n') {
fputs("Input is not a packed PBM\n", stderr);
return 1;
}
/* Check for comment */
uint8_t ch = getc(fin);
while (ch == '#') {
do {
ch = getc(fin);
} while (ch != '\n');
ch = getc(fin);
}
ungetc(ch, fin);
unsigned img_w, img_h, pad_h;
if (fscanf(fin, "%u %u\n", &img_w, &img_h) != 2) {
fputs("PBM image size error\n", stderr);
return 1;
}
if (img_h > IMAGE_ROWS) {
fputs("WARNING: Image truncated\n", stderr);
img_h = IMAGE_ROWS;
}
pad_h = (IMAGE_ROWS - img_h) / 2;
image_w = img_w;
img_w = (image_w + 7)/8;
int i;
for (i = 0; i < IMAGE_ROWS; ++i) {
uint8_t *stripe = calloc(1, img_w);
if (!stripe) {
fputs("malloc failed\n", stderr);
return 1;
}
image_stripes[i] = stripe;
}
for (i = 0; i < img_h; ++i) {
if (fread(image_stripes[i+pad_h], img_w, 1, fin) != 1) {
fputs("PBM ended unexpectedly\n", stderr);
return 1;
}
}
pattern_size = IMAGE_ROWS/8 * image_w;
pattern = calloc(1, pattern_size);
if (!pattern) {
fputs("malloc failed\n", stderr);
return 1;
}
/* Transpose to pattern */
for (i = 0; i < IMAGE_ROWS; ++i) {
unsigned x = 0;
int w, b;
for (w = 0; w < img_w; ++w) {
for (b = 0; b < 8; ++b) {
if ((image_stripes[i][w] << b) & 0x80) {
pattern[x * (IMAGE_ROWS/8) + i/8] |=
1 << (i%8);
}
++x;
}
}
}
if (dump_comm) {
for (i = 0; i < image_w; ++i) {
fprintf(stderr, "%5d [", i);
int j;
for (j = 0; j < 16; ++j) {
fprintf(stderr, "%02X", pattern[i*IMAGE_ROWS/8+j]);
}
fputs("]\n", stderr);
}
}
return 0;
}
/*======================================================================
Option handling
*/
void handle_options(int argc, char **argv)
{
int opt, oval;
while ((opt = getopt(argc, argv, "hvFCHm:t:c:d:")) != -1) {
switch (opt) {
case 'v':
dump_comm = true;
break;
case 'F':
opt_operation = OPERATION_FEED;
break;
case 'C':
opt_operation = OPERATION_CUT;
break;
case 'H':
opt_operation = OPERATION_HALFCUT;
break;
case 'm':
oval = atoi(optarg);
switch (oval) {
case 0: opt_margin = MARGINCODE_NOFEED; break;
case 1: opt_margin = MARGINCODE_SMALL; break;
case 2: opt_margin = MARGINCODE_MEDIUM; break;
case 3: opt_margin = MARGINCODE_LARGE; break;
default:
fputs("Invalid margin setting\n", stderr);
exit(1);
}
break;
case 'c':
oval = atoi(optarg);
switch (oval) {
case 0: opt_cutter = CUTTERCODE_NOCUT; break;
case 1: opt_cutter = CUTTERCODE_HALFCUT; break;
case 2: opt_cutter = CUTTERCODE_FULLCUT; break;
default:
fputs("Invalid cutter setting\n", stderr);
exit(1);
}
break;
case 'd':
oval = atoi(optarg);
switch (oval) {
case 1: opt_density = DENSITYCODE_1; break;
case 2: opt_density = DENSITYCODE_2; break;
case 3: opt_density = DENSITYCODE_3; break;
case 4: opt_density = DENSITYCODE_4; break;
case 5: opt_density = DENSITYCODE_5; break;
default:
fputs("Invalid print density setting\n", stderr);
exit(1);
}
break;
case 't':
oval = atoi(optarg);
switch (oval) {
case 6: opt_tape = TAPECODE_6MM; break;
case 9: opt_tape = TAPECODE_9MM; break;
case 12: opt_tape = TAPECODE_12MM; break;
case 18: opt_tape = TAPECODE_18MM; break;
case 24: opt_tape = TAPECODE_24MM; break;
default:
fputs("Invalid tape size\n", stderr);
exit(1);
}
break;
case 'h':
default:
fprintf(stderr, "Usage: %s [OPTION]...\n", argv[0]);
fputs("Prints the PBM on the standard input\n", stderr);
fputs(" -F Feed the tape an exit\n", stderr);
fputs(" -C Cut the tape an exit\n", stderr);
fputs(" -H Half-cut the tape an exit\n", stderr);
fputs(" -m margin Margin (0 none, *1 small, 2 medium, 3 large)\n", stderr);
fputs(" -t tapesize Tape width in mm (6, 9, *12, 18, 24)\n", stderr);
fputs(" -c cutmode Cut more (0 no cut, *1 half-cut, 2 full-cut)\n", stderr);
fputs(" -d density Set print density (1-5, default 3)\n", stderr);
fputs(" -v Verbose (dump USB communications)\n", stderr);
fputs(" -h Display this help and exit\n", stderr);
exit(1);
}
}
}
/*======================================================================
Main
*/
int main(int argc, char **argv)
{
handle_options(argc, argv);
_Bool need_cancel = false;
int rc = libusb_init(NULL);
if (rc < 0)
return rc;
/* Apre la comunicazione usando VID e PID */
devhnd = libusb_open_device_with_vid_pid(NULL, KLG2_VID, KLG2_PID);
if (!devhnd) {
fputs("Can't find or access printer\n", stderr);
return 1;
}
rc = libusb_claim_interface(devhnd, KLG2_IFACE);
if (rc) {
fputs("Can't claim printer interface\n", stderr);
return 1;
}
/* Sequenza standard */
printer_check_status();
printer_reset();
switch (opt_operation) {
case OPERATION_FEED:
printer_tape_feed();
break;
case OPERATION_CUT:
printer_tape_cut();
break;
case OPERATION_HALFCUT:
printer_tape_halfcut();
break;
case OPERATION_PRINT:
/* Read and prepare the image to be printed */
rc = load_image(stdin);
if (rc)
return 1;
if (!need_cancel)
need_cancel = printer_check_tape(opt_tape);
if (!need_cancel)
need_cancel = printer_reset();
if (!need_cancel)
need_cancel = printer_set_speed();
if (!need_cancel)
need_cancel = printer_set_margin(opt_margin);
if (!need_cancel)
need_cancel = printer_set_density(opt_density);
if (!need_cancel)
need_cancel = printer_set_cutter(opt_cutter);
if (!need_cancel)
need_cancel = printer_check_status();
if (!need_cancel)
need_cancel = printer_send_raster(pattern, pattern_size);
/* The standard program does this even in the success case */
printer_cancel_job();
break;
}
/* Cleanup */
libusb_release_interface(devhnd, KLG2_IFACE);
libusb_close(devhnd);
libusb_exit(NULL);
return 0;
}