1
2 /*
3 * Argyll Color Correction System
4 *
5 * Xrite DTP22 related functions
6 *
7 * Author: Graeme W. Gill
8 * Date: 17/11/2006
9 *
10 * Copyright 1996 - 2013, Graeme W. Gill
11 * All rights reserved.
12 *
13 * This material is licenced under the GNU GENERAL PUBLIC LICENSE Version 2 or later :-
14 * see the License2.txt file for licencing details.
15 */
16
17 /*
18 If you make use of the instrument driver code here, please note
19 that it is the author(s) of the code who take responsibility
20 for its operation. Any problems or queries regarding driving
21 instruments with the Argyll drivers, should be directed to
22 the Argyll's author(s), and not to any other party.
23
24 If there is some instrument feature or function that you
25 would like supported here, it is recommended that you
26 contact Argyll's author(s) first, rather than attempt to
27 modify the software yourself, if you don't have firm knowledge
28 of the instrument communicate protocols. There is a chance
29 that an instrument could be damaged by an incautious command
30 sequence, and the instrument companies generally cannot and
31 will not support developers that they have not qualified
32 and agreed to support.
33 */
34
35 /*
36 Would like to add a thread to return status of
37 switch, so that we can fully run this in progromatic trigger mode.
38 */
39
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <ctype.h>
43 #include <string.h>
44 #include <time.h>
45 #include <stdarg.h>
46 #ifndef SALONEINSTLIB
47 #include "copyright.h"
48 #include "aconfig.h"
49 #include "numlib.h"
50 #else /* !SALONEINSTLIB */
51 #include "sa_config.h"
52 #include "numsup.h"
53 #endif /* !SALONEINSTLIB */
54 #include "xspect.h"
55 #include "insttypes.h"
56 #include "conv.h"
57 #include "icoms.h"
58 #include "dtp22.h"
59 #include "xrga.h"
60
61 /* Default flow control (Instrument doesn't support HW flow control) */
62 #define DEFFC fc_XonXOff
63
64 static inst_code dtp22_interp_code(inst *pp, int ec);
65 static int comp_password(char *out, char *in, unsigned char key[4]);
66 static inst_code activate_mode(dtp22 *p);
67 static inst_code dtp22_get_set_opt(inst *pp, inst_opt_type m, ...);
68
69 #define MAX_MES_SIZE 500 /* Maximum normal message reply size */
70 #define MAX_RD_SIZE 5000 /* Maximum reading messagle reply size */
71
72 /* Known DTP22 challenge/response keys for each OEM */
73 /* (This is a 24 bit key - only the xor of the middle 2 bytes is significant) */
74 /* The keys seem to be base 6/36, using nibbles with 2+2 bits: 3 5 6 9 A C */
75 /* The last digit corresponds to the OEM serial number (ie. 6C + 9base6 = A6) */
76 /* Possibly each digit is offset by the oemsn if counted in the right sequence ? - */
77 /* ie. base 40 sequence or so ? Need more examples of keys to tell. */
78 struct {
79 int oemsn;
80 unsigned char key[4];
81 } keys[] = {
82 { 0, { 0x39, 0xa6, 0x55, 0x6c }}, /* Standard DTP22 */
83 { 9, { 0x5a, 0x66, 0xcc, 0xa6 }}, /* ColorMark calibrator - MacDermid GRAPHICARTS ColorSpan */
84 { -1, } /* End marker */
85 };
86
87 /* Extract an error code from a reply string */
88 /* Return -1 if no error code can be found */
89 static int
extract_ec(char * s)90 extract_ec(char *s) {
91 char *p;
92 char tt[3];
93 int rv;
94 p = s + strlen(s);
95 /* Find the trailing '>' */
96 for (p--; p >= s;p--) {
97 if (*p == '>')
98 break;
99 }
100 if ( (p-3) < s
101 || p[0] != '>'
102 || p[-3] != '<')
103 return -1;
104 tt[0] = p[-2];
105 tt[1] = p[-1];
106 tt[2] = '\000';
107 if (sscanf(tt,"%x",&rv) != 1)
108 return -1;
109 /* For some reason the top bit sometimes get set ? */
110 rv &= 0x7f;
111 return rv;
112 }
113
114 /* Interpret an icoms error into a DTP22 error */
icoms2dtp22_err(int se)115 static int icoms2dtp22_err(int se) {
116 if (se != ICOM_OK) {
117 if (se & ICOM_TO)
118 return DTP22_TIMEOUT;
119 return DTP22_COMS_FAIL;
120 }
121 return DTP22_OK;
122 }
123
124 /* Do a full featured command/response echange with the dtp22 */
125 /* Return the dtp error code. End on the specified number */
126 /* of specified characters, or expiry if the specified timeout */
127 /* Assume standard error code if tc = '>' and ntc = 1 */
128 /* Return a DTP22 error code */
129 static int
dtp22_fcommand(struct _dtp22 * p,char * in,char * out,int bsize,char * tc,int ntc,double to)130 dtp22_fcommand(
131 struct _dtp22 *p,
132 char *in, /* In string */
133 char *out, /* Out string buffer */
134 int bsize, /* Out buffer size */
135 char *tc, /* Terminating characters */
136 int ntc, /* Number of terminating characters */
137 double to) { /* Timout in seconds */
138 int se, rv = DTP22_OK;
139
140 if ((se = p->icom->write_read(p->icom, in, 0, out, bsize, NULL, tc, ntc, to)) != 0) {
141 a1logd(p->log, 1, "dtp22_fcommand: serial i/o failure on write_read '%s'\n",icoms_fix(in));
142 return icoms2dtp22_err(se);
143 }
144 if (tc[0] == '>' && ntc == 1) {
145 rv = extract_ec(out);
146 #ifdef NEVER /* Simulate an error ?? */
147 if (strcmp(in, "0PR\r") == 0)
148 rv = 0x1b;
149 #endif /* NEVER */
150 if (rv > 0) {
151 rv &= inst_imask;
152 if (rv != DTP22_OK) { /* Clear the error */
153 char buf[MAX_MES_SIZE];
154 p->icom->write_read(p->icom, "CE\r", 0, buf, MAX_MES_SIZE, NULL, ">", 1, 0.5);
155 }
156 }
157 }
158 a1logd(p->log, 4, "dtp22_fcommand: command '%s' returned '%s', value 0x%x\n",
159 icoms_fix(in), icoms_fix(out),rv);
160 return rv;
161 }
162
163 /* Do a standard command/response echange with the dtp22 */
164 /* Return the dtp error code */
165 static inst_code
dtp22_command(dtp22 * p,char * in,char * out,int bsize,double to)166 dtp22_command(dtp22 *p, char *in, char *out, int bsize, double to) {
167 int rv = dtp22_fcommand(p, in, out, bsize, ">", 1, to);
168 return dtp22_interp_code((inst *)p, rv);
169 }
170
171 /* Establish communications with a DTP22 */
172 /* If it's a serial port, use the baud rate given, and timeout in to secs */
173 /* Return DTP22_COMS_FAIL on failure to establish communications */
174 static inst_code
dtp22_init_coms(inst * pp,baud_rate br,flow_control fc,double tout)175 dtp22_init_coms(inst *pp, baud_rate br, flow_control fc, double tout) {
176 dtp22 *p = (dtp22 *) pp;
177 char buf[MAX_MES_SIZE];
178 baud_rate brt[5] = { baud_9600, baud_19200, baud_4800, baud_2400, baud_1200 };
179 char *brc[5] = { "30BR\r", "60BR\r", "18BR\r", "0CBR\r", "06BR\r" };
180 char *fcc;
181 unsigned int etime;
182 int ci, bi, i, se;
183 inst_code ev = inst_ok;
184
185 a1logd(p->log, 2, "dtp22_init_coms: About to init Serial I/O\n");
186
187 /* Deal with flow control setting */
188 if (fc == fc_nc)
189 fc = DEFFC;
190 if (fc == fc_XonXOff) {
191 fcc = "0304CF\r";
192 } else if (fc == fc_Hardware) {
193 fcc = "0104CF\r";
194 } else {
195 fc = fc_None;
196 fcc = "0004CF\r";
197 }
198
199 /* Figure DTP22 baud rate being asked for */
200 for (bi = 0; bi < 5; bi++) {
201 if (brt[bi] == br)
202 break;
203 }
204 if (bi >= 5)
205 bi = 0;
206
207 /* Figure current icoms baud rate */
208 for (ci = 0; ci < 5; ci++) {
209 if (brt[ci] == p->icom->br)
210 break;
211 }
212 if (ci >= 5)
213 ci = bi;
214
215 /* The tick to give up on */
216 etime = msec_time() + (long)(1000.0 * tout + 0.5);
217
218
219 /* Until we time out, find the correct baud rate */
220 for (i = ci; msec_time() < etime;) {
221
222 a1logd(p->log, 4, "dtp22_init_coms: Trying %s baud, %d msec to go\n",
223 baud_rate_to_str(brt[i]), etime- msec_time());
224
225 if ((se = p->icom->set_ser_port(p->icom, fc_None, brt[i], parity_none,
226 stop_1, length_8)) != ICOM_OK) {
227 a1logd(p->log, 1, "dtp22_init_coms: set_ser_port failed ICOM err 0x%x\n",se);
228 return dtp22_interp_code((inst *)p, icoms2dtp22_err(se));
229 }
230 if (((ev = dtp22_command(p, "\r", buf, MAX_MES_SIZE, 0.5)) & inst_mask)
231 != inst_coms_fail)
232 goto got_coms; /* We've got coms or user abort */
233
234 /* Check for user abort */
235 if (p->uicallback != NULL) {
236 inst_code ev;
237 if ((ev = p->uicallback(p->uic_cntx, inst_negcoms)) == inst_user_abort) {
238 a1logd(p->log, 1, "dtp22_init_coms: user aborted\n");
239 return ev;
240 }
241 }
242 if (++i >= 5)
243 i = 0;
244 }
245 /* We haven't established comms */
246 return inst_coms_fail;
247
248 got_coms:;
249
250 /* Set the handshaking */
251 if ((ev = dtp22_command(p, fcc, buf, MAX_MES_SIZE, 0.2)) != inst_ok)
252 return ev;
253
254 /* Change the baud rate to the rate we've been told */
255 if ((se = p->icom->write_read(p->icom, brc[bi], 0, buf, MAX_MES_SIZE, NULL, ">", 1, .2)) != 0) {
256 if (extract_ec(buf) != DTP22_OK)
257 return inst_coms_fail;
258 }
259
260 /* Configure our baud rate and handshaking as well */
261 if ((se = p->icom->set_ser_port(p->icom, fc, brt[bi], parity_none, stop_1, length_8)) != ICOM_OK) {
262 a1logd(p->log, 1, "dtp22_init_coms: set_ser_port failed ICOM err 0x%x\n",se);
263 return dtp22_interp_code((inst *)p, icoms2dtp22_err(se));
264 }
265
266 /* Loose a character (not sure why) */
267 p->icom->write_read(p->icom, "\r", 0, buf, MAX_MES_SIZE, NULL, ">", 1, 0.1);
268
269 /* Check instrument is responding, and reset it again. */
270 if ((ev = dtp22_command(p, "\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok
271 || (ev = dtp22_command(p, "0PR\r", buf, MAX_MES_SIZE, 2.0)) != inst_ok) {
272
273 a1logd(p->log, 1, "dtp22_init_coms: failed with ICOM 0x%x\n",ev);
274
275 p->icom->del(p->icom); /* Since caller may not clean up */
276 p->icom = NULL;
277 return inst_coms_fail;
278 }
279
280 a1logd(p->log, 2, "dtp22_init_coms: init coms has suceeded\n");
281
282 p->gotcoms = 1;
283 return inst_ok;
284 }
285
286 /* Initialise the DTP22 */
287 /* return non-zero on an error, with dtp error code */
288 static inst_code
dtp22_init_inst(inst * pp)289 dtp22_init_inst(inst *pp) {
290 dtp22 *p = (dtp22 *)pp;
291 char buf[MAX_MES_SIZE], *bp;
292 inst_code ev = inst_ok;
293 char *envv;
294 int i;
295
296 a1logd(p->log, 2, "dtp22_init_inst: called\n");
297
298 if (p->gotcoms == 0)
299 return inst_internal_error; /* Must establish coms before calling init */
300
301 p->native_calstd = xcalstd_xrdi;
302 p->target_calstd = xcalstd_native; /* Default to native calibration standard*/
303
304 /* Honour Environment override */
305 if ((envv = getenv("ARGYLL_XCALSTD")) != NULL) {
306 if (strcmp(envv, "XRGA") == 0)
307 p->target_calstd = xcalstd_xrga;
308 else if (strcmp(envv, "XRDI") == 0)
309 p->target_calstd = xcalstd_xrdi;
310 else if (strcmp(envv, "GMDI") == 0)
311 p->target_calstd = xcalstd_gmdi;
312 }
313
314 /* Warm reset it */
315 if ((ev = dtp22_command(p, "0PR\r", buf, MAX_MES_SIZE, 2.0)) != inst_ok)
316 return ev;
317
318 /* Get the model and version number */
319 if ((ev = dtp22_command(p, "SV\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
320 return ev;
321
322 /* Check that it is a DTP22 */
323 if ( strlen(buf) < 12
324 || (strncmp(buf,"X-Rite DTP22",12) != 0))
325 return inst_unknown_model;
326
327 /* Factory reset */
328 // if ((ev = dtp22_command(p, "5CRI\r", buf, MAX_MES_SIZE, 10.2)) != inst_ok)
329 // return ev;
330
331 /* Turn echoing of characters off */
332 if ((ev = dtp22_command(p, "0EC\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
333 return ev;
334
335 /* Set decimal point on */
336 if ((ev = dtp22_command(p, "0106CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
337 return ev;
338
339 /* Set color data separator to TAB */
340 if ((ev = dtp22_command(p, "0207CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
341 return ev;
342
343 /* Set delimeter to CR */
344 if ((ev = dtp22_command(p, "0008CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
345 return ev;
346
347 /* Set extra digit resolution (X10) */
348 if ((ev = dtp22_command(p, "010ACF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
349 return ev;
350
351 /* - - - - - - - - - - - - - - - - - - - - - - - - */
352 /* Get some information about the instrument */
353 if ((ev = dtp22_command(p, "GI\r", buf, MAX_MES_SIZE, 0.5)) != inst_ok) {
354 a1logd(p->log, 1, "dtp22: GI command failed with ICOM err 0x%x\n",ev);
355 return ev;
356 }
357
358 /* Extract some of these */
359 if ((bp = strstr(buf, "Serial Number:")) != NULL) {
360 bp += strlen("Serial Number:");
361 p->serno = atoi(bp);
362 } else {
363 p->serno = -1;
364 }
365 if ((bp = strstr(buf, "OEM Serial #:")) != NULL) {
366 bp += strlen("OEM Serial #:");
367 p->oemsn = atoi(bp);
368 } else {
369 p->oemsn = -1;
370 }
371 if ((bp = strstr(buf, "Cal Plaque Serial #:")) != NULL) {
372 bp += strlen("Cal Plaque Serial #:");
373 p->plaqueno = atoi(bp);
374 } else {
375 p->plaqueno = -1;
376 }
377 if (p->log->verb) {
378 int i, j;
379 for (j = i = 0; ;i++) {
380 if (buf[i] == '<' || buf[i] == '\000')
381 break;
382 if (buf[i] == '\r') {
383 buf[i] = '\000';
384 a1logv(p->log, 1, " %s\n",&buf[j]);
385 if (buf[i+1] == '\n')
386 i++;
387 j = i+1;
388 }
389 }
390 }
391
392 /* - - - - - - - - - - - - - - - - - - - - - - - - */
393 /* Setup for the default type of measurements we want to do */
394
395 /* Disable key codes */
396 if ((ev = dtp22_command(p, "0OK\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
397 return ev;
398
399 /* Disable the read microswitch by default */
400 if ((ev = dtp22_command(p, "0PB\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
401 return ev;
402 p->trig = inst_opt_trig_user;
403
404 /* Set format to colorimetric */
405 if ((ev = dtp22_command(p, "0120CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
406 return ev;
407 p->mode &= ~inst_mode_spectral;
408
409 /* Set colorimetric to XYZ */
410 if ((ev = dtp22_command(p, "0221CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
411 return ev;
412
413 /* Disable density */
414 if ((ev = dtp22_command(p, "0022CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
415 return ev;
416
417 /* Enable spectral */
418 if ((ev = dtp22_command(p, "0126CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
419 return ev;
420
421 /* Set Illuminant to D50_2 */
422 if ((ev = dtp22_command(p, "0427CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
423 return ev;
424
425 /* Read the current calibration values */
426 // if ((ev = dtp22_command(p, "0LC\r", buf, MAX_MES_SIZE, 10.2)) != inst_ok)
427 // return ev;
428
429 /* See that we have the correct challenge/response key */
430 for (i = 0; keys[i].oemsn >= 0; i++) {
431 if (keys[i].oemsn == p->oemsn) {
432 p->key[0] = keys[i].key[0];
433 p->key[1] = keys[i].key[1];
434 p->key[2] = keys[i].key[2];
435 p->key[3] = keys[i].key[3];
436 break;
437 }
438 }
439 if (keys[i].oemsn < 0)
440 return inst_unknown_model | DTP22_UNKN_OEM;
441
442 p->inited = 1;
443 a1logd(p->log, 2, "dtp22_init_inst: instrument inited OK\n");
444
445 return inst_ok;
446 }
447
448 /* Read a single sample */
449 /* Return the instrument error code */
450 static inst_code
dtp22_read_sample(inst * pp,char * name,ipatch * val,instClamping clamp)451 dtp22_read_sample(
452 inst *pp,
453 char *name, /* Strip name (7 chars) */
454 ipatch *val, /* Pointer to instrument patch value */
455 instClamping clamp) { /* NZ if clamp XYZ/Lab to be +ve */
456 dtp22 *p = (dtp22 *)pp;
457 char *tp;
458 char buf[MAX_RD_SIZE];
459 char buf2[50];
460 int se;
461 inst_code ev = inst_ok;
462 int switch_trig = 0;
463 int user_trig = 0;
464
465 if (!p->gotcoms)
466 return inst_no_coms;
467 if (!p->inited)
468 return inst_no_init;
469
470 if ((ev = activate_mode(p)) != inst_ok)
471 return ev;
472
473 /* Signal a calibration is needed */
474 if (p->need_cal && p->noutocalib == 0) {
475 return inst_needs_cal; /* Get user to calibrate */
476 }
477
478 /* Request challenge, so that we can return the response */
479 if ((ev = dtp22_command(p, "GP\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
480 return ev;
481
482 if (comp_password(buf2, buf, p->key))
483 return inst_internal_error | DTP22_INTERNAL_ERROR;
484
485 /* Validate the password */
486 strcat(buf2, "VD\r");
487 if ((ev = dtp22_command(p, buf2, buf, MAX_MES_SIZE, 0.2)) != inst_ok)
488 return ev;
489
490 if (strncmp(buf,"PASS", 4) != 0)
491 return inst_unknown_model | DTP22_BAD_PASSWORD;
492
493 if (p->trig == inst_opt_trig_user_switch) {
494
495 /* Enable the read microswitch */
496 if ((ev = dtp22_command(p, "3PB\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
497 return ev;
498
499 /* Wait for the microswitch to be triggered, or the user to trigger */
500 for (;;) {
501 if ((se = p->icom->read(p->icom, buf, MAX_MES_SIZE, NULL, ">", 1, 1.0)) != 0) {
502 if ((se & ICOM_TO) == 0) { /* Some sort of read error */
503 /* Disable the read microswitch */
504 dtp22_command(p, "2PB\r", buf, MAX_MES_SIZE, 0.2);
505 return dtp22_interp_code((inst *)p, icoms2dtp22_err(se));
506 }
507 /* Timed out */
508 if (p->uicallback != NULL) { /* Check for user trigger */
509 if ((ev = p->uicallback(p->uic_cntx, inst_armed)) != inst_ok) {
510 if (ev == inst_user_abort) {
511 /* Disable the read microswitch */
512 dtp22_command(p, "2PB\r", buf, MAX_MES_SIZE, 0.2);
513 return ev; /* User abort */
514 }
515 if (ev == inst_user_trig)
516 break; /* Trigger */
517 }
518 }
519 } else { /* Inst error or switch activated */
520 if (strlen(buf) >= 4
521 && buf[0] == '<' && isdigit(buf[1]) && isdigit(buf[2]) && buf[3] == '>') {
522 if ((ev = dtp22_interp_code((inst *)p, extract_ec(buf))) != inst_ok) {
523 dtp22_command(p, "CE\r", buf, MAX_MES_SIZE, 0.5);
524 dtp22_command(p, "2PB\r", buf, MAX_MES_SIZE, 0.5);
525 return ev;
526 }
527 switch_trig = 1;
528 break; /* Measure triggered via inst switch */
529 }
530 }
531 }
532 /* Disable the read microswitch */
533 if ((ev = dtp22_command(p, "2PB\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
534 return ev;
535 /* Notify of trigger */
536 if (p->uicallback)
537 p->uicallback(p->uic_cntx, inst_triggered);
538
539 } else if (p->trig == inst_opt_trig_user) {
540
541 if (p->uicallback == NULL) {
542 a1logd(p->log, 1, "dtp22: inst_opt_trig_user but no uicallback function set!\n");
543 return inst_unsupported;
544 }
545 for (;;) {
546 if ((ev = p->uicallback(p->uic_cntx, inst_armed)) != inst_ok) {
547 if (ev == inst_user_abort)
548 return ev; /* Abort */
549 if (ev == inst_user_trig)
550 break; /* Trigger */
551 }
552 msec_sleep(200);
553 }
554 /* Notify of trigger */
555 if (p->uicallback)
556 p->uicallback(p->uic_cntx, inst_triggered);
557
558 /* Progromatic Trigger */
559 } else {
560 /* Check for abort */
561 if (p->uicallback != NULL
562 && (ev = p->uicallback(p->uic_cntx, inst_armed)) == inst_user_abort)
563 return ev; /* Abort */
564 }
565
566 /* Trigger a read if the switch has not been used */
567 if (switch_trig == 0) {
568 if ((ev = dtp22_command(p, "RM\r", buf, MAX_RD_SIZE, 20.0)) != inst_ok) {
569 return ev; /* Misread */
570 }
571 }
572
573 /* Gather the results in D50_2 XYZ % reflectance */
574 if ((ev = dtp22_command(p, "0SR\r", buf, MAX_RD_SIZE, 5.0)) != inst_ok)
575 return ev; /* misread */
576
577 /* Parse the buffer */
578 /* Replace '\r' with '\000' */
579 for (tp = buf; *tp != '\000'; tp++) {
580 if (*tp == '\r')
581 *tp = '\000';
582 }
583
584 if (sscanf(buf, " X %lf Y %lf Z %lf ", &val->XYZ[0], &val->XYZ[1], &val->XYZ[2]) != 3) {
585 return inst_protocol_error;
586 }
587
588 /* This may not change anything since instrument may clamp */
589 if (clamp)
590 icmClamp3(val->XYZ, val->XYZ);
591 val->loc[0] = '\000';
592 val->mtype = inst_mrt_reflective;
593 val->XYZ_v = 1;
594 val->sp.spec_n = 0;
595 val->duration = 0.0;
596
597 if (p->mode & inst_mode_spectral
598 || XCALSTD_NEEDED(p->target_calstd, p->native_calstd)) {
599 int j;
600 char *fmt;
601
602 /* Reset tp to point to start of spectral */
603 tp = buf + strlen(buf) + 1;
604
605 /* Different dialects spoken by DTP-22 */
606 if (strcmp(tp, "SPECTRAL DATA") == 0 ) {
607 tp += strlen(tp) + 1;
608 fmt = " w %*lf S %lf ";
609 } else {
610 fmt = " S %lf ";
611 }
612
613 /* Read the spectral value */
614 for (j = 0; j < 31; j++) {
615 if (sscanf(tp, fmt, &val->sp.spec[j]) != 1)
616 return inst_protocol_error;
617 tp += strlen(tp) + 1;
618 }
619
620 val->sp.spec_n = 31;
621 val->sp.spec_wl_short = 400.0;
622 val->sp.spec_wl_long = 700.0;
623 val->sp.norm = 100.0;
624 }
625
626 /* Apply any XRGA conversion */
627 ipatch_convert_xrga(val, 1, xcalstd_nonpol, p->target_calstd, p->native_calstd, clamp);
628
629 if (user_trig)
630 return inst_user_trig;
631 return inst_ok;
632 }
633
634 /* Return needed and available inst_cal_type's */
dtp22_get_n_a_cals(inst * pp,inst_cal_type * pn_cals,inst_cal_type * pa_cals)635 static inst_code dtp22_get_n_a_cals(inst *pp, inst_cal_type *pn_cals, inst_cal_type *pa_cals) {
636 dtp22 *p = (dtp22 *)pp;
637 inst_cal_type n_cals = inst_calt_none;
638 inst_cal_type a_cals = inst_calt_none;
639
640 if (p->need_cal && p->noutocalib == 0)
641 n_cals |= inst_calt_ref_white;
642 a_cals |= inst_calt_ref_white;
643
644 if (pn_cals != NULL)
645 *pn_cals = n_cals;
646
647 if (pa_cals != NULL)
648 *pa_cals = a_cals;
649
650 return inst_ok;
651 }
652
653 /* Request an instrument calibration. */
654 /* This is use if the user decides they want to do a calibration, */
655 /* in anticipation of a calibration (needs_calibration()) to avoid */
656 /* requiring one during measurement, or in response to measuring */
657 /* returning inst_needs_cal. Initially us an inst_cal_cond of inst_calc_none, */
658 /* and then be prepared to setup the right conditions, or ask the */
659 /* user to do so, each time the error inst_cal_setup is returned. */
dtp22_calibrate(inst * pp,inst_cal_type * calt,inst_cal_cond * calc,inst_calc_id_type * idtype,char id[CALIDLEN])660 inst_code dtp22_calibrate(
661 inst *pp,
662 inst_cal_type *calt, /* Calibration type to do/remaining */
663 inst_cal_cond *calc, /* Current condition/desired condition */
664 inst_calc_id_type *idtype, /* Condition identifier type */
665 char id[CALIDLEN] /* Condition identifier (ie. white reference ID) */
666 ) {
667 dtp22 *p = (dtp22 *)pp;
668 char buf[MAX_RD_SIZE];
669 int se;
670 inst_code tv, ev = inst_ok;
671 inst_cal_type needed, available;
672 int swen = 0;
673
674 if (!p->gotcoms)
675 return inst_no_coms;
676 if (!p->inited)
677 return inst_no_init;
678
679 *idtype = inst_calc_id_none;
680 id[0] = '\000';
681
682 if ((ev = dtp22_get_n_a_cals((inst *)p, &needed, &available)) != inst_ok)
683 return ev;
684
685 /* Translate inst_calt_all/needed into something specific */
686 if (*calt == inst_calt_all
687 || *calt == inst_calt_needed
688 || *calt == inst_calt_available) {
689 if (*calt == inst_calt_all)
690 *calt = (needed & inst_calt_n_dfrble_mask) | inst_calt_ap_flag;
691 else if (*calt == inst_calt_needed)
692 *calt = needed & inst_calt_n_dfrble_mask;
693 else if (*calt == inst_calt_available)
694 *calt = available & inst_calt_n_dfrble_mask;
695
696 a1logd(p->log,4,"dtp22_calibrate: doing calt 0x%x\n",calt);
697
698 if ((*calt & inst_calt_n_dfrble_mask) == 0) /* Nothing todo */
699 return inst_ok;
700 }
701
702 /* See if it's a calibration we understand */
703 if (*calt & ~available & inst_calt_all_mask) {
704 return inst_unsupported;
705 }
706
707 if (*calt & inst_calt_ref_white) { /* White calibration */
708
709 *idtype = inst_calc_id_ref_sn;
710 sprintf(id, "%d",p->plaqueno);
711 if ((*calc & inst_calc_cond_mask) != inst_calc_man_ref_whitek) {
712 *calc = inst_calc_man_ref_whitek;
713 ev = inst_cal_setup;
714 goto do_exit;
715 }
716
717 /* Calibration only works when triggered by the read switch... */
718 if (!swen) {
719 if ((ev = dtp22_command(p, "3PB\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
720 return ev;
721 swen = 1;
722 }
723
724 if ((ev = activate_mode(p)) != inst_ok)
725 goto do_exit;
726
727 /* Issue white calibration */
728 if ((se = p->icom->write(p->icom, "1CA\r", 0, 0.5)) != ICOM_OK) {
729 ev = dtp22_interp_code((inst *)p, icoms2dtp22_err(se));
730 goto do_exit;
731 }
732
733 /* Wait for the microswitch to be triggered, or a user trigger via uicallback */
734 for (;;) {
735 if ((se = p->icom->read(p->icom, buf, MAX_MES_SIZE, NULL, ">", 1, 1.0)) != 0) {
736 if ((se & ICOM_TO) == 0) { /* Some sort of read error */
737 ev = dtp22_interp_code((inst *)p, icoms2dtp22_err(se));
738 goto do_exit;
739 }
740 /* Timed out - poll user */
741 if (p->uicallback != NULL) { /* Check for user trigger */
742 if ((ev = p->uicallback(p->uic_cntx, inst_armed)) != inst_ok) {
743 if (ev == inst_user_abort)
744 goto do_exit; /* User abort */
745 if (ev == inst_user_trig)
746 break; /* User trigger */
747 }
748 }
749 } else { /* Inst error or switch activated */
750 if (strlen(buf) >= 4
751 && buf[0] == '<' && isdigit(buf[1]) && isdigit(buf[2]) && buf[3] == '>') {
752 if ((ev = dtp22_interp_code((inst *)p, extract_ec(buf))) != inst_ok) {
753 dtp22_command(p, "CE\r", buf, MAX_MES_SIZE, 0.5);
754 if (ev != inst_ok)
755 goto do_exit; /* Error */
756 }
757 break; /* Switch trigger */
758 }
759 }
760 }
761
762 if (p->uicallback) /* Notify of trigger */
763 p->uicallback(p->uic_cntx, inst_triggered);
764
765 p->need_cal = 0;
766 *calt &= ~inst_calt_ref_white;
767
768 }
769 if (*calt & inst_calt_ref_dark) { /* Black calibration */
770
771 if ((*calc & inst_calc_cond_mask) != inst_calc_man_ref_dark) {
772 *calc = inst_calc_man_ref_dark;
773 ev = inst_cal_setup;
774 goto do_exit;
775 }
776
777 /* Check for abort */
778 if (p->uicallback != NULL
779 && (ev = p->uicallback(p->uic_cntx, inst_armed)) == inst_user_abort) {
780 goto do_exit;
781 }
782
783 /* Calibration only works when triggered by the read switch... */
784 if (!swen) {
785 if ((ev = dtp22_command(p, "3PB\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
786 return ev;
787 swen = 1;
788 }
789
790 if ((ev = activate_mode(p)) != inst_ok)
791 goto do_exit;
792
793 /* Do black calibration */
794 if ((ev = dtp22_command(p, "1CB\r", buf, MAX_RD_SIZE, 20)) != inst_ok)
795 goto do_exit;
796
797 /* Make calibration permanent */
798 if ((ev = dtp22_command(p, "MP\r", buf, MAX_RD_SIZE, 10.0)) != inst_ok)
799 goto do_exit;
800
801 *calt &= ~inst_calt_ref_dark;
802 }
803
804 do_exit:
805
806 if (swen) {
807 /* Disable the read microswitch */
808 if ((tv = dtp22_command(p, "3PB\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok && ev == inst_ok)
809 return tv;
810 swen = 0;
811 }
812
813 return ev;
814 }
815
816 /* Error codes interpretation */
817 static char *
dtp22_interp_error(inst * pp,int ec)818 dtp22_interp_error(inst *pp, int ec) {
819 // dtp22 *p = (dtp22 *)pp;
820 ec &= inst_imask;
821 switch (ec) {
822 case DTP22_INTERNAL_ERROR:
823 return "Internal software error";
824 case DTP22_COMS_FAIL:
825 return "Communications failure";
826 case DTP22_UNKNOWN_MODEL:
827 return "Not a DTP22 or DTP52";
828 case DTP22_DATA_PARSE_ERROR:
829 return "Data from DTP didn't parse as expected";
830 case DTP22_UNKN_OEM:
831 return "Instrument is an unknown OEM version";
832 case DTP22_BAD_PASSWORD:
833 return "Instrument password was rejected";
834
835 case DTP22_OK:
836 return "No device error";
837
838 case DTP22_BAD_COMMAND:
839 return "Unrecognized command";
840 case DTP22_PRM_RANGE:
841 return "Command parameter out of range";
842 case DTP22_MEMORY_OVERFLOW:
843 return "Memory bounds error";
844 case DTP22_INVALID_BAUD_RATE:
845 return "Invalid baud rate";
846 case DTP22_TIMEOUT:
847 return "Receive timeout";
848 case DTP22_SYNTAX_ERROR:
849 return "Badly formed parameter";
850 case DTP22_INCORRECT_DATA_FORMAT:
851 return "Incorrect Data Format";
852 case DTP22_WEAK_LAMP:
853 return "Lamp is weak";
854 case DTP22_LAMP_FAILED:
855 return "Lamp has failed";
856 case DTP22_UNSTABLE_CAL:
857 return "Unstable calibration";
858 case DTP22_CAL_GAIN_ERROR:
859 return "Error setting gains during calibration";
860 case DTP22_SENSOR_FAILURE:
861 return "Sensing cell failure";
862 case DTP22_BLACK_CAL_TOO_HIGH:
863 return "Black calibration values are too high";
864 case DTP22_UNSTABLE_BLACK_CAL:
865 return "Unstable black calibration";
866 case DTP22_CAL_MEM_ERROR:
867 return "Memory error with calibration values";
868 case DTP22_FILTER_MOTOR:
869 return "Filter motor not working";
870 case DTP22_LAMP_FAILED_READING:
871 return "Lamp failed during reading";
872 case DTP22_POWER_INTR_READING:
873 return "Power failed during reading";
874 case DTP22_SIG_OFFSETS_READING:
875 return "Signal offsets exceeded limits during reading";
876 case DTP22_RD_SWITCH_TO_SOON:
877 return "Read switch released too soon";
878 case DTP22_OVERRANGE:
879 return "Overrange reading";
880 case DTP22_FILT_POS_ERROR:
881 return "Filter position sensor error";
882 case DTP22_FACT_TST_CONNECT:
883 return "Factory test connector error";
884 case DTP22_FACT_TST_LAMP_INH:
885 return "Factory test lamp inhibit error";
886
887 case DTP22_EEPROM_FAILURE:
888 return "EEprom write failure";
889 case DTP22_PROGRAM_WRITE_FAIL:
890 return "Loading new program error";
891 case DTP22_MEMORY_WRITE_FAIL:
892 return "Memory write error";
893 default:
894 return "Unknown error code";
895 }
896 }
897
898
899 /* Convert a machine specific error code into an abstract dtp code */
900 static inst_code
dtp22_interp_code(inst * pp,int ec)901 dtp22_interp_code(inst *pp, int ec) {
902 // dtp22 *p = (dtp22 *)pp;
903
904 ec &= inst_imask;
905 switch (ec) {
906
907 case DTP22_OK:
908 return inst_ok;
909
910 case DTP22_INTERNAL_ERROR:
911 return inst_internal_error | ec;
912
913 case DTP22_COMS_FAIL:
914 return inst_coms_fail | ec;
915
916 case DTP22_UNKNOWN_MODEL:
917 case DTP22_UNKN_OEM:
918 case DTP22_BAD_PASSWORD:
919 return inst_unknown_model | ec;
920
921 case DTP22_DATA_PARSE_ERROR:
922 return inst_protocol_error | ec;
923
924 case DTP22_POWER_INTR_READING:
925 case DTP22_RD_SWITCH_TO_SOON:
926 case DTP22_OVERRANGE:
927 case DTP22_SIG_OFFSETS_READING:
928 case DTP22_UNSTABLE_CAL:
929 case DTP22_UNSTABLE_BLACK_CAL:
930 case DTP22_BLACK_CAL_TOO_HIGH:
931 case DTP22_CAL_GAIN_ERROR: /* Or H/W error ? */
932 return inst_misread | ec;
933
934 case DTP22_WEAK_LAMP:
935 case DTP22_LAMP_FAILED:
936 case DTP22_SENSOR_FAILURE:
937 case DTP22_CAL_MEM_ERROR:
938 case DTP22_FILTER_MOTOR:
939 case DTP22_LAMP_FAILED_READING:
940 case DTP22_FACT_TST_CONNECT:
941 case DTP22_FACT_TST_LAMP_INH:
942 case DTP22_EEPROM_FAILURE:
943 case DTP22_PROGRAM_WRITE_FAIL:
944 case DTP22_MEMORY_WRITE_FAIL:
945 case DTP22_FILT_POS_ERROR:
946 return inst_hardware_fail | ec;
947 }
948 return inst_other_error | ec;
949 }
950
951 /* Destroy ourselves */
952 static void
dtp22_del(inst * pp)953 dtp22_del(inst *pp) {
954 dtp22 *p = (dtp22 *)pp;
955 if (p->icom != NULL)
956 p->icom->del(p->icom);
957 p->vdel(pp);
958 free(p);
959 }
960
961 /* Return the instrument capabilities */
dtp22_capabilities(inst * pp,inst_mode * pcap1,inst2_capability * pcap2,inst3_capability * pcap3)962 void dtp22_capabilities(inst *pp,
963 inst_mode *pcap1,
964 inst2_capability *pcap2,
965 inst3_capability *pcap3) {
966 inst_mode cap1 = 0;
967 inst2_capability cap2 = 0;
968
969 cap1 |= inst_mode_ref_spot
970 | inst_mode_colorimeter
971 | inst_mode_spectral
972 ;
973
974 cap2 |= inst2_prog_trig
975 | inst2_user_trig
976 | inst2_user_switch_trig
977 | inst2_cal_using_switch /* DTP22 special */
978 ;
979
980 if (pcap1 != NULL)
981 *pcap1 = cap1;
982 if (pcap2 != NULL)
983 *pcap2 = cap2;
984 if (pcap3 != NULL)
985 *pcap3 = inst3_none;
986 }
987
988 /* Activate the last set mode */
989 static inst_code
activate_mode(dtp22 * p)990 activate_mode(dtp22 *p) {
991 static char buf[MAX_MES_SIZE];
992 inst_code rv;
993
994 if (p->mode != p->lastmode) {
995
996 if ((p->lastmode & inst_mode_spectral) == inst_mode_spectral
997 && (p->mode & inst_mode_spectral) != inst_mode_spectral) {
998
999 /* Set format to colorimetric + spectral */
1000 if ((rv = dtp22_command(p, "0020CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
1001 return rv;
1002 }
1003 if ((p->lastmode & inst_mode_spectral) != inst_mode_spectral
1004 && (p->mode & inst_mode_spectral) == inst_mode_spectral) {
1005
1006 /* Set format to just colorimetric */
1007 if ((rv = dtp22_command(p, "0120CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
1008 return rv;
1009 }
1010 p->mode = p->lastmode;
1011 }
1012 return inst_ok;
1013 }
1014
1015 /*
1016 * check measurement mode
1017 */
1018 static inst_code
dtp22_check_mode(inst * pp,inst_mode m)1019 dtp22_check_mode(inst *pp, inst_mode m) {
1020 dtp22 *p = (dtp22 *)pp;
1021 inst_mode cap;
1022
1023 if (!p->gotcoms)
1024 return inst_no_coms;
1025 if (!p->inited)
1026 return inst_no_init;
1027
1028 pp->capabilities(pp, &cap, NULL, NULL);
1029
1030 /* Simple test */
1031 if (m & ~cap)
1032 return inst_unsupported;
1033
1034 /* General check mode against specific capabilities logic: */
1035 if (!IMODETST(m, inst_mode_ref_spot)) {
1036 return inst_unsupported;
1037 }
1038
1039 return inst_ok;
1040 }
1041
1042 /*
1043 * set measurement mode
1044 */
1045 static inst_code
dtp22_set_mode(inst * pp,inst_mode m)1046 dtp22_set_mode(inst *pp, inst_mode m)
1047 {
1048 dtp22 *p = (dtp22 *)pp;
1049 inst_code ev;
1050
1051 if ((ev = dtp22_check_mode(pp, m)) != inst_ok)
1052 return ev;
1053
1054 p->lastmode = m;
1055
1056 return inst_ok;
1057 }
1058
1059 /* !! It's not clear if there is a way of knowing */
1060 /* whether the instrument has a UV filter. */
1061
1062 /*
1063 * set or reset an optional mode
1064 *
1065 * Since there is no interaction with the instrument,
1066 * was assume that all of these can be done before initialisation.
1067 */
1068 static inst_code
dtp22_get_set_opt(inst * pp,inst_opt_type m,...)1069 dtp22_get_set_opt(inst *pp, inst_opt_type m, ...)
1070 {
1071 dtp22 *p = (dtp22 *)pp;
1072
1073 /* Set xcalstd */
1074 if (m == inst_opt_set_xcalstd) {
1075 xcalstd standard;
1076 va_list args;
1077
1078 va_start(args, m);
1079 standard = va_arg(args, xcalstd);
1080 va_end(args);
1081
1082 p->target_calstd = standard;
1083
1084 return inst_ok;
1085 }
1086
1087 /* Get the current effective xcalstd */
1088 if (m == inst_opt_get_xcalstd) {
1089 xcalstd *standard;
1090 va_list args;
1091
1092 va_start(args, m);
1093 standard = va_arg(args, xcalstd *);
1094 va_end(args);
1095
1096 if (p->target_calstd == xcalstd_native)
1097 *standard = p->native_calstd; /* If not overridden */
1098 else
1099 *standard = p->target_calstd; /* Overidden std. */
1100
1101 return inst_ok;
1102 }
1103
1104 /* Record the trigger mode */
1105 if (m == inst_opt_trig_prog
1106 || m == inst_opt_trig_user
1107 || m == inst_opt_trig_user_switch) {
1108 p->trig = m;
1109 return inst_ok;
1110 }
1111
1112 /* Use default implementation of other inst_opt_type's */
1113 {
1114 inst_code rv;
1115 va_list args;
1116
1117 va_start(args, m);
1118 rv = inst_get_set_opt_def(pp, m, args);
1119 va_end(args);
1120
1121 return rv;
1122 }
1123 }
1124
1125 /* Constructor */
new_dtp22(icoms * icom,instType itype)1126 extern dtp22 *new_dtp22(icoms *icom, instType itype) {
1127 dtp22 *p;
1128 if ((p = (dtp22 *)calloc(sizeof(dtp22),1)) == NULL) {
1129 a1loge(icom->log, 1, "new_dtp22: malloc failed!\n");
1130 return NULL;
1131 }
1132
1133 p->log = new_a1log_d(icom->log);
1134
1135 p->init_coms = dtp22_init_coms;
1136 p->init_inst = dtp22_init_inst;
1137 p->capabilities = dtp22_capabilities;
1138 p->check_mode = dtp22_check_mode;
1139 p->set_mode = dtp22_set_mode;
1140 p->get_set_opt = dtp22_get_set_opt;
1141 p->read_sample = dtp22_read_sample;
1142 p->get_n_a_cals = dtp22_get_n_a_cals;
1143 p->calibrate = dtp22_calibrate;
1144 p->interp_error = dtp22_interp_error;
1145 p->del = dtp22_del;
1146
1147 p->icom = icom;
1148 p->itype = itype;
1149 p->mode = inst_mode_none;
1150 p->need_cal = 1; /* Do a white calibration each time we open the device */
1151
1152 return p;
1153 }
1154
1155 /* Compute the DTP22/Digital Swatchbook password response. */
1156 /* Return NZ if there was an error */
comp_password(char * out,char * in,unsigned char key[4])1157 static int comp_password(char *out, char *in, unsigned char key[4]) {
1158 unsigned short inv[5];
1159 unsigned short outv;
1160
1161 in[10] = '\000';
1162
1163 /* Convert the 10 hex chars of input to 5 unsigned chars */
1164 if (sscanf(in, "%2hx%2hx%2hx%2hx%2hx", &inv[0], &inv[1], &inv[2], &inv[3], &inv[4]) != 5)
1165 return 1;
1166
1167 /* X-Rite magic... */
1168 inv[0] ^= key[0]; /* All seen to have 2 bits set in each nibble. */
1169 inv[1] ^= key[1]; /* ie. taken from set 3,5,6,9,A,C ? */
1170 inv[2] ^= key[2];
1171 inv[4] ^= key[3];
1172 outv = ((inv[0] * 256 + inv[2]) ^ (inv[4] * 256 + inv[1])) + inv[4];
1173
1174 sprintf(out, "%04x", outv);
1175 return 0;
1176 }
1177