summaryrefslogtreecommitdiff
path: root/spectro/kleink10.c
diff options
context:
space:
mode:
Diffstat (limited to 'spectro/kleink10.c')
-rw-r--r--spectro/kleink10.c2810
1 files changed, 2810 insertions, 0 deletions
diff --git a/spectro/kleink10.c b/spectro/kleink10.c
new file mode 100644
index 0000000..d632bdb
--- /dev/null
+++ b/spectro/kleink10.c
@@ -0,0 +1,2810 @@
+
+/*
+ * Argyll Color Correction System
+ *
+ * JETI kleink10 1211/1201 related functions
+ *
+ * Author: Graeme W. Gill
+ * Date: 29/4/2014
+ *
+ * Copyright 1996 - 2014, 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.
+ *
+ * Based on DTP92.c & specbos.c
+ */
+
+/*
+ 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.
+ */
+
+/*
+
+ TTBD:
+
+*/
+
+#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 "kleink10.h"
+
+#undef HIGH_SPEED /* [und] Use high speed flicker measure for refresh rate etc. */
+#define AUTO_AVERAGE /* [def] Automatically average more readings for low light */
+#define RETRY_RANGE_ERROR 3 /* [3] Retry range error readings 3 times */
+
+#undef PLOT_REFRESH /* [und] Plot refresh rate measurement info */
+#undef PLOT_UPDELAY /* [und] Plot update delay measurement info */
+
+#undef TEST_BAUD_CHANGE /* Torture test baud rate change on non high speed K10 */
+
+static inst_disptypesel k10_disptypesel[98];
+static inst_code k10_interp_code(kleink10 *p, int ec);
+static inst_code k10_read_cal_list(kleink10 *p);
+static inst_code set_default_disp_type(kleink10 *p);
+static inst_code k10_read_flicker_samples(kleink10 *p, double duration, double *srate,
+ double **pvals, int *pnsamp, int usefast);
+
+#define MAX_MES_SIZE 500 /* Maximum normal message reply size */
+#define MAX_RD_SIZE 8000 /* Maximum reading message reply size */
+
+/* Decode a K10 error letter */
+static int decodeK10err(char c) {
+ if (c == '0') {
+ return K10_OK;
+ } else if (c == 'B') {
+ return K10_FIRMWARE;
+ } else if (c == 'X') {
+ return K10_FIRMWARE;
+ } else if (c == 'b') {
+ return K10_BLACK_EXCESS;
+ } else if (c == 's') {
+ return K10_BLACK_OVERDRIVE;
+ } else if (c == 't') {
+ return K10_BLACK_ZERO;
+ } else if (c == 'w') {
+ return K10_OVER_HIGH_RANGE;
+ } else if (c == 'v') {
+ return K10_TOP_OVER_RANGE;
+ } else if (c == 'u') {
+ return K10_BOT_UNDER_RANGE;
+ } else if (c == 'L') {
+ return K10_AIMING_LIGHTS;
+ } else {
+ return K10_UNKNOWN;
+ }
+}
+
+/* Extract an error code from a reply string */
+/* Remove the error code from the string and return the */
+/* new length in *nlength */
+/* Return K10_BAD_RETVAL if no error code can be found */
+static int
+extract_ec(char *s, int *nlength, int bread) {
+#define MAXECHARS 1
+ char *f, *p;
+ char tt[MAXECHARS+1];
+ int rv;
+ p = s + bread;
+
+//printf("Got '%s' bread %d\n",s,bread);
+
+ /* Find the trailing '>' */
+ for (p--; p >= s; p--) {
+ if (*p == '>')
+ break;
+ }
+ if (p < s)
+ return K10_BAD_RETVAL;
+//printf("trailing is at %d '%s'\n",p - s, p);
+
+ /* Find the leading '<' */
+ for (f = p-1; f >= (p-MAXECHARS-1) && f >= s; f--) {
+ if (*f == '<')
+ break;
+ if ((*f < '0' || *f > '9')
+ && (*f < 'a' || *f > 'z')
+ && (*f < 'A' || *f > 'Z'))
+ return K10_BAD_RETVAL;
+ }
+ if (f < s || f < (p-MAXECHARS-1) || (p-f) <= 1) {
+//printf("f < s ? %d, f < (p-MAXECHARS-1) ? %d, (p-f) <= 1 ? %d\n",
+//f < s, f < (p-10), (p-f) <= 1);
+ return K10_BAD_RETVAL;
+ }
+//printf("leading is at %d '%s'\n",f - s, f);
+
+ if (p-f-1 <= 0)
+ return K10_BAD_RETVAL;
+
+ strncpy(tt, f+1, p-f-1);
+ tt[p-f-1] = '\000';
+//printf("error code is '%s'\n",tt);
+
+ /* Interpret the error character(s) */
+ /* It's not clear if more than one error can be returned. */
+ /* We are only looking at the first character - we should */
+ /* really prioritize them id more than one can occur. */
+ for (p = tt; *p != '\000'; p++) {
+ rv = decodeK10err(*p);
+ break;
+ }
+
+ /* Remove the error code from the reply */
+ if (nlength != NULL)
+ *nlength = f - s;
+ *f = '\000';
+ return rv;
+}
+
+/* Interpret an icoms error into a KLEINK10 error */
+static int icoms2k10_err(int se) {
+ if (se != ICOM_OK) {
+ if (se & ICOM_TO)
+ return K10_TIMEOUT;
+ return K10_COMS_FAIL;
+ }
+ return K10_OK;
+}
+
+typedef enum {
+ ec_n = 0, /* No error code or command echo */
+ ec_e = 1, /* Error code */
+ ec_c = 2, /* Command echo */
+ ec_ec = 3 /* Both error code and command echo */
+} ichecks;
+
+/* Do a full command/response echange with the kleink10 */
+/* (This level is not multi-thread safe) */
+/* Return the kleink10 error code. */
+static int
+k10_fcommand(
+struct _kleink10 *p,
+char *in, /* In string */
+char *out, /* Out string buffer */
+int bsize, /* Out buffer size */
+int *pbread, /* Bytes read (including '\000') */
+int nchar, /* Number of characters to expect */
+double to, /* Timeout in seconds */
+ichecks xec, /* Error check */
+int nd /* nz to disable debug messages */
+) {
+ int se, rv = K10_OK;
+ int bwrite, bread = 0;
+ char cmd[10];
+
+ bwrite = strlen((char *)in);
+ strncpy((char *)cmd, (char *)in, 2);
+ cmd[2] = '\000';
+
+ if ((se = p->icom->write_read(p->icom, in, 0, out, bsize, &bread, NULL, nchar, to))
+ != ICOM_OK) {
+ rv = icoms2k10_err(se);
+
+ } else {
+
+ if (!nd && p->log->debug >= 6) {
+ a1logd(p->log, 6, "k10_fcommand: command sent\n");
+ adump_bytes(p->log, " ", (unsigned char *)in, 0, bwrite);
+ a1logd(p->log, 6, " returned %d bytes:\n",bread);
+ adump_bytes(p->log, " ", (unsigned char *)out, 0, bread);
+ }
+
+ if (xec & ec_e) {
+ rv = extract_ec(out, &bread, bread);
+ }
+
+ if ((xec & ec_c) && rv == K10_OK && strncmp(cmd, out, 2) != 0) {
+ rv = K10_CMD_VERIFY;
+ }
+ }
+ if (!nd) a1logd(p->log, 6, " error code 0x%x\n",rv);
+
+ if (pbread != NULL)
+ *pbread = bread;
+
+ return rv;
+}
+
+/* Do a normal command/response echange with the kleink10. */
+/* (This level is not multi-thread safe) */
+/* Return the inst code */
+static inst_code
+k10_command(
+kleink10 *p,
+char *in, /* In string */
+char *out, /* Out string buffer */
+int bsize, /* Out buffer size */
+int *bread, /* Bytes read */
+int nchar, /* Number of characters to expect */
+ichecks xec, /* Error check */
+double to) { /* Timout in seconds */
+ int rv = k10_fcommand(p, in, out, bsize, bread, nchar, to, xec, 0);
+ return k10_interp_code(p, rv);
+}
+
+/* Do a write to the kleink10 */
+/* (This level is not multi-thread safe) */
+/* Return the kleink10 error code. */
+static int
+k10_write(
+struct _kleink10 *p,
+char *in, /* In string */
+double to /* Timeout in seconds */
+) {
+ int rv = K10_OK;
+ int se;
+
+ if ((se = p->icom->write(p->icom, in, 0, to)) != ICOM_OK) {
+ rv = icoms2k10_err(se);
+
+ } else {
+
+ if (p->log->debug >= 6) {
+ a1logd(p->log, 6, "k10_write: command sent\n");
+ adump_bytes(p->log, " ", (unsigned char *)in, 0, strlen((char *)in));
+ }
+ }
+ a1logd(p->log, 6, " error code 0x%x\n",rv);
+
+ return rv;
+}
+
+/* Do a read from the kleink10 */
+/* (This level is not multi-thread safe) */
+/* Return the kleink10 error code. */
+static int
+k10_read(
+struct _kleink10 *p,
+char *out, /* Out string buffer */
+int bsize, /* Out buffer size */
+int *pbread, /* Bytes read (including '\000') */
+char *tc, /* Terminating characters, NULL for none or char count mode */
+int nchar, /* Number of terminating characters needed, or char count needed */
+double to /* Timeout in seconds */
+) {
+ int se, rv = K10_OK;
+ int bread = 0;
+
+ if ((se = p->icom->read(p->icom, out, bsize, &bread, tc, nchar, to)) != ICOM_OK) {
+ rv = icoms2k10_err(se);
+ } else {
+
+ if (p->log->debug >= 6) {
+ a1logd(p->log, 6, "k10_read: read %d bytes\n",bread);
+ adump_bytes(p->log, " ", (unsigned char *)out, 0, bread);
+ }
+ }
+ a1logd(p->log, 6, " error code 0x%x\n",rv);
+
+ if (pbread != NULL)
+ *pbread = bread;
+
+ return rv;
+}
+
+/* Change baud rates */
+/* (This level is not multi-thread safe) */
+/* Return the kleink10 error code. */
+static int
+k10_set_baud(
+struct _kleink10 *p,
+baud_rate br
+) {
+ int se, rv = K10_OK;
+ if ((se = p->icom->set_ser_port(p->icom, fc_HardwareDTR, br, parity_none,
+ stop_1, length_8)) != ICOM_OK) {
+ rv = icoms2k10_err(se);
+ } else {
+ if (p->log->debug >= 6) {
+ a1logd(p->log, 6, "k10_set_baud: %d\n",br);
+ }
+ }
+ a1logd(p->log, 6, " error code 0x%x\n",rv);
+
+ return rv;
+}
+
+/* ------------------------------------------------------------ */
+
+/* Establish communications with a kleink10 */
+/* Return K10_COMS_FAIL on failure to establish communications */
+static inst_code
+k10_init_coms(inst *pp, baud_rate br, flow_control fc, double tout) {
+ kleink10 *p = (kleink10 *) pp;
+ char buf[MAX_MES_SIZE];
+ baud_rate brt[] = { baud_9600, baud_nc };
+ unsigned int etime;
+ unsigned int i;
+ instType itype = pp->itype;
+ int se;
+ char *cp;
+
+ inst_code ev = inst_ok;
+
+ a1logd(p->log, 2, "k10_init_coms: About to init Serial I/O\n");
+
+ if (p->gotcoms) {
+ a1logd(p->log, 2, "k10_init_coms: already inited\n");
+ return inst_ok;
+ }
+
+ amutex_lock(p->lock);
+
+ if (p->icom->port_type(p->icom) != icomt_serial
+ && p->icom->port_type(p->icom) != icomt_usbserial) {
+ amutex_unlock(p->lock);
+ a1logd(p->log, 1, "k10_init_coms: wrong communications type for device!\n");
+ return inst_coms_fail;
+ }
+
+ /* The tick to give up on */
+ etime = msec_time() + (long)(500.0 + 0.5);
+
+ a1logd(p->log, 1, "k10_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 = 0; msec_time() < etime; i++) {
+ if (brt[i] == baud_nc) {
+ i = 0;
+ }
+ a1logd(p->log, 5, "k10_init_coms: trying baud ix %d\n",brt[i]);
+ if ((se = p->icom->set_ser_port(p->icom, fc_HardwareDTR, brt[i], parity_none,
+ stop_1, length_8)) != ICOM_OK) {
+ amutex_unlock(p->lock);
+ a1logd(p->log, 5, "k10_init_coms: set_ser_port failed with 0x%x\n",se);
+ return k10_interp_code(p, icoms2k10_err(se));; /* Give up */
+ }
+
+ /* Check instrument is responding */
+ if (((ev = k10_command(p, "P0\r", buf, MAX_MES_SIZE, NULL, 21, ec_ec, 0.5)) & inst_mask)
+ != inst_coms_fail) {
+ break; /* We've got coms or user abort */
+ }
+
+ /* Check for user abort */
+ if (p->uicallback != NULL) {
+ inst_code ev;
+ if ((ev = p->uicallback(p->uic_cntx, inst_negcoms)) == inst_user_abort) {
+ amutex_unlock(p->lock);
+ a1logd(p->log, 1, "k10_init_coms: user aborted\n");
+ return inst_user_abort;
+ }
+ }
+ }
+
+ if (msec_time() >= etime) { /* We haven't established comms */
+ amutex_unlock(p->lock);
+ a1logd(p->log, 2, "k10_init_coms: failed to establish coms\n");
+ return inst_coms_fail;
+ }
+
+ /* Check the response */
+ if (ev != inst_ok) {
+ amutex_unlock(p->lock);
+ a1logd(p->log, 2, "k10_init_coms: status command failed\n");
+ return ev;
+ }
+
+ if (strncmp (buf+2, "K-10 ", 7) == 0)
+ p->model = k10_k10;
+ else if (strncmp (buf+2, "K-10-A ", 7) == 0)
+ p->model = k10_k10a;
+ else if (strncmp (buf+2, "KV-10-A", 7) == 0)
+ p->model = k10_kv10a;
+ else {
+ amutex_unlock(p->lock);
+ a1logd(p->log, 2, "k10_init_coms: unrecognised model '%s'\n",buf);
+ return inst_unknown_model;
+ }
+
+ /* Extract the serial number */
+ strncpy(p->serial_no, buf+9, 9);
+ p->serial_no[20] = '\000';
+
+ a1logd(p->log, 2, "k10_init_coms: coms established\n");
+
+ p->gotcoms = 1;
+
+ amutex_unlock(p->lock);
+
+ /* Get the list of calibrations */
+ if ((ev = k10_read_cal_list(p)) != inst_ok) {
+ return ev;
+ }
+
+ a1logd(p->log, 2, "k10_init_coms: init coms is returning\n");
+ return inst_ok;
+}
+
+/* Initialise the KLEINK10 */
+/* return non-zero on an error, with dtp error code */
+static inst_code
+k10_init_inst(inst *pp) {
+ kleink10 *p = (kleink10 *)pp;
+ char mes[100];
+ char buf[MAX_MES_SIZE];
+ unsigned int stime;
+ int se;
+ inst_code ev = inst_ok;
+
+ a1logd(p->log, 2, "k10_init_inst: called\n");
+
+ if (p->gotcoms == 0)
+ return inst_internal_error; /* Must establish coms before calling init */
+
+ amutex_lock(p->lock);
+
+ /* Make sure the target lights are off */
+ if ((ev = k10_command(p, "L0\r", buf, MAX_MES_SIZE, NULL, 2+3, ec_ec, 1.0)) != inst_ok) {
+ amutex_unlock(p->lock);
+ return ev;
+ }
+ p->lights = 0;
+
+ /* Make sure we are auto ranging by default */
+ if ((ev = k10_command(p, "J8\r", buf, MAX_MES_SIZE, NULL, 2+3, ec_ec, 1.0)) != inst_ok) {
+ amutex_unlock(p->lock);
+ return ev;
+ }
+ p->autor = 1;
+
+ /* Grab the firware version */
+ stime = msec_time();
+ if ((ev = k10_command(p, "P2\r", buf, MAX_MES_SIZE, NULL, 2+8+3, ec_ec, 2.0)) != inst_ok) {
+ amutex_unlock(p->lock);
+ return ev;
+ }
+ p->comdel = (msec_time() - stime)/2; /* Or is this the FD232 update latency ? */
+ strncpy(p->firm_ver, buf+2, 8);
+ p->firm_ver[8] = '\000';
+
+ amutex_unlock(p->lock);
+
+ /* Set a default calibration */
+ if ((ev = set_default_disp_type(p)) != inst_ok) {
+ return ev;
+ }
+
+ p->inited = 1;
+
+ /* Do a flicker read to work around glitch at the 0.4 second mark of the */
+ /* first one after power up. We ignore any error. */
+ if ((ev = k10_read_flicker_samples(p, 0.5, NULL, NULL, NULL, 0)) != inst_ok) {
+ a1logd(p->log, 1, "k10_init_inst: warning - startup k10_read_flicker_samples failed with 0x%x - ignored\n",ev);
+ }
+
+ a1logd(p->log, 2, "k10_init_inst: instrument inited OK\n");
+
+ if (p->log->verb) {
+ char *model = "Unknown";
+ switch (p->model) {
+ case k10_k10:
+ model = "K10";
+ break;
+ case k10_k10a:
+ model = "K10-A";
+ break;
+ case k10_kv10a:
+ model = "KV10-A";
+ break;
+ }
+ a1logv(p->log, 1, " Model: '%s'\n",model);
+ a1logv(p->log, 1, " Serial number: '%s'\n",p->serial_no);
+ a1logv(p->log, 1, " Firmware version: '%s'\n",p->firm_ver);
+ }
+
+ return inst_ok;
+}
+
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+/* Convert a Klein Measurement encoded 24 bit value to a double */
+double KleinMeas2double(char *ibuf) {
+ unsigned char *buf = (unsigned char *)ibuf;
+ unsigned int ip;
+ double op;
+ int sn = 0, ma;
+ int ep;
+
+ ip = (buf[0] << 8) + buf[1];
+ sn = (ip >> 15) & 0x1;
+ ma = ip & ((1 << 15)-1);
+ ep = buf[2];
+ if (ep >= 128)
+ ep -= 256;
+
+ op = (double)ma;
+ op *= pow(2.0, (double)ep-16);
+ if (sn)
+ op = -op;
+ return op;
+}
+
+/* Decode measurement RGB range into 3 x 1..6 */
+static void decodeRange(int *out, char iin) {
+ unsigned char in = (unsigned char)iin;
+ int t, r0, r1, r2, r3;
+ int tt;
+
+ out[0] = (in >> 7) & 1;
+ out[1] = (in >> 6) & 1;
+ out[2] = (in >> 5) & 1;
+
+ in &= 0x1F;
+
+ out[0] += 1 + 2 * ((in / 9) % 3);
+ out[1] += 1 + 2 * ((in / 3) % 3);
+ out[2] += 1 + 2 * (in % 3);
+}
+
+
+/* Convert a Klein Calibration encoded 24 bit value to a double */
+double KleinCal2double(char *ibuf) {
+ unsigned char *buf = (unsigned char *)ibuf;
+ ORD32 ip;
+ double op;
+ ORD32 sn = 0, ma;
+ int ep;
+
+ ip = (buf[0] << 8) + buf[1];
+ sn = (ip >> 15) & 0x1;
+ ma = ip & ((1 << 15)-1);
+ ep = buf[2];
+ if (ep >= 128)
+ ep -= 256;
+
+ op = (double)ma;
+ op *= pow(2.0, (double)ep-15);
+ if (sn)
+ op = -op;
+ return op;
+}
+
+/* Convert a native double to an Klein Calibration encoded 24 bit value, */
+void double2KleinCal(char *ibuf, double d) {
+ unsigned char *buf = (unsigned char *)ibuf;
+ ORD32 sn = 0, ma;
+ int ep;
+ double n;
+
+ if (d < 0.0) {
+ sn = 1;
+ d = -d;
+ }
+ if (d != 0.0) {
+ ep = (int)floor(log(d)/log(2.0)) + 1;
+
+ n = pow(0.5, (double)(ep - 15)); /* Normalisation factor */
+
+ /* If rounding would cause an overflow, adjust exponent */
+ if (floor(d * n + 0.5) >= (double)(1 << 15)) {
+ n *= 0.5;
+ ep++;
+ }
+
+ if (ep < -128) { /* Alow denormalised */
+ ep = -128;
+ n = pow(0.5, (double)(ep - 15)); /* Normalisation factor */
+ }
+
+ if (ep > 127) { /* Saturate maximum */
+ ep = 127;
+ d = (double)(1 << 15)-1;
+ } else {
+ d *= n;
+ if (d < 0.5)
+ ep = 0;
+ }
+ } else {
+ ep = 0; /* Zero */
+ }
+ ma = (((ORD32)floor(d + 0.5)) & ((1 << 16)-1)) | (sn << 15);
+ buf[0] = ((ma >> 8) & 0xff);
+ buf[1] = (ma & 0xff);
+
+ buf[2] = ep;
+}
+
+double CalMan2double(char *ibuf) {
+ unsigned char *buf = (unsigned char *)ibuf;
+ ORD64 val;
+
+ /* Load LE into 64 bit */
+ val = buf[7];
+ val = ((val << 8) + (0xff & buf[6]));
+ val = ((val << 8) + (0xff & buf[5]));
+ val = ((val << 8) + (0xff & buf[4]));
+ val = ((val << 8) + (0xff & buf[3]));
+ val = ((val << 8) + (0xff & buf[2]));
+ val = ((val << 8) + (0xff & buf[1]));
+ val = ((val << 8) + (0xff & buf[0]));
+
+ return IEEE754_64todouble(val);
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+/* Decode an N5 measre command response */
+static inst_code decodeN5(kleink10 *p, double *XYZ, int *range, char *buf, int blen) {
+
+ if (blen < (2 + 3 * 3 + 1)) {
+ a1logd(p->log, 1, "decodeN5: failed to parse '%s'\n",icoms_fix(buf));
+ return inst_protocol_error;
+ }
+
+ if (XYZ != NULL) {
+ XYZ[0] = KleinMeas2double(buf+2);
+ XYZ[1] = KleinMeas2double(buf+5);
+ XYZ[2] = KleinMeas2double(buf+8);
+ }
+
+ if (range != NULL)
+ decodeRange(range, buf[11]);
+
+ return inst_ok;
+}
+
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+/* Read a given calibration matrix */
+static inst_code
+k10_read_cal_matrix(
+kleink10 *p,
+inst_disptypesel *m, /* Matrix calibration to write */
+int ix /* Klein calibration index 1 - 96 */
+) {
+ inst_code ev = inst_protocol_error;
+ int se;
+ char cmd[3];
+ char buf[MAX_MES_SIZE];
+ int bread, i, j, k;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+
+ amutex_lock(p->lock);
+
+ /* Trigger cal matrix read */
+ if ((ev = k10_command(p, "D1\r", buf, MAX_MES_SIZE, &bread, 2, ec_c, 2.0)) != inst_ok) {
+ amutex_unlock(p->lock);
+ return ev;
+ }
+
+ if (buf[0] != 'D' || buf[1] != '1') {
+ amutex_unlock(p->lock);
+ a1logd(p->log, 1, "k10_read_cal_matrix: didn't get echo'd commad D1\n");
+ return inst_protocol_error;
+ }
+
+ /* Send the cal index and read matrix */
+ cmd[0] = ix;
+ cmd[1] = '\r';
+ cmd[2] = '\000';
+
+ if ((ev = k10_command(p, cmd, buf, MAX_MES_SIZE, &bread, 128+3, ec_e, 2.0)) != inst_ok) {
+ amutex_unlock(p->lock);
+ return ev;
+ }
+
+ if (bread < 128) {
+ amutex_unlock(p->lock);
+ a1logd(p->log, 1, "k10_read_cal_matrix: not enough bytes returned (%d)\n",bread);
+ return inst_protocol_error;
+ }
+
+ a1logd(p->log, 6, "Cal '%s':\n",m->desc);
+
+ /* CalMan format matrix */
+ if (buf[21] == 'C') {
+ for (k = 24, i = 0; i < 3; i++) {
+ for (j = 0; j < 3; j++) {
+ if ((bread-k) < 8) {
+ amutex_unlock(p->lock);
+ return inst_protocol_error;
+ }
+ m->mat[i][j] = CalMan2double(buf + k);
+ k += 8;
+ a1logd(p->log, 6, " Mat[%d][%d] = %f\n",i,j,m->mat[i][j]);
+ }
+ }
+
+ /* Klein format matrix */
+ } else {
+ for (k = 101, i = 0; i < 3; i++) {
+ for (j = 0; j < 3; j++) {
+ if ((bread-k) < 3) {
+ amutex_unlock(p->lock);
+ return inst_protocol_error;
+ }
+ m->mat[i][j] = KleinCal2double(buf + k);
+ k += 3;
+ a1logd(p->log, 6, " Mat[%d][%d] = %f\n",i,j,m->mat[i][j]);
+ }
+ }
+ }
+ m->flags |= inst_dtflags_ld; /* It's now loaded */
+ amutex_unlock(p->lock);
+
+ return inst_ok;
+}
+
+/* Guess appropriate disptype and selector letters for standard calibrations */
+static void guess_disptype(inst_disptypesel *s, char *desc) {
+ disptech dtype;
+ disptech_info *i;
+ char *sel = NULL;
+
+ if (strcmp(desc, "Default CRT File") == 0) {
+ dtype = disptech_crt;
+ } else if (strcmp(desc, "Klein DLP Lux") == 0) {
+ dtype = disptech_dlp;
+ sel = "P";
+ } else if (strcmp(desc, "Klein SMPTE C") == 0) {
+ dtype = disptech_crt;
+ sel = "E";
+ } else if (strcmp(desc, "TVL XVM245") == 0) { /* RGB LED LCD Video display */
+ dtype = disptech_lcd_rgbled;
+ } else if (strcmp(desc, "Klein LED Bk LCD") == 0) {
+ dtype = disptech_lcd_rgbled;
+ sel = "d";
+ } else if (strcmp(desc, "Klein Plasma") == 0) {
+ dtype = disptech_plasma;
+ } else if (strcmp(desc, "DLP Screen") == 0) {
+ dtype = disptech_dlp;
+ } else if (strcmp(desc, "TVL LEM150") == 0) { /* OLED */
+ dtype = disptech_oled;
+ } else if (strcmp(desc, "Sony EL OLED") == 0) { /* OLED */
+ dtype = disptech_oled;
+ sel = "O";
+ } else if (strcmp(desc, "Eizo CG LCD") == 0) { /* Wide gamut IPS LCD RGB ? (or RG+P ?)*/
+ dtype = disptech_lcd_rgbled_ips;
+ sel = "z";
+ } else if (strcmp(desc, "FSI 2461W") == 0) { /* Wide gamut IPS ? LCD CCFL */
+ dtype = disptech_lcd_ccfl_wg;
+ } else if (strcmp(desc, "HP DreamColor 2") == 0) { /* Wide gamut IPS ? LCD RG+P */
+ dtype = disptech_lcd_rgledp;
+ } else {
+ dtype = disptech_unknown;
+ }
+
+ i = disptech_get_id(dtype);
+ s->dtech = dtype;
+ if (sel != NULL)
+ strcpy(s->sel, sel);
+ else
+ strcpy(s->sel, i->sel);
+}
+
+/* Read the list of calibrations available */
+static inst_code
+k10_read_cal_list(
+kleink10 *p) {
+ inst_code ev = inst_protocol_error;
+ char buf[MAX_RD_SIZE];
+ int bread, i, j, ix, n;
+ char name[21];
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+
+ /* Make sure factory matrix values is in the first entry */
+ for (i = 0; i < 3; i++) {
+ for (j = 0; j < 3; j++) {
+ if (i == j)
+ k10_disptypesel[0].mat[i][j] = 1.0;
+ else
+ k10_disptypesel[0].mat[i][j] = 0.0;
+ }
+ }
+ k10_disptypesel[0].flags |= inst_dtflags_ld;
+
+ amutex_lock(p->lock);
+
+ /* Grab the raw info */
+ if ((ev = k10_command(p, "D7\r", buf, MAX_RD_SIZE, &bread, 1925, ec_ec, 6.0)) != inst_ok) {
+ amutex_unlock(p->lock);
+ a1logd(p->log, 1, "k10_read_cal_list D7 returning error 0x%x\n",ev);
+ return ev;
+ }
+
+ /* Parse it. There should be 96 calibrations */
+ name[20] = '\000';
+ for (i = 2, ix = 1, n = 1; ix <= 96 && (bread-i) >= 20; i += 20, ix++) {
+
+ for (j = 0; j < 20; j++)
+ name[j] = buf[i + j];
+
+ if (((unsigned char *)name)[0] == 0xff) {
+ continue;
+ }
+ for (j = 19; j >= 0; j--) {
+ if (name[j] != ' ') {
+ name[j+1] = '\000';
+ break;
+ }
+ }
+
+// printf("Adding Cal %d is '%s'\n",ix,name);
+
+ /* Add it to the list */
+ memset((void *)&k10_disptypesel[n], 0, sizeof(inst_disptypesel));
+ k10_disptypesel[n].flags = inst_dtflags_mtx | inst_dtflags_wr; /* Not loaded yet */
+ k10_disptypesel[n].cbid = 0;
+ strcpy(k10_disptypesel[n].desc, name);
+ k10_disptypesel[n].refr = 0;
+ k10_disptypesel[n].ix = ix;
+ guess_disptype(&k10_disptypesel[n], name);
+ n++;
+ }
+
+ /* Put marker at end */
+ k10_disptypesel[n].flags = inst_dtflags_end;
+ k10_disptypesel[n].cbid = 0;
+ k10_disptypesel[n].sel[0] = '\000';
+ k10_disptypesel[n].desc[0] = '\000';
+ k10_disptypesel[n].refr = 0;
+ k10_disptypesel[n].ix = 0;
+
+ amutex_unlock(p->lock);
+
+ return inst_ok;
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+static abort_flicker(kleink10 *p, int isnew, double *retbuf) {
+ char buf[MAX_MES_SIZE];
+ int bread;
+
+ /* Abort flicker transfer */
+ k10_write(p, "N5\r", 2.0);
+
+ /* Flush the buffer of any remaining characters. */
+ k10_read(p, buf, MAX_MES_SIZE, &bread, "<0>", 3, 1.0);
+
+ /* Return the baud rate to normal */
+ if (isnew)
+ k10_set_baud(p, baud_9600);
+
+#ifdef TEST_BAUD_CHANGE
+ else {
+ k10_set_baud(p, baud_19200);
+ k10_set_baud(p, baud_9600);
+ }
+#endif
+
+ /* Clean up everything else */
+ amutex_unlock(p->lock);
+
+ if (retbuf != NULL)
+ free(retbuf);
+}
+
+/* Read flicker samples */
+/* Free *pvals after use */
+static inst_code
+k10_read_flicker_samples(
+kleink10 *p,
+double duration, /* duration to take samples */
+double *srate, /* Return the sampel rate */
+double **pvals, /* Return the sample values */
+int *pnsamp, /* Return the number of samples */
+int usefast /* If nz use fast rate is possible */
+) {
+ int se = K10_OK;
+ inst_code ev = inst_ok;
+ int isnew = 0;
+ double rate = 256;
+ double *retbuf;
+ int tsamp, nsamp;
+ char mes[4] = "JX\r";
+ char buf[MAX_MES_SIZE];
+ int boff, bread;
+ int range[3];
+ unsigned int stime;
+ int derr = 0, rerr = 0;
+ int i;
+
+ stime = msec_time();
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ amutex_lock(p->lock);
+
+#ifdef HIGH_SPEED
+ /* This isn't reliable, because there is no way to ensure that */
+ /* the T1 command has been sent before we change the baud rate, */
+ /* anf if we wait too long will will loose the measurements. */
+ if (usefast && strcmp(p->firm_ver, "v01.09fh") > 0) {
+ isnew = 1; /* We can use faster T1 command */
+ rate = 384;
+ a1logd(p->log, 1, "k10_read_flicker: using faster T1\n");
+ }
+#endif /* HIGH_SPEED */
+
+ /* Target number of samples */
+ tsamp = (int)(duration * (double)rate + 0.5);
+
+ if (tsamp < 1)
+ tsamp = 1;
+
+ a1logd(p->log, 1, "k10_read_flicker: taking %d samples\n",tsamp);
+
+ if ((retbuf = (double *)malloc(sizeof(double) * tsamp)) == NULL) {
+ amutex_unlock(p->lock);
+ a1logd(p->log, 1, "k10_read_flicker: malloc of %d bytes failed\n",sizeof(double) * tsamp);
+ return k10_interp_code(p, K10_INT_MALLOC);
+ }
+
+ /* Make sure the target lights are off */
+ if (p->lights) {
+ int se;
+ if ((ev = k10_command(p, "L0\r", buf, MAX_MES_SIZE, NULL, 2+3, ec_ec, 0.5)) != inst_ok) {
+ amutex_unlock(p->lock);
+ free(retbuf);
+ a1logd(p->log, 1, "k10_read_flicker: L0 failed\n");
+ return ev;
+ }
+ p->lights = 0;
+ }
+
+ /* Make sure we are auto ranging */
+ if (!p->autor) {
+ if ((ev = k10_command(p, "J8\r", buf, MAX_MES_SIZE, NULL, 2+3, ec_ec, 1.0)) != inst_ok) {
+ amutex_unlock(p->lock);
+ a1logd(p->log, 1, "k10_read_flicker: J8 failed with 0x%x\n",ev);
+ return ev;
+ }
+ p->autor = 1;
+ }
+
+ /* Take a measurement to get ranges ? */
+ if ((ev = k10_command(p, "N5\r", buf, MAX_MES_SIZE, &bread, 15, ec_ec, 2.0)) != inst_ok) {
+ amutex_unlock(p->lock);
+ free(retbuf);
+ a1logd(p->log, 1, "k10_read_flicker: N5 failed with 0x%x\n",ev);
+ return ev;
+ }
+
+ if ((ev = decodeN5(p, NULL, range, buf, bread)) != inst_ok) {
+ a1logd(p->log, 1, "k10_read_flicker: decodeN5 failed with 0x%x\n",ev);
+ amutex_unlock(p->lock);
+ return ev;
+ }
+
+ /* Set a fixed range to avoid a range change error */
+ p->autor = 0;
+ if ((ev = k10_command(p, "J7\r", buf, MAX_MES_SIZE, NULL, 2+3, ec_ec, 1.0)) != inst_ok) {
+ amutex_unlock(p->lock);
+ a1logd(p->log, 1, "k10_read_flicker: J7 failed with 0x%x\n",ev);
+ return ev;
+ }
+ mes[1] = '0' + range[1]; /* Green range */
+ if ((ev = k10_command(p, mes, buf, MAX_MES_SIZE, NULL, 2+3, ec_ec, 1.0)) != inst_ok) {
+ amutex_unlock(p->lock);
+ a1logd(p->log, 1, "k10_read_flicker: %s failed with 0x%x\n",buf,ev);
+ return ev;
+ }
+
+ /* Issue an T2 for normal speed flicker measure, or T1 for fast */
+ a1logd(p->log, 6, "k10_read_flicker: issuing T1/T2 command\n");
+ if ((se = k10_write(p, isnew ? "T1\r" : "T2\r", 2.0)) != K10_OK) {
+ amutex_unlock(p->lock);
+ free(retbuf);
+ a1logd(p->log, 1, "k10_read_flicker: T1/T2 failed with 0x%x\n",icoms2k10_err(se));
+ return k10_interp_code(p, se);
+ }
+
+ stime = msec_time() - stime;
+ stime -= p->comdel;
+
+ /* Switch to 19200 baud if using fast */
+ if (isnew) {
+ /* Allow the T1/T2 to flow out before changing the baud rate */
+ msec_sleep(2);
+
+ if ((se = k10_set_baud(p, baud_19200)) != K10_OK) {
+ abort_flicker(p, isnew, retbuf);
+ a1logd(p->log, 1, "k10_read_flicker: T1 19200 baud failed with 0x%x\n",
+ icoms2k10_err(se));
+ return k10_interp_code(p, se);
+ }
+ }
+
+#ifdef TEST_BAUD_CHANGE
+ else {
+ msec_sleep(2);
+ k10_set_baud(p, baud_19200);
+ k10_set_baud(p, baud_9600);
+ }
+#endif
+
+ /* Capture flicker packets until we've got enough samples */
+ for (boff = nsamp = 0; nsamp < tsamp; ) {
+ if ((se = k10_read(p, buf + boff, MAX_MES_SIZE - boff, &bread,
+ NULL, 96, 2.0)) != K10_OK) {
+ abort_flicker(p, isnew, retbuf);
+ a1logd(p->log, 1, "k10_read_flicker: reading packet failed with 0x%x\n",icoms2k10_err(se));
+ return k10_interp_code(p, se);
+ }
+
+ boff += bread;
+
+ /* Extract the values we want */
+ /* (We could get XYZ, range & error value too) */
+ if (boff >= 96) {
+ int trange[3];
+ unsigned char *ubuf = (unsigned char *)buf;
+
+ for (i = 0; i < 32 && nsamp < tsamp; i++, nsamp++)
+ retbuf[nsamp] = ubuf[i * 3 + 1] * 256.0 + ubuf[i * 3 + 2];
+
+ /* Check the error and range */
+ if ((se = decodeK10err(buf[3 * 13])) != K10_OK) {
+ a1logd(p->log, 1, "k10_read_flicker: decode error 0x%x\n",se);
+ derr = se;
+
+ } else {
+
+ decodeRange(trange, buf[3 * 11]);
+
+ if (trange[0] != range[0]
+ || trange[1] != range[1]
+ || trange[2] != range[2]) {
+ a1logd(p->log, 1, "k10_read_flicker: range changed\n");
+ rerr = 1;
+ }
+ }
+
+ /* Shuffle any remaining bytes down */
+ if (boff > 96)
+ memmove(buf, buf + 96, boff - 96);
+ boff -= 96;
+
+#ifdef NEVER
+ { /* Dump */
+ char xtra[32];
+ double XYZ[3];
+ int range[3];
+ char err;
+
+ for (i = 0; i < 32; i++)
+ xtra[i] = buf[i * 3 + 0];
+
+ adump_bytes(p->log, " ", (unsigned char *)buf, 0, 96);
+ printf("Extra bytes:\n");
+ adump_bytes(p->log, " ", (unsigned char *)xtra, 0, 32);
+
+ XYZ[0] = KleinMeas2double(xtra+2);
+ XYZ[1] = KleinMeas2double(xtra+5);
+ XYZ[2] = KleinMeas2double(xtra+8);
+
+ decodeRange(range, xtra[11]);
+ err = xtra[13];
+ printf("XYZ %f %f %f range %d %d %d err '%c'\n\n",
+ XYZ[0], XYZ[1], XYZ[2], range[0], range[1], range[2],err);
+ }
+#endif
+
+ }
+ }
+
+ a1logd(p->log, 6, "k10_read_flicker: read %d samples\n",nsamp);
+
+ /* Then issue an N5 to cancel, and clean up */
+ abort_flicker(p, isnew, NULL);
+
+ if (derr != 0) {
+ free(retbuf);
+ a1logd(p->log, 1, "k10_read_flicker: got error 0x%x during readings\n",derr);
+ return icoms2k10_err(derr);
+ }
+
+ if (rerr != 0) {
+ free(retbuf);
+ a1logd(p->log, 1, "k10_read_flicker: range changed during readings\n");
+ return icoms2k10_err(K10_RANGE_CHANGE);
+ }
+
+#ifdef NEVER
+ { /* Plot */
+ double *xx;
+
+ xx = (double *)malloc(sizeof(double) * tsamp);
+ for (i = 0; i < tsamp; i++)
+ xx[i] = (double)i/(double)rate;
+
+ do_plot(xx, retbuf, NULL, NULL, tsamp);
+ free(xx);
+ }
+#endif
+
+ if (pvals != NULL)
+ *pvals = retbuf;
+ else
+ free(retbuf);
+ if (pnsamp != NULL)
+ *pnsamp = nsamp;
+ if (srate != NULL)
+ *srate = (double)rate;
+
+ return inst_ok;
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+/* Read a single sample */
+static inst_code
+k10_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 */
+ kleink10 *p = (kleink10 *)pp;
+ char buf[MAX_RD_SIZE];
+ int user_trig = 0;
+ int bsize;
+ inst_code rv = inst_protocol_error;
+ int range[3]; /* Range for RGB sensor values */
+ int i, tries,ntav = 1; /* Number of readings to average */
+ double v, vv, XYZ[3];
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ amutex_lock(p->lock);
+
+ if (p->trig == inst_opt_trig_user) {
+ amutex_unlock(p->lock);
+
+ if (p->uicallback == NULL) {
+ a1logd(p->log, 1, "kleink10: inst_opt_trig_user but no uicallback function set!\n");
+ return inst_unsupported;
+ }
+
+ for (;;) {
+ if ((rv = p->uicallback(p->uic_cntx, inst_armed)) != inst_ok) {
+ if (rv == inst_user_abort) {
+ return rv; /* Abort */
+ }
+ if (rv == inst_user_trig) {
+ user_trig = 1;
+ break; /* Trigger */
+ }
+ }
+ msec_sleep(200);
+ }
+ /* Notify of trigger */
+ if (p->uicallback)
+ p->uicallback(p->uic_cntx, inst_triggered);
+ amutex_lock(p->lock);
+
+ /* Progromatic Trigger */
+ } else {
+ /* Check for abort */
+ if (p->uicallback != NULL
+ && (rv = p->uicallback(p->uic_cntx, inst_armed)) == inst_user_abort) {
+ amutex_unlock(p->lock);
+ return rv; /* Abort */
+ }
+ }
+
+ /* Make sure the target lights are off */
+ if (p->lights) {
+ if ((rv = k10_command(p, "L0\r", buf, MAX_MES_SIZE, NULL, 2+3, ec_ec, 1.0)) != inst_ok) {
+ amutex_unlock(p->lock);
+ a1logd(p->log, 1, "k10_read_sample: L0 failed\n");
+ return rv;
+ }
+ p->lights = 0;
+ }
+
+ /* Make sure we are auto ranging */
+ if (!p->autor) {
+ if ((rv = k10_command(p, "J8\r", buf, MAX_MES_SIZE, NULL, 2+3, ec_ec, 1.0)) != inst_ok) {
+ amutex_unlock(p->lock);
+ return rv;
+ }
+ p->autor = 1;
+ }
+
+
+ for (tries = 0; tries < RETRY_RANGE_ERROR; tries++) {
+
+ /* Take a measurement */
+ rv = k10_command(p, "N5\r", buf, MAX_MES_SIZE, &bsize, 15, ec_ec, 2.0);
+
+ if (rv == inst_ok
+ || ( (rv & inst_imask) != K10_TOP_OVER_RANGE
+ && (rv & inst_imask) != K10_BOT_UNDER_RANGE))
+ break;
+ }
+
+ if (rv == inst_ok)
+ rv = decodeN5(p, val->XYZ, range, buf, bsize);
+
+ if (rv == inst_ok) {
+ double thr[4] = { 0.2, 2.0, 20.0, 50.0 }; /* Threshold */
+ double nav[4] = { 20, 10, 4, 2 }; /* Count */
+
+ /* Make v the largest */
+ v = val->XYZ[1];
+ if (val->XYZ[0] > v)
+ v = val->XYZ[0];
+ if (val->XYZ[2] > v)
+ v = val->XYZ[2];
+
+ ntav = 1;
+#ifdef AUTO_AVERAGE
+ if (!IMODETST(p->mode, inst_mode_emis_nonadaptive)) {
+ /* Decide how many extra readings to average into result. */
+ /* Interpolate between the thresholds */
+ if (v < 0.2) {
+ ntav = nav[0];
+ } else if (v < thr[1]) {
+ vv = 1.0 - (v - thr[0]) / (thr[1] - thr[0]);
+ vv = vv * vv * vv;
+ ntav = (int)(vv * (nav[0] - 10) + 10.0 + 0.5);
+ } else if (v < thr[2]) {
+ vv = 1.0 - (v - thr[1]) / (thr[2] - thr[1]);
+ vv = vv * vv * vv;
+ ntav = (int)(vv * (nav[1] - nav[2]) + nav[2] + 0.5);
+ } else if (v < thr[3]) {
+ vv = 1.0 - (v - thr[2]) / (thr[3] - thr[2]);
+ vv = vv * vv * vv;
+ ntav = (int)(vv * (nav[2] - nav[3]) + nav[3] + 0.5);
+ }
+ }
+#endif
+
+ for (i = 1; i < ntav; i++) {
+ if ((rv = k10_command(p, "N5\r", buf, MAX_MES_SIZE, &bsize, 15, ec_ec, 2.0)) != inst_ok)
+ break;
+ if ((rv = decodeN5(p, XYZ, range, buf, bsize)) != inst_ok)
+ break;
+
+ val->XYZ[0] += XYZ[0];
+ val->XYZ[1] += XYZ[1];
+ val->XYZ[2] += XYZ[2];
+ }
+ }
+
+
+ if (rv != inst_ok) {
+ amutex_unlock(p->lock);
+ return rv;
+ }
+
+ val->XYZ[0] /= (double)ntav;
+ val->XYZ[1] /= (double)ntav;
+ val->XYZ[2] /= (double)ntav;
+
+
+ amutex_unlock(p->lock);
+
+ if ((rv = decodeN5(p, val->XYZ, range, buf, bsize)) != inst_ok) {
+ return rv;
+ }
+
+ /* Apply the calibration correction matrix */
+ icmMulBy3x3(val->XYZ, p->ccmat, val->XYZ);
+
+//printf("matrix = %f %f %f\n", p->ccmat[0][0], p->ccmat[0][1], p->ccmat[0][2]);
+//printf(" %f %f %f\n", p->ccmat[1][0], p->ccmat[1][1], p->ccmat[2][2]);
+//printf(" %f %f %f\n", p->ccmat[2][0], p->ccmat[2][1], p->ccmat[2][2]);
+//printf("XYZ = %f %f %f\n", val->XYZ[0], val->XYZ[1], val->XYZ[2]);
+//printf("range = %d %d %d\n", range[0], range[1], range[2]);
+
+ /* This may not change anything since instrument may clamp */
+ if (clamp)
+ icmClamp3(val->XYZ, val->XYZ);
+
+ val->loc[0] = '\000';
+
+ /* Check if the matrix seems to be an Ambient matrix */
+ if ((p->ccmat[0][0] + p->ccmat[1][1] + p->ccmat[2][2])/3.0 > 5.0)
+ val->mtype = inst_mrt_ambient;
+ else
+ val->mtype = inst_mrt_emission;
+ val->XYZ_v = 1; /* These are absolute XYZ readings */
+ val->sp.spec_n = 0;
+ val->duration = 0.0;
+ rv = inst_ok;
+
+
+ if (user_trig)
+ return inst_user_trig;
+ return rv;
+}
+
+/* - - - - - - - - - - - - - - - - */
+/*
+
+ Determining the refresh rate for a refresh type display.
+
+ This is easy because the sample rate of the Kleoin
+ is well above the refresh rates we ant to measure.
+
+ If there is no aparent refresh, or the refresh rate is not determinable,
+ return a period of 0.0 and inst_ok;
+*/
+
+#undef FREQ_SLOW_PRECISE /* [und] Interpolate then autocorrelate, else autc & filter */
+
+#define NFSAMPS 450 /* Maximum number of samples to read (= 1.0sec) */
+#define NFMXTIME 0.5 /* Time to take measurements over */
+#define PBPMS 20 /* bins per msec */
+#define PERMIN ((1000 * PBPMS)/40) /* 40 Hz */
+#define PERMAX ((1000 * PBPMS)/4) /* 4 Hz*/
+#define NPER (PERMAX - PERMIN + 1)
+#define PWIDTH (8 * PBPMS) /* 8 msec bin spread to look for peak in */
+#define MAXPKS 20 /* Number of peaks to find */
+
+static inst_code k10_imp_measure_refresh(
+ kleink10 *p,
+ double *ref_rate
+) {
+ inst_code ev;
+ int i, j, k, mm;
+
+ int nfsamps; /* Actual samples read */
+ double *samp; /* Samples */
+ double srate; /* Sampling rate used to measure frequency */
+ double rsamp; /* Sampling time */
+
+ double minv; /* Minimum reading */
+ double maxv; /* Maximum reading */
+ double maxt; /* Time range */
+
+#ifdef FREQ_SLOW_PRECISE
+ int nbins;
+ double *bins; /* PBPMS sample bins */
+#else
+ double tcorr[NPER]; /* Temp for initial autocorrelation */
+ int ntcorr[NPER]; /* Number accumulated */
+#endif
+ double corr[NPER]; /* Filtered correlation for each period value */
+ double mincv, maxcv; /* Max and min correlation values */
+ double crange; /* Correlation range */
+ double peaks[MAXPKS]; /* Peak wavelength */
+ double peakh[MAXPKS]; /* Peak heighheight */
+ int npeaks; /* Number of peaks */
+ double pval; /* Period value */
+ double rfreq; /* Computed refresh frequency for each try */
+ int tix = 0; /* try index */
+
+ a1logd(p->log,2,"k10_imp_meas_refrate called\n");
+
+ if (ref_rate != NULL)
+ *ref_rate = 0.0; /* Define refresh rate on error */
+
+ rfreq = 0.0;
+ npeaks = 0; /* Number of peaks */
+
+ if ((ev = k10_read_flicker_samples(p, NFMXTIME, &srate, &samp, &nfsamps, 1)) != inst_ok) {
+ return ev;
+ }
+
+ rsamp = 1.0/srate;
+
+#ifdef PLOT_REFRESH
+ /* Plot the raw sensor values */
+ {
+ double xx[NFSAMPS];
+
+ for (i = 0; i < nfsamps; i++)
+ xx[i] = i * rsamp;
+ printf("Fast scan sensor values and time (sec)\n");
+ do_plot(xx, samp, NULL, NULL, nfsamps);
+ }
+#endif /* PLOT_REFRESH */
+
+ /* Locate the smallest values and maximum time */
+ maxt = -1e6;
+ minv = minv = minv = 1e20;
+ maxv = maxv = maxv = -11e20;
+ for (i = nfsamps-1; i >= 0; i--) {
+ if (samp[i] < minv)
+ minv = samp[i];
+ if (samp[i] > maxv)
+ maxv = samp[i];
+ }
+ maxt = (nfsamps-1) * rsamp;
+
+ /* Zero offset the readings */
+ for (i = nfsamps-1; i >= 0; i--)
+ samp[i] -= minv;
+
+#ifdef FREQ_SLOW_PRECISE /* Interp then autocorrelate */
+
+ /* Create PBPMS bins and interpolate readings into them */
+ nbins = 1 + (int)(maxt * 1000.0 * PBPMS + 0.5);
+ if ((bins = (double *)calloc(sizeof(double), nbins)) == NULL) {
+ a1loge(p->log, inst_internal_error, "k10_imp_measure_refresh: malloc nbins %d failed\n",nbins);
+ free(samp);
+ return k10_interp_code(p, K10_INT_MALLOC);
+ }
+
+ /* Do the interpolation */
+ for (k = 0; k < (nfsamps-1); k++) {
+ int sbin, ebin;
+ double ksec = k * rsamp;
+ double ksecp1 = (k+1) * rsamp;
+ sbin = (int)(ksec * 1000.0 * PBPMS + 0.5);
+ ebin = (int)(ksecp1 * 1000.0 * PBPMS + 0.5);
+ for (i = sbin; i <= ebin; i++) {
+ double bl;
+#if defined(__APPLE__) && defined(__POWERPC__)
+ gcc_bug_fix(i);
+#endif
+ bl = (i - sbin)/(double)(ebin - sbin); /* 0.0 to 1.0 */
+ bins[i] = (1.0 - bl) * samp[k] + bl * samp[k+1];
+ }
+ }
+
+#ifdef NEVER
+
+ /* Plot interpolated values */
+ {
+ double *xx = malloc(sizeof(double) * nbins);
+
+ if (xx == NULL) {
+ a1loge(p->log, inst_internal_error, "k10_imp_measure_refresh: malloc plot nbins %d failed\n",nbins);
+ free(samp);
+ return k10_interp_code(p, K10_INT_MALLOC);
+ }
+ for (i = 0; i < nbins; i++)
+ xx[i] = i / (double)PBPMS; /* msec */
+ printf("Interpolated fast scan sensor values and time (msec)\n");
+ do_plot(xx, bins, NULL, NULL, nbins);
+ free(xx);
+ }
+#endif /* NEVER */
+
+ /* Compute auto-correlation at 1/PBPMS msec intervals */
+ /* from 25 msec (40Hz) to 100msec (10 Hz) */
+ mincv = 1e48, maxcv = -1e48;
+ for (i = 0; i < NPER; i++) {
+ int poff = PERMIN + i; /* Offset to corresponding sample */
+
+ corr[i] = 0;
+ for (k = 0; (k + poff) < nbins; k++)
+ corr[i] += bins[k] * bins[k + poff];
+ corr[i] /= (double)k; /* Normalize */
+
+ if (corr[i] > maxcv)
+ maxcv = corr[i];
+ if (corr[i] < mincv)
+ mincv = corr[i];
+ }
+ /* Free the bins */
+ free(bins);
+
+#else /* !FREQ_SLOW_PRECISE Fast - autocorrellate then filter */
+
+ /* Do point by point correllation of samples */
+ for (i = 0; i < NPER; i++) {
+ tcorr[i] = 0.0;
+ ntcorr[i] = 0;
+ }
+
+ for (j = 0; j < (nfsamps-1); j++) {
+
+ for (k = j+1; k < nfsamps; k++) {
+ double del, cor;
+ int bix;
+
+ del = (k - j) * rsamp; /* Sample time delta */
+ bix = (int)(del * 1000.0 * PBPMS + 0.5);
+ if (bix < PERMIN)
+ continue;
+ if (bix > PERMAX)
+ break;
+ bix -= PERMIN;
+
+ cor = samp[j] * samp[k];
+
+//printf("~1 j %d k %d, del %f bix %d cor %f\n",j,k,del,bix,cor);
+ tcorr[bix] += cor;
+ ntcorr[bix]++;
+ }
+ }
+ /* Divide out count and linearly interpolate */
+ j = 0;
+ for (i = 0; i < NPER; i++) {
+ if (ntcorr[i] > 0) {
+ tcorr[i] /= ntcorr[i];
+ if ((i - j) > 1) {
+ if (j == 0) {
+ for (k = j; k < i; k++)
+ tcorr[k] = tcorr[i];
+
+ } else { /* Linearly interpolate from last value */
+ double ww = (double)i-j;
+ for (k = j+1; k < i; k++) {
+ double bl = (k-j)/ww;
+ tcorr[k] = (1.0 - bl) * tcorr[j] + bl * tcorr[i];
+ }
+ }
+ }
+ j = i;
+ }
+ }
+ if (j < (NPER-1)) {
+ for (k = j+1; k < NPER; k++) {
+ tcorr[k] = tcorr[j];
+ }
+ }
+
+#ifdef PLOT_REFRESH
+ /* Plot unfiltered auto correlation */
+ {
+ double xx[NPER];
+ double y1[NPER];
+
+ for (i = 0; i < NPER; i++) {
+ xx[i] = (i + PERMIN) / (double)PBPMS; /* msec */
+ y1[i] = tcorr[i];
+ }
+ printf("Unfiltered auto correlation (msec)\n");
+ do_plot(xx, y1, NULL, NULL, NPER);
+ }
+#endif /* PLOT_REFRESH */
+
+ /* Apply a gausian filter */
+#define FWIDTH 100
+ {
+ double gaus_[2 * FWIDTH * PBPMS + 1];
+ double *gaus = &gaus_[FWIDTH * PBPMS];
+ double bb = 1.0/pow(2, 5.0);
+ double fw = rsamp * 1000.0;
+ int ifw;
+
+//printf("~1 sc = %f = %f msec\n",1.0/rsamp, fw);
+//printf("~1 fw = %f, ifw = %d\n",fw,ifw);
+
+ fw *= 0.9;
+ ifw = (int)ceil(fw * PBPMS);
+ if (ifw > FWIDTH * PBPMS)
+ error("k10: Not enough space for lanczos 2 filter");
+ for (j = -ifw; j <= ifw; j++) {
+ double x, y;
+ x = j/(PBPMS * fw);
+ if (fabs(x) > 1.0)
+ y = 0.0;
+ else
+ y = 1.0/pow(2, 5.0 * x * x) - bb;
+ gaus[j] = y;
+//printf("~1 gaus[%d] = %f\n",j,y);
+ }
+
+ for (i = 0; i < NPER; i++) {
+ double sum = 0.0;
+ double wght = 0.0;
+
+ for (j = -ifw; j <= ifw; j++) {
+ double w;
+ int ix = i + j;
+ if (ix < 0)
+ ix = -ix;
+ if (ix > (NPER-1))
+ ix = 2 * NPER-1 - ix;
+ w = gaus[j];
+ sum += w * tcorr[ix];
+ wght += w;
+ }
+//printf("~1 corr[%d] wgt = %f\n",i,wght);
+ corr[i] = sum / wght;
+ }
+ }
+
+ /* Compute min & max */
+ mincv = 1e48, maxcv = -1e48;
+ for (i = 0; i < NPER; i++) {
+ if (corr[i] > maxcv)
+ maxcv = corr[i];
+ if (corr[i] < mincv)
+ mincv = corr[i];
+ }
+
+#endif /* !FREQ_SLOW_PRECISE Fast - autocorrellate then filter */
+
+ crange = maxcv - mincv;
+ a1logd(p->log,3,"Correlation value range %f - %f = %f = %f%%\n",mincv, maxcv,crange, 100.0 * (maxcv-mincv)/maxcv);
+
+#ifdef PLOT_REFRESH
+ /* Plot this measuremnts auto correlation */
+ {
+ double xx[NPER];
+ double y1[NPER];
+
+ for (i = 0; i < NPER; i++) {
+ xx[i] = (i + PERMIN) / (double)PBPMS; /* msec */
+ y1[i] = corr[i];
+ }
+ printf("Auto correlation (msec)\n");
+ do_plot6(xx, y1, NULL, NULL, NULL, NULL, NULL, NPER);
+ }
+#endif /* PLOT_REFRESH */
+
+#define PFDB 4 // normally debug level 4
+ /* If there is sufficient level and distict correlations */
+ if (crange/maxcv >= 0.1) {
+
+ a1logd(p->log,PFDB,"Searching for peaks\n");
+
+ /* Locate all the peaks starting at the longest correllation */
+ for (i = (NPER-1-PWIDTH); i >= 0 && npeaks < MAXPKS; i--) {
+ double v1, v2, v3;
+ v1 = corr[i];
+ v2 = corr[i + PWIDTH/2]; /* Peak */
+ v3 = corr[i + PWIDTH];
+
+ if (fabs(v3 - v1)/crange < 0.05
+ && (v2 - v1)/crange > 0.025
+ && (v2 - v3)/crange > 0.025
+ && (v2 - mincv)/crange > 0.5) {
+ double pkv; /* Peak value */
+ int pki; /* Peak index */
+ double ii, bl;
+
+#ifdef PLOT_REFRESH
+ a1logd(p->log,PFDB,"Max between %f and %f msec\n",
+ (i + PERMIN)/(double)PBPMS,(i + PWIDTH + PERMIN)/(double)PBPMS);
+#endif
+
+ /* Locate the actual peak */
+ pkv = -1.0;
+ pki = 0;
+ for (j = i; j < (i + PWIDTH); j++) {
+ if (corr[j] > pkv) {
+ pkv = corr[j];
+ pki = j;
+ }
+ }
+#ifdef PLOT_REFRESH
+ a1logd(p->log,PFDB,"Peak is at %f msec, %f corr\n", (pki + PERMIN)/(double)PBPMS, pkv);
+#endif
+
+ /* Interpolate the peak value for higher precision */
+ /* j = bigest */
+ if (corr[pki-1] > corr[pki+1]) {
+ j = pki-1;
+ k = pki+1;
+ } else {
+ j = pki+1;
+ k = pki-1;
+ }
+ bl = (corr[pki] - corr[j])/(corr[pki] - corr[k]);
+ bl = (bl + 1.0)/2.0;
+ ii = bl * pki + (1.0 - bl) * j;
+ pval = (ii + PERMIN)/(double)PBPMS;
+#ifdef PLOT_REFRESH
+ a1logd(p->log,PFDB,"Interpolated peak is at %f msec\n", pval);
+#endif
+ peaks[npeaks] = pval;
+ peakh[npeaks] = corr[pki];
+ npeaks++;
+
+ i -= PWIDTH;
+ }
+#ifdef NEVER
+ if (v2 > v1 && v2 > v3) {
+ printf("Peak rejected:\n");
+ printf("(v3 - v1)/crange = %f < 0.05 ?\n",fabs(v3 - v1)/crange);
+ printf("(v2 - v1)/crange = %f > 0.025 ?\n",(v2 - v1)/crange);
+ printf("(v2 - v3)/crange = %f > 0.025 ?\n",(v2 - v3)/crange);
+ printf("(v2 - mincv)/crange = %f > 0.5 ?\n",(v2 - mincv)/crange);
+ }
+#endif
+ }
+ a1logd(p->log,3,"Number of peaks located = %d\n",npeaks);
+
+ } else {
+ a1logd(p->log,3,"All rejected, crange/maxcv = %f < 0.06\n",crange/maxcv);
+ }
+#undef PFDB
+
+ a1logd(p->log,3,"Number of peaks located = %d\n",npeaks);
+
+ if (npeaks > 1) { /* Compute aparent refresh rate */
+ int nfails;
+ double div, avg, ano;
+
+ /* Try and locate a common divisor amongst all the peaks. */
+ /* This is likely to be the underlying refresh rate. */
+ for (k = 0; k < npeaks; k++) {
+ for (j = 1; j < 25; j++) {
+ avg = ano = 0.0;
+ div = peaks[k]/(double)j;
+ if (div < 5.0)
+ continue; /* Skip anything higher than 200Hz */
+//printf("~1 trying %f Hz\n",1000.0/div);
+ for (nfails = i = 0; i < npeaks; i++) {
+ double rem, cnt;
+
+ rem = peaks[i]/div;
+ cnt = floor(rem + 0.5);
+ rem = fabs(rem - cnt);
+
+#ifdef PLOT_REFRESH
+ a1logd(p->log, 3, "remainder for peak %d = %f\n",i,rem);
+#endif
+ if (rem > 0.06) {
+ if (++nfails > 2)
+ break; /* Fail this divisor */
+ } else {
+ avg += peaks[i]; /* Already weighted by cnt */
+ ano += cnt;
+ }
+ }
+
+ if (nfails == 0 || (nfails <= 2 && npeaks >= 6))
+ break; /* Sucess */
+ /* else go and try a different divisor */
+ }
+ if (j < 25)
+ break; /* Success - found common divisor */
+ }
+ if (k >= npeaks) {
+ a1logd(p->log,3,"Failed to locate common divisor\n");
+
+ } else {
+ pval = 1000.0 * ano/avg;
+ if (pval > srate) {
+ a1logd(p->log,3,"Discarding frequency %f > sample rate %f\n",pval, srate);
+ } else {
+ rfreq = pval;
+ a1logd(p->log,3,"Located frequency %f sum %f dif %f\n",pval, pval + srate, fabs(pval - srate));
+ tix++;
+ }
+ }
+ }
+
+ if (tix) {
+
+ /* The Klein samples so fast, we don't have to deal with */
+ /* sub Nyquist aliases. */
+
+ if (ref_rate != NULL)
+ *ref_rate = rfreq;
+
+ /* Error against my 85Hz CRT - GWG */
+ a1logd(p->log, 1, "Refresh rate %f Hz, error = %.4f%%\n",rfreq,100.0 * fabs(rfreq - 85.0)/(85.0));
+ free(samp);
+ return k10_interp_code(p, K10_OK);
+
+ } else {
+ a1logd(p->log, 3, "Refresh rate was unclear\n");
+ }
+
+ free(samp);
+
+ return k10_interp_code(p, K10_NOREFR_FOUND);
+}
+#undef NFSAMPS
+#undef PBPMS
+#undef PERMIN
+#undef PERMAX
+#undef NPER
+#undef PWIDTH
+
+/* Read an emissive refresh rate */
+static inst_code
+k10_read_refrate(
+inst *pp,
+double *ref_rate
+) {
+ kleink10 *p = (kleink10 *)pp;
+ char buf[MAX_MES_SIZE];
+ double refrate;
+ inst_code rv;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ if (ref_rate != NULL)
+ *ref_rate = 0.0;
+
+ if ((rv = k10_imp_measure_refresh(p, &refrate)) != inst_ok) {
+ return rv;
+ }
+
+ if (refrate == 0.0)
+ return inst_misread;
+
+ if (ref_rate != NULL)
+ *ref_rate = refrate;
+
+ return inst_ok;
+}
+
+/* - - - - - - - - - - - - - - - - */
+/* Measure a display update delay. It is assumed that */
+/* white_stamp(init) has been called, and then a */
+/* white to black change has been made to the displayed color, */
+/* and this will measure the time it took for the update to */
+/* be noticed by the instrument, up to 2.0 seconds. */
+/* (It is assumed that white_change() will be called at the time the patch */
+/* changes color.) */
+/* inst_misread will be returned on failure to find a transition to black. */
+
+#define NDSAMPS 40 /* Maximum samples */
+#define NDMXTIME 2.0 /* Maximum time to take */
+
+static inst_code k10_meas_delay(
+inst *pp,
+int *pdispmsec, /* Return display update delay in msec */
+int *pinstmsec) { /* Return instrument reaction time in msec */
+ kleink10 *p = (kleink10 *)pp;
+ inst_code ev;
+ char mes[MAX_MES_SIZE];
+ int bread;
+ int i, j, k;
+ double sutime, putime, cutime, eutime;
+ struct {
+ double sec;
+ double xyz[3];
+ } samp[NDSAMPS];
+ int ndsamps;
+ double stot, etot, del, thr;
+ double stime, etime;
+ int isdeb;
+ int avgsampsp;
+ int dispmsec, instmsec;
+
+ if (pinstmsec != NULL)
+ *pinstmsec = -230;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+
+ if (!p->inited)
+ return inst_no_init;
+
+ if (usec_time() < 0.0) {
+ a1loge(p->log, inst_internal_error, "k10_imp_meas_delay: No high resolution timers\n");
+ return inst_internal_error;
+ }
+
+ /* Turn debug off so that they doesn't intefere with measurement timing */
+ isdeb = p->log->debug;
+ p->icom->log->debug = 0;
+
+ /* Read the samples */
+ putime = usec_time() / 1000000.0;
+ amutex_lock(p->lock);
+ for (i = 0; i < NDSAMPS; i++) {
+
+ /* Take a measurement to get ranges ? */
+ if ((ev = k10_command(p, "N5\r", mes, MAX_MES_SIZE, &bread, 15, ec_ec, 2.0)) != inst_ok) {
+ amutex_unlock(p->lock);
+ p->log->debug = isdeb;
+ a1logd(p->log, 1, "k10_meas_delay: measurement failed\n");
+ return ev;
+ }
+
+ if ((ev = decodeN5(p, samp[i].xyz, NULL, mes, bread)) != inst_ok) {
+ amutex_unlock(p->lock);
+ p->log->debug = isdeb;
+ a1logd(p->log, 1, "k10_meas_delay: measurement decode failed\n");
+ return ev;
+ }
+
+ cutime = usec_time() / 1000000.0;
+// samp[i].sec = 0.5 * (putime + cutime); /* Mean of before and after stamp ? */
+ samp[i].sec = cutime; /* Assume took until measure was received */
+// samp[i].sec = putime; /* Assume sampled at time triggered */
+ putime = cutime;
+ if (cutime > NDMXTIME)
+ break;
+ }
+ ndsamps = i;
+ amutex_unlock(p->lock);
+
+ /* Average sample spacing in msec */
+ avgsampsp = (int)(1000.0 * (samp[i-1].sec - samp[0].sec)/(i-1.0) + 0.5);
+
+ /* Restore debugging */
+ p->log->debug = isdeb;
+
+ if (ndsamps == 0) {
+ a1logd(p->log, 1, "k10_meas_delay: No measurement samples returned in time\n");
+ return inst_internal_error;
+ }
+
+ if (p->whitestamp < 0.0) {
+ a1logd(p->log, 1, "k10_meas_delay: White transition wasn't timestamped\n");
+ return inst_internal_error;
+ }
+
+ /* Set the times to be white transition relative */
+ for (i = 0; i < ndsamps; i++)
+ samp[i].sec -= p->whitestamp / 1000000.0;
+
+ /* Over the first 100msec, locate the maximum value */
+ stime = samp[0].sec;
+ stot = -1e9;
+ for (i = 0; i < ndsamps; i++) {
+ if (samp[i].xyz[1] > stot)
+ stot = samp[i].xyz[1];
+ if ((samp[i].sec - stime) > 0.1)
+ break;
+ }
+
+ /* Over the last 100msec, locate the maximum value */
+ etime = samp[ndsamps-1].sec;
+ etot = -1e9;
+ for (i = ndsamps-1; i >= 0; i--) {
+ if (samp[i].xyz[1] > etot)
+ etot = samp[i].xyz[1];
+ if ((etime - samp[i].sec) > 0.1)
+ break;
+ }
+
+ del = etot - stot;
+ thr = etot - 0.10 * del; /* 10% of transition threshold */
+
+#ifdef PLOT_UPDELAY
+ a1logd(p->log, 0, "k10_meas_delay: start tot %f end tot %f del %f, thr %f\n", stot, etot, del, thr);
+#endif
+
+#ifdef PLOT_UPDELAY
+ /* Plot the raw sensor values */
+ {
+ double xx[NDSAMPS];
+ double y1[NDSAMPS];
+ double y2[NDSAMPS];
+ double y3[NDSAMPS];
+
+ for (i = 0; i < ndsamps; i++) {
+ xx[i] = samp[i].sec;
+ y1[i] = samp[i].xyz[0];
+ y2[i] = samp[i].xyz[1];
+ y3[i] = samp[i].xyz[2];
+ }
+ printf("Display update delay measure sensor values and time (sec)\n");
+ do_plot(xx, y1, y2, y3, ndsamps);
+ }
+#endif
+
+ /* Check that there has been a transition */
+ if (del < (0.7 * etot)) {
+ a1logd(p->log, 1, "k10_meas_delay: can't detect change from black to white\n");
+ return inst_misread;
+ }
+
+ /* Working from the start, locate the time at which the level was above the threshold */
+ for (i = 0; i < (ndsamps-1); i++) {
+ if (samp[i].xyz[1] > thr)
+ break;
+ }
+
+ a1logd(p->log, 2, "k10_meas_delay: stoped at sample %d time %f\n",i,samp[i].sec);
+
+ /* Compute overall delay */
+ dispmsec = (int)(samp[i].sec * 1000.0 + 0.5);
+
+ /* The 20 Hz filter is probably a FIR which introduces a delay in */
+ /* the samples being measured, creating both a settling delay and */
+ /* a look ahead. A negative inst. reaction time value will cause the */
+ /* patch_delay to be extended by that amount of time. */
+ /* We assume 2 samples times to settle, but round up the patch */
+ /* delay conservatively. */
+ instmsec = -2 * avgsampsp;
+
+#ifdef PLOT_UPDELAY
+ a1logd(p->log, 0, "k10_meas_delay: raw %d & %d msec\n",dispmsec,instmsec);
+#endif
+
+ dispmsec += instmsec; /* Account for lookahead */
+
+ if (dispmsec < 0) /* This can happen if the patch generator delays it's return */
+ dispmsec = 0;
+
+ /* Round the patch delay to to next highest avgsampsp */
+ dispmsec = (int)((1.0 + floor((double)dispmsec/(double)avgsampsp)) * avgsampsp + 0.5);
+
+ if (pdispmsec != NULL)
+ *pdispmsec = dispmsec;
+
+ if (pinstmsec != NULL)
+ *pinstmsec = instmsec;
+
+#ifdef PLOT_UPDELAY
+ a1logd(p->log, 0, "k10_meas_delay: returning %d & %d msec\n",dispmsec,instmsec);
+#endif
+
+ return inst_ok;
+}
+#undef NDSAMPS
+#undef DINTT
+#undef NDMXTIME
+
+
+/* Timestamp the white patch change during meas_delay() */
+static inst_code k10_white_change(
+inst *pp,
+int init) {
+ kleink10 *p = (kleink10 *)pp;
+ inst_code ev;
+
+ if (init)
+ p->whitestamp = -1.0;
+ else {
+ if ((p->whitestamp = usec_time()) < 0.0) {
+ a1loge(p->log, inst_internal_error, "k10_wite_changeO: No high resolution timers\n");
+ return inst_internal_error;
+ }
+ }
+
+ return inst_ok;
+}
+
+/* - - - - - - - - - - - - - - - - */
+
+/* Do a black calibration */
+static inst_code
+k10_do_black_cal(
+ kleink10 *p
+) {
+ inst_code ev;
+ char mes[MAX_MES_SIZE];
+ unsigned char *umes = (unsigned char *)mes;
+ int bread;
+ int i, j, k;
+ int val, th1, th2;
+ int bvals[6][3]; /* Black values for range 1 to 6 */
+ int thermal; /* Thermal value */
+
+ amutex_lock(p->lock);
+
+ /* First get the Measure Count to check that TH1 and TH2 are between 50 and 200 */
+ /* (Don't know why or what these mean - something to do with temperature compensation */
+ /* values not being setup ?) */
+ if ((ev = k10_command(p, "M6\r", mes, MAX_MES_SIZE, &bread, 20, ec_e, 2.0)) != inst_ok) {
+ amutex_unlock(p->lock);
+ a1logd(p->log, 1, "k10_do_black_cal: M6 failed\n");
+ return ev;
+ }
+
+ if (bread < 17) {
+ amutex_unlock(p->lock);
+ a1logd(p->log, 1, "k10_do_black_cal: not enough bytes returned from M6 (%d)\n",bread);
+ return inst_protocol_error;
+ }
+
+ th1 = umes[14];
+ th2 = umes[15];
+
+ if (th1 < 50 || th1 > 200
+ || th2 < 50 || th2 > 200) {
+ amutex_unlock(p->lock);
+ a1logd(p->log, 1, "th1 %d or th2 %d is out of range 50-200\n",th1,th2);
+ return k10_interp_code(p, K10_BLACK_CAL_INIT);
+ }
+
+ /* Do the black calibration */
+ if ((ev = k10_command(p, "B9\r", mes, MAX_MES_SIZE, &bread, 43, ec_ec, 15.0)) != inst_ok) {
+ a1logd(p->log, 1, "k10_do_black_cal: B9 failed\n");
+ amutex_unlock(p->lock);
+ return ev;
+ }
+
+ if (bread < 40) {
+ amutex_unlock(p->lock);
+ a1logd(p->log, 1, "k10_do_black_cal: not enough bytes returned from B9 (%d)\n",bread);
+ return inst_protocol_error;
+ }
+
+ /* Parse the black values that resulted */
+ for (k = i = 0; i < 6; i++) {
+ for (j = 0; j < 3; j++, k++) {
+ val = umes[2 + 2 * k] * 256 + umes[2 + 2 * k + 1];
+ if (val < 500 || val > 2500) {
+ amutex_unlock(p->lock);
+ a1logd(p->log, 1, "k10_do_black_cal: B9 black result value out of range\n");
+ return k10_interp_code(p, K10_BLACK_CAL_FAIL);
+ }
+ bvals[i][j] = val;
+ }
+ }
+ val = umes[2 + 2 * k] * 256 + umes[2 + 2 * k + 1];
+ if (val < 500 || val > 2500) {
+ amutex_unlock(p->lock);
+ a1logd(p->log, 1, "k10_do_black_cal: B9 black thermal result value out of range\n");
+ return k10_interp_code(p, K10_BLACK_CAL_FAIL);
+ }
+ thermal = val;
+
+ if (p->log->debug >= 4) {
+ for (i = 0; i < 6; i++)
+ a1logd(p->log, 4, "Black cal. Range %d XYZ = %d %d %d\n",
+ i+1, bvals[i][0], bvals[i][1], bvals[i][2]);
+ a1logd(p->log, 4, "Thermal %d\n",thermal);
+ }
+
+ /* All looks well - copy into Flash ROM */
+ if ((ev = k10_command(p, "B7\r", mes, MAX_MES_SIZE, &bread, 2, ec_c, 2.0)) != inst_ok) {
+ amutex_unlock(p->lock);
+ a1logd(p->log, 1, "k10_do_black_cal: B7 failed\n");
+ return ev;
+ }
+
+ /* Send verification code and get error code*/
+ if ((ev = k10_command(p, "{00000000}@%#\r", mes, MAX_MES_SIZE, &bread, 3, ec_e, 2.0)) != inst_ok) {
+ amutex_unlock(p->lock);
+ a1logd(p->log, 1, "k10_do_black_cal: B7 followup failed\n");
+ return ev;
+ }
+ amutex_unlock(p->lock);
+
+ a1logd(p->log, 4, "k10_do_black_cal: Done\n");
+
+ return inst_ok;
+}
+
+/* - - - - - - - - - - - - - - - - */
+
+/* Return needed and available inst_cal_type's */
+static inst_code k10_get_n_a_cals(inst *pp, inst_cal_type *pn_cals, inst_cal_type *pa_cals) {
+ kleink10 *p = (kleink10 *)pp;
+ inst_cal_type n_cals = inst_calt_none;
+ inst_cal_type a_cals = inst_calt_none;
+
+ /* Can do a black cal, but not required */
+ a_cals |= inst_calt_emis_offset;
+
+ if (pn_cals != NULL)
+ *pn_cals = n_cals;
+
+ if (pa_cals != NULL)
+ *pa_cals = a_cals;
+
+ return inst_ok;
+}
+
+/* Request an instrument calibration. */
+inst_code k10_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) */
+) {
+ kleink10 *p = (kleink10 *)pp;
+ 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 = k10_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,"k10_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;
+ }
+
+ /* Do the appropriate calibration */
+ if (*calt & inst_calt_emis_offset) {
+
+ if (*calc != inst_calc_man_em_dark) {
+ *calc = inst_calc_man_em_dark;
+ return inst_cal_setup;
+ }
+
+ /* Do black offset calibration */
+ if ((ev = k10_do_black_cal(p)) != inst_ok)
+ return ev;
+
+ *calt &= ~inst_calc_man_em_dark;
+ }
+ return inst_ok;
+}
+
+/* Error codes interpretation */
+static char *
+k10_interp_error(inst *pp, int ec) {
+ kleink10 *p = (kleink10 *)pp;
+ ec &= inst_imask;
+ switch (ec) {
+ case K10_INTERNAL_ERROR:
+ return "Internal software error";
+ case K10_TIMEOUT:
+ return "Communications timeout";
+ case K10_COMS_FAIL:
+ return "Communications failure";
+ case K10_UNKNOWN_MODEL:
+ return "Not a Klein K10";
+ case K10_DATA_PARSE_ERROR:
+ return "Data from kleink10 didn't parse as expected";
+// case K10_SPOS_EMIS:
+// return "Ambient filter should be removed";
+// case K10_SPOS_AMB:
+// return "Ambient filter should be used";
+
+ case K10_OK:
+ return "No device error";
+
+ case K10_CMD_VERIFY:
+ return "Instrument didn't echo command code";
+ case K10_BAD_RETVAL:
+ return "Unable to parse return instruction return code";
+
+ case K10_FIRMWARE:
+ return "Firmware error";
+
+ case K10_BLACK_EXCESS:
+ return "Black Excessive";
+ case K10_BLACK_OVERDRIVE:
+ return "Black Overdrive";
+ case K10_BLACK_ZERO:
+ return "Black Zero";
+
+ case K10_OVER_HIGH_RANGE:
+ return "Over High Range";
+ case K10_TOP_OVER_RANGE:
+ return "Top over range";
+ case K10_BOT_UNDER_RANGE:
+ return "Bottom under range";
+ case K10_AIMING_LIGHTS:
+ return "Aiming lights on when measuring";
+
+ case K10_UNKNOWN:
+ return "Unknown error from instrument";
+
+ case K10_INT_MALLOC:
+ return "Memory allocation failure";
+
+ case K10_NOREFR_FOUND:
+ return "No refresh rate detected or failed to measure it";
+
+ case K10_NOTRANS_FOUND:
+ return "No delay measurment transition found";
+
+ case K10_RANGE_CHANGE:
+ return "Range changed during measurement";
+
+ case K10_BLACK_CAL_INIT:
+ return "Instrument hasn't been setup for black calibration";
+ case K10_BLACK_CAL_FAIL:
+ return "Black calibration failed";
+
+ default:
+ return "Unknown error code";
+ }
+}
+
+
+/* Convert a machine specific error code into an abstract dtp code */
+static inst_code
+k10_interp_code(kleink10 *p, int ec) {
+
+ ec &= inst_imask;
+ switch (ec) {
+
+ case K10_OK:
+ return inst_ok;
+
+ case K10_INTERNAL_ERROR:
+ case K10_AIMING_LIGHTS:
+ case K10_UNKNOWN:
+ case K10_INT_MALLOC:
+ return inst_internal_error | ec;
+
+ case K10_TIMEOUT:
+ case K10_COMS_FAIL:
+ return inst_coms_fail | ec;
+
+ case K10_UNKNOWN_MODEL:
+ return inst_unknown_model | ec;
+
+ case K10_CMD_VERIFY:
+ case K10_BAD_RETVAL:
+ case K10_DATA_PARSE_ERROR:
+ return inst_protocol_error | ec;
+
+ case K10_FIRMWARE:
+ case K10_BLACK_EXCESS: // ?
+ case K10_BLACK_OVERDRIVE: // ?
+ case K10_BLACK_ZERO: // ?
+ case K10_BLACK_CAL_INIT:
+ return inst_hardware_fail | ec;
+
+ case K10_OVER_HIGH_RANGE:
+ case K10_TOP_OVER_RANGE:
+ case K10_BOT_UNDER_RANGE:
+ case K10_NOREFR_FOUND:
+ case K10_NOTRANS_FOUND:
+ case K10_RANGE_CHANGE:
+ case K10_BLACK_CAL_FAIL:
+ return inst_misread | ec;
+
+ }
+ return inst_other_error | ec;
+}
+
+/* Destroy ourselves */
+static void
+k10_del(inst *pp) {
+ if (pp != NULL) {
+ kleink10 *p = (kleink10 *)pp;
+ if (p->icom != NULL)
+ p->icom->del(p->icom);
+ amutex_del(p->lock);
+ free(p);
+ }
+}
+
+/* Return the instrument mode capabilities */
+static void k10_capabilities(inst *pp,
+inst_mode *pcap1,
+inst2_capability *pcap2,
+inst3_capability *pcap3) {
+ kleink10 *p = (kleink10 *)pp;
+ inst_mode cap1 = 0;
+ inst2_capability cap2 = 0;
+
+ cap1 |= inst_mode_emis_tele
+ | inst_mode_emis_spot
+ | inst_mode_ambient /* But cc matrix is up to user */
+ | inst_mode_emis_nonadaptive
+ | inst_mode_colorimeter
+ ;
+
+ /* can inst2_has_sensmode, but not report it asynchronously */
+ cap2 |= inst2_prog_trig
+ | inst2_user_trig
+ | inst2_disptype
+ | inst2_has_target /* Has target lights */
+ | inst2_ccmx
+ | inst2_emis_refr_meas
+ | inst2_meas_disp_update
+ ;
+
+
+ if (pcap1 != NULL)
+ *pcap1 = cap1;
+ if (pcap2 != NULL)
+ *pcap2 = cap2;
+ if (pcap3 != NULL)
+ *pcap3 = inst3_none;
+}
+
+/* Check device measurement mode */
+static inst_code k10_check_mode(inst *pp, inst_mode m) {
+ inst_mode cap;
+
+ if (!pp->gotcoms)
+ return inst_no_coms;
+ if (!pp->inited)
+ return inst_no_init;
+
+ pp->capabilities(pp, &cap, NULL, NULL);
+
+ /* Simple test */
+ if (m & ~cap)
+ return inst_unsupported;
+
+ if (!IMODETST(m, inst_mode_emis_spot)
+ && !IMODETST(m, inst_mode_emis_tele)
+ && !IMODETST(m, inst_mode_emis_ambient)) {
+ return inst_unsupported;
+ }
+
+ return inst_ok;
+}
+
+/* Set device measurement mode */
+static inst_code k10_set_mode(inst *pp, inst_mode m) {
+ kleink10 *p = (kleink10 *)pp;
+ int refrmode;
+ inst_code ev;
+
+ if ((ev = k10_check_mode(pp, m)) != inst_ok)
+ return ev;
+
+ p->mode = m;
+
+ return inst_ok;
+}
+
+/* This table gets extended on initialisation */
+/* There is 1 factory + 96 programmable + end marker */
+static inst_disptypesel k10_disptypesel[98] = {
+ {
+ inst_dtflags_default | inst_dtflags_mtx, /* flags */
+ 1, /* cbid */
+ "F", /* sel */
+ "Factory Default", /* desc */
+ 0, /* refr */
+ disptech_unknown, /* disptype */
+ 0 /* ix */
+ },
+ {
+ inst_dtflags_end,
+ 0,
+ "",
+ "",
+ 0,
+ disptech_none,
+ 0
+ }
+};
+
+/* Get mode and option details */
+static inst_code k10_get_disptypesel(
+inst *pp,
+int *pnsels, /* Return number of display types */
+inst_disptypesel **psels, /* Return the array of display types */
+int allconfig, /* nz to return list for all configs, not just current. */
+int recreate /* nz to re-check for new ccmx & ccss files */
+) {
+ kleink10 *p = (kleink10 *)pp;
+ inst_code rv = inst_ok;
+
+ /* Create/Re-create a current list of available display types */
+ if (p->dtlist == NULL || recreate) {
+ if ((rv = inst_creat_disptype_list(pp, &p->ndtlist, &p->dtlist,
+ k10_disptypesel, 0 /* doccss*/, 1 /* doccmx */)) != inst_ok)
+ return rv;
+ }
+
+ if (pnsels != NULL)
+ *pnsels = p->ndtlist;
+
+ if (psels != NULL)
+ *psels = p->dtlist;
+
+ return inst_ok;
+}
+
+/* Given a display type entry, setup calibration from that type */
+static inst_code set_disp_type(kleink10 *p, inst_disptypesel *dentry) {
+
+ /* If aninbuilt matrix hasn't been read from the instrument, */
+ /* read it now. */
+ if ((dentry->flags & inst_dtflags_mtx)
+ && (dentry->flags & inst_dtflags_ld) == 0) {
+ inst_code rv;
+ if ((rv = k10_read_cal_matrix(p, dentry, dentry->ix)) != inst_ok)
+ return rv;
+ }
+
+ if (dentry->flags & inst_dtflags_ccmx) {
+ if (dentry->cc_cbid != 1) {
+ a1loge(p->log, 1, "k10: matrix must use cbid 1!\n",dentry->cc_cbid);
+ return inst_wrong_setup;
+ }
+
+ p->dtech = dentry->dtech;
+ icmCpy3x3(p->ccmat, dentry->mat);
+ p->cbid = 0; /* Can't be a base type now */
+
+ } else {
+ p->dtech = dentry->dtech;
+ icmCpy3x3(p->ccmat, dentry->mat);
+ p->cbid = dentry->cbid;
+ p->ucbid = dentry->cbid; /* This is underying base if dentry is base selection */
+ }
+
+ if (p->log->debug >= 4) {
+ a1logd(p->log,4,"ccmat = %f %f %f\n",
+ p->ccmat[0][0], p->ccmat[0][1], p->ccmat[0][2]);
+ a1logd(p->log,4," %f %f %f\n",
+ p->ccmat[1][0], p->ccmat[1][1], p->ccmat[1][2]);
+ a1logd(p->log,4," %f %f %f\n\n",
+ p->ccmat[2][0], p->ccmat[2][1], p->ccmat[2][2]);
+ a1logd(p->log,4,"ucbid = %d, cbid = %d\n",p->ucbid, p->cbid);
+ a1logd(p->log,4,"\n");
+ }
+
+ return inst_ok;
+}
+
+/* Setup the default display type */
+static inst_code set_default_disp_type(kleink10 *p) {
+ inst_code ev;
+ int i;
+
+ if (p->dtlist == NULL) {
+ if ((ev = inst_creat_disptype_list((inst *)p, &p->ndtlist, &p->dtlist,
+ k10_disptypesel, 0 /* doccss*/, 1 /* doccmx */)) != inst_ok)
+ return ev;
+ }
+
+ for (i = 0; !(p->dtlist[i].flags & inst_dtflags_end); i++) {
+ if (p->dtlist[i].flags & inst_dtflags_default)
+ break;
+ }
+ if (p->dtlist[i].flags & inst_dtflags_end) {
+ a1loge(p->log, 1, "set_default_disp_type: failed to find type!\n");
+ return inst_internal_error;
+ }
+ if ((ev = set_disp_type(p, &p->dtlist[i])) != inst_ok) {
+ return ev;
+ }
+
+ return inst_ok;
+}
+
+/* Set the display type */
+static inst_code k10_set_disptype(inst *pp, int ix) {
+ kleink10 *p = (kleink10 *)pp;
+ inst_code ev;
+ inst_disptypesel *dentry;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ if (p->dtlist == NULL) {
+ if ((ev = inst_creat_disptype_list((inst *)p, &p->ndtlist, &p->dtlist,
+ k10_disptypesel, 0 /* doccss*/, 1 /* doccmx */)) != inst_ok)
+ return ev;
+ }
+
+ if (ix < 0 || ix >= p->ndtlist)
+ return inst_unsupported;
+
+ dentry = &p->dtlist[ix];
+
+ if ((ev = set_disp_type(p, dentry)) != inst_ok) {
+ return ev;
+ }
+
+ return inst_ok;
+}
+
+/* Get the disptech and other corresponding info for the current */
+/* selected display type. Returns disptype_unknown by default. */
+/* Because refrmode can be overridden, it may not match the refrmode */
+/* of the dtech. (Pointers may be NULL if not needed) */
+static inst_code k10_get_disptechi(
+inst *pp,
+disptech *dtech,
+int *refrmode,
+int *cbid) {
+ kleink10 *p = (kleink10 *)pp;
+ if (dtech != NULL)
+ *dtech = p->dtech;
+ if (refrmode != NULL)
+ *refrmode = disptech_get_id(disptech_unknown)->refr;
+ if (cbid != NULL)
+ *cbid = p->cbid;
+ return inst_ok;
+}
+
+/* Insert a colorimetric correction matrix in the instrument XYZ readings */
+/* This is only valid for colorimetric instruments. */
+/* To remove the matrix, pass NULL for the filter filename */
+inst_code k10_col_cor_mat(
+inst *pp,
+disptech dtech, /* Use disptech_unknown if not known */ \
+int cbid, /* Calibration display type base ID, 1 if unknown */\
+double mtx[3][3]
+) {
+ kleink10 *p = (kleink10 *)pp;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ /* We don't have to set the base type since the instrument always returns factory */
+ if (cbid != 1) {
+ a1loge(p->log, 1, "k10: matrix must use cbid 1!\n",cbid);
+ return inst_wrong_setup;
+ }
+
+ if (mtx == NULL) {
+ icmSetUnity3x3(p->ccmat);
+ } else {
+ icmCpy3x3(p->ccmat, mtx);
+ }
+
+ p->dtech = dtech;
+ p->cbid = 0;
+
+ if (p->log->debug >= 4) {
+ a1logd(p->log,4,"ccmat = %f %f %f\n",
+ p->ccmat[0][0], p->ccmat[0][1], p->ccmat[0][2]);
+ a1logd(p->log,4," %f %f %f\n",
+ p->ccmat[1][0], p->ccmat[1][1], p->ccmat[1][2]);
+ a1logd(p->log,4," %f %f %f\n\n",
+ p->ccmat[2][0], p->ccmat[2][1], p->ccmat[2][2]);
+ a1logd(p->log,4,"ucbid = %d, cbid = %d\n",p->ucbid, p->cbid);
+ a1logd(p->log,4,"\n");
+ }
+
+ return inst_ok;
+}
+
+/*
+ * set or reset an optional mode
+ *
+ * Some options talk to the instrument, and these will
+ * error if it hasn't been initialised.
+ */
+static inst_code
+k10_get_set_opt(inst *pp, inst_opt_type m, ...)
+{
+ kleink10 *p = (kleink10 *)pp;
+ char buf[MAX_MES_SIZE];
+ int se;
+
+ a1logd(p->log, 5, "k10_get_set_opt: opt type 0x%x\n",m);
+
+ /* Record the trigger mode */
+ if (m == inst_opt_trig_prog
+ || m == inst_opt_trig_user) {
+ p->trig = m;
+ return inst_ok;
+ }
+
+ /* Get target light state */
+ if (m == inst_opt_get_target_state) {
+ va_list args;
+ int *pstate, lstate = 0;
+
+ va_start(args, m);
+ pstate = va_arg(args, int *);
+ va_end(args);
+
+ if (pstate != NULL)
+ *pstate = p->lights;
+
+ return inst_ok;
+
+ /* Set target light state */
+ } else if (m == inst_opt_set_target_state) {
+ inst_code ev;
+ va_list args;
+ int state = 0;
+
+ va_start(args, m);
+ state = va_arg(args, int);
+ va_end(args);
+
+ amutex_lock(p->lock);
+
+ if (state == 2) { /* Toggle */
+ if (p->lights)
+ state = 0;
+ else
+ state = 1;
+ }
+
+ if (state == 1) { /* Turn on */
+ if ((ev = k10_command(p, "L1\r", buf, MAX_MES_SIZE, NULL, 2+3, ec_ec, 0.5)) != inst_ok) {
+ amutex_unlock(p->lock);
+ a1logd(p->log, 1, "k10_get_set_opt: L1 failed\n");
+ return ev;
+ }
+ p->lights = 1;
+ } else if (state == 0) { /* Turn off */
+ if ((ev = k10_command(p, "L0\r", buf, MAX_MES_SIZE, NULL, 2+3, ec_ec, 0.5)) != inst_ok) {
+ amutex_unlock(p->lock);
+ a1logd(p->log, 1, "k10_get_set_opt: L0 failed\n");
+ return ev;
+ }
+ p->lights = 0;
+ }
+ amutex_unlock(p->lock);
+ return inst_ok;
+ }
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ return inst_unsupported;
+}
+
+/* Constructor */
+extern kleink10 *new_kleink10(icoms *icom, instType itype) {
+ kleink10 *p;
+ if ((p = (kleink10 *)calloc(sizeof(kleink10),1)) == NULL) {
+ a1loge(icom->log, 1, "new_kleink10: malloc failed!\n");
+ return NULL;
+ }
+
+ p->log = new_a1log_d(icom->log);
+
+ p->init_coms = k10_init_coms;
+ p->init_inst = k10_init_inst;
+ p->capabilities = k10_capabilities;
+ p->check_mode = k10_check_mode;
+ p->set_mode = k10_set_mode;
+ p->get_disptypesel = k10_get_disptypesel;
+ p->set_disptype = k10_set_disptype;
+ p->get_disptechi = k10_get_disptechi;
+ p->get_set_opt = k10_get_set_opt;
+ p->read_sample = k10_read_sample;
+ p->read_refrate = k10_read_refrate;
+ p->get_n_a_cals = k10_get_n_a_cals;
+ p->calibrate = k10_calibrate;
+ p->col_cor_mat = k10_col_cor_mat;
+ p->meas_delay = k10_meas_delay;
+ p->white_change = k10_white_change;
+ p->interp_error = k10_interp_error;
+ p->del = k10_del;
+
+ p->icom = icom;
+ p->itype = icom->itype;
+ p->dtech = disptech_unknown;
+
+ amutex_init(p->lock);
+
+ /* Attempt to get the calibration list */
+ k10_init_coms((inst *)p, baud_nc, fc_nc, 0.0);
+
+ return p;
+}
+