summaryrefslogtreecommitdiff
path: root/bjnp-levels.c
diff options
context:
space:
mode:
Diffstat (limited to 'bjnp-levels.c')
-rw-r--r--bjnp-levels.c647
1 files changed, 647 insertions, 0 deletions
diff --git a/bjnp-levels.c b/bjnp-levels.c
new file mode 100644
index 0000000..22b6998
--- /dev/null
+++ b/bjnp-levels.c
@@ -0,0 +1,647 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <errno.h>
+#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;
+}