diff options
author | Clemens Fries <github-raster@xenoworld.de> | 2015-07-18 23:33:23 +0200 |
---|---|---|
committer | Clemens Fries <github-raster@xenoworld.de> | 2015-07-18 23:33:23 +0200 |
commit | 18838e644f1149db333c5ee441e025ba4b89283f (patch) | |
tree | d80afecb41ae8f3e6a7a3882b22b0ded9de5026f /src | |
parent | 033512bf1bd8fee437d3fa9dafbcf916bd7cb814 (diff) |
Moved code and documentation from landfill to tidy repository.
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile | 11 | ||||
-rw-r--r-- | src/examples/minimal.c | 119 | ||||
-rw-r--r-- | src/ql570.c | 280 | ||||
-rw-r--r-- | src/ql570.h | 300 | ||||
-rw-r--r-- | src/rastertoql570.c | 361 | ||||
-rw-r--r-- | src/rastertoql570.h | 32 |
6 files changed, 1103 insertions, 0 deletions
diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..2c2cf5f --- /dev/null +++ b/src/Makefile @@ -0,0 +1,11 @@ +CC=gcc +CFLAGS=-Wall -Wextra -g +#CFLAGS=-g + +rastertoql570: ql570.h ql570.c rastertoql570.h rastertoql570.c + rm -f ../rastertoql570 + $(CC) $(CFLAGS) -lcups -lcupsimage ql570.c rastertoql570.c -o ../rastertoql570 + +minimal: ql570.h ql570.c examples/minimal.c + rm -f ../minimal + $(CC) $(CFLAGS) -lcups -lcupsimage ql570.c examples/minimal.c -o ../minimal diff --git a/src/examples/minimal.c b/src/examples/minimal.c new file mode 100644 index 0000000..5550e9e --- /dev/null +++ b/src/examples/minimal.c @@ -0,0 +1,119 @@ +/* minimal.c: a minmal example for using the QL-570 utility functions + * + * Copyright (C) 2015 Clemens Fries <github-raster@xenoworld.de> + * + * This file is part of rastertoql570. + * + * rastertoql570 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. + * + * rastertoql570 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 rastertoql570. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "../ql570.h" + +/** + * Raster line length for the QL-570 is 90 bytes. There are some other QL + * series printers that need a larger buffer. + */ +#define BUFFER_SIZE 90 + +/** + * Minimal example program for direct printer access. + * + * For a better understanding of what goes on here, please refer + * to the documentation of each function. At times there are + * interesting bits of information over there. + * + */ +int main() +{ + FILE *device = fopen("/dev/usb/lp0", "wb"); + ql_status status = {0}; + uint8_t buffer[BUFFER_SIZE]; + + /* + * Request a print job with 150 lines (the minimal amount of lines for + * the QL-570). Note that this number is mandatory. If you print less + * than 150 lines, or indicate some wrong amount of lines here, the + * printer will just resort to blinking its red LED while observing you + * with displeasure. + */ + ql_print_info print_info = { .raster_number[0] = 150 }; + + /* + * Initialise printer. The first flag indicates that we won't be + * flushing the printer with 200 bytes of zeroes. + */ + ql_init(false, device); + + /* + * Ask the printer to give us a status report. + */ + ql_status_request(device); + + /* + * Retrieve the status report. + */ + ql_status_read(&status, device); + + /* + * Send the contents of `print_info` to the printer. + */ + ql_page_start(&print_info, device); + + /* + * Set some extended options: cut-at-end plus 300dpi printing. + */ + ql_set_extended_options(true, false, device); + + /* + * Print some black lines. + */ + for (int i = 0; i < 150; i++) { + + /* + * Every now and then: Many, many 1s. + */ + memset(buffer, i % 5 == 0 ? 0xFF : 0x00, BUFFER_SIZE); + + /* + * We empty the first two bytes in order to force a small + * margin. Note that for 62mm the right and left margin is + * about 12 dots. I observed that I have a margin any way on + * one side, but it looks like it is printing outside the label + * area on the other side. It's not that important, but I do + * it anyway because I'm not sure about the mechanics and how + * well the print heads cope with doing their thing outside + * the printable area. + * + */ + buffer[0] = 0x00; + buffer[1] = 0x00; + + /* + * Send this raster line to the printer. + */ + ql_raster(BUFFER_SIZE, buffer, device); + } + + /* + * Indicate the end of raster data to the printer. + */ + ql_raster_end(BUFFER_SIZE, device); + + /* + * Tell it that this was the last page. + */ + ql_page_end(true, device); + + fclose(device); +} diff --git a/src/ql570.c b/src/ql570.c new file mode 100644 index 0000000..e642f91 --- /dev/null +++ b/src/ql570.c @@ -0,0 +1,280 @@ +/* ql570.c: facilities for communicating with the QL-570 label printer + * + * Copyright (C) 2015 Clemens Fries <github-raster@xenoworld.de> + * + * This file is part of rastertoql570. + * + * rastertoql570 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. + * + * rastertoql570 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 rastertoql570. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "ql570.h" + +/** + * Request status from printer. + * + * @param device file descriptor to write to + */ +void +ql_status_request(FILE *device) +{ + uint8_t request[3] = {QL_ESC, 0x69, 0x53}; + fwrite(request, 3, 1, device); + fflush(device); +} + +/** + * Read a status response from the printer. + * + * @param status ql_status struct holding the response + * @param device file descriptor to write to + * @return true if successful, false if short read + */ +bool +ql_status_read(ql_status *status, FILE *device) +{ + size_t len = fread(status, sizeof(ql_status), 1, device); + + if (len != sizeof(ql_status)) { + return false; + } + + return true; +} + +/** + * Initialise printer. + * + * The protocol specification has an optional recommendation to flush lingering + * partial commands with 200 bytes of 0x00. + * + * @param flush prepend 200 bytes of invalid (0x00) data + * @param device file descriptor to write to + */ +void +ql_init(bool flush, FILE *device) +{ + if (flush) { + uint8_t init_buffer[200]; + memset(&init_buffer, 0x0, 200); + fwrite(init_buffer, sizeof(uint8_t), 200, device); + fflush(device); + } + + uint8_t request[2] = {QL_ESC, 0x40}; + fwrite(request, 2, 1, device); + fflush(device); +} + +/** + * Write one line of raster data to the printer. + * + * For most printers in the QL series one raster line is at most 90 bytes long. + * As these are monochrome printers this means a line has 720 pixels (at most). + * + * @param length length of the raster data + * @param data pointer to the raster data + * @param device file descriptor to write to + */ +void +ql_raster(uint8_t length, uint8_t *data, FILE *device) +{ + uint8_t request[3] = {0x67, 0x00, length}; + fwrite(request, 3, 1, device); + fwrite(data, length, 1, device); + fflush(device); +} + +/** + * Signal end of raster data. + * + * @param length length of raster data (0x00) + * @param device file descriptor to write to + */ +void +ql_raster_end(uint8_t length, FILE *device) +{ + uint8_t request[3] = {0x67, 0xFF, length}; + + uint8_t data[length]; + memset(&data, 0x00, length); + + fwrite(request, 3, 1, device); + fwrite(data, length, 1, device); + + fflush(device); +} + +/** + * Start a new page. + * + * This will write out the contents of the print_info pointer to the printer. + * + * @param print_info print_info + * @param device file descriptor to write to + */ +void +ql_page_start(ql_print_info *print_info, FILE *device) +{ + uint8_t request[3] = {QL_ESC, 0x69, 0x7A}; + fwrite(request, 3, 1, device); + fwrite(print_info, sizeof(ql_print_info), 1, device); + fflush(device); +} + +/** + * End the current page. + * + * @param last_page indicate that this is the last page + * @param device file descriptor to write to + */ +void +ql_page_end(bool last_page, FILE *device) +{ + uint8_t request; + + if (last_page) { + request = 0x1A; + } else { + request = 0x0C; + } + + fwrite(&request, 1, 1, device); + fflush(device); +} + + +/** + * Set extended options. + * + * This is called 'set expanded mode' in the specification. + * + * ### Cutting + * + * Pain points with the specification: + * + * * The diagram is wrong. The description says, this is bit 3, the diagram + * says, this is bit 4. This cost me some time. + * TODO: Check 720N/NW. The documentation there lacks the diagram and the + * description sys it is bit 4. I bet this is wrong! + * * Not cutting is the default. The description says that 'cut at end' is the + * default. + * + * TODO: This seems to cut after each page, regardless of whether last_page is + * set. I don't get it. + * + * ### High resolution printing + * + * Note that this is the resolution along the label _length_. The resolution + * along the _width_ of the media will stay the same. This means that more + * lines will be squeezed in the same space. + * + * Selecting high resolution printing, will allow the shortest label produced + * to be shrunk from 12.7mm to 6.35mm. The minimum label length is 150 lines, + * doubling the resolution will halve the length. + * + * @param cut_at_end + * @param high_resolution false: 300x300 dpi, true: 300x600 dpi + * @param device file descriptor to write to + */ +void +ql_set_extended_options(bool cut_at_end, bool high_resolution, FILE *device) +{ + uint8_t options = 0x00; + + if (cut_at_end) + options |= OPT_CUT_AT_END; + + if (high_resolution) + options |= OPT_HIGH_RESOLUTION; + + uint8_t request[4] = {QL_ESC, 0x69, 0x4B, options}; + fwrite(request, 4, 1, device); + fflush(device); +} + +/** + * Enable automatic label cutting. + * + * The specification calls this "set each mode". The bits of the last byte in + * the command sequence are either described as unused or undefined, setting + * bit 6 enables auto cut. + * + * @param device file descriptor to write to + */ +void +ql_autocut_enable(FILE *device) +{ + uint8_t request[4] = {QL_ESC, 0x69, 0x4D, 0b01000000}; + fwrite(request, 4, 1, device); + fflush(device); +} + +/** + * Cut after each _n_ labels. + * + * For continuous length labels it makes sense to cut after every page. For + * other label types it might make sense to cut after several labels. + * + * TODO: default is 1, does that work? If yes, note here + * + * @param interval cut after _interval_ number of pages + * @param device file descriptor to write to + */ +void +ql_autocut_interval(uint8_t interval, FILE *device) +{ + uint8_t request[4] = {QL_ESC, 0x69, 0x41, interval}; + fwrite(request, 4, 1, device); + fflush(device); +} + +/** + * Sets the default margins. + * + * The specification recommends setting the margins to 35 for several printer + * types, including the QL-570. For other types this is not indicated. Setting + * this seems to be optional. + * + * If the media is 'die cut', then the margins will always be 0. + * + * See ql_set_margins for details. + * + * @param + * @param device file descriptor to write to + * + */ +void +ql_set_default_margins(enum ql_media_type media_type, FILE *device) +{ + if (media_type == MT_CONTINUOUS) + ql_set_margins(35, device); + else + ql_set_margins(0, device); +} + +/** + * Set margins on continuous tape. + * + * This adds _margins_ amount of blank lines before and after the label. + * + * @param margins number of blank lines + * @param device file descriptor to write to + */ +void +ql_set_margins(uint16_t margins, FILE *device) +{ + uint8_t request[5] = {QL_ESC, 0x69, 0x64, margins & 0x00FF, (margins & 0xFF00) >> 8}; + fwrite(request, 5, 1, device); + fflush(device); +} diff --git a/src/ql570.h b/src/ql570.h new file mode 100644 index 0000000..c346e9a --- /dev/null +++ b/src/ql570.h @@ -0,0 +1,300 @@ +/* ql570.h: facilities for communicating with the QL-570 label printer + * + * Copyright (C) 2015 Clemens Fries <github-raster@xenoworld.de> + * + * This file is part of rastertoql570. + * + * rastertoql570 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. + * + * rastertoql570 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 rastertoql570. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _QL570_H_ +#define _QL570_H_ + +#include <stdio.h> +#include <stdint.h> +#include <stdbool.h> +#include <string.h> + +#define QL_ESC 0x1b +#define QL_INVALID 0x00 + +enum ql_extended_option { + OPT_CUT_AT_END = 0x08, + OPT_HIGH_RESOLUTION = 0x40 +}; + +enum ql_printer_type { + QL_OTHER = 0x00, + QL_500_550 = 0x4F, + QL_560 = 0x31, + QL_570 = 0x32, + QL_580N = 0x33, + QL_650TD = 0x51, + QL_700 = 0x35, + QL_1050 = 0x50, + QL_1060N = 0x34 +}; + +enum ql_error_info_1 { + NO_MEDIA = 0x01, + END_OF_MEDIA = 0x02, + TAPE_CUTTER_JAM = 0x04, + MAIN_UNIT_IN_USE = 0x10, + FAN_MALFUNCTION = 0x80 +}; + +enum ql_error_info_2 { + /** + * Requested media type is not loaded into the printer. + * + * Another gem from the specification. The table in section 4.2.1 + * describes that bit 0 of _Error information 2_ is unused. The + * command description for _Print information command_ states that bit + * 0 of that field is set to `true` when media type, width and length + * are set in the struct's `valid_flag` and the wrong media is loaded. + */ + WRONG_MEDIA = 0x01, + TRANSMISSION_ERROR = 0x04, + COVER_OPENED = 0x10, + CANNOT_FEED = 0x40, + SYSTEM_ERROR = 0x80 +}; + +enum ql_media_type { + MT_CONTINUOUS = 0x0A, + MT_DIE_CUT = 0x0B +}; + +enum ql_status_type { + ST_REPLY = 0x00, + ST_COMPLETED = 0x01, + ST_ERROR = 0x02, + ST_NOTIFICATION = 0x05, + ST_PHASE_CHANGE = 0x06 +}; + +enum ql_notification_type { + NT_NA = 0x00, + NT_COOLING_STARTED = 0x03, + NT_COOLING_FINISHED = 0x04 +}; + +enum ql_phase_type { + PT_WAITING = 0x00, + PT_PRINTING = 0x01 +}; + +enum ql_print_info_validity { + /** + * Stop with error (`ql_error_info_2.WRONG_MEDIA`) if the requested + * media type is different from the loaded media type. + */ + PIV_MEDIA_TYPE = 0x02, + + /** + * Stop with error (`ql_error_info_2.WRONG_MEDIA`) if the requested + * media width is different from the loaded media width. + */ + PIV_MEDIA_WIDTH = 0x04, + + /** + * Stop with error (`ql_error_info_2.WRONG_MEDIA`) if the requested + * media length is different from the loaded media length. + */ + PIV_MEDIA_LENGTH = 0x08, + + /** + * Prefer quality over speed. + */ + PIV_QUALITY = 0x40, + + /** + * A mysterious flag described as "Always ON" in the specification. An + * intial suspicion that this might signal the printer to recover from + * errors after a short amount of time turned out to be wrong. (This + * was based on the name `PI_RECOVER` as used in the specification.) + */ + PIV_RECOVER = 0x80 +}; + +typedef struct ql_print_info ql_print_info; +struct ql_print_info { + /* + * See #ql_print_info_validity. + */ + uint8_t valid_flag; + uint8_t media_type; + uint8_t media_width; + uint8_t media_length; + + /* + * Number of lines to be printed. + * + * TODO: rename? + */ + uint8_t raster_number[4]; + + /** + * TODO: What are the effects of this setting? + * + * Set to `0` on the first page, `1` for successive pages; + */ + uint8_t successive_page; + uint8_t _fixed; +}; + +typedef struct ql_status ql_status; +struct ql_status { + /** + * Always 0x80. + */ + uint8_t print_head_mark; + + /** + * Always 32 bytes. + */ + uint8_t size; + uint8_t _reserved3; + uint8_t _reserved4; + + /** + * Officially named 'reserved' but according to the documentation it + * indeed does identify the printer. Note that the specification is + * from 2011 and thus cannot include any newer printers in the QL + * series. + */ + uint8_t printer_id; + + /** + * Always 0x30. + */ + uint8_t _reserved6; + + /** + * Always 0x00. + */ + uint8_t _reserved7; + + /** + * Always 0x00. + */ + uint8_t _reserved8; + + /** + * See #ql_error_info_1. + */ + uint8_t error_info_1; + + /** + * See #ql_error_info_2. + */ + uint8_t error_info_2; + + /** + * Width of the label. + */ + uint8_t media_width; + + /** + * See #ql_media_type. + */ + uint8_t media_type; + + /** + * Always 0x00. + */ + uint8_t _reserved13; + + /** + * Always 0x00. + */ + uint8_t _reserved14; + + /** + * Not set. + */ + uint8_t _reserved15; + + /** + * Not set. + */ + uint8_t _reserved16; + + /** + * Always 0x00. + */ + uint8_t _reserved17; + + /** + * Length of ready-to-use labels, or zero for continuous labels. + */ + uint8_t media_length; + + /** + * See #ql_status_type. + */ + uint8_t status_type; + + /** + * See #ql_phase_type. + */ + uint8_t phase_type; + + /** + * Effectively 0x00. The specification states that this fields is 0x00 + * if it is not used (i.e. no 'phase change' was indicated in the + * status_type field.). The table for the 'phase types' also indicates + * that this field is 0x00 in both phases. + */ + uint8_t phase_num_h; + + /** + * Effectively 0x00. The specification states that this fields is 0x00 + * if it is not used (i.e. no 'phase change' was indicated in the + * status_type field.). The table for the 'phase types' also indicates + * that this field is 0x00 in both phases. + */ + uint8_t phase_num_l; + + /** + * See #ql_notification_type. + */ + uint8_t notification_type; + + /** + * Not set. + */ + uint8_t _reserved24; + + /** + * Not set. This is probably just for padding the struct to 32 bytes. + */ + uint8_t _reserved25[8]; +}; + +void ql_init(bool flush, FILE* device); +void ql_status_request(FILE* device); +bool ql_status_read(ql_status* status, FILE* device); +void ql_status_debug(ql_status* status); +void ql_raster(uint8_t length, uint8_t* data, FILE* device); +void ql_raster_end(uint8_t length, FILE* device); +void ql_page_start(ql_print_info* print_info, FILE* device); +void ql_page_end(bool last_page, FILE* device); +void ql_set_extended_options(bool cutAtEnd, bool highResolution, FILE* device); +void ql_autocut_enable(FILE* device); +void ql_autocut_interval(uint8_t interval, FILE* device); +void ql_set_default_margins(enum ql_media_type, FILE* device); +void ql_set_margins(uint16_t margins, FILE* device); + +#endif diff --git a/src/rastertoql570.c b/src/rastertoql570.c new file mode 100644 index 0000000..ffd16b5 --- /dev/null +++ b/src/rastertoql570.c @@ -0,0 +1,361 @@ +/* rastertoql570.c: a CUPS driver for the QL-570 label printer + * + * Copyright (C) 2015 Clemens Fries <github-raster@xenoworld.de> + * + * This file is part of rastertoql570. + * + * rastertoql570 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. + * + * rastertoql570 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 rastertoql570. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdio.h> +#include <unistd.h> +#include <signal.h> +#include <string.h> +#include <stdint.h> +#include <stdbool.h> +#include <time.h> +#include <cups/cups.h> +#include <cups/raster.h> +#include <cups/sidechannel.h> + +#include "ql570.h" +#include "rastertoql570.h" + +int +main(int argc, char** argv) +{ + // As per recommendation in the CUPS documentation + // TODO: use sigaction() + signal(SIGPIPE, SIG_IGN); + + FILE *fout = fdopen(STDOUT_FILENO, "wb"); + + if (fout == NULL) { + fprintf(stderr, "CRIT: Error while opening file.\n"); + return 1; + } + + // NOTE: Currently the resulting update of `status` by `init` (below) + // is not used. We still query for some status in order to determine if + // the printer is responding. Also, at a later stage, we can use the + // information in `status` to determine the printer type and set some + // variables, such as the raster buffer size, the minimal raster line + // count, etc. + ql_status status = { 0 }; + + if (!init(&status, fout)) { + fprintf(stderr, "CRIT: Could not get status information.\n"); + return 1; + } + + cups_raster_t *raster = cupsRasterOpen(0, CUPS_RASTER_READ); + cups_page_header2_t header; + unsigned int page_counter = 0; + + while (cupsRasterReadHeader2(raster, &header)) { + handle_page(raster, header, status, page_counter, fout); + page_counter++; + + // Printing this information will also end up on the jobs page + // of the CUPS web interface. I've seen a lot of printers that + // do not include this information and so the "Pages" number + // will end up being "Unknown". + fprintf(stderr, "PAGE: %d #-pages\n", page_counter); + } + + cupsRasterClose(raster); + fclose(fout); + + return EXIT_SUCCESS; +} + +/** + * Print a page. + * + * This was factored out from main(), so (for the moment) it is a bit unwieldy + * with regards to parameters. + * + */ +void +handle_page(cups_raster_t *raster, cups_page_header2_t header, ql_status status, unsigned int page_counter, FILE *fout) +{ + /* // TODO: Support some safety option for testing. + if( header.cupsHeight > 900 ) + header.cupsHeight = 900; + */ + + uint32_t cupsHeight = header.cupsHeight; + + // Enforce a minimum of 150 lines. + if( cupsHeight < 150 ) { + cupsHeight = 150; + } + + ql_print_info print_info = { + .valid_flag = PIV_QUALITY, + .raster_number[0] = cupsHeight & 0x00FF, + .raster_number[1] = (cupsHeight & 0xFF00) >> 8, + .successive_page = page_counter > 0 + }; + + ql_page_start(&print_info, fout); + + if (header.HWResolution[1] == 600) + ql_set_extended_options(true, true, fout); + else + ql_set_extended_options(true, false, fout); + + uint8_t buffer[header.cupsBytesPerLine]; + size_t output_buffer_size = 90; // TODO: Depends on printer type + uint8_t output_buffer[output_buffer_size]; + memset(output_buffer, 0x00, output_buffer_size); + + /* + * We insert blank lines before and after the raster output, if the + * line count of the original raster data is below 150. + * + * TODO: Depends on printer type, some other types need 295 lines. + */ + int blanks = 150 - header.cupsHeight; + + if (blanks > 0) + print_blank_lines(blanks / 2, output_buffer_size, fout); + + for (unsigned int i = 0; i < header.cupsHeight; ++i) { + if(cupsRasterReadPixels(raster, buffer, header.cupsBytesPerLine) == 0) + break; + + // In the (undesirable) case that the input_buffer is smaller than + // the output_buffer + if (header.cupsBytesPerLine < output_buffer_size) + memcpy(output_buffer, buffer, header.cupsBytesPerLine); + else + memcpy(output_buffer, buffer, output_buffer_size); + + ql_raster(output_buffer_size, output_buffer, fout); + } + + if (blanks > 0) + print_blank_lines(blanks / 2 + (blanks % 2), output_buffer_size, fout); + + ql_raster_end(output_buffer_size, fout); + + // TODO: Determine total number of pages and correctly indicate last page. + ql_page_end(false, fout); + + // Give the printer a moment to return status data. + nanosleep(&(struct timespec){0, 100e6}, NULL); + + wait_for_page_end(); +} + +/** + * Wait for a status indicating that the next page can be sent. + * + * This function tries to follow the printer status after a page has been + * submitted. It will try up to 25 times to read a status struct and find out + * if an end state has been reached. + */ +void +wait_for_page_end() +{ + ql_status status = {0}; + + for (uint8_t i = 0; i < 25; i++) { + // Sleep and skip in case of error. + if (!backchannel_read_status(&status)) { + nanosleep(&(struct timespec){0, 100e6}, NULL); + fprintf(stderr, "ERROR: Backchannel short read, retrying.\n"); + continue; + } + + // Skip this round if data seems to be corrupt. + if (status.print_head_mark != 0x80) { + fprintf(stderr, "ERROR: Print status returned is invalid, retrying.\n"); + continue; + } + + if (handle_status(&status)) + return; + } +} + + +/** + * Turns status information into user visible information. + * + * This function returns true to indicate that an end state has been reached. + * This means either an error occured or the printer changed to PT_WAITING + * (ready state). + * + * TODO: Maybe split this into further pieces, just look at that switch beneath + * a case! + * + * @param status the printer status to be inspected + * @returns true to indicate that polling can be stopped. + */ +bool +handle_status(ql_status *status) +{ + switch(status->status_type) { + case ST_COMPLETED: + fprintf(stderr, "INFO: Page completed.\n"); + break; + + // TODO: Reduce general ugliness (the problem is that multiple error + // conditions might exist at the same time) + case ST_ERROR: + if(status->error_info_1 & NO_MEDIA) + fprintf(stderr, "ERROR: No media.\n"); + + if(status->error_info_1 & END_OF_MEDIA) + fprintf(stderr, "ERROR: End of media.\n"); + + if(status->error_info_1 & TAPE_CUTTER_JAM) + fprintf(stderr, "ERROR: Tape cutter jam.\n"); + + if(status->error_info_1 & MAIN_UNIT_IN_USE) + fprintf(stderr, "ERROR: Main unit in use.\n"); + + if(status->error_info_1 & FAN_MALFUNCTION) + fprintf(stderr, "ERROR: Fan malfunction.\n"); + + if(status->error_info_2 & WRONG_MEDIA) + fprintf(stderr, "ERROR: Wrong media.\n"); + + if(status->error_info_2 & TRANSMISSION_ERROR) + fprintf(stderr, "ERROR: Transmission error.\n"); + + if(status->error_info_2 & COVER_OPENED) + fprintf(stderr, "ERROR: Cover opened.\n"); + + if(status->error_info_2 & CANNOT_FEED) + fprintf(stderr, "ERROR: Cannot feed.\n"); + + return true; // stop polling + + case ST_NOTIFICATION: + switch (status->notification_type) { + case NT_COOLING_STARTED: + fprintf(stderr, "INFO: Cooling started.\n"); + break; + + case NT_COOLING_FINISHED: + fprintf(stderr, "INFO: Cooling finished.\n"); + break; + } + break; + + case ST_PHASE_CHANGE: + switch(status->phase_type) { + case PT_WAITING: + fprintf(stderr, "INFO: Ready.\n"); + return true; // stop polling + + case PT_PRINTING: + fprintf(stderr, "INFO: Printing...\n"); + break; + } + break; + } + + return false; +} + +/** + * Initialise the printer. + * + * This will try a few times to initalise the printer. After the first try, + * ql_init() will be called with flush=true. We will sleep for 100ms after each + * call to ql_init(), because I observed that sometimes data is not ready, but + * the timeout on cupsBackChannelRead will no come into effect. I guess I am + * misunderstanding something with regards to the timeout. + * + * TODO: Understand timeout, rewrite above paragraph. It is mucho ugly. + * + * @param status status struct to populate with a response from the printer + * @param device file descriptor to write to + */ +bool +init(ql_status *status, FILE* device) +{ + bool flush = false; + + for(int i = 0; i < 10; ++i) { + ql_init(flush, device); + + nanosleep(&(struct timespec){0, 100e6}, NULL); + + if (!request_status(status, device)) + continue; + + if (status->print_head_mark == 0x80) + return true; + + // Flush printer buffers on subsequent retries. + flush = true; + } + + return false; +} + +/** + * Read status information from backchannel. + * + * @param status status struct to fill + * @returns false if number of bytes read was not `sizeof(ql_status)` + */ +bool +backchannel_read_status(ql_status* status) +{ + ssize_t ret = cupsBackChannelRead((uint8_t*)status, sizeof(ql_status), 10.0); + + if (ret != sizeof(ql_status)) + return false; + + return true; +} + +/** + * Insert blank lines into raster data. + * + * @param count number of lines + * @param buffer_size size of the raster line (e.g. 90) + * @param device file descriptor to write to + */ +void +print_blank_lines(uint32_t count, size_t buffer_size, FILE *device) +{ + uint8_t buffer[buffer_size]; + memset(buffer, 0x00, buffer_size); + + for(uint32_t i = 0; i < count; i++) + ql_raster(buffer_size, buffer, device); +} + +/** + * Request status and read response from backchannel. + * + * @param status status struct to fill + * @returns status of backchannel_read_status() operation + */ +bool +request_status(ql_status *status, FILE *device) +{ + // Send status request + ql_status_request(device); + + // Read response + return backchannel_read_status(status); +} diff --git a/src/rastertoql570.h b/src/rastertoql570.h new file mode 100644 index 0000000..7e1fe2d --- /dev/null +++ b/src/rastertoql570.h @@ -0,0 +1,32 @@ +/* rastertoql570.c: a CUPS driver for the QL-570 label printer + * + * Copyright (C) 2015 Clemens Fries <github-raster@xenoworld.de> + * + * This file is part of rastertoql570. + * + * rastertoql570 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. + * + * rastertoql570 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 rastertoql570. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _RASTERTOQL570_H +#define _RASTERTOQL570_H + +bool backchannel_read_status(ql_status*); +bool init(ql_status*, FILE*); +bool request_status(ql_status*, FILE*); +void wait_for_page_end(); +bool handle_status(ql_status*); +void print_blank_lines(uint32_t count, size_t buffer_size, FILE *device); +void handle_page(cups_raster_t*, cups_page_header2_t, ql_status, unsigned int, FILE*); + +#endif |