1 /*
2 * Mitsubishi P93D/P95D Monochrome Thermal Photo Printer CUPS backend
3 *
4 * (c) 2016-2018 Solomon Peachy <pizza@shaftnet.org>
5 *
6 * Development of this backend was sponsored by:
7 *
8 * A benefactor who wishes to remain anonymous
9 *
10 * The latest version of this program can be found at:
11 *
12 * http://git.shaftnet.org/cgit/selphy_print.git
13 *
14 * This program is free software; you can redistribute it and/or modify it
15 * under the terms of the GNU General Public License as published by the Free
16 * Software Foundation; either version 2 of the License, or (at your option)
17 * any later version.
18 *
19 * This program is distributed in the hope that it will be useful, but
20 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
21 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
22 * for more details.
23 *
24 * You should have received a copy of the GNU General Public License
25 * along with this program. If not, see <https://www.gnu.org/licenses/>.
26 *
27 * [http://www.gnu.org/licenses/gpl-2.0.html]
28 *
29 * SPDX-License-Identifier: GPL-2.0+
30 *
31 */
32
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <unistd.h>
37
38 #include <sys/types.h>
39 #include <sys/stat.h>
40 #include <fcntl.h>
41 #include <signal.h>
42
43 #define BACKEND mitsup95d_backend
44
45 #include "backend_common.h"
46
47 #define USB_VID_MITSU 0x06D3
48 #define USB_PID_MITSU_P93D 0x0398
49 #define USB_PID_MITSU_P95D 0x3b10
50
51 /* Private data structure */
52 struct mitsup95d_printjob {
53 uint8_t *databuf;
54 uint32_t datalen;
55
56 uint8_t hdr[2]; // 1b 51
57 uint8_t hdr1[50]; // 1b 57 20 2e ...
58 uint8_t hdr2[50]; // 1b 57 21 2e ...
59 uint8_t hdr3[50]; // 1b 57 22 2e ...
60 uint8_t hdr4[42]; // 1b 58 ...
61 int hdr4_len; // 36 (P95) or 42 (P93)
62
63 uint8_t plane[12]; // 1b 5a 74 00 ...
64
65 uint8_t mem_clr[4]; // 1b 5a 43 00
66 int mem_clr_present;
67
68 uint8_t ftr[2];
69 };
70
71 struct mitsup95d_ctx {
72 struct libusb_device_handle *dev;
73 uint8_t endp_up;
74 uint8_t endp_down;
75
76 int type;
77
78 struct marker marker;
79 };
80
81 #define QUERYRESP_SIZE_MAX 9
82
mitsup93d_errors(uint8_t code)83 static const char *mitsup93d_errors(uint8_t code)
84 {
85 switch (code) {
86 case 0x6f: return "Door Open";
87 case 0x50: return "No Paper";
88 default: return "Unknown Error";
89 }
90 }
91
mitsup95d_errors(uint8_t code)92 static const char *mitsup95d_errors(uint8_t code)
93 {
94 switch (code & 0xf) {
95 case 3: return "Door Open";
96 case 4: return "No Paper";
97 default: return "Unknown Error";
98 }
99 }
100
mitsup95d_init(void)101 static void *mitsup95d_init(void)
102 {
103 struct mitsup95d_ctx *ctx = malloc(sizeof(struct mitsup95d_ctx));
104 if (!ctx) {
105 ERROR("Memory Allocation Failure!\n");
106 return NULL;
107 }
108 memset(ctx, 0, sizeof(struct mitsup95d_ctx));
109
110 return ctx;
111 }
112
mitsup95d_get_status(struct mitsup95d_ctx * ctx,uint8_t * resp)113 static int mitsup95d_get_status(struct mitsup95d_ctx *ctx, uint8_t *resp)
114 {
115 uint8_t querycmd[4] = { 0x1b, 0x72, 0x00, 0x00 };
116 int ret;
117 int num;
118
119 /* P93D is ... special. Windows switches to this halfway through
120 but it seems be okay to use it everywhere */
121 if (ctx->type == P_MITSU_P93D) {
122 querycmd[2] = 0x03;
123 }
124
125 /* Query Status to sanity-check job */
126 if ((ret = send_data(ctx->dev, ctx->endp_down,
127 querycmd, sizeof(querycmd))))
128 return CUPS_BACKEND_FAILED;
129 ret = read_data(ctx->dev, ctx->endp_up,
130 resp, QUERYRESP_SIZE_MAX, &num);
131
132 if (ret < 0)
133 return CUPS_BACKEND_FAILED;
134 if (ctx->type == P_MITSU_P95D && num != 9) {
135 return CUPS_BACKEND_FAILED;
136 } else if (ctx->type == P_MITSU_P93D && num != 8) {
137 return CUPS_BACKEND_FAILED;
138 }
139 return CUPS_BACKEND_OK;
140 }
141
mitsup95d_attach(void * vctx,struct libusb_device_handle * dev,int type,uint8_t endp_up,uint8_t endp_down,uint8_t jobid)142 static int mitsup95d_attach(void *vctx, struct libusb_device_handle *dev, int type,
143 uint8_t endp_up, uint8_t endp_down, uint8_t jobid)
144 {
145 struct mitsup95d_ctx *ctx = vctx;
146
147 UNUSED(jobid);
148
149 ctx->dev = dev;
150 ctx->endp_up = endp_up;
151 ctx->endp_down = endp_down;
152 ctx->type = type;
153
154 ctx->marker.color = "#000000"; /* Ie black! */
155 ctx->marker.name = "Unknown";
156 ctx->marker.levelmax = -1;
157 ctx->marker.levelnow = -2;
158
159 return CUPS_BACKEND_OK;
160 }
161
mitsup95d_cleanup_job(const void * vjob)162 static void mitsup95d_cleanup_job(const void *vjob)
163 {
164 const struct mitsup95d_printjob *job = vjob;
165
166 if (job->databuf)
167 free(job->databuf);
168
169 free((void*)job);
170 }
171
mitsup95d_read_parse(void * vctx,const void ** vjob,int data_fd,int copies)172 static int mitsup95d_read_parse(void *vctx, const void **vjob, int data_fd, int copies) {
173 struct mitsup95d_ctx *ctx = vctx;
174 uint8_t buf[2]; /* Enough to read in any header */
175 uint8_t tmphdr[50];
176 uint8_t *ptr;
177 int i;
178 int remain;
179 int ptr_offset;
180
181 struct mitsup95d_printjob *job = NULL;
182
183 if (!ctx)
184 return CUPS_BACKEND_FAILED;
185
186 job = malloc(sizeof(*job));
187 if (!job) {
188 ERROR("Memory allocation failure!\n");
189 return CUPS_BACKEND_RETRY_CURRENT;
190 }
191 memset(job, 0, sizeof(*job));
192
193 job->mem_clr_present = 0;
194
195 top:
196 i = read(data_fd, buf, sizeof(buf));
197
198 if (i == 0) {
199 mitsup95d_cleanup_job(job);
200 return CUPS_BACKEND_CANCEL;
201 }
202 if (i < 0) {
203 mitsup95d_cleanup_job(job);
204 return CUPS_BACKEND_CANCEL;
205 }
206 if (buf[0] != 0x1b) {
207 ERROR("malformed data stream\n");
208 mitsup95d_cleanup_job(job);
209 return CUPS_BACKEND_CANCEL;
210 }
211
212 switch (buf[1]) {
213 case 0x50: /* Footer */
214 remain = 2;
215 ptr = job->ftr;
216 break;
217 case 0x51: /* Job Header */
218 remain = 2;
219 ptr = job->hdr;
220 break;
221 case 0x57: /* Geeneral headers */
222 remain = sizeof(tmphdr);
223 ptr = tmphdr;
224 break;
225 case 0x58: /* User Comment */
226 if (ctx->type == P_MITSU_P93D)
227 job->hdr4_len = 42;
228 else
229 job->hdr4_len = 36;
230 remain = job->hdr4_len;
231 ptr = job->hdr4;
232 break;
233 case 0x5a: /* Plane header OR printer reset */
234 // reset memory: 1b 5a 43 ... [len 04]
235 // plane header: 1b 5a 74 ... [len 12]
236 // Read in the minimum length, and clean it up later */
237 ptr = tmphdr;
238 remain = 4;
239 break;
240 default:
241 ERROR("Unrecognized command! (%02x %02x)\n", buf[0], buf[1]);
242 mitsup95d_cleanup_job(job);
243 return CUPS_BACKEND_CANCEL;
244 }
245
246 memcpy(ptr, buf, sizeof(buf));
247 remain -= sizeof(buf);
248 ptr_offset = sizeof(buf);
249
250 while (remain) {
251 i = read(data_fd, ptr + ptr_offset, remain);
252 if (i == 0) {
253 mitsup95d_cleanup_job(job);
254 return CUPS_BACKEND_CANCEL;
255 }
256 if (i < 0) {
257 mitsup95d_cleanup_job(job);
258 return CUPS_BACKEND_CANCEL;
259 }
260 remain -= i;
261 ptr_offset += i;
262
263 /* Handle the ambiguous 0x5a block */
264 if (buf[1] == 0x5a && remain == 0) {
265 if (tmphdr[2] == 0x74) { /* plane header */
266 ptr = job->plane;
267 remain = 12 - ptr_offset; /* Finish reading */
268 } else if (tmphdr[2] == 0x43) { /* reset memory */
269 ptr = job->mem_clr;
270 job->mem_clr_present = 1;
271 remain = 4 - ptr_offset;
272 }
273 memcpy(ptr, tmphdr, ptr_offset);
274 buf[1] = 0xff;
275 }
276 }
277
278 if (ptr == tmphdr) {
279 if (tmphdr[3] != 46) {
280 ERROR("Unexpected header chunk: %02x %02x %02x %02x\n",
281 tmphdr[0], tmphdr[1], tmphdr[2], tmphdr[3]);
282 mitsup95d_cleanup_job(job);
283 return CUPS_BACKEND_CANCEL;
284 }
285 switch (tmphdr[2]) {
286 case 0x20:
287 ptr = job->hdr1;
288 break;
289 case 0x21:
290 ptr = job->hdr2;
291 break;
292 case 0x22:
293 ptr = job->hdr3;
294 break;
295 default:
296 WARNING("Unexpected header chunk: %02x %02x %02x %02x\n",
297 tmphdr[0], tmphdr[1], tmphdr[2], tmphdr[3]);
298 }
299 memcpy(ptr, tmphdr, sizeof(tmphdr));
300 } else if (ptr == job->plane) {
301 uint16_t rows = job->plane[10] << 8 | job->plane[11];
302 uint16_t cols = job->plane[8] << 8 | job->plane[9];
303
304 remain = rows * cols;
305
306 /* Allocate buffer for the payload */
307 job->datalen = 0;
308 job->databuf = malloc(remain);
309 if (!job->databuf) {
310 ERROR("Memory allocation failure!\n");
311 mitsup95d_cleanup_job(job);
312 return CUPS_BACKEND_RETRY_CURRENT;
313 }
314
315 /* Read it in */
316 while (remain) {
317 i = read(data_fd, job->databuf + job->datalen, remain);
318 if (i == 0) {
319 mitsup95d_cleanup_job(job);
320 return CUPS_BACKEND_CANCEL;
321 }
322 if (i < 0) {
323 mitsup95d_cleanup_job(job);
324 return CUPS_BACKEND_CANCEL;
325 }
326 remain -= i;
327 job->datalen += i;
328 }
329 } else if (ptr == job->ftr) {
330
331 /* XXX Update unknown header field to match sniffs */
332 if (ctx->type == P_MITSU_P95D) {
333 if (job->hdr1[18] == 0x00)
334 job->hdr1[18] = 0x01;
335 }
336
337 /* Update printjob header to reflect number of requested copies */
338 if (job->hdr2[13] != 0xff)
339 job->hdr2[13] = copies;
340
341 *vjob = job;
342 return CUPS_BACKEND_OK;
343 }
344
345 goto top;
346 }
347
mitsup95d_main_loop(void * vctx,const void * vjob)348 static int mitsup95d_main_loop(void *vctx, const void *vjob) {
349 struct mitsup95d_ctx *ctx = vctx;
350 uint8_t queryresp[QUERYRESP_SIZE_MAX];
351 int ret;
352
353 const struct mitsup95d_printjob *job = vjob;
354
355 if (!ctx)
356 return CUPS_BACKEND_FAILED;
357 if (!job)
358 return CUPS_BACKEND_FAILED;
359
360
361 INFO("Waiting for printer idle\n");
362
363 /* Query Status to make sure printer is idle */
364 do {
365 ret = mitsup95d_get_status(ctx, queryresp);
366 if (ret)
367 return ret;
368
369 if (ctx->type == P_MITSU_P95D) {
370 if (queryresp[6] & 0x40) {
371 INFO("Printer Status: %s (%02x)\n", mitsup95d_errors(queryresp[6]), queryresp[6]);
372 return CUPS_BACKEND_STOP;
373 }
374 if (queryresp[5] == 0x00)
375 break;
376 } else {
377 if (queryresp[6] == 0x45) {
378 ERROR("Printer error %02x\n", queryresp[7]);
379 return CUPS_BACKEND_STOP;
380 }
381 if (queryresp[6] == 0x30)
382 break;
383 }
384
385 sleep(1);
386 } while (1);
387
388 INFO("Sending print job\n");
389
390 /* Send over Memory Clear, if present */
391 if (job->mem_clr_present) {
392 if ((ret = send_data(ctx->dev, ctx->endp_down,
393 job->mem_clr, sizeof(job->mem_clr))))
394 return CUPS_BACKEND_FAILED;
395 }
396
397 /* Send Job Start */
398 if ((ret = send_data(ctx->dev, ctx->endp_down,
399 job->hdr, sizeof(job->hdr))))
400 return CUPS_BACKEND_FAILED;
401
402 /* Send over headers */
403 if ((ret = send_data(ctx->dev, ctx->endp_down,
404 job->hdr1, sizeof(job->hdr1))))
405 return CUPS_BACKEND_FAILED;
406 if ((ret = send_data(ctx->dev, ctx->endp_down,
407 job->hdr2, sizeof(job->hdr2))))
408 return CUPS_BACKEND_FAILED;
409 if ((ret = send_data(ctx->dev, ctx->endp_down,
410 job->hdr3, sizeof(job->hdr3))))
411 return CUPS_BACKEND_FAILED;
412 if ((ret = send_data(ctx->dev, ctx->endp_down,
413 job->hdr4, job->hdr4_len)))
414 return CUPS_BACKEND_FAILED;
415
416 /* Send plane header and image data */
417 if ((ret = send_data(ctx->dev, ctx->endp_down,
418 job->plane, sizeof(job->plane))))
419 return CUPS_BACKEND_FAILED;
420 if ((ret = send_data(ctx->dev, ctx->endp_down,
421 job->databuf, job->datalen)))
422 return CUPS_BACKEND_FAILED;
423
424 /* Query Status to sanity-check job */
425 ret = mitsup95d_get_status(ctx, queryresp);
426 if (ret)
427 return ret;
428
429 if (ctx->type == P_MITSU_P95D) {
430 if (queryresp[6] & 0x40) {
431 INFO("Printer Status: %s (%02x)\n", mitsup95d_errors(queryresp[6]), queryresp[6]);
432 return CUPS_BACKEND_STOP;
433 }
434 if (queryresp[5] != 0x00) {
435 ERROR("Printer not ready (%02x)!\n", queryresp[5]);
436 return CUPS_BACKEND_CANCEL;
437 }
438 } else {
439 if (queryresp[6] == 0x45) {
440 INFO("Printer Status: %s (%02x)\n", mitsup93d_errors(queryresp[7]), queryresp[7]);
441 return CUPS_BACKEND_STOP;
442 }
443 if (queryresp[6] != 0x30) {
444 ERROR("Printer not ready (%02x)!\n", queryresp[6]);
445 return CUPS_BACKEND_CANCEL;
446 }
447 }
448
449 /* Send over Footer */
450 if ((ret = send_data(ctx->dev, ctx->endp_down,
451 job->ftr, sizeof(job->ftr))))
452 return CUPS_BACKEND_FAILED;
453
454 INFO("Waiting for completion\n");
455
456 /* Query status until we're done.. */
457 do {
458 sleep(1);
459
460 /* Query Status */
461 ret = mitsup95d_get_status(ctx, queryresp);
462 if (ret)
463 return ret;
464
465 if (ctx->type == P_MITSU_P95D) {
466 if (queryresp[6] & 0x40) {
467 INFO("Printer Status: %s (%02x)\n", mitsup95d_errors(queryresp[6]), queryresp[6]);
468 return CUPS_BACKEND_STOP;
469 }
470 if (queryresp[5] == 0x00)
471 break;
472
473 if (queryresp[7] > 0) {
474 if (fast_return) {
475 INFO("Fast return mode enabled.\n");
476 break;
477 }
478 }
479 } else {
480 if (queryresp[6] == 0x45) {
481 INFO("Printer Status: %s (%02x)\n", mitsup93d_errors(queryresp[7]), queryresp[7]);
482 return CUPS_BACKEND_STOP;
483 }
484 if (queryresp[6] == 0x30)
485 break;
486 if (queryresp[6] == 0x43 && queryresp[7] > 0) {
487 if (fast_return) {
488 INFO("Fast return mode enabled.\n");
489 break;
490 }
491 }
492 }
493 } while(1);
494
495 INFO("Print complete\n");
496 return CUPS_BACKEND_OK;
497 }
498
mitsup95d_dump_status(struct mitsup95d_ctx * ctx)499 static int mitsup95d_dump_status(struct mitsup95d_ctx *ctx)
500 {
501 uint8_t queryresp[QUERYRESP_SIZE_MAX];
502 int ret;
503
504 ret = mitsup95d_get_status(ctx, queryresp);
505 if (ret)
506 return ret;
507
508 if (ctx->type == P_MITSU_P95D) {
509 if (queryresp[6] & 0x40) {
510 INFO("Printer Status: %s (%02x)\n", mitsup95d_errors(queryresp[6]), queryresp[6]);
511 } else if (queryresp[5] == 0x00) {
512 INFO("Printer Status: Idle\n");
513 } else if (queryresp[5] == 0x02 && queryresp[7] > 0) {
514 INFO("Printer Status: Printing (%d) copies remaining\n", queryresp[7]);
515 }
516 } else {
517 if (queryresp[6] == 0x45) {
518 INFO("Printer Status: %s (%02x)\n", mitsup93d_errors(queryresp[7]), queryresp[7]);
519 } else if (queryresp[6] == 0x30) {
520 INFO("Printer Status: Idle\n");
521 } else if (queryresp[6] == 0x43 && queryresp[7] > 0) {
522 INFO("Printer Status: Printing (%d) copies remaining\n", queryresp[7]);
523 }
524 }
525
526 return CUPS_BACKEND_OK;
527 }
528
mitsup95d_cmdline(void)529 static void mitsup95d_cmdline(void)
530 {
531 DEBUG("\t\t[ -s ] # Query status\n");
532 }
533
mitsup95d_cmdline_arg(void * vctx,int argc,char ** argv)534 static int mitsup95d_cmdline_arg(void *vctx, int argc, char **argv)
535 {
536 struct mitsup95d_ctx *ctx = vctx;
537 int i, j = 0;
538
539 if (!ctx)
540 return -1;
541
542 while ((i = getopt(argc, argv, GETOPT_LIST_GLOBAL "s")) >= 0) {
543 switch(i) {
544 GETOPT_PROCESS_GLOBAL
545 case 's':
546 j = mitsup95d_dump_status(ctx);
547 break;
548 default:
549 break; /* Ignore completely */
550 }
551
552 if (j) return j;
553 }
554
555 return 0;
556 }
557
mitsup95d_query_markers(void * vctx,struct marker ** markers,int * count)558 static int mitsup95d_query_markers(void *vctx, struct marker **markers, int *count)
559 {
560 struct mitsup95d_ctx *ctx = vctx;
561 uint8_t queryresp[QUERYRESP_SIZE_MAX];
562
563 if (mitsup95d_get_status(ctx, queryresp))
564 return CUPS_BACKEND_FAILED;
565
566 ctx->marker.levelnow = -3;
567
568 if (ctx->type == P_MITSU_P95D) {
569 if (queryresp[6] & 0x40) {
570 ctx->marker.levelnow = 0;
571 }
572 } else {
573 if (queryresp[6] == 0x45) {
574 ctx->marker.levelnow = 0;
575 }
576 }
577
578 /* Lot state */
579 if (ctx->marker.levelnow)
580 STATE("-media-empty\n");
581 else
582 STATE("+media-empty\n");
583
584
585 *markers = &ctx->marker;
586 *count = 1;
587
588 return CUPS_BACKEND_OK;
589 }
590
591 static const char *mitsup95d_prefixes[] = {
592 "mitsup9x", // Family driver name
593 "mitsubishi-p95d", "mitsubishi-p93d",
594 // backwards compatibility
595 "mitsup95d", "mitsup93d",
596 NULL
597 };
598
599 /* Exported */
600 struct dyesub_backend mitsup95d_backend = {
601 .name = "Mitsubishi P93D/P95D",
602 .version = "0.12",
603 .uri_prefixes = mitsup95d_prefixes,
604 .cmdline_arg = mitsup95d_cmdline_arg,
605 .cmdline_usage = mitsup95d_cmdline,
606 .init = mitsup95d_init,
607 .attach = mitsup95d_attach,
608 .cleanup_job = mitsup95d_cleanup_job,
609 .read_parse = mitsup95d_read_parse,
610 .main_loop = mitsup95d_main_loop,
611 .query_markers = mitsup95d_query_markers,
612 .devices = {
613 { USB_VID_MITSU, USB_PID_MITSU_P93D, P_MITSU_P93D, NULL, "mitsubishi-p93d"},
614 { USB_VID_MITSU, USB_PID_MITSU_P95D, P_MITSU_P95D, NULL, "mitsubishi-p95d"},
615 { 0, 0, 0, NULL, NULL}
616 }
617 };
618
619 /*****************************************************
620
621 Mitsubishi P93D/P95D Spool Format
622
623 ...All fields are BIG ENDIAN.
624
625 MEMORY_CLEAR (optional)
626
627 1b 5a 43 00
628
629 JOB_HDR
630
631 1b 51
632
633 PRINT_SETUP
634
635 1b 57 20 2e 00 0a 00 ZZ 00 00 00 00 00 00 CC CC
636 RR RR XX 00 00 00 00 00 00 00 00 00 00 00 00 00
637 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
638 00 00
639
640 XX == 01 seen in sniffs, 00 seen in dumps. Unknown purpose.
641 ZZ == 00 on P93D, 02 on P95D
642
643 CC CC = columns, RR RR = rows (print dimensions)
644
645 PRINT_OPTIONS
646
647 P95:
648
649 1b 57 21 2e 00 4a aa 00 20 TT 00 00 64 NN 00 MM
650 [[ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
651 00 ]] 00 00 00 02 00 00 00 00 00 00 00 00 00 00
652 00 XY
653
654 P93:
655
656 1b 57 21 2e 00 4a aa 00 00 TT 00 00 00 NN 00 MM
657 [[ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
658 00 ]] 00 00 00 02 00 00 00 00 00 00 00 00 00 00
659 00 XY
660
661 NN = copies
662 1..200
663 0xff (continuous print)
664 MM = comment type
665 00 = None
666 01 = Printer Setting
667 02 = Date
668 03 = DateTime
669 [[ .. ]] = actual comment (18 bytes), see below.
670
671 TT = media type
672
673 P95D:
674
675 00 = Standard
676 01 = High Density
677 02 = High Glossy
678 03 = High Glossy (K95HG)
679
680 P93D:
681
682 00 = High Density
683 01 = High Glossy
684 02 = Standard
685
686 X = media cut length (P95D ONLY. P93 is 0)
687 4..8 (mm)
688 Y = flags
689 0x04 = Paper save
690 0x03 = Buzzer (3 = high, 2 = low, 0 = off)
691
692 GAMMA (P95)
693
694 1b 57 22 2e 00 15 TT 00 00 00 00 00 LL BB CC 00
695 [[ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
696 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
697 00 ]]
698
699 LL = Gamma table
700 00 = Printer Setting
701 01..05 Gamma table 1..5
702 10 = Use LUT
703 BB = Brightness (signed 8-bit)
704 CC = Contrast (signed 8-bit)
705 TT = Table present
706 00 = No
707 01 = Yes
708 [[ .. ]] = Gamma table, loaded from LUT on disk. (skip first 16 bytes)
709
710 GAMMA (P93)
711
712 1b 57 22 2e 00 d5 00 00 00 00 00 00 SS 00 LL 00
713 BB 00 CC 00 00 00 00 00 00 00 00 00 00 00 00 00
714 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
715 00 00
716
717 SS = Sharpening (0 = low, 1 = normal, 2 = high)
718 LL = Gamma table
719 00..04 Gamma table 1..5
720 BB = Brightness (signed 8-bit)
721 CC = Contrast (signed 8-bit)
722
723 USER_COMMENT (P95)
724
725 1b 58 [[ 20 20 20 20 20 20 20 20 20 20 20 20 20
726 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
727 20 20 20 ]]
728
729 [[ .. ]] = Actual comment. 34 bytes payload, 0x20 -> 0x7e
730 (Null terminated?)
731
732 USER_COMMENT (P93)
733
734 1b 58 [[ 20 20 20 20 20 20 20 20 20 20 20 20 20
735 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
736 20 20 20 20 20 20 20 20 20 20 ]]
737
738 [[ .. ]] = Actual comment. 40 bytes payload, 0x20 -> 0x7e
739 (Null terminated?)
740
741 IMAGE_DATA
742
743 1b 5a 74 00 00 00 YY YY CC CC RR RR
744 [[ .. data ... ]]
745
746 CC CC = columns
747 RR CC = rows
748 YY YY = row offset
749
750 Followed by C*R bytes of monochrome data, 0xff = white, 0x00 = black
751
752 PRINT_START
753
754 1b 50
755
756 *********************************
757
758 P95D Printer Comms:
759
760 STATUS query
761
762 -> 1b 72 00 00
763 <- e4 72 00 00 04 XX ZZ YY 00
764
765 YY == remaining copies
766 XX == Status?
767 00 == Idle
768 02 == Printing
769 ZZ == Error!
770 00 == None
771 43 == Door open
772 44 == No Paper
773 4? == "Button"
774 4? == "Gear Lock"
775 4? == Head Up
776 ^
777 \--- 0x40 appears to be a flag that indicates error.
778
779 P93D Printer Comms:
780
781 STATUS query
782
783 -> 1b 72 0? 00
784 <- e4 72 0? 00 03 XX YY ZZ
785
786 ? could be 0x00 or 0x03. Seen both.
787
788 Seen: 30 30 30
789 30 43 01 <- 1 copies remaining
790 30 43 00 <- 0 copies remaining
791 ^^
792 \-- 30 == idle, 43 == printing
793
794 30 45 6f <- door open
795 30 45 50 <- no paper
796
797 45 == error?
798 ****************************
799
800 UNKNOWNS:
801
802 * How multiple images are stacked for printing on a single page
803 (col offset too? write four, then tell PRINT?)
804 * How to adjust P95D printer sharpness?
805 * Serial number query (iSerial appears bogus)
806 * What "custom gamma" table does to spool file?
807
808 */
809