/*
* Printer status, Ink-level and paper status related functions for the
* bjnp backend for the Common UNIX Printing System (CUPS).
* Copyright 2014 by Louis Lagendijk
*
* 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 only.
*
* 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 "bjnp.h"
#include "bjnp-protocol.h"
#include "bjnp-commands.h"
#include "bjnp-io.h"
/*
* Ink level warnings as reported by the printer
*/
static struct {
char *string;
int warning_level;
} warning_level[] = {
{ "SET", LEVEL_OK },
{ "LOW", LEVEL_LOW },
{ "IO", LEVEL_EMPTY },
{ "", LEVEL_UNKNOWN }
};
/*
* Lookup table for cartridge properties
*/
static struct {
char *cart_type; /* string identifying type of cartridge */
char *marker_type; /* type of marker, e.g. "ink" */
char *marker_color; /* sRGB encoded color */
char *marker_name; /* supply name */
char *marker_low_level; /* almost empty level */
char *marker_high_level; /* full level */
} cartridge_types[] = {
/* first define values for unknown cartridges */
{ "", "ink", "#FFFFFF", "Unknown", "10", "100" },
/* This are the types we know about */
/* all strings must be less than 16 chars in length */
{ "K", "ink", "#000000", "Black", "10", "100" },
{ "C", "ink", "#00FFFF", "Cyan", "10", "100" },
{ "M", "ink", "#FF00FF", "Magenta", "10", "100" },
{ "Y", "ink", "#FFFF00", "Yellow", "10", "100" },
{ "BK", "ink", "#000000", "Black", "10", "100" },
{ "LC", "ink", "#E0FFFF", "LightCyan", "10", "100" },
{ "LM", "ink", "#FF77FF", "LightMagenta", "10", "100" },
{ "GY", "ink", "#808080", "Gray", "10", "100" },
{ "PBK", "ink", "#000000", "PhotoBlack", "10", "100" },
{ "CL", "ink", "#00FFFF#FF00FF#FFFF00", "Color", "10", "100" },
/* end of array record */
{ NULL, NULL, NULL, NULL, "10", "100" }
};
#define reset_capability(a,b) b &= ~(a)
/*
* Find cartridge properties
* Returns index in table
*/
static int cart_type(char *desc)
{
int i;
/* index 0 is for unknown cartridges, so we start from 1 */
for (i = 1; cartridge_types[i].cart_type != NULL; i++) {
if (strcmp(cartridge_types[i].cart_type, desc) == 0) {
return i;
}
}
/* unrecognized carttridge */
bjnp_debug(LOG_NOTICE, "Unknown cartdige type %s, please report via %s!\n",
desc, BJNP_REPORT_URL);
return 0;
}
static int
get_argument(char *buf, char *token, char *argument, int size, char *error_string)
{
/*
* retrieve argument for parameter token
* stores argument in buffer argument of max size size
*/
char *parameter_str;
int i;
/* find token string in status */
if ((parameter_str = strstr(buf, token)) == NULL) {
bjnp_debug(LOG_NOTICE,
"Cannot report %s, (token %s not found in status reponse)!\n", error_string, token);
return 0;
}
parameter_str += strlen(token);
/* copy argument */
for (i = 0; i < size - 1; i++) {
if (parameter_str[i] == PARAMETER_SEPARATOR ||
parameter_str[i] == '\0') {
break;
}
argument[i] = parameter_str[i];
}
argument[i] = '\0';
return strlen(argument);
}
static void
report_marker_levels(printer_t *printer, char *status_buf, char first_output)
{
char levels[BJNP_ARG_MAX];
char *token;
int no_cartridges = 0;
char *p;
int i;
int type;
long val;
int level_changed = 0;
int expected_type;
char marker_colors[BJNP_REPORT_MAX] = "";
char marker_low_levels[BJNP_REPORT_MAX] = "";
char marker_high_levels[BJNP_REPORT_MAX] = "";
char marker_names[BJNP_REPORT_MAX] = "";
char marker_types[BJNP_REPORT_MAX] = "";
char marker_levels[BJNP_REPORT_MAX] = "";
char level[16];
if ((printer->reporting_capabilities & BJNP_REPORT_MARKER_LEVELS) == 0 ||
(get_argument(status_buf, INK_LEVEL_TOKEN, levels, BJNP_ARG_MAX, "marker levels") == 0)) {
reset_capability(BJNP_REPORT_MARKER_LEVELS, printer->reporting_capabilities);
return;
}
/* now parse cartridges */
token = strtok(levels, INK_LEVEL_DELIMITER);
while ((token != NULL) && (no_cartridges < 16)) {
if ((p = index(token, '=')) == NULL) {
bjnp_debug(LOG_ERROR, "error parsing ink levels in %s, no ink levels output!\n",
token);
return;
}
*p = '\0';
p++;
/* get ink level */
errno = 0;
val = strtol(p, NULL, 10);
if (errno != 0 || val > 100 || val < 0) {
bjnp_debug(LOG_ERROR,
"error parsing ink level %s for %s, no ink levels output!\n",
p, token);
}
type = cart_type(token);
if (printer->no_cartridges == -1) {
/* first time we find cartridges, just store the type */
printer->cartridges[no_cartridges].cart_index = type;
} else if (printer->cartridges[no_cartridges].cart_index != type) {
/* unexpected cartridge type found */
expected_type = printer->cartridges[no_cartridges].cart_index;
bjnp_debug(LOG_ERROR, "Cartridge type %s (%s) expected, but found type %s (%s) in ink levels\n",
cartridge_types[expected_type].cart_type, cartridge_types[expected_type].marker_name,
token, cartridge_types[type].marker_name);
return;
}
/* check the level */
if (val != printer->cartridges[no_cartridges].marker_level) {
level_changed = 1;
printer->cartridges[no_cartridges].marker_level = val;
}
/* Next token & cartridge */
no_cartridges++;
token = strtok(NULL, INK_LEVEL_DELIMITER);
}
/* check if the number of cartridges found matches our expectations */
if (printer->no_cartridges != -1 && printer->no_cartridges != no_cartridges) {
bjnp_debug(LOG_ERROR, "Found %d ink levels, but expected %d\n",
no_cartridges, printer->no_cartridges);
} else if (printer->no_cartridges == -1) {
printer->no_cartridges = no_cartridges;
}
if (no_cartridges == 0) {
/* nothing to output */
bjnp_debug(LOG_ERROR, "No cartridges found!\n");
return;
}
/* When a level level_changed or reporting interval is exceeded, report ink levels */
if (level_changed || (printer->last_level_report - BJNP_MARKER_INTERVAL > 0)) {
printer->last_level_report = time(NULL);
/* report ink levels */
if (first_output) {
i = 0;
type = printer->cartridges[i].cart_index;
strcat(marker_colors, cartridge_types[type].marker_color);
strcat(marker_low_levels, cartridge_types[type].marker_low_level);
strcat(marker_high_levels, cartridge_types[type].marker_high_level);
strcat(marker_names, cartridge_types[type].marker_name);
strcat(marker_types, cartridge_types[type].marker_type);
while (++i < no_cartridges) {
type = printer->cartridges[i].cart_index;
strcat(marker_colors, ",");
strcat(marker_colors, cartridge_types[type].marker_color);
strcat(marker_low_levels, ",");
strcat(marker_low_levels, cartridge_types[type].marker_low_level);
strcat(marker_high_levels, ",");
strcat(marker_high_levels, cartridge_types[type].marker_high_level);
strcat(marker_names, ",");
strcat(marker_names, cartridge_types[type].marker_name);
strcat(marker_types, ",");
strcat(marker_types, cartridge_types[type].marker_type);
}
fprintf(stderr, "ATTR: marker-colors=\"%s\"\n", marker_colors);
fprintf(stderr, "ATTR: marker-low-levels=\"%s\"\n", marker_low_levels);
fprintf(stderr, "ATTR: marker-high-levels=\"%s\"\n", marker_high_levels);
fprintf(stderr, "ATTR: marker-names=\"%s\"\n", marker_names);
fprintf(stderr, "ATTR: marker-types=\"%s\"\n", marker_types);
}
i = 0;
sprintf(level, "%d", printer->cartridges[i].marker_level);
strcat(marker_levels, level);
while (++i < no_cartridges) {
strcat(marker_levels, ",");
sprintf(level, "%d", printer->cartridges[i].marker_level);
strcat(marker_levels, level);
}
fprintf(stderr, "ATTR: marker-levels=\"%s\"\n", marker_levels);
}
}
static int
get_warning_level(char *string)
{
int i = 0;
while (warning_level[i].warning_level != LEVEL_UNKNOWN) {
if (strcmp(string, warning_level[i].string) == 0) {
return i;
}
i++;
}
return LEVEL_UNKNOWN;
}
static char *strstrtok(char *in, const char *sep)
{
static char *next = NULL;
char *p;
char *ret;
if (in != NULL) {
next = in;
}
if (next == NULL) {
return NULL;
}
ret = next;
if ((p = strstr(next, sep)) == NULL) {
next = NULL;
return ret;
}
*p = '\0';
next = p + strlen(sep);
return ret;
}
static int report_standard_ink_warnings(int old_level, int low, int empty)
{
int new_level;
if (empty) {
new_level = LEVEL_EMPTY;
} else if (low) {
new_level = LEVEL_LOW;
} else {
new_level = LEVEL_OK;
}
if (new_level != old_level) {
/* reset old warning levels */
switch (old_level) {
case LEVEL_LOW:
fputs("STATE: -marker-supply-low-warning\n", stderr);
break;
case LEVEL_EMPTY:
fputs("STATE: -marker-supply-empty-error\n", stderr);
break;
case LEVEL_UNKNOWN:
if (new_level != LEVEL_LOW) {
fputs("STATE: -marker-supply-low-warning\n", stderr);
}
if (new_level != LEVEL_EMPTY) {
fputs("STATE: -marker-supply-empty-error\n", stderr);
}
break;
}
/* set new warning levels */
switch (new_level) {
case LEVEL_LOW:
fputs("STATE: +marker-supply-low-warning\n", stderr);
break;
case LEVEL_EMPTY:
fputs("STATE: +marker-supply-empty-error\n", stderr);
break;
default:
break;
}
}
return new_level;
}
static void report_vendor_ink_warnings(char *marker_color, int old, int new)
{
if (old == new) {
/* nothing to do */
return;
}
/* remove no longer valid warnings */
switch (old) {
case LEVEL_LOW:
fprintf(stderr, "STATE: -%s.%s-ink-low-warning\n", BJNP_VENDOR_PREFIX,
marker_color);
break;
case LEVEL_EMPTY:
fprintf(stderr, "STATE: -%s.%s-ink-empty-error\n", BJNP_VENDOR_PREFIX,
marker_color);
break;
case LEVEL_UNKNOWN:
if (new != LEVEL_LOW) {
fprintf(stderr, "STATE: -%s.%s-ink-low-warning\n", BJNP_VENDOR_PREFIX,
marker_color);
}
if (new != LEVEL_EMPTY) {
fprintf(stderr, "STATE: -%s.%s-ink-empty-error\n", BJNP_VENDOR_PREFIX,
marker_color);
}
break;
}
/* Set new level warnings */
switch (new) {
case LEVEL_LOW:
fprintf(stderr, "STATE: +%s.%s-ink-low-warning\n", BJNP_VENDOR_PREFIX,
marker_color);
break;
case LEVEL_EMPTY:
fprintf(stderr, "STATE: +%s.%s-ink-empty-error\n", BJNP_VENDOR_PREFIX,
marker_color);
break;
default:
break;
}
}
static int report_ink_status_messages(printer_t *printer, char *status_buf,
char first_output)
{
char *token;
char warnings[BJNP_ARG_MAX];
int warning_level;
char level_low = 0;
char level_empty = 0;
int no_cartridges = 0;
int cartridge_type;
char *p;
if ((printer->reporting_capabilities & BJNP_REPORT_INK_STATUS) == 0 ||
get_argument(status_buf, INK_WARNING_TOKEN, warnings, BJNP_ARG_MAX, "ink status") == 0) {
reset_capability(BJNP_REPORT_INK_STATUS, printer->reporting_capabilities);
return LEVEL_OK;
}
/* now parse cartridges */
token = strstrtok(warnings, INK_WARNING_DELIMITER);
while ((token != NULL) && (no_cartridges < BJNP_CARTRIDGES_MAX)) {
if ((p = index(token, ',')) == NULL) {
bjnp_debug(LOG_ERROR,
"error in parsing warning levels in status reponse, "
"warning levels not supported! token = %s\n", token);
return !level_empty;
}
*p = '\0';
p++;
cartridge_type = cart_type(token);
if (printer->no_cartridges == -1) {
printer->cartridges[no_cartridges].cart_index = cartridge_type;
} else {
if (printer->cartridges[no_cartridges].cart_index !=
cartridge_type) {
bjnp_debug(LOG_ERROR,
"error in parsing warning levels in status reponse, "
"warning sequence does not match! token = %s\n",
token);
return !level_empty;
}
}
warning_level = get_warning_level(p);
switch (warning_level) {
case LEVEL_LOW:
level_low = 1;
break;
case LEVEL_EMPTY:
level_empty = 1;
break;
default:
/* nothing to do */
break;
}
report_vendor_ink_warnings(cartridge_types[cartridge_type].marker_name,
printer->cartridges[no_cartridges].warning,
warning_level);
printer->cartridges[no_cartridges].warning = warning_level;
no_cartridges++;
token = strstrtok(NULL, INK_WARNING_DELIMITER);
}
if (printer->no_cartridges == -1) {
/* nr of cartridges not set before */
printer->no_cartridges = no_cartridges;
} else {
if (printer->no_cartridges != no_cartridges)
bjnp_debug(LOG_ERROR, "Number of cartridges in this run is different "
"from first run: now: %d, first %d\n",
printer->no_cartridges, no_cartridges);
}
if (no_cartridges == 0) {
/* nothing to output */
bjnp_debug(LOG_ERROR, "No cartridges found!\n");
return LEVEL_OK;
}
printer->global_ink_warning_level = report_standard_ink_warnings(
printer->global_ink_warning_level, level_low, level_empty);
return level_empty;
}
static int
report_printer_status(printer_t *printer, char *status_buf)
{
/*
* parses the status string of the printer to retrieve status
* of the printer
* Returns: BJNP_OK = printer available
* BJNP_PRINTER_BUSY = Printer busy
*/
char argument[BJNP_ARG_MAX];
unsigned int status;
int printer_status;
int ret = 0;
if ((printer->reporting_capabilities & BJNP_REPORT_PRINTER_STATUS) == 0 ||
(get_argument(status_buf, PRINTER_STATUS_TOKEN, argument, BJNP_ARG_MAX, "printer status") == 0)) {
reset_capability(BJNP_REPORT_PRINTER_STATUS, printer->reporting_capabilities);
return BJNP_OK;
}
if (sscanf(argument, "%2x", &status) == 1) {
bjnp_debug(LOG_DEBUG,
"Read printer status: %u, Printing = %d, Busy = %d, Error = %d\n",
status,
((status & BST_PRINTING) != 0),
((status & BST_BUSY) != 0),
((status & BST_ERROR) != 0));
printer_status = status & (BST_PRINTING | BST_BUSY);
if (printer_status) {
ret = BJNP_PRINTER_BUSY;
} else {
ret = BJNP_OK;
}
if (status & BST_ERROR) {
ret |= BJNP_PRINTER_ERROR;
}
return ret;
}
bjnp_debug(LOG_WARN, "Could not parse printer status for tag: %s!\n",
PRINTER_STATUS_TOKEN);
return BJNP_OK;
}
static int
report_paper_status(printer_t *printer, char *status_buf, int first_output)
{
/*
* parses the status string of the printer to retrieve paper status
* of the printer
* Returns: BJNP_OK
* BJNP_NO_PAPER
*/
char argument[BJNP_ARG_MAX];
if ((printer->reporting_capabilities & BJNP_REPORT_PAPER_STATUS) == 0 ||
get_argument(status_buf, PAPER_STATUS_TOKEN, argument, BJNP_ARG_MAX, "paper status") == 0) {
reset_capability(BJNP_REPORT_PAPER_STATUS, printer->reporting_capabilities);
return BJNP_OK;
}
if (first_output) {
/* on startup (first output) we set the out of paper state immediately */
if (strcmp(argument, DJS_PAPER_OUT) == 0) {
fputs("STATE: +media-empty-error\n", stderr);
printer->paper_out = BJNP_PAPER_OUT_THRESHOLD;
} else {
fputs("STATE: -media-empty-error\n", stderr);
printer->paper_out = 0;
}
} else {
/* report paper out only when we see the condition reported a number of times */
if (strcmp(argument, DJS_PAPER_OUT) == 0) {
if (printer->paper_out < BJNP_PAPER_OUT_THRESHOLD) {
printer->paper_out++;
bjnp_debug(LOG_DEBUG, "Paperout, condition seen %d times\n",
printer->paper_out);
if (printer->paper_out >= BJNP_PAPER_OUT_THRESHOLD) {
fputs("STATE: +media-empty-error\n", stderr);
}
}
} else {
/* paper out condition not found, report so if applicable,
* unless printing was cancelled on the printer
*/
if (printer->paper_out >= BJNP_PAPER_OUT_THRESHOLD &&
strcmp(argument, DJS_CANCELLING) != 0) {
fputs("STATE: -media-empty-error\n", stderr);
printer->paper_out = 0;
}
}
}
return printer->paper_out >= BJNP_PAPER_OUT_THRESHOLD ? BJNP_NO_PAPER : BJNP_OK;
}
int bjnp_report_levels(printer_t *printer)
{
char status_buf[BJNP_STATUS_MAX];
int ret = 0;
if (bjnp_get_status(printer, status_buf) != BJNP_OK) {
bjnp_debug(LOG_ERROR, "Cannot retrieve status, no level information!\n");
return BJNP_OK;
}
report_marker_levels(printer, status_buf, printer->first_output);
ret |= report_printer_status(printer, status_buf);
ret |= report_ink_status_messages(printer, status_buf, printer->first_output);
ret |= report_paper_status(printer, status_buf, printer->first_output);
printer->first_output = 0;
return ret;
}