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