summaryrefslogtreecommitdiff
path: root/spectro/dtp22.c
diff options
context:
space:
mode:
Diffstat (limited to 'spectro/dtp22.c')
-rw-r--r--spectro/dtp22.c1110
1 files changed, 1110 insertions, 0 deletions
diff --git a/spectro/dtp22.c b/spectro/dtp22.c
new file mode 100644
index 0000000..5831a1d
--- /dev/null
+++ b/spectro/dtp22.c
@@ -0,0 +1,1110 @@
+
+/*
+ * Argyll Color Correction System
+ *
+ * Xrite DTP22 related functions
+ *
+ * Author: Graeme W. Gill
+ * Date: 17/11/2006
+ *
+ * Copyright 1996 - 2013, Graeme W. Gill
+ * All rights reserved.
+ *
+ * This material is licenced under the GNU GENERAL PUBLIC LICENSE Version 2 or later :-
+ * see the License2.txt file for licencing details.
+ */
+
+/*
+ If you make use of the instrument driver code here, please note
+ that it is the author(s) of the code who take responsibility
+ for its operation. Any problems or queries regarding driving
+ instruments with the Argyll drivers, should be directed to
+ the Argyll's author(s), and not to any other party.
+
+ If there is some instrument feature or function that you
+ would like supported here, it is recommended that you
+ contact Argyll's author(s) first, rather than attempt to
+ modify the software yourself, if you don't have firm knowledge
+ of the instrument communicate protocols. There is a chance
+ that an instrument could be damaged by an incautious command
+ sequence, and the instrument companies generally cannot and
+ will not support developers that they have not qualified
+ and agreed to support.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+#include <stdarg.h>
+#ifndef SALONEINSTLIB
+#include "copyright.h"
+#include "aconfig.h"
+#include "numlib.h"
+#else /* !SALONEINSTLIB */
+#include "sa_config.h"
+#include "numsup.h"
+#endif /* !SALONEINSTLIB */
+#include "xspect.h"
+#include "insttypes.h"
+#include "conv.h"
+#include "icoms.h"
+#include "dtp22.h"
+
+/* Default flow control (Instrument doesn't support HW flow control) */
+#define DEFFC fc_XonXOff
+
+static inst_code dtp22_interp_code(inst *pp, int ec);
+static int comp_password(char *out, char *in, unsigned char key[4]);
+static inst_code activate_mode(dtp22 *p);
+static inst_code dtp22_get_set_opt(inst *pp, inst_opt_type m, ...);
+
+#define MAX_MES_SIZE 500 /* Maximum normal message reply size */
+#define MAX_RD_SIZE 5000 /* Maximum reading messagle reply size */
+
+/* Known DTP22 challenge/response keys for each OEM */
+/* (This is a 24 bit key - only the xor of the middle 2 bytes is significant) */
+/* The keys seem to be base 6/36, using nibbles with 2+2 bits: 3 5 6 9 A C */
+/* The last digit corresponds to the OEM serial number (ie. 6C + 9base6 = A6) */
+/* Possibly each digit is offset by the oemsn if counted in the right sequence ? - */
+/* ie. base 40 sequence or so ? Need more examples of keys to tell. */
+struct {
+ int oemsn;
+ unsigned char key[4];
+} keys[] = {
+ { 0, { 0x39, 0xa6, 0x55, 0x6c }}, /* Standard DTP22 */
+ { 9, { 0x5a, 0x66, 0xcc, 0xa6 }}, /* ColorMark calibrator - MacDermid GRAPHICARTS ColorSpan */
+ { -1, } /* End marker */
+};
+
+/* Extract an error code from a reply string */
+/* Return -1 if no error code can be found */
+static int
+extract_ec(char *s) {
+ char *p;
+ char tt[3];
+ int rv;
+ p = s + strlen(s);
+ /* Find the trailing '>' */
+ for (p--; p >= s;p--) {
+ if (*p == '>')
+ break;
+ }
+ if ( (p-3) < s
+ || p[0] != '>'
+ || p[-3] != '<')
+ return -1;
+ tt[0] = p[-2];
+ tt[1] = p[-1];
+ tt[2] = '\000';
+ if (sscanf(tt,"%x",&rv) != 1)
+ return -1;
+ /* For some reason the top bit sometimes get set ? */
+ rv &= 0x7f;
+ return rv;
+}
+
+/* Interpret an icoms error into a DTP22 error */
+static int icoms2dtp22_err(int se) {
+ if (se != ICOM_OK) {
+ if (se & ICOM_TO)
+ return DTP22_TIMEOUT;
+ return DTP22_COMS_FAIL;
+ }
+ return DTP22_OK;
+}
+
+/* Do a full featured command/response echange with the dtp22 */
+/* Return the dtp error code. End on the specified number */
+/* of specified characters, or expiry if the specified timeout */
+/* Assume standard error code if tc = '>' and ntc = 1 */
+/* Return a DTP22 error code */
+static int
+dtp22_fcommand(
+ struct _dtp22 *p,
+ char *in, /* In string */
+ char *out, /* Out string buffer */
+ int bsize, /* Out buffer size */
+ char tc, /* Terminating character */
+ int ntc, /* Number of terminating characters */
+ double to) { /* Timout in seconds */
+ int se, rv = DTP22_OK;
+
+ if ((se = p->icom->write_read(p->icom, in, out, bsize, tc, ntc, to)) != 0) {
+ a1logd(p->log, 1, "dtp22_fcommand: serial i/o failure on write_read '%s'\n",icoms_fix(in));
+ return icoms2dtp22_err(se);
+ }
+ if (tc == '>' && ntc == 1) {
+ rv = extract_ec(out);
+#ifdef NEVER /* Simulate an error ?? */
+ if (strcmp(in, "0PR\r") == 0)
+ rv = 0x1b;
+#endif /* NEVER */
+ if (rv > 0) {
+ rv &= inst_imask;
+ if (rv != DTP22_OK) { /* Clear the error */
+ char buf[MAX_MES_SIZE];
+ p->icom->write_read(p->icom, "CE\r", buf, MAX_MES_SIZE, '>', 1, 0.5);
+ }
+ }
+ }
+ a1logd(p->log, 4, "dtp22_fcommand: command '%s' returned '%s', value 0x%x\n",
+ icoms_fix(in), icoms_fix(out),rv);
+ return rv;
+}
+
+/* Do a standard command/response echange with the dtp22 */
+/* Return the dtp error code */
+static inst_code
+dtp22_command(dtp22 *p, char *in, char *out, int bsize, double to) {
+ int rv = dtp22_fcommand(p, in, out, bsize, '>', 1, to);
+ return dtp22_interp_code((inst *)p, rv);
+}
+
+/* Establish communications with a DTP22 */
+/* If it's a serial port, use the baud rate given, and timeout in to secs */
+/* Return DTP_COMS_FAIL on failure to establish communications */
+static inst_code
+dtp22_init_coms(inst *pp, baud_rate br, flow_control fc, double tout) {
+ dtp22 *p = (dtp22 *) pp;
+ char buf[MAX_MES_SIZE];
+ baud_rate brt[5] = { baud_9600, baud_19200, baud_4800, baud_2400, baud_1200 };
+ char *brc[5] = { "30BR\r", "60BR\r", "18BR\r", "0CBR\r", "06BR\r" };
+ char *fcc;
+ unsigned int etime;
+ int ci, bi, i, se;
+ inst_code ev = inst_ok;
+
+ a1logd(p->log, 2, "dtp22_init_coms: About to init Serial I/O\n");
+
+ /* Deal with flow control setting */
+ if (fc == fc_nc)
+ fc = DEFFC;
+ if (fc == fc_XonXOff) {
+ fcc = "0304CF\r";
+ } else if (fc == fc_Hardware) {
+ fcc = "0104CF\r";
+ } else {
+ fc = fc_none;
+ fcc = "0004CF\r";
+ }
+
+ /* Figure DTP22 baud rate being asked for */
+ for (bi = 0; bi < 5; bi++) {
+ if (brt[bi] == br)
+ break;
+ }
+ if (bi >= 5)
+ bi = 0;
+
+ /* Figure current icoms baud rate */
+ for (ci = 0; ci < 5; ci++) {
+ if (brt[ci] == p->icom->br)
+ break;
+ }
+ if (ci >= 5)
+ ci = bi;
+
+ /* The tick to give up on */
+ etime = msec_time() + (long)(1000.0 * tout + 0.5);
+
+ while (msec_time() < etime) {
+
+ a1logd(p->log, 4, "dtp22_init_coms: Trying different baud rates (%u msec to go)\n",
+ etime - msec_time());
+
+ /* Until we time out, find the correct baud rate */
+ for (i = ci; msec_time() < etime;) {
+
+ if ((se = p->icom->set_ser_port(p->icom, fc_none, brt[i], parity_none,
+ stop_1, length_8)) != ICOM_OK) {
+ a1logd(p->log, 1, "dtp22_init_coms: set_ser_port failed ICOM err 0x%x\n",se);
+ return dtp22_interp_code((inst *)p, icoms2dtp22_err(se));
+ }
+ if (((ev = dtp22_command(p, "\r", buf, MAX_MES_SIZE, 0.5)) & inst_mask)
+ != inst_coms_fail)
+ break; /* We've got coms */
+
+ /* Check for user abort */
+ if (p->uicallback != NULL) {
+ inst_code ev;
+ if ((ev = p->uicallback(p->uic_cntx, inst_negcoms)) == inst_user_abort) {
+ a1logd(p->log, 1, "dtp22_init_coms: user aborted\n");
+ return ev;
+ }
+ }
+ if (++i >= 5)
+ i = 0;
+ }
+ break; /* Got coms */
+ }
+
+ if (msec_time() >= etime) { /* We haven't established comms */
+ return inst_coms_fail;
+ }
+
+ /* Set the handshaking */
+ if ((ev = dtp22_command(p, fcc, buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+
+ /* Change the baud rate to the rate we've been told */
+ if ((se = p->icom->write_read(p->icom, brc[bi], buf, MAX_MES_SIZE, '>', 1, .2)) != 0) {
+ if (extract_ec(buf) != DTP22_OK)
+ return inst_coms_fail;
+ }
+
+ /* Configure our baud rate and handshaking as well */
+ if ((se = p->icom->set_ser_port(p->icom, fc, brt[bi], parity_none, stop_1, length_8)) != ICOM_OK) {
+ a1logd(p->log, 1, "dtp22_init_coms: set_ser_port failed ICOM err 0x%x\n",se);
+ return dtp22_interp_code((inst *)p, icoms2dtp22_err(se));
+ }
+
+ /* Loose a character (not sure why) */
+ p->icom->write_read(p->icom, "\r", buf, MAX_MES_SIZE, '>', 1, 0.1);
+
+ /* Check instrument is responding, and reset it again. */
+ if ((ev = dtp22_command(p, "\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok
+ || (ev = dtp22_command(p, "0PR\r", buf, MAX_MES_SIZE, 2.0)) != inst_ok) {
+
+ a1logd(p->log, 1, "dtp22_init_coms: failed with ICOM 0x%x\n",ev);
+
+ p->icom->del(p->icom); /* Since caller may not clean up */
+ p->icom = NULL;
+ return inst_coms_fail;
+ }
+
+ a1logd(p->log, 2, "dtp22_init_coms: init coms has suceeded\n");
+
+ p->gotcoms = 1;
+ return inst_ok;
+}
+
+/* Initialise the DTP22 */
+/* return non-zero on an error, with dtp error code */
+static inst_code
+dtp22_init_inst(inst *pp) {
+ dtp22 *p = (dtp22 *)pp;
+ char buf[MAX_MES_SIZE], *bp;
+ inst_code ev = inst_ok;
+ int i;
+
+ a1logd(p->log, 2, "dtp22_init_inst: called\n");
+
+ if (p->gotcoms == 0)
+ return inst_internal_error; /* Must establish coms before calling init */
+
+ /* Warm reset it */
+ if ((ev = dtp22_command(p, "0PR\r", buf, MAX_MES_SIZE, 2.0)) != inst_ok)
+ return ev;
+
+ /* Get the model and version number */
+ if ((ev = dtp22_command(p, "SV\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+
+ /* Check that it is a DTP22 */
+ if ( strlen(buf) < 12
+ || (strncmp(buf,"X-Rite DTP22",12) != 0))
+ return inst_unknown_model;
+
+ /* Factory reset */
+// if ((ev = dtp22_command(p, "5CRI\r", buf, MAX_MES_SIZE, 10.2)) != inst_ok)
+// return ev;
+
+ /* Turn echoing of characters off */
+ if ((ev = dtp22_command(p, "0EC\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+
+ /* Set decimal point on */
+ if ((ev = dtp22_command(p, "0106CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+
+ /* Set color data separator to TAB */
+ if ((ev = dtp22_command(p, "0207CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+
+ /* Set delimeter to CR */
+ if ((ev = dtp22_command(p, "0008CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+
+ /* Set extra digit resolution (X10) */
+ if ((ev = dtp22_command(p, "010ACF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+
+ /* - - - - - - - - - - - - - - - - - - - - - - - - */
+ /* Get some information about the instrument */
+ if ((ev = dtp22_command(p, "GI\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok) {
+ a1logd(p->log, 1, "dtp22: GI command failed with ICOM err 0x%x\n",ev);
+ return ev;
+ }
+
+ /* Extract some of these */
+ if ((bp = strstr(buf, "Serial Number:")) != NULL) {
+ bp += strlen("Serial Number:");
+ p->serno = atoi(bp);
+ } else {
+ p->serno = -1;
+ }
+ if ((bp = strstr(buf, "OEM Serial #:")) != NULL) {
+ bp += strlen("OEM Serial #:");
+ p->oemsn = atoi(bp);
+ } else {
+ p->oemsn = -1;
+ }
+ if ((bp = strstr(buf, "Cal Plaque Serial #:")) != NULL) {
+ bp += strlen("Cal Plaque Serial #:");
+ p->plaqueno = atoi(bp);
+ } else {
+ p->plaqueno = -1;
+ }
+ if (p->log->verb) {
+ int i, j;
+ for (j = i = 0; ;i++) {
+ if (buf[i] == '<' || buf[i] == '\000')
+ break;
+ if (buf[i] == '\r') {
+ buf[i] = '\000';
+ a1logv(p->log, 1, " %s\n",&buf[j]);
+ if (buf[i+1] == '\n')
+ i++;
+ j = i+1;
+ }
+ }
+ }
+
+ /* - - - - - - - - - - - - - - - - - - - - - - - - */
+ /* Setup for the default type of measurements we want to do */
+
+ /* Disable key codes */
+ if ((ev = dtp22_command(p, "0OK\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+
+ /* Disable the read microswitch by default */
+ if ((ev = dtp22_command(p, "0PB\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+ p->trig = inst_opt_trig_user;
+
+ /* Set format to colorimetric */
+ if ((ev = dtp22_command(p, "0120CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+ p->mode &= ~inst_mode_spectral;
+
+ /* Set colorimetric to XYZ */
+ if ((ev = dtp22_command(p, "0221CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+
+ /* Disable density */
+ if ((ev = dtp22_command(p, "0022CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+
+ /* Enable spectral */
+ if ((ev = dtp22_command(p, "0126CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+
+ /* Set Illuminant to D50_2 */
+ if ((ev = dtp22_command(p, "0427CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+
+ /* Read the current calibration values */
+// if ((ev = dtp22_command(p, "0LC\r", buf, MAX_MES_SIZE, 10.2)) != inst_ok)
+// return ev;
+
+ /* See that we have the correct challenge/response key */
+ for (i = 0; keys[i].oemsn >= 0; i++) {
+ if (keys[i].oemsn == p->oemsn) {
+ p->key[0] = keys[i].key[0];
+ p->key[1] = keys[i].key[1];
+ p->key[2] = keys[i].key[2];
+ p->key[3] = keys[i].key[3];
+ break;
+ }
+ }
+ if (keys[i].oemsn < 0)
+ return inst_unknown_model | DTP22_UNKN_OEM;
+
+ p->inited = 1;
+ a1logd(p->log, 2, "dtp22_init_inst: instrument inited OK\n");
+
+ return inst_ok;
+}
+
+/* Read a single sample */
+/* Return the instrument error code */
+static inst_code
+dtp22_read_sample(
+inst *pp,
+char *name, /* Strip name (7 chars) */
+ipatch *val, /* Pointer to instrument patch value */
+instClamping clamp) { /* NZ if clamp XYZ/Lab to be +ve */
+ dtp22 *p = (dtp22 *)pp;
+ char *tp;
+ char buf[MAX_RD_SIZE];
+ char buf2[50];
+ int se;
+ inst_code ev = inst_ok;
+ int switch_trig = 0;
+ int user_trig = 0;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ if ((ev = activate_mode(p)) != inst_ok)
+ return ev;
+
+ /* Signal a calibration is needed */
+ if (p->need_cal && p->noutocalib == 0) {
+ return inst_needs_cal; /* Get user to calibrate */
+ }
+
+ /* Request challenge, so that we can return the response */
+ if ((ev = dtp22_command(p, "GP\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+
+ if (comp_password(buf2, buf, p->key))
+ return inst_internal_error | DTP22_INTERNAL_ERROR;
+
+ /* Validate the password */
+ strcat(buf2, "VD\r");
+ if ((ev = dtp22_command(p, buf2, buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+
+ if (strncmp(buf,"PASS", 4) != 0)
+ return inst_unknown_model | DTP22_BAD_PASSWORD;
+
+ if (p->trig == inst_opt_trig_user_switch) {
+
+ /* Enable the read microswitch */
+ if ((ev = dtp22_command(p, "3PB\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+
+ /* Wait for the microswitch to be triggered, or the user to trigger */
+ for (;;) {
+ if ((se = p->icom->read(p->icom, buf, MAX_MES_SIZE, '>', 1, 1.0)) != 0) {
+ if ((se & ICOM_TO) == 0) { /* Some sort of read error */
+ /* Disable the read microswitch */
+ dtp22_command(p, "2PB\r", buf, MAX_MES_SIZE, 0.2);
+ return dtp22_interp_code((inst *)p, icoms2dtp22_err(se));
+ }
+ /* Timed out */
+ if (p->uicallback != NULL) { /* Check for user trigger */
+ if ((ev = p->uicallback(p->uic_cntx, inst_armed)) != inst_ok) {
+ if (ev == inst_user_abort) {
+ /* Disable the read microswitch */
+ dtp22_command(p, "2PB\r", buf, MAX_MES_SIZE, 0.2);
+ return ev; /* User abort */
+ }
+ if (ev == inst_user_trig)
+ break; /* Trigger */
+ }
+ }
+ } else { /* Inst error or switch activated */
+ if (strlen(buf) >= 4
+ && buf[0] == '<' && isdigit(buf[1]) && isdigit(buf[2]) && buf[3] == '>') {
+ if ((ev = dtp22_interp_code((inst *)p, extract_ec(buf))) != inst_ok) {
+ dtp22_command(p, "CE\r", buf, MAX_MES_SIZE, 0.5);
+ dtp22_command(p, "2PB\r", buf, MAX_MES_SIZE, 0.5);
+ return ev;
+ }
+ switch_trig = 1;
+ break; /* Measure triggered via inst switch */
+ }
+ }
+ }
+ /* Disable the read microswitch */
+ if ((ev = dtp22_command(p, "2PB\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+ /* Notify of trigger */
+ if (p->uicallback)
+ p->uicallback(p->uic_cntx, inst_triggered);
+
+ } else if (p->trig == inst_opt_trig_user) {
+
+ if (p->uicallback == NULL) {
+ a1logd(p->log, 1, "dtp22: inst_opt_trig_user but no uicallback function set!\n");
+ return inst_unsupported;
+ }
+ for (;;) {
+ if ((ev = p->uicallback(p->uic_cntx, inst_armed)) != inst_ok) {
+ if (ev == inst_user_abort)
+ return ev; /* Abort */
+ if (ev == inst_user_trig)
+ break; /* Trigger */
+ }
+ msec_sleep(200);
+ }
+ /* Notify of trigger */
+ if (p->uicallback)
+ p->uicallback(p->uic_cntx, inst_triggered);
+
+ /* Progromatic Trigger */
+ } else {
+ /* Check for abort */
+ if (p->uicallback != NULL
+ && (ev = p->uicallback(p->uic_cntx, inst_armed)) == inst_user_abort)
+ return ev; /* Abort */
+ }
+
+ /* Trigger a read if the switch has not been used */
+ if (switch_trig == 0) {
+ if ((ev = dtp22_command(p, "RM\r", buf, MAX_RD_SIZE, 20.0)) != inst_ok) {
+ return ev; /* Misread */
+ }
+ }
+
+ /* Gather the results in D50_2 XYZ % reflectance */
+ if ((ev = dtp22_command(p, "0SR\r", buf, MAX_RD_SIZE, 5.0)) != inst_ok)
+ return ev; /* misread */
+
+ /* Parse the buffer */
+ /* Replace '\r' with '\000' */
+ for (tp = buf; *tp != '\000'; tp++) {
+ if (*tp == '\r')
+ *tp = '\000';
+ }
+
+ if (sscanf(buf, " X %lf Y %lf Z %lf ", &val->XYZ[0], &val->XYZ[1], &val->XYZ[2]) != 3) {
+ return inst_protocol_error;
+ }
+
+ /* This may not change anything since instrument may clamp */
+ if (clamp)
+ icmClamp3(val->XYZ, val->XYZ);
+ val->loc[0] = '\000';
+ val->mtype = inst_mrt_reflective;
+ val->XYZ_v = 1;
+ val->sp.spec_n = 0;
+ val->duration = 0.0;
+
+ if (p->mode & inst_mode_spectral) {
+ int j;
+ char *fmt;
+
+ /* Reset tp to point to start of spectral */
+ tp = buf + strlen(buf) + 1;
+
+ /* Different dialects spoken by DTP-22 */
+ if (strcmp(tp, "SPECTRAL DATA") == 0 ) {
+ tp += strlen(tp) + 1;
+ fmt = " w %*lf S %lf ";
+ } else {
+ fmt = " S %lf ";
+ }
+
+ /* Read the spectral value */
+ for (j = 0; j < 31; j++) {
+ if (sscanf(tp, fmt, &val->sp.spec[j]) != 1)
+ return inst_protocol_error;
+ tp += strlen(tp) + 1;
+ }
+
+ val->sp.spec_n = 31;
+ val->sp.spec_wl_short = 400.0;
+ val->sp.spec_wl_long = 700.0;
+ val->sp.norm = 100.0;
+ }
+
+ if (user_trig)
+ return inst_user_trig;
+ return inst_ok;
+}
+
+/* Return needed and available inst_cal_type's */
+static inst_code dtp22_get_n_a_cals(inst *pp, inst_cal_type *pn_cals, inst_cal_type *pa_cals) {
+ dtp22 *p = (dtp22 *)pp;
+ inst_cal_type n_cals = inst_calt_none;
+ inst_cal_type a_cals = inst_calt_none;
+
+ if (p->need_cal && p->noutocalib == 0)
+ n_cals |= inst_calt_ref_white;
+ a_cals |= inst_calt_ref_white;
+
+ if (pn_cals != NULL)
+ *pn_cals = n_cals;
+
+ if (pa_cals != NULL)
+ *pa_cals = a_cals;
+
+ return inst_ok;
+}
+
+/* Request an instrument calibration. */
+/* This is use if the user decides they want to do a calibration, */
+/* in anticipation of a calibration (needs_calibration()) to avoid */
+/* requiring one during measurement, or in response to measuring */
+/* returning inst_needs_cal. Initially us an inst_cal_cond of inst_calc_none, */
+/* and then be prepared to setup the right conditions, or ask the */
+/* user to do so, each time the error inst_cal_setup is returned. */
+inst_code dtp22_calibrate(
+inst *pp,
+inst_cal_type *calt, /* Calibration type to do/remaining */
+inst_cal_cond *calc, /* Current condition/desired condition */
+char id[CALIDLEN] /* Condition identifier (ie. white reference ID) */
+) {
+ dtp22 *p = (dtp22 *)pp;
+ char buf[MAX_RD_SIZE];
+ int se;
+ inst_code tv, ev = inst_ok;
+ inst_cal_type needed, available;
+ int swen = 0;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ id[0] = '\000';
+
+ if ((ev = dtp22_get_n_a_cals((inst *)p, &needed, &available)) != inst_ok)
+ return ev;
+
+ /* Translate inst_calt_all/needed into something specific */
+ if (*calt == inst_calt_all
+ || *calt == inst_calt_needed
+ || *calt == inst_calt_available) {
+ if (*calt == inst_calt_all)
+ *calt = (needed & inst_calt_n_dfrble_mask) | inst_calt_ap_flag;
+ else if (*calt == inst_calt_needed)
+ *calt = needed & inst_calt_n_dfrble_mask;
+ else if (*calt == inst_calt_available)
+ *calt = available & inst_calt_n_dfrble_mask;
+
+ a1logd(p->log,4,"dtp22_calibrate: doing calt 0x%x\n",calt);
+
+ if ((*calt & inst_calt_n_dfrble_mask) == 0) /* Nothing todo */
+ return inst_ok;
+ }
+
+ /* See if it's a calibration we understand */
+ if (*calt & ~available & inst_calt_all_mask) {
+ return inst_unsupported;
+ }
+
+ if (*calt & inst_calt_ref_white) { /* White calibration */
+
+ sprintf(id, "Serial no. %d",p->plaqueno);
+ if (*calc != inst_calc_man_ref_whitek) {
+ *calc = inst_calc_man_ref_whitek;
+ ev = inst_cal_setup;
+ goto do_exit;
+ }
+
+ /* Calibration only works when triggered by the read switch... */
+ if (!swen) {
+ if ((ev = dtp22_command(p, "3PB\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+ swen = 1;
+ }
+
+ if ((ev = activate_mode(p)) != inst_ok)
+ goto do_exit;
+
+ /* Issue white calibration */
+ if ((se = p->icom->write(p->icom, "1CA\r", 0.5)) != ICOM_OK) {
+ ev = dtp22_interp_code((inst *)p, icoms2dtp22_err(se));
+ goto do_exit;
+ }
+
+ /* Wait for the microswitch to be triggered, or a user trigger via uicallback */
+ for (;;) {
+ if ((se = p->icom->read(p->icom, buf, MAX_MES_SIZE, '>', 1, 1.0)) != 0) {
+ if ((se & ICOM_TO) == 0) { /* Some sort of read error */
+ ev = dtp22_interp_code((inst *)p, icoms2dtp22_err(se));
+ goto do_exit;
+ }
+ /* Timed out - poll user */
+ if (p->uicallback != NULL) { /* Check for user trigger */
+ if ((ev = p->uicallback(p->uic_cntx, inst_armed)) != inst_ok) {
+ if (ev == inst_user_abort)
+ goto do_exit; /* User abort */
+ if (ev == inst_user_trig)
+ break; /* User trigger */
+ }
+ }
+ } else { /* Inst error or switch activated */
+ if (strlen(buf) >= 4
+ && buf[0] == '<' && isdigit(buf[1]) && isdigit(buf[2]) && buf[3] == '>') {
+ if ((ev = dtp22_interp_code((inst *)p, extract_ec(buf))) != inst_ok) {
+ dtp22_command(p, "CE\r", buf, MAX_MES_SIZE, 0.5);
+ if (ev != inst_ok)
+ goto do_exit; /* Error */
+ }
+ break; /* Switch trigger */
+ }
+ }
+ }
+
+ if (p->uicallback) /* Notify of trigger */
+ p->uicallback(p->uic_cntx, inst_triggered);
+
+ p->need_cal = 0;
+ *calt &= ~inst_calt_ref_white;
+
+ }
+ if (*calt & inst_calt_ref_dark) { /* Black calibration */
+
+ if (*calc != inst_calc_man_ref_dark) {
+ *calc = inst_calc_man_ref_dark;
+ ev = inst_cal_setup;
+ goto do_exit;
+ }
+
+ /* Check for abort */
+ if (p->uicallback != NULL
+ && (ev = p->uicallback(p->uic_cntx, inst_armed)) == inst_user_abort) {
+ goto do_exit;
+ }
+
+ /* Calibration only works when triggered by the read switch... */
+ if (!swen) {
+ if ((ev = dtp22_command(p, "3PB\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+ swen = 1;
+ }
+
+ if ((ev = activate_mode(p)) != inst_ok)
+ goto do_exit;
+
+ /* Do black calibration */
+ if ((ev = dtp22_command(p, "1CB\r", buf, MAX_RD_SIZE, 20)) != inst_ok)
+ goto do_exit;
+
+ /* Make calibration permanent */
+ if ((ev = dtp22_command(p, "MP\r", buf, MAX_RD_SIZE, 10.0)) != inst_ok)
+ goto do_exit;
+
+ *calt &= ~inst_calt_ref_dark;
+ }
+
+ do_exit:
+
+ if (swen) {
+ /* Disable the read microswitch */
+ if ((tv = dtp22_command(p, "3PB\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok && ev == inst_ok)
+ return tv;
+ swen = 0;
+ }
+
+ return ev;
+}
+
+/* Error codes interpretation */
+static char *
+dtp22_interp_error(inst *pp, int ec) {
+// dtp22 *p = (dtp22 *)pp;
+ ec &= inst_imask;
+ switch (ec) {
+ case DTP22_INTERNAL_ERROR:
+ return "Internal software error";
+ case DTP22_COMS_FAIL:
+ return "Communications failure";
+ case DTP22_UNKNOWN_MODEL:
+ return "Not a DTP22 or DTP52";
+ case DTP22_DATA_PARSE_ERROR:
+ return "Data from DTP didn't parse as expected";
+ case DTP22_UNKN_OEM:
+ return "Instrument is an unknown OEM version";
+ case DTP22_BAD_PASSWORD:
+ return "Instrument password was rejected";
+
+ case DTP22_OK:
+ return "No device error";
+
+ case DTP22_BAD_COMMAND:
+ return "Unrecognized command";
+ case DTP22_PRM_RANGE:
+ return "Command parameter out of range";
+ case DTP22_MEMORY_OVERFLOW:
+ return "Memory bounds error";
+ case DTP22_INVALID_BAUD_RATE:
+ return "Invalid baud rate";
+ case DTP22_TIMEOUT:
+ return "Receive timeout";
+ case DTP22_SYNTAX_ERROR:
+ return "Badly formed parameter";
+ case DTP22_INCORRECT_DATA_FORMAT:
+ return "Incorrect Data Format";
+ case DTP22_WEAK_LAMP:
+ return "Lamp is weak";
+ case DTP22_LAMP_FAILED:
+ return "Lamp has failed";
+ case DTP22_UNSTABLE_CAL:
+ return "Unstable calibration";
+ case DTP22_CAL_GAIN_ERROR:
+ return "Error setting gains during calibration";
+ case DTP22_SENSOR_FAILURE:
+ return "Sensing cell failure";
+ case DTP22_BLACK_CAL_TOO_HIGH:
+ return "Black calibration values are too high";
+ case DTP22_UNSTABLE_BLACK_CAL:
+ return "Unstable black calibration";
+ case DTP22_CAL_MEM_ERROR:
+ return "Memory error with calibration values";
+ case DTP22_FILTER_MOTOR:
+ return "Filter motor not working";
+ case DTP22_LAMP_FAILED_READING:
+ return "Lamp failed during reading";
+ case DTP22_POWER_INTR_READING:
+ return "Power failed during reading";
+ case DTP22_SIG_OFFSETS_READING:
+ return "Signal offsets exceeded limits during reading";
+ case DTP22_RD_SWITCH_TO_SOON:
+ return "Read switch released too soon";
+ case DTP22_OVERRANGE:
+ return "Overrange reading";
+ case DTP22_FILT_POS_ERROR:
+ return "Filter position sensor error";
+ case DTP22_FACT_TST_CONNECT:
+ return "Factory test connector error";
+ case DTP22_FACT_TST_LAMP_INH:
+ return "Factory test lamp inhibit error";
+
+ case DTP22_EEPROM_FAILURE:
+ return "EEprom write failure";
+ case DTP22_PROGRAM_WRITE_FAIL:
+ return "Loading new program error";
+ case DTP22_MEMORY_WRITE_FAIL:
+ return "Memory write error";
+ default:
+ return "Unknown error code";
+ }
+}
+
+
+/* Convert a machine specific error code into an abstract dtp code */
+static inst_code
+dtp22_interp_code(inst *pp, int ec) {
+// dtp22 *p = (dtp22 *)pp;
+
+ ec &= inst_imask;
+ switch (ec) {
+
+ case DTP22_OK:
+ return inst_ok;
+
+ case DTP22_INTERNAL_ERROR:
+ return inst_internal_error | ec;
+
+ case DTP22_COMS_FAIL:
+ return inst_coms_fail | ec;
+
+ case DTP22_UNKNOWN_MODEL:
+ case DTP22_UNKN_OEM:
+ case DTP22_BAD_PASSWORD:
+ return inst_unknown_model | ec;
+
+ case DTP22_DATA_PARSE_ERROR:
+ return inst_protocol_error | ec;
+
+ case DTP22_POWER_INTR_READING:
+ case DTP22_RD_SWITCH_TO_SOON:
+ case DTP22_OVERRANGE:
+ case DTP22_SIG_OFFSETS_READING:
+ case DTP22_UNSTABLE_CAL:
+ case DTP22_UNSTABLE_BLACK_CAL:
+ case DTP22_BLACK_CAL_TOO_HIGH:
+ case DTP22_CAL_GAIN_ERROR: /* Or H/W error ? */
+ return inst_misread | ec;
+
+ case DTP22_WEAK_LAMP:
+ case DTP22_LAMP_FAILED:
+ case DTP22_SENSOR_FAILURE:
+ case DTP22_CAL_MEM_ERROR:
+ case DTP22_FILTER_MOTOR:
+ case DTP22_LAMP_FAILED_READING:
+ case DTP22_FACT_TST_CONNECT:
+ case DTP22_FACT_TST_LAMP_INH:
+ case DTP22_EEPROM_FAILURE:
+ case DTP22_PROGRAM_WRITE_FAIL:
+ case DTP22_MEMORY_WRITE_FAIL:
+ case DTP22_FILT_POS_ERROR:
+ return inst_hardware_fail | ec;
+ }
+ return inst_other_error | ec;
+}
+
+/* Destroy ourselves */
+static void
+dtp22_del(inst *pp) {
+ dtp22 *p = (dtp22 *)pp;
+ if (p->icom != NULL)
+ p->icom->del(p->icom);
+ free(p);
+}
+
+/* Return the instrument capabilities */
+void dtp22_capabilities(inst *pp,
+inst_mode *pcap1,
+inst2_capability *pcap2,
+inst3_capability *pcap3) {
+ inst_mode cap1 = 0;
+ inst2_capability cap2 = 0;
+
+ cap1 |= inst_mode_ref_spot
+ | inst_mode_colorimeter
+ | inst_mode_spectral
+ ;
+
+ cap2 |= inst2_prog_trig
+ | inst2_user_trig
+ | inst2_user_switch_trig
+ | inst2_cal_using_switch /* DTP22 special */
+ ;
+
+ if (pcap1 != NULL)
+ *pcap1 = cap1;
+ if (pcap2 != NULL)
+ *pcap2 = cap2;
+ if (pcap3 != NULL)
+ *pcap3 = inst3_none;
+}
+
+/* Activate the last set mode */
+static inst_code
+activate_mode(dtp22 *p) {
+ static char buf[MAX_MES_SIZE];
+ inst_code rv;
+
+ if (p->mode != p->lastmode) {
+
+ if ((p->lastmode & inst_mode_spectral) == inst_mode_spectral
+ && (p->mode & inst_mode_spectral) != inst_mode_spectral) {
+
+ /* Set format to colorimetric + spectral */
+ if ((rv = dtp22_command(p, "0020CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return rv;
+ }
+ if ((p->lastmode & inst_mode_spectral) != inst_mode_spectral
+ && (p->mode & inst_mode_spectral) == inst_mode_spectral) {
+
+ /* Set format to just colorimetric */
+ if ((rv = dtp22_command(p, "0120CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return rv;
+ }
+ p->mode = p->lastmode;
+ }
+ return inst_ok;
+}
+
+/*
+ * check measurement mode
+ */
+static inst_code
+dtp22_check_mode(inst *pp, inst_mode m) {
+ dtp22 *p = (dtp22 *)pp;
+ inst_mode cap;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ pp->capabilities(pp, &cap, NULL, NULL);
+
+ /* Simple test */
+ if (m & ~cap)
+ return inst_unsupported;
+
+ /* General check mode against specific capabilities logic: */
+ if (!IMODETST(m, inst_mode_ref_spot)) {
+ return inst_unsupported;
+ }
+
+ return inst_ok;
+}
+
+/*
+ * set measurement mode
+ */
+static inst_code
+dtp22_set_mode(inst *pp, inst_mode m)
+{
+ dtp22 *p = (dtp22 *)pp;
+ inst_code ev;
+
+ if ((ev = dtp22_check_mode(pp, m)) != inst_ok)
+ return ev;
+
+ p->lastmode = m;
+
+ return inst_ok;
+}
+
+/* !! It's not clear if there is a way of knowing */
+/* whether the instrument has a UV filter. */
+
+/*
+ * set or reset an optional mode
+ *
+ * Since there is no interaction with the instrument,
+ * was assume that all of these can be done before initialisation.
+ */
+static inst_code
+dtp22_get_set_opt(inst *pp, inst_opt_type m, ...)
+{
+ dtp22 *p = (dtp22 *)pp;
+
+ /* Record the trigger mode */
+ if (m == inst_opt_trig_prog
+ || m == inst_opt_trig_user
+ || m == inst_opt_trig_user_switch) {
+ p->trig = m;
+ return inst_ok;
+ }
+
+ return inst_unsupported;
+}
+
+/* Constructor */
+extern dtp22 *new_dtp22(icoms *icom, instType itype) {
+ dtp22 *p;
+ if ((p = (dtp22 *)calloc(sizeof(dtp22),1)) == NULL) {
+ a1loge(icom->log, 1, "new_dtp22: malloc failed!\n");
+ return NULL;
+ }
+
+ p->log = new_a1log_d(icom->log);
+
+ p->init_coms = dtp22_init_coms;
+ p->init_inst = dtp22_init_inst;
+ p->capabilities = dtp22_capabilities;
+ p->check_mode = dtp22_check_mode;
+ p->set_mode = dtp22_set_mode;
+ p->get_set_opt = dtp22_get_set_opt;
+ p->read_sample = dtp22_read_sample;
+ p->get_n_a_cals = dtp22_get_n_a_cals;
+ p->calibrate = dtp22_calibrate;
+ p->interp_error = dtp22_interp_error;
+ p->del = dtp22_del;
+
+ p->icom = icom;
+ p->itype = icom->itype;
+ p->mode = inst_mode_none;
+ p->need_cal = 1; /* Do a white calibration each time we open the device */
+
+ return p;
+}
+
+/* Compute the DTP22/Digital Swatchbook password response. */
+/* Return NZ if there was an error */
+static int comp_password(char *out, char *in, unsigned char key[4]) {
+ unsigned short inv[5];
+ unsigned short outv;
+
+ in[10] = '\000';
+
+ /* Convert the 10 hex chars of input to 5 unsigned chars */
+ if (sscanf(in, "%2hx%2hx%2hx%2hx%2hx", &inv[0], &inv[1], &inv[2], &inv[3], &inv[4]) != 5)
+ return 1;
+
+ /* X-Rite magic... */
+ inv[0] ^= key[0]; /* All seen to have 2 bits set in each nibble. */
+ inv[1] ^= key[1]; /* ie. taken from set 3,5,6,9,A,C ? */
+ inv[2] ^= key[2];
+ inv[4] ^= key[3];
+ outv = ((inv[0] * 256 + inv[2]) ^ (inv[4] * 256 + inv[1])) + inv[4];
+
+ sprintf(out, "%04x", outv);
+ return 0;
+}