/*
* Copyright 2017 DiUS Computing Pty Ltd. All rights reserved.
*
* Released under GPLv3, see LICENSE for details.
*
* @author Johny Mattsson <jmattsson@dius.com.au>
*/
#include "ql.h"
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
struct ql_ctx {
char *printer;
int fd;
};
#define ESC 0x1b
#define STATUS_READ_TIMEOUT 5 /* seconds */
#define full_write(fd, buf) (retry_write(fd, buf, sizeof(buf)) == (ssize_t)sizeof(buf))
ssize_t retry_write(int fd, const char *buf, size_t len)
{
size_t written = 0;
int retval;
fd_set wfds;
struct timeval tv;
while (written != len) {
FD_ZERO(&wfds);
FD_SET(fd, &wfds);
tv.tv_sec = 5;
tv.tv_usec = 0;
retval = select(fd + 1, NULL, &wfds, NULL, &tv);
if (retval == -1)
perror("select()");
else if (FD_ISSET(fd, &wfds)) {
ssize_t n = write(fd, buf + written, len - written);
if (n == -1) {
if (errno == EAGAIN || errno == EINTR)
continue;
else
break;
} else
written += n;
}
}
return written;
}
static void get_mono_time(struct timespec *tp)
{
if (clock_gettime(CLOCK_MONOTONIC, tp) == -1) {
perror("clock_gettime");
exit(EXIT_FAILURE);
}
}
ql_ctx_t ql_open(const char *printer)
{
int fd = open(printer, O_RDWR | O_NONBLOCK);
if (fd < 0)
return NULL;
ql_ctx_t ctx = malloc(sizeof(struct ql_ctx));
if (!ctx)
return NULL;
ctx->printer = strdup(printer);
ctx->fd = fd;
const char clear[200] = { 0, };
(void)full_write(fd, clear); // recommended to clear old/errored jobs
return ctx;
}
void ql_close(ql_ctx_t ctx)
{
free(ctx->printer);
close(ctx->fd);
free(ctx);
}
bool ql_init(ql_ctx_t ctx)
{
const char init[] = { ESC, '@' };
return full_write(ctx->fd, init);
}
bool ql_request_status(ql_ctx_t ctx)
{
const char status_req[] = { ESC, 'i', 'S' };
return full_write(ctx->fd, status_req);
}
bool ql_read_status(ql_ctx_t ctx, ql_status_t * status)
{
int retval;
fd_set rfds;
struct timeval tv;
struct timespec tp_begin;
struct timespec tp_now;
get_mono_time(&tp_begin);
/* If select(2) selects returns early, then the outer loop calls
* usleep(3) and tries again, until the timeout is reached. */
int i = 0;
do {
i++;
FD_ZERO(&rfds);
FD_SET(ctx->fd, &rfds);
if (tp_now.tv_sec == 0) {
tv.tv_sec = STATUS_READ_TIMEOUT;
} else {
tv.tv_sec = tp_begin.tv_sec + STATUS_READ_TIMEOUT - tp_now.tv_sec;
}
tv.tv_usec = 0;
retval = select(ctx->fd + 1, &rfds, NULL, NULL, &tv);
if (retval == -1) {
perror("select()");
} else if (retval == 0) {
break; /* timeout */
} else if (FD_ISSET(ctx->fd, &rfds)) {
int ret = read(ctx->fd, status, sizeof(*status));
if (ret == 32) {
return true;
} else {
usleep(i * 5000); /* i * 5 ms */
}
}
get_mono_time(&tp_now);
} while (tp_now.tv_sec - tp_begin.tv_sec < STATUS_READ_TIMEOUT);
errno = ETIME;
return false;
}
bool ql_set_mode(ql_ctx_t ctx, unsigned mode)
{
char cmd[] = { ESC, 'i', 'M', mode };
return full_write(ctx->fd, cmd);
}
bool ql_set_expanded_mode(ql_ctx_t ctx, unsigned mode)
{
char cmd[] = { ESC, 'i', 'K', mode };
return full_write(ctx->fd, cmd);
}
bool ql_set_autocut_every_n(ql_ctx_t ctx, uint8_t n)
{
char cmd[] = { ESC, 'i', 'A', n };
return full_write(ctx->fd, cmd);
}
bool ql_set_margin(ql_ctx_t ctx, uint16_t dots)
{
char cmd[] = { ESC, 'i', 'd', dots & 0xff, dots >> 8 };
return full_write(ctx->fd, cmd);
}
bool ql_needs_mode_switch(const ql_status_t * status)
{
switch (status->model_code) {
case '3':
case '4':
case 'P':
case 'Q':
return true;
default:
break;
}
return false;
}
bool ql_switch_to_raster_mode(ql_ctx_t ctx)
{
#define MODE_ESC_P 0
#define MODE_RASTER 1
#define MODE_P_TOUCH_TEMPLATE 3
char cmd[] = { ESC, 'i', 'a', MODE_RASTER };
return full_write(ctx->fd, cmd);
}
static void pack_column(uint8_t * out, uint16_t bytes, uint16_t colno,
const ql_raster_image_t * img, uint8_t black_below_v)
{
for (unsigned n = 0; n < bytes; ++n, ++out) {
*out = 0;
for (unsigned i = 0; i < 8; ++i) {
unsigned img_row = n * 8 + i;
if (img_row < img->height)
if (img->data[img_row * img->width + colno] < black_below_v)
*out |= 1 << (7 - i);
}
//for(int i = 7; i >= 0; --i) fprintf(stderr, "%c", (*out & (1<<i)) ? '#' : '.');
}
//fprintf(stderr,"\n");
}
bool ql_print_raster_image(ql_ctx_t ctx, const ql_status_t * status,
const ql_raster_image_t * img, const ql_print_cfg_t * cfg)
{
unsigned dn = 90; // default raster transmission block size (720 pixels)
if (status->model_code == 'P' || status->model_code == '4')
dn = 162; // 1296 pixels
if (img->height > dn * 8)
return false; // image too wide for printer
char print_info[] = { ESC, 'i', 'z',
cfg->flags | 0x80,
(cfg->flags & QL_PRINT_CFG_MEDIA_TYPE) ? cfg->media_type : 0,
(cfg->flags & QL_PRINT_CFG_MEDIA_WIDTH) ? cfg->media_width : 0,
(cfg->flags & QL_PRINT_CFG_MEDIA_LENGTH) ? cfg->media_length : 0,
img->width & 0xff, img->width >> 8, 0, 0,
cfg->first_page ? 0 : 1, 0
};
if (!full_write(ctx->fd, print_info))
return false;
for (unsigned w = 0; w < img->width; ++w) {
char block[dn + 3];
block[0] = 'g';
block[1] = 0;
block[2] = dn;
pack_column((uint8_t *) block + 3, dn, w, img, cfg->threshold);
if (!full_write(ctx->fd, block))
return false;
}
char done[] = { 0x1a }; // print with feeding
return full_write(ctx->fd, done);
}
const char *ql_decode_model(const ql_status_t * status)
{
switch (status->model_code) {
case '1':
return "QL-560";
case '2':
return "QL-570";
case '3':
return "QL-580N";
case '4':
return "QL-1060N";
case '5':
return "QL-700";
case '6':
return "QL-710W";
case '7':
return "QL-720NW";
case 'O':
return "QL-500/550";
case 'P':
return "QL-1050";
case 'Q':
return "QL-650TD";
default:
{
static char buf[] = "unrecognised (type code 0x$$)";
char *q = strchr(buf, '$');
sprintf(q, "%02hhx)", status->model_code);
return buf;
}
}
}
const char *ql_decode_mode(const ql_status_t * status)
{
if (status->mode & QL_MODE_AUTOCUT)
return "auto-cut";
else
return "no-auto-cut";
}
const char *ql_decode_errors(const ql_status_t * status)
{
typedef struct {
uint16_t bit;
const char *str;
} strmap_t;
#define ERR1(x) (x << 0)
#define ERR2(x) (x << 8)
const strmap_t strmap[] = {
{ERR1(QL_ERR_1_NO_MEDIA), "no-media "},
{ERR1(QL_ERR_1_END_OF_MEDIA), "end-of-media "},
{ERR1(QL_ERR_1_CUTTER_JAM), "cutter-jam "},
{ERR1(QL_ERR_1_PRINTER_IN_USE), "printer-in-use "},
{ERR1(QL_ERR_1_PRINTER_TURNED_OFF), "printer-turned-off "},
{ERR1(QL_ERR_1_HIGH_VOLTAGE_ADAPTER), "high-voltage-adapter "},
{ERR1(QL_ERR_1_FAN_MOTOR_ERROR), "fan-motor-error "},
{ERR2(QL_ERR_2_REPLACE_MEDIA), "replace-media "},
{ERR2(QL_ERR_2_EXPANSION_BUFFER_FULL), "expansion-buffer-full "},
{ERR2(QL_ERR_2_COMMUNICATION_ERROR), "communication-error "},
{ERR2(QL_ERR_2_COMMUNICATION_BUFFER_FULL), "communication-buffer-full "},
{ERR2(QL_ERR_2_COVER_OPEN), "cover-open "},
{ERR2(QL_ERR_2_CANCEL_KEY), "cancel-key-pressed "},
{ERR2(QL_ERR_2_MEDIA_CANNOT_BE_FED), "media-cannot-be-fed "},
{ERR2(QL_ERR_2_SYSTEM_ERROR), "system-error "}
};
static char *buf = 0;
if (!buf) {
int len = 0;
for (unsigned i = 0; i < (sizeof(strmap) / sizeof(strmap[0])); ++i)
len += strlen(strmap[i].str);
buf = malloc(len + 1);
if (!buf)
return "<host-out-of-memory>";
}
uint16_t errs = ERR1(status->err_info_1) | ERR2(status->err_info_2);
buf[0] = 0;
for (unsigned i = 0; i < (sizeof(strmap) / sizeof(strmap[0])); ++i) {
if (errs & strmap[i].bit)
strcat(buf, strmap[i].str);
}
return (buf[0] == 0) ? "none" : buf;
}
const char *ql_decode_media_type(const ql_status_t * status)
{
switch (status->media_type) {
case QL_MEDIA_TYPE_NO_MEDIA:
return "no-media";
case QL_MEDIA_TYPE_CONTINUOUS:
case QL_MEDIA_TYPE_CONTINUOUS_ALT:
return "continuous-length-tape";
case QL_MEDIA_TYPE_DIECUT_LABELS:
case QL_MEDIA_TYPE_DIECUT_LABELS_ALT:
return "die-cut-labels";
default:{
static char buf[] = "unknown (code 0x$$)";
char *q = strchr(buf, '$');
sprintf(q, "%02hhx)", status->media_type);
return buf;
}
}
}
void ql_decode_print_status(FILE * f, const ql_status_t * status, unsigned flags)
{
if (!status)
return;
const char *fmt_s = "%17s: %s\n";
const char *fmt_u = "%17s: %u\n";
if (flags & QL_DECODE_MODEL)
fprintf(f, fmt_s, "Printer", ql_decode_model(status));
if (flags & QL_DECODE_MODE)
fprintf(f, fmt_s, "Mode", ql_decode_mode(status));
if (flags & QL_DECODE_ERROR)
fprintf(f, fmt_s, "Errors", ql_decode_errors(status));
if (flags & QL_DECODE_MEDIA) {
fprintf(f, fmt_s, "Media type", ql_decode_media_type(status));
fprintf(f, fmt_u, "Media width (mm)", status->media_width_mm);
if (status->media_type != QL_MEDIA_TYPE_CONTINUOUS)
fprintf(f, fmt_u, "Media length (mm)", status->media_length_mm);
}
}