sane-project-backends/backend/epsonds-cmd.c

908 wiersze
18 KiB
C
Czysty Zwykły widok Historia

/*
* epsonds-cmd.c - Epson ESC/I-2 routines.
*
* Copyright (C) 2015 Tower Technologies
* Author: Alessandro Zummo <a.zummo@towertech.it>
*
* This file is part of the SANE package.
*
* 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, version 2.
*/
#define DEBUG_DECLARE_ONLY
#include "sane/config.h"
#include <ctype.h>
#include <unistd.h> /* sleep */
#include "epsonds.h"
#include "epsonds-io.h"
#include "epsonds-cmd.h"
#include "epsonds-ops.h"
#include "epsonds-net.h"
static SANE_Status
esci2_parse_block(char *buf, int len, void *userdata, SANE_Status (*cb)(void *userdata, char *token, int len))
{
SANE_Status status = SANE_STATUS_GOOD;
SANE_Status delayed_status = SANE_STATUS_GOOD;
char *start = buf;
char *end = (buf + len) - 1;
/* 0 : #
* 1-3: param
* 4- : data
*/
while (1) {
char param[4];
while (*start != '#' && start < end)
start++;
if (*start != '#')
break;
param[0] = *++start;
param[1] = *++start;
param[2] = *++start;
param[3] = '\0';
if (strncmp("---", param, 3) == 0)
break;
/* ugly hack to skip over GMT in RESA */
if (strncmp("GMT", param, 3) == 0 && *(start + 5) == 'h') {
start = start + 4 + 0x100;
continue;
}
/* find the end of the token */
{
int tlen;
char *next = start;
while (*next != '#' && *next != 0x00 && next < end)
next++;
tlen = next - start - 1;
if (cb) {
status = cb(userdata, start - 2, tlen);
if (status != SANE_STATUS_GOOD) {
delayed_status = status;
}
}
start = next;
}
}
if (delayed_status != SANE_STATUS_GOOD)
return delayed_status;
return status;
}
static SANE_Bool
esci2_check_header(const char *cmd, const char *buf, unsigned int *more)
{
int err;
*more = 0;
if (strncmp(cmd, buf, 4) != 0) {
if (strncmp("UNKN", buf, 4) == 0) {
DBG(1, "UNKN reply code received\n");
} else if (strncmp("INVD", buf, 4) == 0) {
DBG(1, "INVD reply code received\n");
} else {
DBG(1, "%c%c%c%c, unexpected reply code\n", buf[0], buf[1], buf[2], buf[3]);
}
return 0;
}
/* INFOx0000100#.... */
/* read the answer len */
if (buf[4] != 'x') {
DBG(1, "unknown type in header: %c\n", buf[4]);
return 0;
}
err = sscanf(&buf[5], "%x#", more);
if (err != 1) {
DBG(1, "cannot decode length from header\n");
return 0;
}
return 1;
}
static SANE_Status esci2_cmd(epsonds_scanner* s,
char *cmd, size_t len,
char *payload, size_t plen,
void *userdata, SANE_Status (*cb)(void *userdata, char *token, int len))
{
SANE_Status status;
unsigned int more;
char header[13], rbuf[64]; /* add one more byte for header buffer to correct buffer overflow issue,*/
DBG(8, "%s: %4s len %lu, payload len: %lu\n", __func__, cmd, len, plen);
memset(header, 0x00, sizeof(header));
memset(rbuf, 0x00, sizeof(rbuf));
// extra safety check, will not happen
if (len != 12) {
DBG(1, "%s: command has wrong size (%lu != 12)\n", __func__, len);
return SANE_STATUS_INVAL;
}
// merge ParameterBlock size
sprintf(header, "%4.4sx%07x", cmd, (unsigned int)plen);
// send RequestBlock, request immediate response if there's no payload
status = eds_txrx(s, header, len, rbuf, (plen > 0) ? 0 : 64);
if (status != SANE_STATUS_GOOD) {
return status;
}
/* send ParameterBlock, request response */
if (plen) {
DBG(8, " %12.12s (%lu)\n", header, plen);
status = eds_txrx(s, payload, plen, rbuf, 64);
if (status != SANE_STATUS_GOOD) {
return status;
}
}
/* rxbuf holds the DataHeaderBlock, which should be
* parsed to know if we need to read more data
*/
if (!esci2_check_header(cmd, rbuf, &more)) {
return SANE_STATUS_IO_ERROR;
}
/* parse the received header block */
if (cb) {
status = esci2_parse_block(rbuf + 12, 64 - 12, userdata, cb);
if (status != SANE_STATUS_GOOD && status != SANE_STATUS_DEVICE_BUSY) {
DBG(1, "%s: %4s error while parsing received header\n", __func__, cmd);
}
}
/* header valid, get the data block if present */
if (more) {
char *pbuf = malloc(more);
if (pbuf) {
if (s->hw->connection == SANE_EPSONDS_NET) {
epsonds_net_request_read(s, more);
}
ssize_t read = eds_recv(s, pbuf, more, &status);
if (read != more) {
}
/* parse the received data block */
if (cb) {
status = esci2_parse_block(pbuf, more, userdata, cb);
if (status != SANE_STATUS_GOOD) {
DBG(1, "%s: %4s error while parsing received data block\n", __func__, cmd);
}
}
free(pbuf);
} else {
return SANE_STATUS_NO_MEM;
}
}
return status;
}
static SANE_Status esci2_cmd_simple(epsonds_scanner* s, char *cmd, SANE_Status (*cb)(void *userdata, char *token, int len))
{
return esci2_cmd(s, cmd, 12, NULL, 0, s, cb);
}
SANE_Status esci2_fin(epsonds_scanner *s)
{
SANE_Status status;
DBG(5, "%s\n", __func__);
status = esci2_cmd_simple(s, "FIN x0000000", NULL);
s->locked = 0;
return status;
}
SANE_Status esci2_can(epsonds_scanner *s)
{
return esci2_cmd_simple(s, "CAN x0000000", NULL);
}
static int decode_value(char *buf, int len)
{
char tmp[10];
memcpy(tmp, buf, len);
tmp[len] = '\0';
if (buf[0] == 'd' && len == 4) {
return strtol(buf + 1, NULL, 10);
} else if (buf[0] == 'i' && len == 8) {
return strtol(buf + 1, NULL, 10);
} else if (buf[0] == 'x' && len == 8) {
return strtol(buf + 1, NULL, 16);
} else if (buf[0] == 'h' && len == 4) {
return strtol(buf + 1, NULL, 16);
}
return -1;
}
/* h000 */
static char *decode_binary(char *buf)
{
char tmp[6];
int hl;
memcpy(tmp, buf, 4);
tmp[4] = '\0';
if (buf[0] != 'h')
return NULL;
hl = strtol(tmp + 1, NULL, 16);
if (hl) {
char *v = malloc(hl + 1);
memcpy(v, buf + 4, hl);
v[hl] = '\0';
return v;
}
return NULL;
}
static char *decode_string(char *buf)
{
char *p, *s = decode_binary(buf);
if (s == NULL)
return NULL;
/* trim white space at the end */
p = s + strlen(s);
while (*--p == ' ')
*p = '\0';
return s;
}
static void debug_token(int level, const char *func, char *token, int len)
{
char *tdata = malloc(len + 1);
memcpy(tdata, token + 3, len);
tdata[len] = '\0';
DBG(level, "%s: %3.3s / %s / %d\n", func, token, tdata, len);
free(tdata);
}
static SANE_Status info_cb(void *userdata, char *token, int len)
{
epsonds_scanner *s = (epsonds_scanner *)userdata;
char *value;
if (DBG_LEVEL >= 11) {
debug_token(DBG_LEVEL, __func__, token, len);
}
/* pointer to the token's value */
value = token + 3;
/* nrd / nrdBUSY */
if (strncmp("nrd", token, 3) == 0) {
if (strncmp("BUSY", value, 4) == 0) {
return SANE_STATUS_DEVICE_BUSY;
}
}
if (strncmp("PRD", token, 3) == 0) {
free(s->hw->model);
s->hw->model = decode_string(value);
s->hw->sane.model = s->hw->model;
DBG(1, " product: %s\n", s->hw->model);
/* we will free the string later */
}
if (strncmp("VER", token, 3) == 0) {
char *v = decode_string(value);
DBG(1, " version: %s\n", v);
free(v);
}
if (strncmp("S/N", token, 3) == 0) {
char *v = decode_string(value);
DBG(1, " serial: %s\n", v);
free(v);
}
if (strncmp("ADF", token, 3) == 0) {
s->hw->has_adf = 1;
if (len == 8) {
if (strncmp("TYPEPAGE", value, len) == 0) {
DBG(1, " ADF: page type\n");
}
if (strncmp("TYPEFEED", value, len) == 0) {
DBG(1, " ADF: sheet feed type\n");
}
if (strncmp("DPLX1SCN", value, len) == 0) {
DBG(1, " ADF: duplex single pass\n");
s->hw->adf_singlepass = 1;
}
if (strncmp("DPLX2SCN", value, len) == 0) {
DBG(1, " ADF: duplex double pass\n");
s->hw->adf_singlepass = 0;
}
if (strncmp("FORDPF1N", value, len) == 0) {
DBG(1, " ADF: order is 1 to N\n");
}
if (strncmp("FORDPFN1", value, len) == 0) {
DBG(1, " ADF: order is N to 1\n");
}
if (strncmp("ALGNLEFT", value, len) == 0) {
DBG(1, " ADF: left aligned\n");
s->hw->adf_alignment = 0;
}
if (strncmp("ALGNCNTR", value, len) == 0) {
DBG(1, " ADF: center aligned\n");
s->hw->adf_alignment = 1;
}
if (strncmp("ALGNRIGT", value, len) == 0) {
DBG(1, " ADF: right aligned (not supported!)\n");
s->hw->adf_alignment = 2;
}
}
if (len == 4) {
if (strncmp("PREF", value, len) == 0) {
DBG(1, " ADF: auto pre-feed\n");
}
if (strncmp("ASCN", value, len) == 0) {
DBG(1, " ADF: auto scan\n");
}
if (strncmp("RCVR", value, len) == 0) {
DBG(1, " ADF: auto recovery\n");
}
}
if (len == 20) {
/* ADFAREAi0000850i0001400 */
if (strncmp("AREA", value, 4) == 0) {
int min = decode_value(value + 4, 8);
int max = decode_value(value + 4 + 8, 8);
DBG(1, " ADF: area %dx%d @ 100dpi\n", min, max);
}
if (strncmp("AMIN", value, 4) == 0) {
int min = decode_value(value + 4, 8);
int max = decode_value(value + 4 + 8, 8);
DBG(1, " ADF: min %dx%d @ 100dpi\n", min, max);
}
if (strncmp("AMAX", value, 4) == 0) {
int min = decode_value(value + 4, 8);
int max = decode_value(value + 4 + 8, 8);
DBG(1, " ADF: max %dx%d @ 100dpi\n", min, max);
2015-04-08 00:29:20 +00:00
eds_set_adf_area(s->hw, min, max, 100);
}
}
if (len == 12) {
/* RESOi0000600 */
if (strncmp("RESO", value, 4) == 0) {
int res = decode_value(value + 4, 8);
DBG(1, " ADF: basic resolution is %d dpi\n", res);
}
/* OVSNd025d035 */
if (strncmp("OVSN", value, 4) == 0) {
int x = decode_value(value + 4, 4);
int y = decode_value(value + 4 + 4, 4);
DBG(1, " ADF: overscan %dx%d @ 100dpi\n", x, y);
}
}
}
if (strncmp("FB ", token, 3) == 0) {
s->hw->has_fb = 1;
if (len == 20) {
/* AREAi0000850i0001400 */
if (strncmp("AREA", value, 4) == 0) {
int min = decode_value(value + 4, 8);
int max = decode_value(value + 4 + 8, 8);
DBG(1, " FB: area %dx%d @ 100dpi\n", min, max);
eds_set_fbf_area(s->hw, min, max, 100);
}
}
if (len == 8) {
if (strncmp("ALGNLEFT", value, len) == 0) {
DBG(1, " FB: left aligned\n");
s->hw->fbf_alignment = 0;
}
if (strncmp("ALGNCNTR", value, len) == 0) {
DBG(1, " FB: center aligned\n");
s->hw->fbf_alignment = 1;
}
if (strncmp("ALGNRIGT", value, len) == 0) {
DBG(1, " FB: right aligned (not supported!)\n");
s->hw->fbf_alignment = 2;
}
}
if (len == 12) {
/* RESOi0000600 */
if (strncmp("RESO", value, 4) == 0) {
int res = decode_value(value + 4, 8);
DBG(1, " FB: basic resolution is %d dpi\n", res);
}
/* OVSNd025d035 */
if (strncmp("OVSN", value, 4) == 0) {
int x = decode_value(value + 4, 4);
int y = decode_value(value + 4 + 4, 4);
DBG(1, " FB: overscan %dx%d @ 100dpi\n", x, y);
}
}
if (len == 4) {
if (strncmp("DETX", value, len) == 0) {
DBG(1, " FB: paper width detection\n");
}
if (strncmp("DETY", value, len) == 0) {
DBG(1, " FB: paper height detection\n");
}
}
}
return SANE_STATUS_GOOD;
}
SANE_Status esci2_info(epsonds_scanner *s)
{
SANE_Status status;
int i = 4;
DBG(1, "= gathering device information\n");
do {
status = esci2_cmd_simple(s, "INFOx0000000", &info_cb);
if (status == SANE_STATUS_DEVICE_BUSY) {
sleep(2);
}
i--;
} while (status == SANE_STATUS_DEVICE_BUSY && i);
return status;
}
/* CAPA */
static SANE_Status capa_cb(void *userdata, char *token, int len)
{
epsonds_scanner *s = (epsonds_scanner *)userdata;
char *value = token + 3;
if (DBG_LEVEL >= 11) {
debug_token(DBG_LEVEL, __func__, token, len);
}
2015-04-03 23:53:18 +00:00
if (len == 4) {
2015-04-03 23:53:18 +00:00
if (strncmp("ADFDPLX", token, 3 + 4) == 0) {
DBG(1, " ADF: duplex\n");
s->hw->adf_is_duplex = 1;
}
2015-04-03 23:53:18 +00:00
if (strncmp("ADFSKEW", token, 3 + 4) == 0) {
DBG(1, " ADF: skew correction\n");
s->hw->adf_has_skew = 1;
}
2015-04-03 23:53:18 +00:00
if (strncmp("ADFOVSN", token, 3 + 4) == 0) {
DBG(1, " ADF: overscan\n");
}
2015-04-03 23:53:18 +00:00
if (strncmp("ADFPEDT", token, 3 + 4) == 0) {
DBG(1, " ADF: paper end detection\n");
}
2015-04-03 23:53:18 +00:00
if (strncmp("ADFLOAD", token, 3 + 4) == 0) {
DBG(1, " ADF: paper load\n");
s->hw->adf_has_load = 1;
}
2015-04-03 23:53:18 +00:00
if (strncmp("ADFEJCT", token, 3 + 4) == 0) {
DBG(1, " ADF: paper eject\n");
s->hw->adf_has_eject = 1;
}
2015-04-03 23:53:18 +00:00
if (strncmp("ADFCRP ", token, 3 + 4) == 0) {
DBG(1, " ADF: image cropping\n");
}
2015-04-03 23:53:18 +00:00
if (strncmp("ADFFAST", token, 3 + 4) == 0) {
DBG(1, " ADF: fast mode available\n");
}
if (strncmp("ADFDFL1", token, 3 + 4) == 0) {
DBG(1, " ADF: double feed detection\n");
2015-04-26 21:11:37 +00:00
s->hw->adf_has_dfd = 1;
2015-04-03 23:53:18 +00:00
}
}
if (len == 8 && strncmp("ADFDFL1DFL2", token, 3 + 4) == 0) {
DBG(1, " ADF: double feed detection (high sensitivity)\n");
2015-04-26 21:11:37 +00:00
s->hw->adf_has_dfd = 2;
}
if (strncmp("FMT", token, 3) == 0) {
/* a bit ugly... */
if (len >= 8) {
if (strncmp("RAW ", value + 4, 4) == 0) {
s->hw->has_raw = 1;
}
}
if (len >= 12) {
if (strncmp("RAW ", value + 8, 4) == 0) {
s->hw->has_raw = 1;
}
}
}
/* RSMRANGi0000050i0000600 */
if (strncmp("RSMRANG", token, 3 + 4) == 0) {
char *p = token + 3 + 4;
if (p[0] == 'i') {
int min = decode_value(p, 8);
int max = decode_value(p + 8, 8);
eds_set_resolution_range(s->hw, min, max);
DBG(1, "resolution min/max %d/%d\n", min, max);
}
}
/* RSMLISTi0000300i0000600 */
if (strncmp("RSMLIST", token, 3 + 4) == 0) {
char *p = token + 3 + 4;
if (p[0] == 'i') {
int i;
int count = (len - 4) / 8;
for (i = 0; i < count; i++) {
eds_add_resolution(s->hw, decode_value(p, 8));
p += 8;
}
}
}
return SANE_STATUS_GOOD;
}
SANE_Status esci2_capa(epsonds_scanner *s)
{
return esci2_cmd_simple(s, "CAPAx0000000", &capa_cb);
}
/* STAT */
static SANE_Status stat_cb(void *userdata, char *token, int len)
{
/*
epsonds_scanner *s = (epsonds_scanner *)userdata;
char *value = token + 3;
*/
userdata = userdata;
if (DBG_LEVEL >= 11) {
debug_token(DBG_LEVEL, __func__, token, len);
}
return SANE_STATUS_GOOD;
}
SANE_Status esci2_stat(epsonds_scanner *s)
{
return esci2_cmd_simple(s, "STATx0000000", &stat_cb);
}
/* RESA */
static SANE_Status resa_cb(void *userdata, char *token, int len)
{
/* epsonds_scanner *s = (epsonds_scanner *)userdata; */
userdata = userdata;
if (DBG_LEVEL >= 11) {
debug_token(DBG_LEVEL, __func__, token, len);
}
return SANE_STATUS_GOOD;
}
SANE_Status esci2_resa(epsonds_scanner *s)
{
return esci2_cmd_simple(s, "RESAx0000000", &resa_cb);
}
/* PARA */
static SANE_Status para_cb(void *userdata, char *token, int len)
{
if (DBG_LEVEL >= 11) {
debug_token(DBG_LEVEL, __func__, token, len);
}
userdata = userdata;
if (strncmp("par", token, 3) == 0) {
if (strncmp("FAIL", token + 3, 4) == 0) {
DBG(1, "%s: parameter setting failed\n", __func__);
return SANE_STATUS_INVAL;
}
}
return SANE_STATUS_GOOD;
}
SANE_Status esci2_para(epsonds_scanner *s, char *parameters)
{
DBG(8, "%s: %s\n", __func__, parameters);
return esci2_cmd(s, "PARAx0000000", 12, parameters, strlen(parameters), NULL, &para_cb);
}
SANE_Status esci2_mech(epsonds_scanner *s, char *parameters)
{
DBG(8, "%s: %s\n", __func__, parameters);
return esci2_cmd(s, "MECHx0000000", 12, parameters, strlen(parameters), NULL, &para_cb);
}
SANE_Status esci2_trdt(epsonds_scanner *s)
{
return esci2_cmd_simple(s, "TRDTx0000000", NULL);
}
static SANE_Status img_cb(void *userdata, char *token, int len)
{
struct epsonds_scanner *s = userdata;
if (DBG_LEVEL >= 11) {
debug_token(DBG_LEVEL, __func__, token, len);
}
/* psti0000256i0000000i0000945 / 24 */
/* integer comparison first so it's faster */
if (len == 24 && strncmp("pst", token, 3) == 0) {
s->dummy = decode_value(token + 3 + 8, 8);
DBG(10, "%s: pst width: %d, height: %d, dummy: %d\n",
__func__,
decode_value(token + 3, 8),
decode_value(token + 3 + 8 + 8, 8),
s->dummy);
return SANE_STATUS_GOOD;
}
if (len == 16 && strncmp("pen", token, 3) == 0) {
DBG(10, "%s: page end\n", __func__);
s->eof = 1;
return SANE_STATUS_EOF;
}
/* typIMGA or typIMGB */
if (len == 4 && strncmp("typ", token, 3) == 0) {
if (token[6] == 'B')
s->backside = 1;
else
s->backside = 0;
2015-04-03 23:53:18 +00:00
return SANE_STATUS_GOOD;
}
if (strncmp("err", token, 3) == 0) {
char *option = token + 3; /* ADF, TPU, FB */
char *cause = token + 3 + 4; /* OPN, PJ, PE, ERR, LTF, LOCK, DFED, DTCL, AUT, PERM */
s->scanning = 0;
DBG(1, "%s: error on option %3.3s, cause %4.4s\n",
__func__, option, cause);
if (cause[0] == 'P' && cause[1] == 'J')
return SANE_STATUS_JAMMED;
if (cause[0] == 'P' && cause[1] == 'E')
return SANE_STATUS_NO_DOCS;
if (cause[0] == 'O' && cause[1] == 'P' && cause[2] == 'N')
return SANE_STATUS_COVER_OPEN;
return SANE_STATUS_IO_ERROR;
}
if (len == 4 && strncmp("atnCAN ", token, 3 + 4) == 0) {
2015-04-03 23:53:18 +00:00
DBG(1, "%s: cancel request\n", __func__);
s->canceling = 1;
s->scanning = 0;
return SANE_STATUS_CANCELLED;
}
if (len == 4 && strncmp("lftd000", token, 3 + 4) == 0) {
s->scanning = 0;
}
return SANE_STATUS_GOOD;
}
SANE_Status
esci2_img(struct epsonds_scanner *s, SANE_Int *length)
{
SANE_Status status = SANE_STATUS_GOOD;
SANE_Status parse_status;
unsigned int more;
ssize_t read;
*length = 0;
if (s->canceling)
return SANE_STATUS_CANCELLED;
/* request image data */
eds_send(s, "IMG x0000000", 12, &status, 64);
if (status != SANE_STATUS_GOOD) {
return status;
}
/* receive DataHeaderBlock */
memset(s->buf, 0x00, 64);
eds_recv(s, s->buf, 64, &status);
if (status != SANE_STATUS_GOOD) {
return status;
}
/* check if we need to read any image data */
more = 0;
if (!esci2_check_header("IMG ", (char *)s->buf, &more)) {
return SANE_STATUS_IO_ERROR;
}
/* this handles eof and errors */
parse_status = esci2_parse_block((char *)s->buf + 12, 64 - 12, s, &img_cb);
/* no more data? return using the status of the esci2_parse_block
* call, which might hold other error conditions.
*/
if (!more) {
return parse_status;
}
/* ALWAYS read image data */
if (s->hw->connection == SANE_EPSONDS_NET) {
epsonds_net_request_read(s, more);
}
read = eds_recv(s, s->buf, more, &status);
if (status != SANE_STATUS_GOOD) {
return status;
}
if (read != more) {
return SANE_STATUS_IO_ERROR;
}
/* handle esci2_parse_block errors */
if (parse_status != SANE_STATUS_GOOD) {
return parse_status;
}
DBG(15, "%s: read %lu bytes, status: %d\n", __func__, (unsigned long) read, status);
*length = read;
if (s->canceling) {
return SANE_STATUS_CANCELLED;
}
return SANE_STATUS_GOOD;
}