From 22f703cab05b7cd368f4de9e03991b7664dc5022 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Frings-F=C3=BCrst?= Date: Mon, 1 Sep 2014 13:56:46 +0200 Subject: Initial import of argyll version 1.5.1-8 --- spectro/dtp20.c | 1695 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1695 insertions(+) create mode 100644 spectro/dtp20.c (limited to 'spectro/dtp20.c') diff --git a/spectro/dtp20.c b/spectro/dtp20.c new file mode 100644 index 0000000..84c4f2b --- /dev/null +++ b/spectro/dtp20.c @@ -0,0 +1,1695 @@ + +/* + * Argyll Color Correction System + * + * Xrite DTP20 related functions + * + * Author: Graeme W. Gill + * Date: 10/3/2001 + * + * 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. + * + * Derived from DTP41.c + * + * Thanks to Rolf Gierling for contributing to the devlopment of this driver. + */ + +/* + 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 +#include +#include +#include +#include +#include +#ifndef SALONEINSTLIB +#include "copyright.h" +#include "aconfig.h" +#else /* SALONEINSTLIB */ +#include "sa_config.h" +#endif /* SALONEINSTLIB */ +#include "numsup.h" +#include "xspect.h" +#include "insttypes.h" +#include "conv.h" +#include "icoms.h" +#include "dtp20.h" + +static inst_code dtp20_interp_code(inst *pp, int ec); +static inst_code activate_mode(dtp20 *p); + +#define MIN_MES_SIZE 10 /* Minimum normal message reply size */ +#define MAX_MES_SIZE 500 /* Maximum normal message reply size */ +#define MAX_RD_SIZE 100000 /* Maximum reading messagle reply size */ + +/* 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 ? (ie. multiple errors ?)*/ + rv &= 0x7f; + return rv; +} + +/* Interpret an icoms error into a DTP20 error */ +static int icoms2dtp20_err(int se) { + if (se != ICOM_OK) { + if (se & ICOM_TO) + return DTP20_TIMEOUT; + return DTP20_COMS_FAIL; + } + return DTP20_OK; +} + +/* Do a standard command/response echange with the dtp20 */ +/* Return the instrument error code */ +static inst_code +dtp20_command( +dtp20 *p, +char *in, /* In string */ +char *out, /* Out string buffer */ +int bsize, /* Out buffer size */ +double to) { /* Timout in seconts */ + char tc = '>'; /* Terminating character */ + int ntc = 1; /* Number of terminating characters */ + int rv, se, insize; + + a1logd(p->log, 4, "dtp20: Sending '%s'",icoms_fix(in)); + + insize = strlen(in); + if (insize > 0) { + if ((se = p->icom->usb_control(p->icom, 0x41, 0x00, 0x00, 0x00, (unsigned char *)in, insize, to)) != ICOM_OK) { + a1logd(p->log, 1, "dtp20: send command failed ICOM err 0x%x\n",se); + return dtp20_interp_code((inst *)p, icoms2dtp20_err(se)); + } + } + + if ((se = p->icom->read(p->icom, out, bsize, tc, ntc, to)) != 0) { + a1logd(p->log, 1, "dtp20: read response failed ICOM err 0x%x\n",se); + return dtp20_interp_code((inst *)p, icoms2dtp20_err(se)); + } + + rv = DTP20_OK; + if (tc == '>' && ntc == 1) { /* Expecting DTP type error code */ + rv = extract_ec(out); + if (rv > 0) { + rv &= inst_imask; + if (rv != DTP20_OK) { /* Clear the error */ + char buf[MAX_MES_SIZE]; + p->icom->usb_control(p->icom, 0x41, 0x00, 0x00, 0x00, (unsigned char *)"CE\r", 3, 0.5); + p->icom->read(p->icom, buf, MAX_MES_SIZE, tc, ntc, 0.5); + } + } + } + + a1logd(p->log, 4, "dtp20: response '%s' ICOM err 0x%x\n",icoms_fix(out),rv); + return dtp20_interp_code((inst *)p, rv); +} + +/* Do a command/binary response echange with the dtp20, */ +/* for reading binary spectral values. */ +/* This is for reading binary spectral data */ +/* Return the instrument error code */ +static inst_code +dtp20_bin_command( +dtp20 *p, +char *in, /* In string */ +char *out, /* Out string buffer */ +int bsize, /* Out buffer size & bytes to read */ +double top) { /* Timout in seconds */ + int rv, se, insize; + int bread = 0; + char *op; + + a1logd(p->log, 4, "dtp20: Sending '%s'",icoms_fix(in)); + + insize = strlen(in); + if (insize > 0) { + if ((se = p->icom->usb_control(p->icom, 0x41, 0x00, 0x00, 0x00, (unsigned char *)in, insize, top)) != ICOM_OK) { + a1logd(p->log, 1, "dtp20: send failed ICOM err 0x%x\n",se); + return dtp20_interp_code((inst *)p, icoms2dtp20_err(se)); + } + } + + /* Read in 62 byte chunks */ + for (op = out; bsize > 0;) { + int rsize = 62; + + if (rsize > bsize) + rsize = bsize; + + if ((se = p->icom->usb_read(p->icom, NULL, 0x81, (unsigned char *)op, rsize, &bread, top)) != ICOM_OK) { + if (se == ICOM_SHORT) { + a1logd(p->log, 1, "dtp20: response failed expected %d got %d ICOM err 0x%x\n", + rsize,bread,se); + } else { + a1logd(p->log, 1, "dtp20: response failed ICOM err 0x%x\n",se); + } + return dtp20_interp_code((inst *)p, icoms2dtp20_err(se)); + } + bsize -= bread; + op += bread; + } + + rv = DTP20_OK; + + a1logd(p->log, 4, "dtp20: response '%s' ICOM err 0x%x\n", + icoms_tohex((unsigned char *)out, bread),rv); + return dtp20_interp_code((inst *)p, rv); +} + +/* Establish communications with a DTP20 */ +/* Return DTP_COMS_FAIL on failure to establish communications */ +static inst_code +dtp20_init_coms(inst *pp, baud_rate br, flow_control fc, double tout) { + dtp20 *p = (dtp20 *)pp; + char buf[MAX_MES_SIZE]; + int se; + inst_code ev = inst_ok; + + if (p->icom->port_type(p->icom) == icomt_usb) { + + a1logd(p->log, 4, "dtp20: About to init USB\n"); + + /* Set config, interface, write end point, read end point, read quanta */ + if ((se = p->icom->set_usb_port(p->icom, 1, 0x00, 0x81, icomuf_none, 0, NULL)) != ICOM_OK) { + a1logd(p->log, 1, "dtp20: set_usbe_port failed ICOM err 0x%x\n",se); + return dtp20_interp_code((inst *)p, icoms2dtp20_err(se)); + } + + /* Blind reset it twice - it seems to sometimes hang up */ + /* otherwise under OSX */ + dtp20_command(p, "0PR\r", buf, MAX_MES_SIZE, 0.5); + dtp20_command(p, "0PR\r", buf, MAX_MES_SIZE, 0.5); + + } else { + a1logd(p->log, 1, "dtp20: Failed to find connection to instrument\n"); + return inst_coms_fail; + } + + /* Check instrument is responding */ + if ((ev = dtp20_command(p, "\r", buf, MAX_MES_SIZE, 1.5)) != inst_ok) { + a1logd(p->log, 1, "dtp20: Failed to get a response from instrument\n"); + return inst_coms_fail; + } + + /* Print the general information returned by instrument */ + if (p->log->verb) { + int i, j; + if ((ev = dtp20_command(p, "GI\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok) { + a1logd(p->log, 1, "dtp20: GI command failed with ICOM err 0x%x\n",ev); + return ev; + } + 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; + } + } + } + + a1logd(p->log, 4, "dtp20: Got coms OK\n"); + + p->gotcoms = 1; + return inst_ok; +} + +/* Build a strip definition as a set of passes, including DS command */ +static void +build_strip( +dtp20 *p, +char *tp, /* pointer to string buffer */ +char *name, /* Strip name (7 chars) (not used) */ +int npatch, /* Number of patches in the pass */ +char *pname, /* Pass name (3 chars) (not used) */ +int sguide, /* Guide number (not used) */ +double pwid, /* Patch length in mm */ +double gwid, /* Gap length in mm */ +double twid /* Trailer length in mm (For DTP41T) */ +) { + + /* Enable SID field but don't report it, */ + /* since we expect to be reading DTP20 type charts. */ + /* (Could set to '1' for reading DTP41 type charts) */ + tp[0] = '0'; + + /* Gap width */ + if((gwid <= 0.25)) + tp[1] = '0'; + else if(gwid <= 0.75) + tp[1] = '1'; + else if(gwid <= 1.25) + tp[1] = '2'; + else if(gwid <= 1.75) + tp[1] = '3'; + else if(gwid <= 2.25) + tp[1] = '4'; + else if(gwid <= 2.75) + tp[1] = '5'; + else if(gwid <= 3.25) + tp[1] = '6'; + else + tp[1] = '7'; + + /* Patch width in mm, dd.dd */ + if(pwid <= 6.75) + tp[2] = '0'; + else if(pwid <= 8.0) + tp[2] = '1'; + else if(pwid <= 11.25) + tp[2] = '4'; + else if(pwid <= 12.75) + tp[2] = '6'; + else + tp[2] = '7'; + + /* Number of patches in strip */ + tp += 3; + sprintf(tp, "%02d",npatch); + tp += 2; + + *tp++ = 'D'; /* The DS command */ + *tp++ = 'S'; + *tp++ = '\r'; /* The CR */ + *tp++ = '\000'; /* The end */ + +} + +/* Initialise the DTP20. */ +/* return non-zero on an error, with instrument error code */ +static inst_code +dtp20_init_inst(inst *pp) { + dtp20 *p = (dtp20 *)pp; + char buf[MAX_MES_SIZE]; + inst_code rv = inst_ok; + + a1logd(p->log, 2, "dtp20_init_inst: called\n"); + + if (p->gotcoms == 0) + return inst_no_coms; /* Must establish coms before calling init */ + + /* Reset it (without disconnecting USB or clearing stored data) */ + if ((rv = dtp20_command(p, "0PR\r", buf, MAX_MES_SIZE, 2.0)) != inst_ok) + return rv; +// if ((rv = dtp20_command(p, "RI\r", buf, MAX_MES_SIZE, 2.0)) != inst_ok) +// return rv; + + /* Set Response delimeter to CR */ + if ((rv = dtp20_command(p, "0008CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok) + return rv; + + /* Get the model and version number */ + if ((rv = dtp20_command(p, "SV\r", buf, MAX_MES_SIZE, 0.5)) != inst_ok) + return rv; + + /* Check that it is a DTP20 */ + if ( strlen(buf) < 12 + || (strncmp(buf,"X-Rite DTP20",12) != 0)) + return inst_unknown_model; + +// /* Set Beeper to off */ +// if ((rv = dtp20_command(p, "0001CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok) +// return rv; + + /* Set Beeper to on */ + if ((rv = dtp20_command(p, "0101CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok) + return rv; + + /* Set Automatic Transmit off */ + if ((rv = dtp20_command(p, "0005CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok) + return rv; + + /* Set color data separator to TAB */ + if ((rv = dtp20_command(p, "0207CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok) + return rv; + + /* Set 2 decimal digit resolution */ + if ((rv = dtp20_command(p, "020ACF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok) + return rv; + + /* set default trigger mode */ + p->trig = inst_opt_trig_user_switch; + + /* - - - - - - - - - - - - - - - - - - - - - - - - */ + /* Setup for the type of measurements we want to do */ + /* Set instrument to XYZ mode */ + if ((rv = dtp20_command(p, "0518CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok) + return rv; + + /* Disable multiple data output */ + if ((rv = dtp20_command(p, "001ACF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok) + return rv; + + /* Set Illuminant to D50_2 */ + if ((rv = dtp20_command(p, "0416CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok) + return rv; + + /* Reset retrieval of saved spot readings */ + p->savix = 0; + + a1logd(p->log, 2, "dtp20_init_inst: instrument inited OK\n"); + p->inited = 1; + + return inst_ok; +} + +/* Read a full test chart composed of multiple sheets. */ +/* This only works if the sheets have been read off-line */ +/* DOESN'T use the trigger mode */ +/* Return the inst error code */ +static inst_code dtp20_read_chart( +inst *pp, +int npatch, /* Total patches/values in chart */ +int pich, /* Passes (rows) in chart */ +int sip, /* Steps in each pass (patches in each row) */ +int *pis, /* Passes in each strip (rows in each sheet) */ +int chid, /* Chart ID number */ +ipatch *vals) { /* Pointer to array of values */ + dtp20 *p = (dtp20 *)pp; + inst_code ev = inst_ok; + char buf[MAX_RD_SIZE]; + int cs, sl, ttlp; + double pw, gw; + int u[10]; + int id = -1; + ipatch *tvals; + int six; /* strip index */ + + if (!p->gotcoms) + return inst_no_coms; + if (!p->inited) + return inst_no_init; + + if (!IMODETST(p->mode,inst_mode_s_ref_chart)) + return inst_unsupported; + + /* Confirm that there is a chart ready to read */ + if ((ev = dtp20_command(p, "CS\r", buf, MAX_RD_SIZE, 0.5)) != inst_ok) + return ev; + if (sscanf(buf," %d ", &cs) != 1) + return inst_protocol_error; + if (cs != 3) { + /* Seems to be no chart saved, but double check, in case of old firmware ( < 1.03) */ + if ((ev = dtp20_command(p, "00TS\r", buf, MAX_RD_SIZE, 0.5)) != inst_ok) + return inst_nonesaved; + if (sscanf(buf," %d ", &cs) != 1) + return inst_nonesaved; + if (cs == 0) + return inst_nonesaved; + } + + /* Get the TID */ + if ((ev = dtp20_command(p, "ST\r", buf, MAX_RD_SIZE, 0.5)) != inst_ok) + return ev; + if (sscanf(buf,"Strip Length: %d Total Patches: %d Patch Width: %lf mm Gap Width: %lf mm" + " User 1: %d User 2: %d User 3: %d User 4: %d User 5: %d User 6: %d" + " User 7: %d User 8: %d User 9: %d User 10: %d ", + &sl, &ttlp, &pw, &gw, &u[0], &u[1], &u[2], &u[3], &u[4], + &u[5], &u[6], &u[7], &u[8], &u[9]) != 14) { + return inst_protocol_error; + } + /* Compute the user data/chart id */ + if (u[0] == 0) { /* This seems to be a chart ID */ + id = ((u[1] * 8 + u[2]) * 8 + u[3]) * 8 + u[4]; + } + + /* Check that the chart matches what we're reading */ + /* (Should we have a way of ignoring a chart if mismatch ?) */ + if (ttlp != npatch + || sl != sip + || (id != -1 && id != chid)) { + a1logd(p->log, 2, "dtp20: Got %d, xpt %d patches, got %d xpt %d strip lgth, " + "got %d xpt %d chart id\n",ttlp,npatch,sl,sip,id,chid); + return inst_nochmatch; + } + + a1logv(p->log, 1, "Chart has %d patches, %d per strip, chart id %d\n",ttlp,sl,id); + + /* Disable multiple data output */ + if ((ev = dtp20_command(p, "001ACF\r", buf, MAX_MES_SIZE, 0.5)) != inst_ok) + return ev; + + /* Read each strip's values in turn */ + for (tvals = vals, six = 1; six <= pich; six++, tvals += sip) { + char *tp, cmd[10]; + int i; + + a1logv(p->log, 1, "Reading saved strip %d of %d\n",six,pich); + + /* Select the strip to read */ + sprintf(cmd, "%03d01TS\r",six); + + /* Gather the results in D50_2 XYZ. This instruction seems unreliable! */ + if ((ev = dtp20_command(p, "0518CF\r", buf, MAX_RD_SIZE, 0.5)) != inst_ok) + return ev; + if ((ev = dtp20_command(p, "0518CF\r", buf, MAX_RD_SIZE, 0.5)) != inst_ok) + return ev; + if ((ev = dtp20_command(p, cmd, buf, MAX_RD_SIZE, 0.5)) != inst_ok) + return ev; + + /* Parse the buffer */ + /* Replace '\r' with '\000' */ + for (tp = buf; *tp != '\000'; tp++) { + if (*tp == '\r') + *tp = '\000'; + } + for (tp = buf, i = 0; i < sip; i++) { + if (*tp == '\000' || strlen(tp) > 40) + return inst_protocol_error; + if (sscanf(tp, " %lf %lf %lf ", + &tvals[i].XYZ[0], &tvals[i].XYZ[1], &tvals[i].XYZ[2]) != 3) { + if (sscanf(tp, " %lf %lf %lf ", + &tvals[i].XYZ[0], &tvals[i].XYZ[1], &tvals[i].XYZ[2]) != 3) { + return inst_protocol_error; + } + } + tvals[i].loc[0] = '\000'; + tvals[i].mtype = inst_mrt_reflective; + tvals[i].XYZ_v = 1; + tvals[i].sp.spec_n = 0; + tvals[i].duration = 0.0; + tp += strlen(tp) + 1; + } + + if (p->mode & inst_mode_spectral) { + + /* Gather the results in Spectral reflectance */ + if ((ev = dtp20_command(p, "0318CF\r", buf, MAX_RD_SIZE, 0.5)) != inst_ok) + return ev; + /* Set to binary */ + if ((ev = dtp20_command(p, "011BCF\r", buf, MAX_RD_SIZE, 0.5)) != inst_ok) + return ev; + /* Read the strip */ + if ((ev = dtp20_bin_command(p, cmd, buf, 62 * sip, 5.0)) != inst_ok) + return ev; + + /* Get each patches spectra */ + for (tp = buf, i = 0; i < sip; i++) { + int j; + + /* Read the spectral value */ + for (j = 0; j < 31; j++, tp += 2) { + int vv; + vv = (unsigned char)tp[0]; + vv = vv * 256 + (unsigned char)tp[1]; + tvals[i].sp.spec[j] = vv * 200.0/65535.0; + } + + tvals[i].sp.spec_n = 31; + tvals[i].sp.spec_wl_short = 400.0; + tvals[i].sp.spec_wl_long = 700.0; + tvals[i].sp.norm = 100.0; + } + + /* Set to ASCII */ + if ((ev = dtp20_command(p, "001BCF\r", buf, MAX_MES_SIZE, 0.5)) != inst_ok) + return ev; + /* Set back to in D50_2 XYZ. This instruction seems unreliable! */ + if ((ev = dtp20_command(p, "0518CF\r", buf, MAX_RD_SIZE, 0.5)) != inst_ok) + return ev; + } + } + + a1logv(p->log, 1, "All saved strips read\n"); + return inst_ok; +} + +/* Read a set of strips. This only works reading on-line */ +/* Return the instrument error code */ +static inst_code +dtp20_read_strip( +inst *pp, +char *name, /* Strip name (7 chars) */ +int npatch, /* Number of patches in the pass */ +char *pname, /* Pass name (3 chars) */ +int sguide, /* Guide number */ +double pwid, /* Patch length in mm (For DTP20/DTP41) */ +double gwid, /* Gap length in mm (For DTP20/DTP41) */ +double twid, /* Trailer length in mm (For DTP41T) */ +ipatch *vals) { /* Pointer to array of instrument patch values */ + dtp20 *p = (dtp20 *)pp; + char tbuf[200], *tp; + char buf[MAX_RD_SIZE]; + int i, cs, se; + inst_code ev = inst_ok; + int user_trig = 0; + int switch_trig = 0; + + if (!p->gotcoms) + return inst_no_coms; + if (!p->inited) + return inst_no_init; + + /* This funtion isn't valid in saved data mode */ + if (!IMODETST(p->mode, inst_mode_ref_strip)) + return inst_wrong_setup; + + /* Until we get the right status or give up */ + for (i = 0;;i++) { + /* Confirm that we are ready to read strips */ + if ((ev = dtp20_command(p, "CS\r", buf, MAX_RD_SIZE, 0.5)) != inst_ok) + return ev; + if (sscanf(buf," %d ", &cs) != 1) + return inst_protocol_error; + if (cs == 1) + break; /* Read and empty of offline patches */ + if (cs == 2 || cs == 3) + return (inst_misread | DTP20_NOT_EMPTY); /* Has onffline patches */ + if (i < 20) { + if (cs == 0 || (cs >= 4 && cs <= 12)) { /* Ignore transient status */ + msec_sleep(100); + continue; + } + } + return (inst_misread | DTP20_UNEXPECTED_STATUS); + } + + /* Send strip definition */ + build_strip(p, tbuf, name, npatch, pname, sguide, pwid, gwid, twid); + + if ((ev = dtp20_command(p, tbuf, buf, MAX_MES_SIZE, 1.5)) != inst_ok) { + a1logv(p->log, 1, "Interactive strip reading won't work on Firmware earlier than V1.03 !\n"); + return ev; + } + + if (p->trig == inst_opt_trig_user_switch) { + int touts = 0; + + /* Wait for a strip value to turn up, or a user command/abort */ + for (;;) { + + ev = dtp20_command(p, "CS\r", buf, MAX_MES_SIZE, 0.2); + + if (ev == (inst_coms_fail | DTP20_TIMEOUT)) { + /* Ignore transient timeouts */ + if (touts++ > 40) + return ev; + continue; + + } else if (ev != inst_ok) { + if ((ev & inst_mask) == inst_needs_cal) + p->need_cal = 1; + return ev; /* Error */ + } else { + int stat; + if (sscanf(buf, " %d ", &stat) != 1) + stat = 6; + + /* Ignore benign status */ + if (stat != 4 && stat != 6 && stat != 7) { + /* Not ready - Check for user trigger or command */ + if (p->uicallback != NULL) { + if ((ev = p->uicallback(p->uic_cntx, inst_armed)) != inst_ok) { + if (ev == inst_user_abort) { + return ev; /* Error or user abort */ + } else if (ev == inst_user_trig) { + user_trig = 1; /* User trigger */ + break; + } + } + } + /* Keep waiting */ + msec_sleep(200); + continue; + + } else { + /* Ready - continue on */ + switch_trig = 1; + break; + } + } + } + /* 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, "dtp20: 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; + if (ev == inst_user_trig) { + user_trig = 1; + 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; + } + + /* Trigger a read if the switch has not been used */ + if (switch_trig == 0) { + /* Do a strip read */ + if ((ev = dtp20_command(p, "RM\r", buf, MAX_MES_SIZE, 10.0)) != inst_ok) { + if ((ev & inst_mask) == inst_needs_cal) + p->need_cal = 1; + return ev; + } + } + + /* Disable multiple data output */ + if ((ev = dtp20_command(p, "001ACF\r", buf, MAX_MES_SIZE, 0.5)) != inst_ok) + return ev; + /* Gather the results in D50_2 XYZ This instruction seems unreliable! */ + if ((ev = dtp20_command(p, "0518CF\r", buf, MAX_RD_SIZE, 0.5)) != inst_ok) + return ev; + if ((ev = dtp20_command(p, "0518CF\r", buf, MAX_RD_SIZE, 0.5)) != inst_ok) + return ev; + if ((ev = dtp20_command(p, "01TS\r", buf, MAX_RD_SIZE, 0.5)) != inst_ok) + return ev; + + /* Parse the buffer */ + /* Replace '\r' with '\000' */ + for (tp = buf; *tp != '\000'; tp++) { + if (*tp == '\r') + *tp = '\000'; + } + for (tp = buf, i = 0; i < npatch; i++) { + if (*tp == '\000' || strlen(tp) > 40) + return inst_protocol_error; + if (sscanf(tp, " %lf %lf %lf ", + &vals[i].XYZ[0], &vals[i].XYZ[1], &vals[i].XYZ[2]) != 3) { + if (sscanf(tp, " %lf %lf %lf ", + &vals[i].XYZ[0], &vals[i].XYZ[1], &vals[i].XYZ[2]) != 3) { + return inst_protocol_error; + } + } + vals[i].loc[0] = '\000'; + vals[i].mtype = inst_mrt_reflective; + vals[i].XYZ_v = 1; + vals[i].sp.spec_n = 0; + vals[i].duration = 0.0; + tp += strlen(tp) + 1; + } + + if (p->mode & inst_mode_spectral) { + + /* Gather the results in Spectral reflectance */ + if ((ev = dtp20_command(p, "0318CF\r", buf, MAX_RD_SIZE, 0.5)) != inst_ok) + return ev; + /* Set to binary */ + if ((ev = dtp20_command(p, "011BCF\r", buf, MAX_RD_SIZE, 0.5)) != inst_ok) + return ev; + + /* Read the strip */ + if ((ev = dtp20_bin_command(p, "01TS\r", buf, 62 * npatch, 5.0)) != inst_ok) + return ev; + + /* Get each patches spectra */ + for (tp = buf, i = 0; i < npatch; i++) { + int j; + + /* Read the spectral value */ + for (j = 0; j < 31; j++, tp += 2) { + int vv; + vv = (unsigned char)tp[0]; + vv = vv * 256 + (unsigned char)tp[1]; + vals[i].sp.spec[j] = vv * 200.0/65535.0; + } + + vals[i].sp.spec_n = 31; + vals[i].sp.spec_wl_short = 400.0; + vals[i].sp.spec_wl_long = 700.0; + vals[i].sp.norm = 100.0; + } + + /* Set to ASCII */ + if ((ev = dtp20_command(p, "001BCF\r", buf, MAX_MES_SIZE, 0.5)) != inst_ok) + return ev; + /* Set back to D50 2 degree */ + if ((ev = dtp20_command(p, "0518CF\r", buf, MAX_RD_SIZE, 0.5)) != inst_ok) + return ev; + } + + /* Wait for status to change */ + for (;;) { + if ((ev = dtp20_command(p, "CS\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok) { + return ev; + } else { + int stat = 4; + if (sscanf(buf, " %d ", &stat) != 1 || stat != 4) + break; + msec_sleep(200); + } + } + + if (user_trig) + return inst_user_trig; + return inst_ok; +} + + +/* Read a single sample. */ +/* This works both on-line and off-line. */ +/* Return the instrument error code */ +static inst_code +dtp20_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 */ + dtp20 *p = (dtp20 *)pp; + char buf[MAX_MES_SIZE], *tp; + 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; + + /* This combination doesn't make any sense... */ + if (IMODETST(p->mode, inst_mode_s_ref_spot) && p->trig == inst_opt_trig_user_switch) { + return inst_wrong_setup; + } + + if (p->trig == inst_opt_trig_user_switch) { + int touts = 0; + + /* Wait for a sample value to turn up, or a user abort */ + for (;;) { + + /* Check for user trigger */ + if (p->uicallback != NULL) { + if ((ev = p->uicallback(p->uic_cntx, inst_armed)) != inst_ok) { + if (ev == inst_user_abort) + return ev; /* User abort */ + if (ev == inst_user_trig) { + user_trig = 1; + break; + } + } + } + + /* Check for an instrument switch trigger */ + ev = dtp20_command(p, "CS\r", buf, MAX_MES_SIZE, 0.2); + + if (ev == (inst_coms_fail | DTP20_TIMEOUT)) { /* Assume we're waiting for trigger */ + if (touts++ > 20) + return ev; + continue; + + } else if (ev != inst_ok) { + if ((ev & inst_mask) == inst_needs_cal) + p->need_cal = 1; + return ev; /* Error */ + + } else { /* Got a CS response */ + int stat; + if (sscanf(buf, " %d ", &stat) != 1) + stat = 6; + /* Ingnore benign status */ + if (stat != 4 && stat != 6 && stat != 7) { + msec_sleep(200); + continue; + } + switch_trig = 1; + break; + } + } + /* 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, "dtp20: 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) { + user_trig = 1; + 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 */ + } + + /* Read saved spot values */ + if (IMODETST(p->mode, inst_mode_s_ref_spot)) { + char cmd[10]; + int nsr; + + /* See how many saved spot readings there are */ + if ((ev = dtp20_command(p, "00GM\r", buf, MAX_MES_SIZE, 0.5)) != inst_ok) + return ev; + if (sscanf(buf," %d ", &nsr) != 1) + return inst_protocol_error; + + if (nsr <= p->savix) + return inst_nonesaved; + + p->savix++; + sprintf(cmd, "%03d01GM\r",p->savix); + /* Disable multiple data output */ + if ((ev = dtp20_command(p, "001ACF\r", buf, MAX_MES_SIZE, 0.5)) != inst_ok) + return ev; + /* Gather the results in D50_2 XYZ This instruction seems unreliable! */ + if ((ev = dtp20_command(p, "0518CF\r", buf, MAX_MES_SIZE, 0.5)) != inst_ok) + return ev; + if ((ev = dtp20_command(p, "0518CF\r", buf, MAX_MES_SIZE, 0.5)) != inst_ok) + return ev; + if ((ev = dtp20_command(p, cmd, buf, MAX_MES_SIZE, 0.5)) != inst_ok) + return ev; + + /* Read real spot values */ + } else { + /* Trigger a read if the switch has not been used */ + if (switch_trig == 0) { + /* Do a spot read */ + if ((ev = dtp20_command(p, "RM\r", buf, MAX_MES_SIZE, 5.0)) != inst_ok) { + if ((ev & inst_mask) == inst_needs_cal) + p->need_cal = 1; + return ev; + } + } + + /* Disable multiple data output */ + if ((ev = dtp20_command(p, "001ACF\r", buf, MAX_MES_SIZE, 0.5)) != inst_ok) + return ev; + /* Gather the results in D50_2 XYZ */ + if ((ev = dtp20_command(p, "0518CF\r", buf, MAX_MES_SIZE, 0.5)) != inst_ok) + return ev; + if ((ev = dtp20_command(p, "0518CF\r", buf, MAX_MES_SIZE, 0.5)) != inst_ok) + return ev; + if ((ev = dtp20_command(p, "01GM\r", buf, MAX_MES_SIZE, 0.5)) != inst_ok) + return ev; + + } + + val->XYZ[0] = val->XYZ[1] = val->XYZ[2] = 0.0; + + if (*buf == '\000' || strlen(buf) > 40) + return inst_protocol_error; + + if (sscanf(buf, " %lf %lf %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; + + /* Set to read spectral reflectance */ + if ((ev = dtp20_command(p, "0318CF\r", buf, MAX_MES_SIZE, 0.5)) != inst_ok) + return ev; + /* Set to binary */ + if ((ev = dtp20_command(p, "011BCF\r", buf, MAX_MES_SIZE, 0.5)) != inst_ok) + return ev; + /* Get measurement */ + if ((ev = dtp20_bin_command(p, "01GM\r", buf, 62, 2.0)) != inst_ok) + return ev; + + for (j = 0; j < 31; j++) + val->sp.spec[j] = 0.0; + + /* Read the spectral value */ + for (tp = buf, j = 0; j < 31; j++, tp += 2) { + int vv; + vv = (unsigned char)tp[0]; + vv = vv * 256 + (unsigned char)tp[1]; + val->sp.spec[j] = vv * 200.0/65535.0; + } + + val->sp.spec_n = 31; + val->sp.spec_wl_short = 400.0; + val->sp.spec_wl_long = 700.0; + val->sp.norm = 100.0; + + /* Set to ASCII */ + if ((ev = dtp20_command(p, "001BCF\r", buf, MAX_MES_SIZE, 0.5)) != inst_ok) + return ev; + /* Set to D50 2 degree */ + if ((ev = dtp20_command(p, "0518CF\r", buf, MAX_MES_SIZE, 0.5)) != inst_ok) + return ev; + } + + if (!IMODETST(p->mode, inst_mode_s_ref_spot)) { + + /* Clear the spot database so our reading doesn't appear as a stored reading */ + if ((ev = dtp20_command(p, "02CD\r", buf, MAX_MES_SIZE, 1.0)) != inst_ok) + return ev; + p->savix = 0; + + /* Wait for status to change */ + for (;;) { + if ((ev = dtp20_command(p, "CS\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok) { + return ev; + } else { + int stat = 4; + if (sscanf(buf, " %d ", &stat) != 1 || stat != 4) + break; + msec_sleep(200); + } + } + } + + if (user_trig) + return inst_user_trig; + return inst_ok; +} + +/* Return needed and available inst_cal_type's */ +static inst_code dtp20_get_n_a_cals(inst *pp, inst_cal_type *pn_cals, inst_cal_type *pa_cals) { + dtp20 *p = (dtp20 *)pp; + inst_cal_type n_cals = inst_calt_none; + inst_cal_type a_cals = inst_calt_none; + + if (p->need_cal) + 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 dtp20_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) */ +) { + dtp20 *p = (dtp20 *)pp; + char buf[MAX_MES_SIZE]; + inst_code ev; + inst_cal_type needed, available; + + if (!p->gotcoms) + return inst_no_coms; + if (!p->inited) + return inst_no_init; + + id[0] = '\000'; + + if ((ev = dtp20_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,"dtp20_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) { + + if (*calc != inst_calc_man_ref_white) { + char *cp; + if ((ev = dtp20_command(p, "04SN\r", buf, MAX_MES_SIZE, 4.5)) != inst_ok) + return ev; + for (cp = buf; *cp >= '0' && *cp <= '9'; cp++) + ; + *cp = '\000'; + strcpy(id, buf); + *calc = inst_calc_man_ref_white; + return inst_cal_setup; + } + + if ((ev = dtp20_command(p, "CR\r", buf, MAX_MES_SIZE, 4.5)) != inst_ok) + return ev; + + p->need_cal = 0; + *calt &= ~inst_calt_ref_white; + } + + return inst_ok; +} + +/* Error codes interpretation */ +static char * +dtp20_interp_error(inst *pp, int ec) { +// dtp20 *p = (dtp20 *)pp; + ec &= inst_imask; + switch (ec) { + + case DTP20_INTERNAL_ERROR: + return "Internal software error"; + case DTP20_COMS_FAIL: + return "Communications failure"; + case DTP20_UNKNOWN_MODEL: + return "Not a DTP20"; + case DTP20_DATA_PARSE_ERROR: + return "Data from DTP didn't parse as expected"; + + case DTP20_NOT_EMPTY: + return "Trying to read strips when there is already\n" + "an offline chart partially read. Clear the instrument and try again"; + case DTP20_UNEXPECTED_STATUS: + return "Unexpected instrument status"; + + case DTP20_OK: + return "No device error"; + + case DTP20_MEASUREMENT_STATUS: + return "Measurement complete"; + + case DTP20_BAD_COMMAND: + return "Unrecognised command"; + case DTP20_BAD_PARAMETERS: + return "Wrong number of parameters"; + case DTP20_PRM_RANGE_ERROR: + return "One or more parameters are out of range"; + case DTP20_BUSY: + return "Instrument is busy - command ignored"; + + case DTP20_MEASUREMENT_ERROR: + return "General measurement error"; + case DTP20_TIMEOUT: + return "Receive timeout"; + case DTP20_BAD_STRIP: + return "Bad strip"; + + case DTP20_NEEDS_CAL_ERROR: + return "Instrument needs calibration"; + case DTP20_CAL_FAILURE_ERROR: + return "Calibration failed"; + + case DTP20_INSTRUMENT_ERROR: + return "General instrument error"; + case DTP20_LAMP_ERROR: + return "Reflectance lamp error"; + + case DTP20_BAD_TID: + return "Invalid TID detected, Re-scan TID"; + case DTP20_FLASH_ERASE_FAILURE: + return "Flash erase operation failed, Contact support"; + case DTP20_FLASH_WRITE_FAILURE: + return "Flash write operation failed, Contact support"; + case DTP20_FLASH_VERIFY_FAILURE: + return "Flash verify operation failed, Contact support"; + case DTP20_MEMORY_ERROR: + return "Memory access failed, Contact support"; + case DTP20_ADC_ERROR: + return "Analog to digital converter error, Contact support"; + case DTP20_PROCESSOR_ERROR: + return "General processor error, Contact support"; + case DTP20_BATTERY_ERROR: + return "General battery error occurred, Contact support"; + case DTP20_BATTERY_LOW_ERROR: + return "Battery level too low to measure, Charge battery"; + case DTP20_INPUT_POWER_ERROR: + return "Input power out of range, Contact support"; + + case DTP20_BATTERY_ABSENT_ERROR: + return "Battery could not be detected, Contact support"; + case DTP20_BAD_CONFIGURATION: + return "Stored configuration data invalid, Set as desired"; + + case DTP20_BAD_SPOT: + return "Invalid spot reading was requested, Re-read or resend"; + case DTP20_END_OF_DATA: + return "End of profile reached, None"; + case DTP20_DBASE_PROFILE_NOT_EMPTY: + return "Profile database not empty, Clear profile data"; + case DTP20_MEMORY_OVERFLOW_ERROR: + return "Memory overflow error, Contact support"; + case DTP20_BAD_CALIBRATION: + return "Bad calibration data detected, Contact support"; + + case DTP20_CYAN_CAL_ERROR: + return "Failed cyan calibration during TID read, Re-scan TID"; + case DTP20_MAGENTA_CAL_ERROR: + return "Failed magenta calibration during TID read, Re-scan TID"; + case DTP20_YELLOW_CAL_ERROR: + return "Failed yellow calibration during TID read, Re-scan TID"; + case DTP20_PATCH_SIZE_ERROR: + return "Invalid strip patch size was detected, Re-scan"; + case DTP20_FAIL_PAPER_CHECK: + return "Failed to verify scan started/stopped on paper, Re-scan"; + case DTP20_SHORT_SCAN_ERROR: + return "Less than minimum positional ticks detected, Re-scan"; + case DTP20_STRIP_READ_ERROR: + return "General strip reading error, Re-scan"; + case DTP20_SHORT_TID_ERROR: + return "Failed TID length verification, Re-scan TID"; + case DTP20_SHORT_STRIP_ERROR: + return "Strip length invalid, Re-scan"; + case DTP20_EDGE_COLOR_ERROR: + return "Strip edge color was measured invalid, Re-scan"; + case DTP20_SPEED_ERROR: + return "Manual scan too fast to gather data, Re-scan"; + case DTP20_UNDEFINED_SCAN_ERROR: + return "General scan error, Re-scan"; + case DTP20_INVALID_STRIP_ID: + return "A strip ID field was out-of-range, Re-scan"; + case DTP20_BAD_SERIAL_NUMBER: + return "A bad serial number has been detected, Contact support"; + case DTP20_TID_ALREADY_SCANNED: + return "A TID has already been scanned, Scan strips"; + case DTP20_PROFILE_DATABASE_FULL: + return "Profile database is full, Clear profile data"; + + case DTP20_SPOT_DATABASE_FULL: + return "Spot database is full, Clear spot data"; + case DTP20_TID_STRIP_MIN_ERROR: + return "A TID was specified with fewer than 5 patches, Re-define TID"; + case DTP20_REREAD_DATABASE_FULL: + return "Strip reread database is full (can't reread), Clear profile data"; + case DTP20_STRIP_DEFINE_TOO_SHORT: + return "Strip definition contains too few patches, Re-define strip"; + case DTP20_STRIP_DEFINE_TOO_LONG: + return "Strip definition contains too many patches, Re-define strip"; + case DTP20_BAD_STRIP_DEFINE: + return "No valid strip defined, Define strip"; + + case DTP20_BOOTLOADER_MODE: + return "Instrument is in FW update mode, Reset or load FW"; + + default: + return "Unknown error code"; + } +} + +/* Convert a machine specific error code into an abstract dtp code */ +static inst_code +dtp20_interp_code(inst *pp, int ec) { +// dtp20 *p = (dtp20 *)pp; + + switch (ec) { + + case DTP20_OK: + return inst_ok; + + case DTP20_MEASUREMENT_STATUS: + case DTP20_END_OF_DATA: + return inst_notify | ec; + + case DTP20_INTERNAL_ERROR: + return inst_internal_error | ec; + + case DTP20_TIMEOUT: + case DTP20_COMS_FAIL: + return inst_coms_fail | ec; + + case DTP20_UNKNOWN_MODEL: + return inst_unknown_model | ec; + + case DTP20_DATA_PARSE_ERROR: + case DTP20_BAD_COMMAND: + case DTP20_BAD_PARAMETERS: + case DTP20_PRM_RANGE_ERROR: + case DTP20_BUSY: + return inst_protocol_error | ec; + + case DTP20_MEASUREMENT_ERROR: + case DTP20_BAD_STRIP: + case DTP20_BAD_SPOT: + case DTP20_CAL_FAILURE_ERROR: + case DTP20_BAD_TID: + case DTP20_CYAN_CAL_ERROR: + case DTP20_MAGENTA_CAL_ERROR: + case DTP20_YELLOW_CAL_ERROR: + case DTP20_PATCH_SIZE_ERROR: + case DTP20_FAIL_PAPER_CHECK: + case DTP20_SHORT_SCAN_ERROR: + case DTP20_STRIP_READ_ERROR: + case DTP20_SHORT_TID_ERROR: + case DTP20_SHORT_STRIP_ERROR: + case DTP20_EDGE_COLOR_ERROR: + case DTP20_SPEED_ERROR: + case DTP20_UNDEFINED_SCAN_ERROR: + case DTP20_INVALID_STRIP_ID: + case DTP20_TID_ALREADY_SCANNED: + case DTP20_NOT_EMPTY: + case DTP20_UNEXPECTED_STATUS: + return inst_misread | ec; + + case DTP20_NEEDS_CAL_ERROR: + return inst_needs_cal | ec; + + case DTP20_INSTRUMENT_ERROR: + case DTP20_LAMP_ERROR: + case DTP20_FLASH_ERASE_FAILURE: + case DTP20_FLASH_WRITE_FAILURE: + case DTP20_FLASH_VERIFY_FAILURE: + case DTP20_MEMORY_ERROR: + case DTP20_ADC_ERROR: + case DTP20_PROCESSOR_ERROR: + case DTP20_BATTERY_ERROR: + case DTP20_INPUT_POWER_ERROR: + case DTP20_BATTERY_LOW_ERROR: + case DTP20_BATTERY_ABSENT_ERROR: + case DTP20_MEMORY_OVERFLOW_ERROR: + case DTP20_BAD_CALIBRATION: + case DTP20_BAD_SERIAL_NUMBER: + return inst_hardware_fail | ec; + + case DTP20_BAD_CONFIGURATION: + case DTP20_DBASE_PROFILE_NOT_EMPTY: + case DTP20_PROFILE_DATABASE_FULL: + case DTP20_SPOT_DATABASE_FULL: + case DTP20_TID_STRIP_MIN_ERROR: + case DTP20_REREAD_DATABASE_FULL: + case DTP20_STRIP_DEFINE_TOO_SHORT: + case DTP20_STRIP_DEFINE_TOO_LONG: + case DTP20_BAD_STRIP_DEFINE: + case DTP20_BOOTLOADER_MODE: + return inst_wrong_setup | ec; + } + return inst_other_error | ec; +} + +/* Destroy ourselves */ +static void +dtp20_del(inst *pp) { + dtp20 *p = (dtp20 *)pp; + if (p->icom != NULL) + p->icom->del(p->icom); + free (p); +} + +/* Set the instrument capabilities */ +static void set_capabilities(dtp20 *p) { + + p->cap = inst_mode_ref_spot + | inst_mode_ref_strip + | inst_mode_s_ref_spot + | inst_mode_s_ref_chart + | inst_mode_colorimeter + | inst_mode_spectral + ; + + p->cap2 = inst2_prog_trig + | inst2_user_switch_trig + | inst2_user_trig + | inst2_has_battery + ; + + p->cap3 = inst3_none; +} + + +/* Return the instrument capabilities */ +void dtp20_capabilities(inst *pp, +inst_mode *cap1, +inst2_capability *cap2, +inst3_capability *cap3) { + dtp20 *p = (dtp20 *)pp; + + if (p->cap == inst_mode_none) + set_capabilities(p); + + if (cap1 != NULL) + *cap1 = p->cap; + if (cap2 != NULL) + *cap2 = p->cap2; + if (cap3 != NULL) + *cap3 = p->cap3; +} + +/* + * check measurement mode + */ +static inst_code +dtp20_check_mode(inst *pp, inst_mode m) { + dtp20 *p = (dtp20 *)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; + + /* Check specific modes */ + if (!IMODETST(m, inst_mode_ref_spot) + && !IMODETST(m, inst_mode_ref_strip) + && !IMODETST(m, inst_mode_s_ref_spot) + && !IMODETST(m, inst_mode_s_ref_chart)) { + return inst_unsupported; + } + + return inst_ok; +} + +/* + * set measurement mode + */ +static inst_code +dtp20_set_mode(inst *pp, inst_mode m) { + dtp20 *p = (dtp20 *)pp; + inst_code ev; + + if ((ev = dtp20_check_mode(pp, m)) != inst_ok) + return ev; + + p->mode = m; + + return inst_ok; +} + +/* Get a status or get or set an option */ +static inst_code dtp20_get_set_opt( +inst *pp, +inst_opt_type m, /* Requested status type */ +...) { /* Status parameters */ + dtp20 *p = (dtp20 *)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; + } + + if (!p->gotcoms) + return inst_no_coms; + if (!p->inited) + return inst_no_init; + + if (m == inst_stat_saved_readings) { + char buf[MAX_MES_SIZE]; + int ev; + va_list args; + inst_stat_savdrd *fe; + int nsr, cs; + + va_start(args, m); + fe = va_arg(args, inst_stat_savdrd *); + va_end(args); + + *fe = inst_stat_savdrd_none; + + /* See how many saved spot readings there are */ + if ((ev = dtp20_command(p, "00GM\r", buf, MAX_MES_SIZE, 0.5)) != inst_ok) + return ev; + if (sscanf(buf," %d ", &nsr) != 1) + return inst_protocol_error; + if (nsr > p->savix) + *fe |= inst_stat_savdrd_spot; + + /* See if the instrument has read a partial or complete target */ + if ((ev = dtp20_command(p, "CS\r", buf, MAX_MES_SIZE, 0.5)) != inst_ok) + return ev; + if (sscanf(buf," %d ", &cs) != 1) + return inst_protocol_error; + if (0 && (cs == 2 || cs == 3)) { // ??? Is this unreliable ??? + *fe |= inst_stat_savdrd_chart; + } else { + /* Seems to be no chart saved, but double check, in case of old firmware */ + if ((ev = dtp20_command(p, "00TS\r", buf, MAX_MES_SIZE, 0.5)) == inst_ok) { + if (sscanf(buf," %d ", &cs) == 1) { + if (cs != 0) { + *fe |= inst_stat_savdrd_chart; + } + } + } + } + return inst_ok; + } + + /* Return the number of saved spot readings */ + if (m == inst_stat_s_spot) { + int ev; + char buf[MAX_MES_SIZE]; + int *pnsr; + va_list args; + + va_start(args, m); + pnsr = va_arg(args, int *); + va_end(args); + + *pnsr = -1; + + /* See how many saved spot readings there are */ + if ((ev = dtp20_command(p, "00GM\r", buf, MAX_MES_SIZE, 0.5)) != inst_ok) + return ev; + if (sscanf(buf," %d ", pnsr) != 1) + return inst_protocol_error; + + if (*pnsr -= p->savix) /* ??? */ + + return inst_ok; + } + + /* Return the saved chart details */ + if (m == inst_stat_s_chart) { + inst_code ev = inst_ok; + char buf[MAX_RD_SIZE], *bp, *ep; + int u[10]; + va_list args; + int *no_patches, *no_rows, *pat_per_row, *chart_id, *missing_row; + int i, cs; + double pw, gw; + + va_start(args, m); + no_patches = va_arg(args, int *); + no_rows = va_arg(args, int *); + pat_per_row = va_arg(args, int *); + chart_id = va_arg(args, int *); + missing_row = va_arg(args, int *); + va_end(args); + + *no_patches = *no_rows = *pat_per_row = *chart_id = *missing_row = -1; + + /* Get the TID */ + if ((ev = dtp20_command(p, "ST\r", buf, MAX_RD_SIZE, 0.5)) != inst_ok) + return ev; + if (sscanf(buf,"Strip Length: %d Total Patches: %d Patch Width: %lf mm Gap Width: %lf mm" + " User 1: %d User 2: %d User 3: %d User 4: %d User 5: %d User 6: %d" + " User 7: %d User 8: %d User 9: %d User 10: %d ", + pat_per_row, no_patches, &pw, &gw, &u[0], &u[1], &u[2], &u[3], &u[4], + &u[5], &u[6], &u[7], &u[8], &u[9]) != 14) { + return inst_protocol_error; + } + /* Compute number of rows */ + *no_rows = *no_patches / *pat_per_row; + + /* Compute the user data/chart id */ + if (u[0] == 0) /* This seems to be a chart ID */ + *chart_id = ((u[1] * 8 + u[2]) * 8 + u[3]) * 8 + u[4]; + + /* See if any strips are missing */ + if ((ev = dtp20_command(p, "CS\r", buf, MAX_MES_SIZE, 0.5)) != inst_ok) + return ev; + if (sscanf(buf," %d ", &cs) != 1) + return inst_protocol_error; + if (cs == 2) { + if ((ev = dtp20_command(p, "01TT\r", buf, MAX_MES_SIZE, 0.5)) != inst_ok) + return ev; + bp = buf; + for (i = 1; i <= *no_rows; i++) { + int strno = 0; + + /* Locate the next number */ + while(*bp != '\000' && (*bp < '0' || *bp > '9')) + bp++; + + /* Locate the end of the number */ + ep = bp; + while (*ep != '\000' && *ep >= '0' && *ep <= '9') + ep++; + *ep = '\000'; + + if (ep > bp) + strno = atoi(bp); + if (strno != i) { + *missing_row = i; /* Assume no missing rows */ + break; + } + bp = ep+1; + if (bp >= (buf + MAX_MES_SIZE)) + return inst_protocol_error; + } + } + + return inst_ok; + } + + /* Return the charged status of the battery */ + if (m == inst_stat_battery) { + int ev; + char buf[MAX_MES_SIZE]; + double *pbchl; + va_list args; + int cs; + + va_start(args, m); + pbchl = va_arg(args, double *); + va_end(args); + + *pbchl = -1.0; + + /* See how charged the battery is */ + if ((ev = dtp20_command(p, "06BA\r", buf, MAX_MES_SIZE, 0.5)) != inst_ok) + return ev; + if (sscanf(buf," %d ", &cs) != 1) + return inst_protocol_error; + + if (cs == 4) + *pbchl = 1.0; + else if (cs == 3) + *pbchl = 0.75; + else if (cs == 2) + *pbchl = 0.50; + else if (cs == 1) + *pbchl = 0.25; + else + *pbchl = 0.0; + + return inst_ok; + } + + /* !! It's not clear if there is a way of knowing */ + /* whether the instrument has a UV filter. */ + + /* Use default implementation of other inst_opt_type's */ + { + inst_code rv; + va_list args; + + va_start(args, m); + rv = inst_get_set_opt_def(pp, m, args); + va_end(args); + + return rv; + } +} + +/* Constructor */ +extern dtp20 *new_dtp20(icoms *icom, instType itype) { + dtp20 *p; + if ((p = (dtp20 *)calloc(sizeof(dtp20),1)) == NULL) { + a1loge(icom->log, 1, "new_dtp20: malloc failed!\n"); + return NULL; + } + + p->log = new_a1log_d(icom->log); + + p->init_coms = dtp20_init_coms; + p->init_inst = dtp20_init_inst; + p->capabilities = dtp20_capabilities; + p->check_mode = dtp20_check_mode; + p->set_mode = dtp20_set_mode; + p->get_set_opt = dtp20_get_set_opt; + p->read_chart = dtp20_read_chart; + p->read_strip = dtp20_read_strip; + p->read_sample = dtp20_read_sample; + p->get_n_a_cals = dtp20_get_n_a_cals; + p->calibrate = dtp20_calibrate; + p->interp_error = dtp20_interp_error; + p->del = dtp20_del; + + p->icom = icom; + p->itype = icom->itype; + p->cap = inst_mode_none; /* Unknown until set */ + p->mode = inst_mode_none; /* Not in a known mode yet */ + + return p; +} -- cgit v1.2.3