summaryrefslogtreecommitdiff
path: root/spectro/specbos.c
diff options
context:
space:
mode:
Diffstat (limited to 'spectro/specbos.c')
-rw-r--r--spectro/specbos.c1674
1 files changed, 1674 insertions, 0 deletions
diff --git a/spectro/specbos.c b/spectro/specbos.c
new file mode 100644
index 0000000..f81f2d7
--- /dev/null
+++ b/spectro/specbos.c
@@ -0,0 +1,1674 @@
+
+/*
+ * Argyll Color Correction System
+ *
+ * JETI specbos 1211/1201 related functions
+ *
+ * Author: Graeme W. Gill
+ * Date: 13/3/2013
+ *
+ * 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
+ */
+
+/*
+ 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:
+
+ Should add a reflective and transmissive modes,
+ by doing a white calibration and dividing the measurement.
+
+*/
+
+#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 "specbos.h"
+
+/* Default flow control */
+#define DEFFC fc_none
+
+static inst_code specbos_interp_code(inst *pp, int ec);
+
+#define MAX_MES_SIZE 500 /* Maximum normal message reply size */
+#define MAX_RD_SIZE 8000 /* Maximum reading message reply size */
+
+/* Interpret an icoms error into a SPECBOS error */
+static int icoms2specbos_err(int se) {
+ if (se != ICOM_OK) {
+ if (se & ICOM_TO)
+ return SPECBOS_TIMEOUT;
+ return SPECBOS_COMS_FAIL;
+ }
+ return SPECBOS_OK;
+}
+
+/* Do a full command/response echange with the specbos */
+/* (This level is not multi-thread safe) */
+/* Return the specbos error code. */
+static int
+specbos_fcommand(
+struct _specbos *p,
+char *in, /* In string */
+char *out, /* Out string buffer */
+int bsize, /* Out buffer size */
+double to, /* Timeout in seconds */
+int ntc, /* Number or termination chars */
+int ctype, /* 0 = normal, 1 = *init, 2 = refr reading */
+int nd /* nz to disable debug messages */
+) {
+ int se;
+ char *cp, *tc = "", *dp;
+
+ if (ctype == 0)
+ tc = "\r\006\025"; /* Return, Ack or Nak */
+ else if (ctype == 1)
+ tc = "\007\025"; /* Bell or Nak */
+ else if (ctype == 2)
+ tc = "\r\025"; /* Return or Nak */
+
+ if ((se = p->icom->write_read(p->icom, in, out, bsize, tc, ntc, to)) != 0) {
+ if (!nd) a1logd(p->log, 1, "specbos_fcommand: serial i/o failure on write_read '%s' 0x%x\n",icoms_fix(in),se);
+ return icoms2specbos_err(se);
+ }
+
+ /* See if there was an error, and remove any enquire codes */
+ for (dp = cp = out; *cp != '\000' && (out - dp) < bsize; cp++) {
+ if (*cp == '\025') { /* Got a Nak */
+ char buf[100];
+
+ if ((se = p->icom->write_read(p->icom, "*stat:err?\r", buf, 100, "\r", 1, 1.0)) != 0) {
+ if (!nd) a1logd(p->log, 1, "specbos_fcommand: serial i/o failure on write_read '%s'\n",icoms_fix(in));
+ if (sscanf(buf, "Error Code: %d ",&se) != 1) {
+ if (!nd) a1logd(p->log, 1, "specbos_fcommand: failed to parse error code '%s'\n",icoms_fix(buf));
+ return icoms2specbos_err(se);
+ }
+ }
+ break;
+ }
+ if (*cp == '\005') /* Got an Enquire */
+ continue; /* remove it */
+ *dp = *cp;
+ dp++;
+ }
+ out[bsize-1] = '\000';
+
+ if (!nd) a1logd(p->log, 4, "specbos_fcommand: command '%s' returned '%s'\n",
+ icoms_fix(in), icoms_fix(out));
+
+ return SPECBOS_OK;
+}
+
+/* Do a normal command/response echange with the specbos. */
+/* (This level is not multi-thread safe) */
+/* Return the inst code */
+static inst_code
+specbos_command(
+struct _specbos *p,
+char *in, /* In string */
+char *out, /* Out string buffer */
+int bsize, /* Out buffer size */
+double to) { /* Timout in seconds */
+ int rv = specbos_fcommand(p, in, out, bsize, to, 1, 0, 0);
+ return specbos_interp_code((inst *)p, rv);
+}
+
+/* Read another line of response */
+/* (This level is not multi-thread safe) */
+/* Return the inst code */
+static int
+specbos_readresp(
+struct _specbos *p,
+char *out, /* Out string buffer */
+int bsize, /* Out buffer size */
+double to /* Timeout in seconds */
+) {
+ int rv, se;
+ char *cp, *tc = "\r\006\025"; /* Return, Ack or Nak */
+
+ if ((se = p->icom->read(p->icom, out, bsize, tc, 1, to)) != 0) {
+ a1logd(p->log, 1, "specbos_readresp: serial i/o failure\n");
+ return icoms2specbos_err(se);
+ }
+ return inst_ok;
+}
+
+/* Establish communications with a specbos */
+/* Ignore the serial parameters - leave serial in default state. */
+/* Return SPECBOS_COMS_FAIL on failure to establish communications */
+static inst_code
+specbos_init_coms(inst *pp, baud_rate br, flow_control fc, double tout) {
+ specbos *p = (specbos *) pp;
+ char buf[MAX_MES_SIZE];
+ baud_rate brt[] = { baud_921600, baud_115200, baud_38400, baud_nc };
+ unsigned int etime;
+ unsigned int i;
+ instType itype = pp->itype;
+ int se;
+
+ inst_code ev = inst_ok;
+
+ a1logd(p->log, 2, "specbos_init_coms: About to init Serial I/O\n");
+
+ 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, "specbos_init_coms: wrong communications type for device!\n");
+ return inst_coms_fail;
+ }
+
+ /* The tick to give up on */
+ etime = msec_time() + (long)(1500.0 + 0.5);
+
+ a1logd(p->log, 1, "specbos_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, "specbos_init_coms: trying baud ix %d\n",brt[i]);
+ if ((se = p->icom->set_ser_port(p->icom, fc_none, brt[i], parity_none,
+ stop_1, length_8)) != ICOM_OK) {
+ amutex_unlock(p->lock);
+ a1logd(p->log, 5, "specbos_init_coms: set_ser_port failed with 0x%x\n",se);
+ return specbos_interp_code((inst *)p, icoms2specbos_err(se));; /* Give up */
+ }
+
+
+ /* Check instrument is responding */
+ if (((ev = specbos_command(p, "*idn?\r", buf, MAX_MES_SIZE, 0.2)) & 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, "specbos_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, "specbos_init_coms: failed to establish coms\n");
+ return inst_coms_fail;
+ }
+
+ /* Check the response */
+ p->model = 0;
+
+ if (strncmp (buf, "SB05", 4) == 0) /* Special case */
+ p->model = 1201;
+ else {
+ if (strlen(buf) < 11
+ || sscanf(buf, "JETI_SB%d\r", &p->model) != 1) {
+ amutex_unlock(p->lock);
+ a1logd(p->log, 2, "specbos_init_coms: unrecognised ident string '%s'\n",icoms_fix(buf));
+ return inst_protocol_error;
+ }
+ }
+
+ if (p->model != 1201 && p->model != 1211) {
+ amutex_unlock(p->lock);
+ a1logd(p->log, 2, "specbos_init_coms: unrecognised model %04d\n",p->model);
+ return inst_unknown_model;
+ }
+ a1logd(p->log, 2, "specbos_init_coms: init coms has suceeded\n");
+
+ p->gotcoms = 1;
+
+#ifdef NEVER /* Test a change in baud rate on next plug in */
+ a1logd(p->log, 2, "specbos_init_coms: changing baudrate\n");
+// if ((ev = specbos_command(p, "*para:baud 384\r", buf, MAX_MES_SIZE, 1.0)) != inst_ok)
+// if ((ev = specbos_command(p, "*para:baud 115\r", buf, MAX_MES_SIZE, 1.0)) != inst_ok)
+ if ((ev = specbos_command(p, "*para:baud 921\r", buf, MAX_MES_SIZE, 1.0)) != inst_ok)
+ {
+ amutex_unlock(p->lock);
+ return ev;
+ }
+ if ((ev = specbos_command(p, "*para:save\r", buf, MAX_MES_SIZE, 1.0)) != inst_ok) {
+ amutex_unlock(p->lock);
+ return ev;
+ }
+#endif
+
+ amutex_unlock(p->lock);
+
+ return inst_ok;
+}
+
+static inst_code set_default_disp_type(specbos *p);
+
+/*
+ Notes on commands
+
+ *conf is temporary, *para can be saved to instrument
+ Since we set the instrument up every time, use *conf ?
+ *PARAmeter:SAVE
+
+ Set calibration to auto on presense of ambient cap
+ *para:calibn 0
+
+ To check what mode it's in:
+ *contr:mhead?
+ 0 for emissive, 1 for ambient.
+
+ Set auto exposure
+ *para:expo 1 // Adapt
+ *para:adap 2 // Adapt if under or over
+ *conf:maxtin 4000 // Limit to 4 secs for display measurement rather than 60000
+
+ Set output format
+ *conf:form 10 // interpolated ASCII with wavelength
+ *conf:form 12 // H/L 32 bit float with length & checksum - doesn't seem to work :-(
+
+ Measurement function
+ *conf:func 6 // Radiometric spectrum
+
+ Set to wavelength range and step
+ *conf:wran 350 850 1
+
+ Set to average just 1 reading
+ *conf:aver 1
+
+ Do a configured measurement
+ *init
+
+ Fetch the results
+ *fetch:XYZ // Fetches XYZ whatever the function ?
+ *fetch 10 // Fetch configured function in format 10
+
+ Measure refresh
+ *conf:cycmod 1
+ *contr:cyctim 200 4000 // Returns msec
+
+ Set refresh sync mode
+ *conf:cycmod 1
+ *conf:cyctim cyctim // in usec
+
+ message terminations:
+
+ *xxx? \r after data \r\r after spectral ??
+ \025 Nak on error ??
+
+ *para
+ *conf
+ *contr \006 Ack on success,
+ \025 Nak on error
+
+ All enquiries and parameter setting: '\r'
+
+ *init \006 Ack on command accepted
+ \025 Nak on error
+ \007 Bell on completion
+
+ *fetch
+ *calc
+ *calib
+ *stat
+ *help
+ *fetch \r after data, \r\r after spectral ??
+
+*/
+
+static inst_code specbos_get_diffpos(specbos *p, int *pos, int nd);
+
+/* Diffuser position thread. */
+/* Poll the instrument at 500msec intervals */
+int specbos_diff_thread(void *pp) {
+ int nfailed = 0;
+ specbos *p = (specbos *)pp;
+ inst_code rv = inst_ok;
+ a1logd(p->log,3,"Diffuser thread started\n");
+ for (nfailed = 0; nfailed < 5;) {
+ int pos;
+
+ amutex_lock(p->lock);
+ rv = specbos_get_diffpos(p, &pos, 1);
+ amutex_unlock(p->lock);
+
+ if (p->th_term) {
+ p->th_termed = 1;
+ break;
+ }
+ if (rv != inst_ok) {
+ nfailed++;
+ a1logd(p->log,3,"Diffuser thread failed with 0x%x\n",rv);
+ continue;
+ }
+ if (pos != p->dpos) {
+ p->dpos = pos;
+ if (p->eventcallback != NULL) {
+ p->eventcallback(p->event_cntx, inst_event_mconf);
+ }
+ }
+ msec_sleep(500);
+ }
+ a1logd(p->log,3,"Diffuser thread returning\n");
+ return rv;
+}
+
+/* Initialise the SPECBOS */
+/* return non-zero on an error, with dtp error code */
+static inst_code
+specbos_init_inst(inst *pp) {
+ specbos *p = (specbos *)pp;
+ char mes[100];
+ char buf[MAX_MES_SIZE];
+ inst_code ev = inst_ok;
+
+ a1logd(p->log, 2, "specbos_init_inst: called\n");
+
+ if (p->gotcoms == 0)
+ return inst_internal_error; /* Must establish coms before calling init */
+
+ amutex_lock(p->lock);
+
+ /* Restore the instrument to it's default settings */
+ if ((ev = specbos_command(p, "*conf:default\r", buf, MAX_MES_SIZE, 1.0)) != inst_ok)
+ return ev;
+
+ /* Set calibration type to auto on ambient cap */
+ if ((ev = specbos_command(p, "*para:calibn 0\r", buf, MAX_MES_SIZE, 1.0)) != inst_ok) {
+ amutex_unlock(p->lock);
+ return ev;
+ }
+
+ /* Set auto exposure/integration time */
+ /* Set calibration type to auto on ambient cap */
+ if ((ev = specbos_command(p, "*para:expo 1\r", buf, MAX_MES_SIZE, 1.0)) != inst_ok
+ || (ev = specbos_command(p, "*para:adapt 2\r", buf, MAX_MES_SIZE, 1.0)) != inst_ok) {
+ amutex_unlock(p->lock);
+ return ev;
+ }
+
+ p->measto = 20.0; /* default */
+
+ if (p->model == 1211)
+ p->measto = 5.0; /* Same overall time as i1d3 ?? */
+ else if (p->model == 1201)
+ p->measto = 15.0;
+
+ /* Set maximum integration time to speed up display measurement */
+ sprintf(mes, "*conf:maxtin %d\r", (int)(p->measto * 1000.0+0.5));
+ if ((ev = specbos_command(p, mes, buf, MAX_MES_SIZE, 1.0)) != inst_ok) {
+ amutex_unlock(p->lock);
+ return ev;
+ }
+
+ /* Set the measurement function to be Radiometric spectrum */
+ if ((ev = specbos_command(p, "*conf:func 6\r", buf, MAX_MES_SIZE, 1.0)) != inst_ok) {
+ amutex_unlock(p->lock);
+ return ev;
+ }
+
+ /* Set the measurement format to ASCII */
+ if ((ev = specbos_command(p, "*conf:form 4\r", buf, MAX_MES_SIZE, 1.0)) != inst_ok) {
+ amutex_unlock(p->lock);
+ return ev;
+ }
+
+ if ((ev = specbos_command(p, "*para:wavbeg?\r", buf, MAX_MES_SIZE, 1.0)) != inst_ok) {
+ amutex_unlock(p->lock);
+ return ev;
+ }
+ if (sscanf(buf, "Predefined start wave: %lf ",&p->wl_short) != 1) {
+ amutex_unlock(p->lock);
+ a1loge(p->log, 1, "specbos_init_inst: failed to parse start wave\n");
+ return ev;
+ }
+ a1logd(p->log, 1, " Short wl range %f\n",p->wl_short);
+
+ if ((ev = specbos_command(p, "*para:wavend?\r", buf, MAX_MES_SIZE, 1.0)) != inst_ok) {
+ amutex_unlock(p->lock);
+ return ev;
+ }
+ if (sscanf(buf, "Predefined end wave: %lf ",&p->wl_long) != 1) {
+ amutex_unlock(p->lock);
+ a1loge(p->log, 1, "specbos_init_inst: failed to parse end wave\n");
+ return ev;
+ }
+ if (p->wl_long > 830.0) /* Could go to 1000 with 1211 */
+ p->wl_long = 830.0;
+
+ a1logd(p->log, 1, " Long wl range %f\n",p->wl_long);
+
+ p->nbands = (int)((p->wl_long - p->wl_short + 1.0)/1.0 + 0.5);
+
+ /* Set the wavelength range and resolution */
+ sprintf(mes, "*conf:wran %d %d 1\r", (int)(p->wl_short+0.5), (int)(p->wl_long+0.5));
+ if ((ev = specbos_command(p, mes, buf, MAX_MES_SIZE, 1.0)) != inst_ok) {
+ amutex_unlock(p->lock);
+ return ev;
+ }
+
+ /* Set to average just 1 reading */
+ if ((ev = specbos_command(p, "*conf:aver 1\r", buf, MAX_MES_SIZE, 1.0)) != inst_ok) {
+ amutex_unlock(p->lock);
+ return ev;
+ }
+
+ if (p->log->verb) {
+ int val;
+ char *sp;
+
+ if ((ev = specbos_command(p, "*idn?\r", buf, MAX_MES_SIZE, 1.0)) != inst_ok) {
+ amutex_unlock(p->lock);
+ return ev;
+ }
+ if ((sp = strrchr(buf, '\r')) != NULL)
+ *sp = '\000';
+ a1logv(p->log, 1, " Identificaton: %s\n",buf);
+
+ if ((ev = specbos_command(p, "*vers?\r", buf, MAX_MES_SIZE, 1.0)) != inst_ok) {
+ amutex_unlock(p->lock);
+ return ev;
+ }
+
+ if ((sp = strrchr(buf, '\r')) != NULL)
+ *sp = '\000';
+ a1logv(p->log, 1, " Firmware: %s\n",buf);
+
+ if ((ev = specbos_command(p, "*para:spnum?\r", buf, MAX_MES_SIZE, 1.0)) != inst_ok) {
+ amutex_unlock(p->lock);
+ return ev;
+ }
+ if (sscanf(buf, "spectrometer number: %d ",&val) == 1) {
+ a1logv(p->log, 1, " Spectrometer number: %d\n",val);
+ }
+
+ if ((ev = specbos_command(p, "*para:serno?\r", buf, MAX_MES_SIZE, 1.0)) != inst_ok) {
+ amutex_unlock(p->lock);
+ return ev;
+ }
+ if (sscanf(buf, "serial number: %d ",&val) == 1) {
+ a1logv(p->log, 1, " Serial number: %d\n",val);
+ }
+ }
+
+ /* Start the diffuser monitoring thread */
+ if ((p->th = new_athread(specbos_diff_thread, (void *)p)) == NULL) {
+ amutex_unlock(p->lock);
+ return SPECBOS_INT_THREADFAILED;
+ }
+
+ p->inited = 1;
+ a1logd(p->log, 2, "specbos_init_inst: instrument inited OK\n");
+ amutex_unlock(p->lock);
+
+ return inst_ok;
+}
+
+static inst_code specbos_measure_set_refresh(specbos *p);
+static inst_code specbos_imp_set_refresh(specbos *p);
+
+/* Get the ambient diffuser position */
+/* (This is not multithread safe) */
+static inst_code
+specbos_get_diffpos(
+ specbos *p, /* Object */
+ int *pos, /* 0 = display, 1 = ambient */
+ int nd /* nz = no debug message */
+) {
+ char buf[MAX_RD_SIZE];
+ int ec;
+
+ /* See if we're in emissive or ambient mode */
+ if ((ec = specbos_fcommand(p, "*contr:mhead?\r", buf, MAX_MES_SIZE, 1.0, 1, 0, nd)) != inst_ok) {
+ return specbos_interp_code((inst *)p, ec);
+ }
+ if (sscanf(buf, "mhead: %d ",pos) != 1) {
+ a1logd(p->log, 2, "specbos_init_coms: unrecognised measuring head string '%s'\n",icoms_fix(buf));
+ return inst_protocol_error;
+ }
+ return inst_ok;
+}
+
+/* Read a single sample */
+/* Return the dtp error code */
+static inst_code
+specbos_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 */
+ specbos *p = (specbos *)pp;
+ char buf[MAX_RD_SIZE];
+ int ec;
+ int user_trig = 0;
+ int pos = -1;
+ inst_code rv = inst_protocol_error;
+
+ 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, "specbos: 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 */
+ }
+ }
+
+ /* See if we're in emissive or ambient mode */
+ if ((rv = specbos_get_diffpos(p, &pos, 0) ) != inst_ok) {
+ amutex_unlock(p->lock);
+ return rv;
+ }
+
+ if (p->mode & inst_mode_ambient) {
+ if (pos != 1) {
+ amutex_unlock(p->lock);
+ return specbos_interp_code((inst *)p, SPECBOS_SPOS_AMB);
+ }
+ } else {
+ if (pos != 0) {
+ amutex_unlock(p->lock);
+ return specbos_interp_code((inst *)p, SPECBOS_SPOS_EMIS);
+ }
+ }
+
+ /* Make sure the target laser is off */
+ if ((rv = specbos_command(p, "*contr:laser 0\r", buf, MAX_MES_SIZE, 1.0)) != inst_ok) {
+ amutex_unlock(p->lock);
+ return rv;
+ }
+
+ /* Attempt a refresh display frame rate calibration if needed */
+ if (p->refrmode != 0 && p->rrset == 0) {
+ if ((rv = specbos_measure_set_refresh(p)) != inst_ok) {
+ amutex_unlock(p->lock);
+ return rv;
+ }
+ }
+
+ /* Trigger a measurement */
+ if ((ec = specbos_fcommand(p, "*init\r", buf, MAX_MES_SIZE, 5.0 * p->measto + 2.0 , 1, 1, 0)) != SPECBOS_OK) {
+ amutex_unlock(p->lock);
+ return specbos_interp_code((inst *)p, ec);
+ }
+
+ /* Read the XYZ */
+ if ((ec = specbos_fcommand(p, "*fetch:XYZ\r", buf, MAX_RD_SIZE, 0.5, 3, 0, 0)) != SPECBOS_OK) {
+ amutex_unlock(p->lock);
+ return specbos_interp_code((inst *)p, ec);
+ }
+
+ if (sscanf(buf, " X: %lf Y: %lf Z: %lf ",
+ &val->XYZ[0], &val->XYZ[1], &val->XYZ[2]) == 3) {
+
+ /* This may not change anything since instrument may clamp */
+ if (clamp)
+ icmClamp3(val->XYZ, val->XYZ);
+ val->loc[0] = '\000';
+ if (p->mode & inst_mode_ambient) {
+ 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;
+ } else {
+ amutex_unlock(p->lock);
+ a1logd(p->log, 1, "specbos_read_sample: failed to parse '%s'\n",buf);
+ return inst_protocol_error;
+ }
+
+ /* spectrum data is returned only if requested */
+ if (p->mode & inst_mode_spectral) {
+ int i, xsize;
+ char *cp, *ncp;
+
+ /* (Format 12 doesn't seem to work on the 1211) */
+ /* (Format 9 reportedly doesn't work on the 1201) */
+ /* The folling works on the 1211 and is reported to work on the 1201 */
+
+ /* Fetch the spectral readings */
+ if ((ec = specbos_fcommand(p, "*fetch:sprad\r", buf, MAX_RD_SIZE, 1.0, 2+p->nbands+1, 0, 0)) != SPECBOS_OK) {
+ return specbos_interp_code((inst *)p, ec);
+ }
+
+ val->sp.spec_n = p->nbands;
+ val->sp.spec_wl_short = p->wl_short;
+ val->sp.spec_wl_long = p->wl_long;
+
+ /* Spectral data is in W/nm/m^2 */
+ val->sp.norm = 1.0;
+ cp = buf;
+ for (i = -2; i < val->sp.spec_n; i++) {
+ if ((ncp = strchr(cp, '\r')) == NULL) {
+ amutex_unlock(p->lock);
+ a1logd(p->log, 1, "specbos_read_sample: failed to parse spectral\n");
+ return inst_protocol_error;
+ }
+ *ncp = '\000';
+ if (i >= 0) {
+ val->sp.spec[i] = 1000.0 * atof(cp); /* Convert to mW/m^2/nm */
+ if (p->mode & inst_mode_ambient)
+ val->mtype = inst_mrt_ambient;
+ }
+ cp = ncp+1;
+ }
+ }
+ amutex_unlock(p->lock);
+
+
+ if (user_trig)
+ return inst_user_trig;
+ return rv;
+}
+
+/* Set the instrument to match the current refresh settings */
+/* (Not thread safe) */
+static inst_code
+specbos_imp_set_refresh(specbos *p) {
+ char buf[MAX_MES_SIZE];
+ inst_code rv;
+
+ if (p->model == 1201)
+ return inst_unsupported;
+
+ /* Set synchronised read if we should do so */
+ if (p->refrmode != 0 && p->refrvalid) {
+ char mes[100];
+ if ((rv = specbos_command(p, "*conf:cycmod 1\r", buf, MAX_MES_SIZE, 1.0)) != inst_ok) {
+ return rv;
+ }
+ sprintf(mes,"*conf:cyctim %f\r",p->refperiod * 1e6);
+ if ((rv = specbos_command(p, mes, buf, MAX_MES_SIZE, 1.0)) != inst_ok) {
+ return rv;
+ }
+ a1logd(p->log,5,"specbos_imp_set_refresh set refresh rate to %f Hz\n",1.0/p->refperiod);
+ } else {
+ if ((rv = specbos_command(p, "*conf:cycmod 0\r", buf, MAX_MES_SIZE, 1.0)) != inst_ok) {
+ return rv;
+ }
+ a1logd(p->log,5,"specbos_imp_set_refresh set non-refresh mode\n");
+ }
+ return inst_ok;
+}
+
+/* Implementation of read refresh rate */
+/* (Not thread safe) */
+/* Return 0.0 if none detectable */
+static inst_code
+specbos_imp_measure_refresh(
+specbos *p,
+double *ref_rate
+) {
+ char buf[MAX_MES_SIZE], *cp;
+ double refperiod = 0.0;
+ int ec;
+ inst_code rv;
+
+ if (p->model == 1201)
+ return inst_unsupported;
+
+ /* Make sure the target laser is off */
+ if ((rv = specbos_command(p, "*contr:laser 0\r", buf, MAX_MES_SIZE, 1.0)) != inst_ok) {
+ return rv;
+ }
+
+ if ((ec = specbos_fcommand(p, "*contr:cyctim 200 4000\r", buf, MAX_MES_SIZE, 5.0, 1, 2, 0)) != SPECBOS_OK) {
+ return specbos_interp_code((inst *)p, ec);
+ }
+
+ if ((cp = strchr(buf, 'c')) == NULL)
+ cp = buf;
+ if (sscanf(cp, "cyctim[ms]: %lf ", &refperiod) != 1) {
+ a1logd(p->log, 1, "specbos_read_refrate rate: failed to parse string '%s'\n",icoms_fix(buf));
+ *ref_rate = 0.0;
+ return inst_misread;
+ }
+
+ if (refperiod == 0.0)
+ *ref_rate = 0.0;
+ else
+ *ref_rate = 1000.0/refperiod;
+
+ return inst_ok;
+}
+
+/* Read an emissive refresh rate */
+static inst_code
+specbos_read_refrate(
+inst *pp,
+double *ref_rate
+) {
+ specbos *p = (specbos *)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;
+
+ amutex_lock(p->lock);
+ if ((rv = specbos_imp_measure_refresh(p, &refrate)) != inst_ok) {
+ amutex_unlock(p->lock);
+ return rv;
+ }
+ amutex_unlock(p->lock);
+
+ if (refrate == 0.0)
+ return inst_misread;
+
+ *ref_rate = refrate;
+
+ return inst_ok;
+}
+
+/* Measure and then set refperiod, refrate if possible */
+static inst_code
+specbos_measure_set_refresh(
+ specbos *p /* Object */
+) {
+ inst_code rv;
+ double refrate = 0.0;
+ int mul;
+ double pval;
+
+ amutex_lock(p->lock);
+ if ((rv = specbos_imp_measure_refresh(p, &refrate)) != inst_ok) {
+ amutex_unlock(p->lock);
+ return rv;
+ }
+
+ if (refrate != 0.0) {
+ p->refrate = refrate;
+ p->refrvalid = 1;
+ p->refperiod = 1.0/refrate;
+ } else {
+ p->refrate = 0.0;
+ p->refrvalid = 0;
+ p->refperiod = 0.0;
+ }
+ p->rrset = 1;
+
+ if ((rv = specbos_imp_set_refresh(p)) != inst_ok) {
+ amutex_unlock(p->lock);
+ return rv;
+ }
+ amutex_unlock(p->lock);
+
+ return inst_ok;
+}
+
+/* Return needed and available inst_cal_type's */
+static inst_code specbos_get_n_a_cals(inst *pp, inst_cal_type *pn_cals, inst_cal_type *pa_cals) {
+ specbos *p = (specbos *)pp;
+ inst_cal_type n_cals = inst_calt_none;
+ inst_cal_type a_cals = inst_calt_none;
+
+ if (p->refrmode != 0) {
+ if (p->rrset == 0)
+ n_cals |= inst_calt_ref_freq;
+ a_cals |= inst_calt_ref_freq;
+ }
+
+ 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 specbos_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) */
+) {
+ specbos *p = (specbos *)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 = specbos_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,"specbos_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_freq) && p->refrmode != 0) {
+ inst_code ev = inst_ok;
+
+
+ if (*calc != inst_calc_emis_80pc) {
+ *calc = inst_calc_emis_80pc;
+ return inst_cal_setup;
+ }
+
+ /* Do refresh display rate calibration */
+ if ((ev = specbos_measure_set_refresh(p)) != inst_ok)
+ return ev;
+
+ *calt &= ~inst_calt_ref_freq;
+ }
+ return inst_ok;
+}
+
+/* Return the last calibrated refresh rate in Hz. Returns: */
+static inst_code specbos_get_refr_rate(inst *pp,
+double *ref_rate
+) {
+ specbos *p = (specbos *)pp;
+ if (p->refrvalid) {
+ *ref_rate = p->refrate;
+ return inst_ok;
+ } else if (p->rrset) {
+ *ref_rate = 0.0;
+ return inst_misread;
+ }
+ return inst_needs_cal;
+}
+
+/* Set the calibrated refresh rate in Hz. */
+/* Set refresh rate to 0.0 to mark it as invalid */
+/* Rates outside the range 5.0 to 150.0 Hz will return an error */
+static inst_code specbos_set_refr_rate(inst *pp,
+double ref_rate
+) {
+ specbos *p = (specbos *)pp;
+ inst_code rv;
+
+ a1logd(p->log,5,"specbos_set_refr_rate %f Hz\n",ref_rate);
+
+ if (ref_rate != 0.0 && (ref_rate < 5.0 || ref_rate > 150.0))
+ return inst_bad_parameter;
+
+ p->refrate = ref_rate;
+ if (ref_rate == 0.0)
+ p->refrvalid = 0;
+ else {
+ p->refperiod = 1.0/ref_rate;
+ p->refrvalid = 1;
+ }
+ p->rrset = 1;
+
+ /* Set the instrument to given refresh rate */
+ amutex_lock(p->lock);
+ if ((rv = specbos_imp_set_refresh(p)) != inst_ok) {
+ amutex_unlock(p->lock);
+ return rv;
+ }
+ amutex_unlock(p->lock);
+
+ return inst_ok;
+}
+
+
+/* Error codes interpretation */
+static char *
+specbos_interp_error(inst *pp, int ec) {
+// specbos *p = (specbos *)pp;
+ ec &= inst_imask;
+ switch (ec) {
+ case SPECBOS_INTERNAL_ERROR:
+ return "Internal software error";
+ case SPECBOS_TIMEOUT:
+ return "Communications timeout";
+ case SPECBOS_COMS_FAIL:
+ return "Communications failure";
+ case SPECBOS_UNKNOWN_MODEL:
+ return "Not a JETI specbos";
+ case SPECBOS_DATA_PARSE_ERROR:
+ return "Data from specbos didn't parse as expected";
+ case SPECBOS_SPOS_EMIS:
+ return "Ambient filter should be removed";
+ case SPECBOS_SPOS_AMB:
+ return "Ambient filter should be used";
+
+ case SPECBOS_OK:
+ return "No device error";
+
+ case SPECBOS_COMMAND:
+ return "Command";
+ case SPECBOS_PASSWORD:
+ return "Password";
+ case SPECBOS_DIGIT:
+ return "Digit";
+ case SPECBOS_ARG_1:
+ return "Argument 1";
+ case SPECBOS_ARG_2:
+ return "Argument 2";
+ case SPECBOS_ARG_3:
+ return "Argument 3";
+ case SPECBOS_ARG_4:
+ return "Argument 4";
+ case SPECBOS_PARAM:
+ return "Parameter argument";
+ case SPECBOS_CONFIG_ARG:
+ return "Config argument";
+ case SPECBOS_CONTROL_ARG:
+ return "Control argument";
+ case SPECBOS_READ_ARG:
+ return "Read argument";
+ case SPECBOS_FETCH_ARG:
+ return "Fetch argument";
+ case SPECBOS_MEAS_ARG:
+ return "Measuring argument";
+ case SPECBOS_CALC_ARG:
+ return "Calculation argument";
+ case SPECBOS_CAL_ARG:
+ return "Calibration argument";
+ case SPECBOS_PARAM_CHSUM:
+ return "Parameter checksum";
+ case SPECBOS_USERFILE_CHSUM:
+ return "Userfile checksum";
+ case SPECBOS_USERFILE2_CHSUM:
+ return "Userfile2 checksum";
+ case SPECBOS_USERFILE2_ARG:
+ return "Userfile2 argument";
+ case SPECBOS_OVEREXPOSED:
+ return "Overexposure";
+ case SPECBOS_UNDEREXPOSED:
+ return "Underexposure";
+ case SPECBOS_ADAPT_INT_TIME:
+ return "Adaption integration time";
+ case SPECBOS_NO_SHUTTER:
+ return "Shutter doesn't exist";
+ case SPECBOS_NO_DARK_MEAS:
+ return "No dark measurement";
+ case SPECBOS_NO_REF_MEAS:
+ return "No reference measurement";
+ case SPECBOS_NO_TRANS_MEAS:
+ return "No transmission measurement";
+ case SPECBOS_NO_RADMTRC_CALC:
+ return "No radiometric calculation";
+ case SPECBOS_NO_CCT_CALC:
+ return "No CCT calculation";
+ case SPECBOS_NO_CRI_CALC:
+ return "No CRI calculation";
+ case SPECBOS_NO_DARK_COMP:
+ return "No dark compensation";
+ case SPECBOS_NO_LIGHT_MEAS:
+ return "No light measurement";
+ case SPECBOS_NO_PEAK_CALC:
+ return "No peak calculation";
+ case SPECBOS_CAL_DATA:
+ return "Calibration data";
+ case SPECBOS_EXCEED_CAL_WL:
+ return "Exceeded calibration wavelength";
+ case SPECBOS_SCAN_BREAK:
+ return "Scan break";
+ case SPECBOS_TO_CYC_OPT_TRIG:
+ return "Timeout cycle on optical trigger";
+ case SPECBOS_DIV_CYC_TIME:
+ return "Divider cycle time";
+ case SPECBOS_WRITE_FLASH_PARM:
+ return "Write parameter to flash";
+ case SPECBOS_READ_FLASH_PARM:
+ return "Read parameter from flash";
+ case SPECBOS_FLASH_ERASE:
+ return "Erase flash";
+ case SPECBOS_NO_CALIB_FILE:
+ return "No calibration file";
+ case SPECBOS_CALIB_FILE_HEADER:
+ return "Calibration file header";
+ case SPECBOS_WRITE_CALIB_FILE:
+ return "Write calibration file";
+ case SPECBOS_CALIB_FILE_VALS:
+ return "Calibration file values";
+ case SPECBOS_CALIB_FILE_NO:
+ return "Calibration file number";
+ case SPECBOS_CLEAR_CALIB_FILE:
+ return "Clear calibration file";
+ case SPECBOS_CLEAR_CALIB_ARG:
+ return "Clear calibration file argument";
+ case SPECBOS_NO_LAMP_FILE:
+ return "No lamp file";
+ case SPECBOS_LAMP_FILE_HEADER:
+ return "Lamp file header";
+ case SPECBOS_WRITE_LAMP_FILE:
+ return "Write lamp file";
+ case SPECBOS_LAMP_FILE_VALS:
+ return "Lamp file values";
+ case SPECBOS_LAMP_FILE_NO:
+ return "Lamp file number";
+ case SPECBOS_CLEAR_LAMP_FILE:
+ return "Clear lamp file";
+ case SPECBOS_CLEAR_LAMP_FILE_ARG:
+ return "Clear lamp file argument";
+ case SPECBOS_RAM_CHECK:
+ return "RAM check";
+ case SPECBOS_DATA_OUTPUT:
+ return "Data output";
+ case SPECBOS_RAM_LOW:
+ return "Insufcient RAM";
+ case SPECBOS_FIRST_MEM_ALLOC:
+ return "First memory allocation";
+ case SPECBOS_SECOND_MEM_ALLOC:
+ return "Second memory allocation";
+ case SPECBOS_THIRD_MEM_ALLOC:
+ return "Third memory allocation";
+ case SPECBOS_RADMTRC_WL_RANGE:
+ return "Wavelength range for radiometric calculation";
+ case SPECBOS_BOOT_BAT_POWER:
+ return "Boot by battery power";
+ case SPECBOS_TRIG_CONF_1:
+ return "Trigger configuration 1";
+ case SPECBOS_TRIG_CONF_2:
+ return "Trigger configuration 2";
+
+ case SPECBOS_INT_THREADFAILED:
+ return "Starting diffuser position thread failed";
+
+ default:
+ return "Unknown error code";
+ }
+}
+
+
+/* Convert a machine specific error code into an abstract dtp code */
+static inst_code
+specbos_interp_code(inst *pp, int ec) {
+
+ ec &= inst_imask;
+ switch (ec) {
+
+ case SPECBOS_OK:
+ return inst_ok;
+
+ case SPECBOS_INTERNAL_ERROR:
+ case SPECBOS_INT_THREADFAILED:
+ return inst_internal_error | ec;
+
+ case SPECBOS_TIMEOUT:
+ case SPECBOS_COMS_FAIL:
+ return inst_coms_fail | ec;
+
+ case SPECBOS_UNKNOWN_MODEL:
+ return inst_unknown_model | ec;
+
+ case SPECBOS_DATA_PARSE_ERROR:
+ return inst_protocol_error | ec;
+
+ case SPECBOS_SPOS_EMIS:
+ case SPECBOS_SPOS_AMB:
+ return inst_wrong_config | ec;
+
+ case SPECBOS_COMMAND:
+ case SPECBOS_DIGIT:
+ case SPECBOS_ARG_1:
+ case SPECBOS_ARG_2:
+ case SPECBOS_ARG_3:
+ case SPECBOS_ARG_4:
+ case SPECBOS_PARAM:
+ case SPECBOS_CONFIG_ARG:
+ case SPECBOS_CONTROL_ARG:
+ case SPECBOS_READ_ARG:
+ case SPECBOS_FETCH_ARG:
+ case SPECBOS_MEAS_ARG:
+ case SPECBOS_CALC_ARG:
+ case SPECBOS_CAL_ARG:
+ return inst_bad_parameter | ec;
+
+ case SPECBOS_OVEREXPOSED:
+ case SPECBOS_UNDEREXPOSED:
+ return inst_misread | ec;
+
+ case SPECBOS_PASSWORD:
+ case SPECBOS_PARAM_CHSUM:
+ case SPECBOS_USERFILE_CHSUM:
+ case SPECBOS_USERFILE2_CHSUM:
+ case SPECBOS_USERFILE2_ARG:
+ case SPECBOS_ADAPT_INT_TIME:
+ case SPECBOS_NO_SHUTTER:
+ case SPECBOS_NO_DARK_MEAS:
+ case SPECBOS_NO_REF_MEAS:
+ case SPECBOS_NO_TRANS_MEAS:
+ case SPECBOS_NO_RADMTRC_CALC:
+ case SPECBOS_NO_CCT_CALC:
+ case SPECBOS_NO_CRI_CALC:
+ case SPECBOS_NO_DARK_COMP:
+ case SPECBOS_NO_LIGHT_MEAS:
+ case SPECBOS_NO_PEAK_CALC:
+ case SPECBOS_CAL_DATA:
+ case SPECBOS_EXCEED_CAL_WL:
+ case SPECBOS_SCAN_BREAK:
+ case SPECBOS_TO_CYC_OPT_TRIG:
+ case SPECBOS_DIV_CYC_TIME:
+ case SPECBOS_WRITE_FLASH_PARM:
+ case SPECBOS_READ_FLASH_PARM:
+ case SPECBOS_FLASH_ERASE:
+ case SPECBOS_NO_CALIB_FILE:
+ case SPECBOS_CALIB_FILE_HEADER:
+ case SPECBOS_WRITE_CALIB_FILE:
+ case SPECBOS_CALIB_FILE_VALS:
+ case SPECBOS_CALIB_FILE_NO:
+ case SPECBOS_CLEAR_CALIB_FILE:
+ case SPECBOS_CLEAR_CALIB_ARG:
+ case SPECBOS_NO_LAMP_FILE:
+ case SPECBOS_LAMP_FILE_HEADER:
+ case SPECBOS_WRITE_LAMP_FILE:
+ case SPECBOS_LAMP_FILE_VALS:
+ case SPECBOS_LAMP_FILE_NO:
+ case SPECBOS_CLEAR_LAMP_FILE:
+ case SPECBOS_CLEAR_LAMP_FILE_ARG:
+ case SPECBOS_RAM_CHECK:
+ case SPECBOS_DATA_OUTPUT:
+ case SPECBOS_RAM_LOW:
+ case SPECBOS_FIRST_MEM_ALLOC:
+ case SPECBOS_SECOND_MEM_ALLOC:
+ case SPECBOS_THIRD_MEM_ALLOC:
+ case SPECBOS_RADMTRC_WL_RANGE:
+ case SPECBOS_BOOT_BAT_POWER:
+ case SPECBOS_TRIG_CONF_1:
+ case SPECBOS_TRIG_CONF_2:
+ return inst_hardware_fail | ec;
+ }
+ return inst_other_error | ec;
+}
+
+/* Destroy ourselves */
+static void
+specbos_del(inst *pp) {
+ if (pp != NULL) {
+ specbos *p = (specbos *)pp;
+ if (p->th != NULL) { /* Terminate diffuser monitor thread */
+ int i;
+ p->th_term = 1; /* Tell thread to exit on error */
+ for (i = 0; p->th_termed == 0 && i < 5; i++)
+ msec_sleep(100); /* Wait for thread to terminate */
+ if (i >= 5) {
+ a1logd(p->log,3,"specbos diffuser thread termination failed\n");
+ }
+ p->th->del(p->th);
+ }
+ if (p->icom != NULL)
+ p->icom->del(p->icom);
+ amutex_del(p->lock);
+ free(p);
+ }
+}
+
+/* Return the instrument mode capabilities */
+void specbos_capabilities(inst *pp,
+inst_mode *pcap1,
+inst2_capability *pcap2,
+inst3_capability *pcap3) {
+ specbos *p = (specbos *)pp;
+ inst_mode cap1 = 0;
+ inst2_capability cap2 = 0;
+
+ cap1 |= inst_mode_emis_tele
+ | inst_mode_ambient
+ | inst_mode_colorimeter
+ | inst_mode_spectral
+ | inst_mode_emis_refresh_ovd
+ | inst_mode_emis_norefresh_ovd
+ ;
+
+ /* can inst2_has_sensmode, but not report it asynchronously */
+ cap2 |= inst2_prog_trig
+ | inst2_user_trig
+ | inst2_disptype
+ | inst2_has_target /* Has a laser target */
+ ;
+
+ if (p->model != 1201) {
+ cap2 |= inst2_emis_refr_meas;
+ cap2 |= inst2_set_refresh_rate;
+ cap2 |= inst2_get_refresh_rate;
+ }
+
+ if (pcap1 != NULL)
+ *pcap1 = cap1;
+ if (pcap2 != NULL)
+ *pcap2 = cap2;
+ if (pcap3 != NULL)
+ *pcap3 = inst3_none;
+}
+
+/* Return current or given configuration available measurement modes. */
+static inst_code specbos_meas_config(
+inst *pp,
+inst_mode *mmodes,
+inst_cal_cond *cconds,
+int *conf_ix
+) {
+ specbos *p = (specbos *)pp;
+ inst_code ev;
+ inst_mode mval;
+ int pos;
+
+ if (mmodes != NULL)
+ *mmodes = inst_mode_none;
+ if (cconds != NULL)
+ *cconds = inst_calc_unknown;
+
+ if (conf_ix == NULL
+ || *conf_ix < 0
+ || *conf_ix > 1) {
+ /* Return current configuration measrement modes */
+ amutex_lock(p->lock);
+ if ((ev = specbos_get_diffpos(p, &pos, 0)) != inst_ok) {
+ amutex_unlock(p->lock);
+ return ev;
+ }
+ amutex_unlock(p->lock);
+ } else {
+ /* Return given configuration measurement modes */
+ pos = *conf_ix;
+ }
+
+ if (pos == 1) {
+ mval = inst_mode_emis_ambient;
+ } else if (pos == 0) {
+ mval |= inst_mode_emis_tele;
+ }
+
+ /* Add the extra dependent and independent modes */
+ mval |= inst_mode_emis_refresh_ovd
+ | inst_mode_emis_norefresh_ovd
+ | inst_mode_colorimeter
+ | inst_mode_spectral;
+
+ if (mmodes != NULL)
+ *mmodes = mval;
+
+ /* Return configuration index returned */
+ if (conf_ix != NULL)
+ *conf_ix = pos;
+
+ return inst_ok;
+}
+
+/* Check device measurement mode */
+inst_code specbos_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;
+
+ /* Only tele emission mode supported */
+ if (!IMODETST(m, inst_mode_emis_tele)
+ && !IMODETST(m, inst_mode_emis_ambient)) {
+ return inst_unsupported;
+ }
+
+ return inst_ok;
+}
+
+/* Set device measurement mode */
+inst_code specbos_set_mode(inst *pp, inst_mode m) {
+ specbos *p = (specbos *)pp;
+ int refrmode;
+ inst_code ev;
+
+ if ((ev = specbos_check_mode(pp, m)) != inst_ok)
+ return ev;
+
+ p->mode = m;
+
+
+ if (p->model != 1201) { /* Can't set refresh mode on 1201 */
+
+ /* Effective refresh mode may change */
+ refrmode = p->refrmode;
+ if ( IMODETST(p->mode, inst_mode_emis_norefresh_ovd)) { /* Must test this first! */
+ refrmode = 0;
+ } else if (IMODETST(p->mode, inst_mode_emis_refresh_ovd)) {
+ refrmode = 1;
+ }
+
+ if (p->refrmode != refrmode) {
+ p->rrset = 0; /* This is a hint we may have swapped displays */
+ p->refrvalid = 0;
+ }
+ p->refrmode = refrmode;
+ }
+
+ return inst_ok;
+}
+
+inst_disptypesel specbos_disptypesel[3] = {
+ {
+ inst_dtflags_default,
+ 1,
+ "nl",
+ "Non-Refresh display",
+ 0,
+ 0
+ },
+ {
+ inst_dtflags_none, /* flags */
+ 2, /* cbid */
+ "rc", /* sel */
+ "Refresh display", /* desc */
+ 1, /* refr */
+ 1 /* ix */
+ },
+ {
+ inst_dtflags_end,
+ 0,
+ "",
+ "",
+ 0,
+ 0
+ }
+};
+
+/* Get mode and option details */
+static inst_code specbos_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 */
+) {
+ specbos *p = (specbos *)pp;
+ inst_code rv = inst_ok;
+
+ if ((!allconfig && (p->mode & inst_mode_ambient)) /* If set to Ambient */
+ || p->model == 1201) { /* Or 1201, return empty list */
+
+ if (pnsels != NULL)
+ *pnsels = 0;
+
+ if (psels != NULL)
+ *psels = NULL;
+
+ return inst_ok;
+ }
+
+
+ if (pnsels != NULL)
+ *pnsels = 2;
+
+ if (psels != NULL)
+ *psels = specbos_disptypesel;
+
+ return inst_ok;
+}
+
+/* Given a display type entry, setup for that type */
+static inst_code set_disp_type(specbos *p, inst_disptypesel *dentry) {
+ inst_code rv;
+ int refrmode;
+
+ refrmode = dentry->refr;
+
+ a1logd(p->log,5,"specbos set_disp_type refmode %d\n",refrmode);
+
+ if ( IMODETST(p->mode, inst_mode_emis_norefresh_ovd)) { /* Must test this first! */
+ refrmode = 0;
+ } else if (IMODETST(p->mode, inst_mode_emis_refresh_ovd)) {
+ refrmode = 1;
+ }
+
+ if (p->refrmode != refrmode)
+ p->rrset = 0; /* This is a hint we may have swapped displays */
+ p->refrmode = refrmode;
+
+ amutex_lock(p->lock);
+ if ((rv = specbos_imp_set_refresh(p)) != inst_ok) {
+ amutex_unlock(p->lock);
+ return rv;
+ }
+ amutex_unlock(p->lock);
+
+ return inst_ok;
+}
+
+/* Set the display type */
+static inst_code specbos_set_disptype(inst *pp, int ix) {
+ specbos *p = (specbos *)pp;
+ inst_code ev;
+ inst_disptypesel *dentry;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ if (p->model == 1201) /* No display type to select on 1201 */
+ return inst_unsupported;
+
+ if (ix < 0 || ix >= 2)
+ return inst_unsupported;
+
+ a1logd(p->log,5,"specbos specbos_set_disptype ix %d\n",ix);
+ dentry = &specbos_disptypesel[ix];
+
+ if ((ev = set_disp_type(p, dentry)) != inst_ok) {
+ return ev;
+ }
+
+ 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
+specbos_get_set_opt(inst *pp, inst_opt_type m, ...)
+{
+ specbos *p = (specbos *)pp;
+ char buf[MAX_MES_SIZE];
+ inst_code ev = inst_ok;
+
+ a1logd(p->log, 5, "specbos_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;
+ }
+
+ /* Set laser target state */
+ if (m == inst_opt_set_target_state) {
+ 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) {
+ int lstate = -1;
+ if ((ev = specbos_command(p, "*contr:laser?\r", buf, MAX_MES_SIZE, 1.0)) != inst_ok) {
+ amutex_unlock(p->lock);
+ a1loge(p->log, 1, "specbos_get_set_opt: failed to send laser? command\n");
+ return ev;
+ }
+ if (sscanf(buf, "laser: %d ",&lstate) != 1) {
+ amutex_unlock(p->lock);
+ a1loge(p->log, 1, "specbos_get_set_opt: failed to parse laser state\n");
+ return ev;
+ }
+ a1logd(p->log, 5, " Laser state = %d\n",lstate);
+ if (lstate == 0)
+ lstate = 1;
+ else if (lstate == 1)
+ lstate = 0;
+ if (lstate == 0 || lstate == 1) {
+ char mes[100];
+ sprintf(mes,"*contr:laser %d\r",lstate);
+ if ((ev = specbos_command(p, mes, buf, MAX_MES_SIZE, 1.0)) != inst_ok) {
+ amutex_unlock(p->lock);
+ return ev;
+ }
+ }
+ } else if (state == 1) {
+ if ((ev = specbos_command(p, "*contr:laser 1\r", buf, MAX_MES_SIZE, 1.0)) != inst_ok) {
+ amutex_unlock(p->lock);
+ return ev;
+ }
+ } else if (state == 0) {
+ if ((ev = specbos_command(p, "*contr:laser 0\r", buf, MAX_MES_SIZE, 1.0)) != inst_ok) {
+ amutex_unlock(p->lock);
+ return ev;
+ }
+ }
+ 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 specbos *new_specbos(icoms *icom, instType itype) {
+ specbos *p;
+ if ((p = (specbos *)calloc(sizeof(specbos),1)) == NULL) {
+ a1loge(icom->log, 1, "new_specbos: malloc failed!\n");
+ return NULL;
+ }
+
+ p->log = new_a1log_d(icom->log);
+
+ p->init_coms = specbos_init_coms;
+ p->init_inst = specbos_init_inst;
+ p->capabilities = specbos_capabilities;
+ p->meas_config = specbos_meas_config;
+ p->check_mode = specbos_check_mode;
+ p->set_mode = specbos_set_mode;
+ p->get_disptypesel = specbos_get_disptypesel;
+ p->set_disptype = specbos_set_disptype;
+ p->get_set_opt = specbos_get_set_opt;
+ p->read_sample = specbos_read_sample;
+ p->read_refrate = specbos_read_refrate;
+ p->get_n_a_cals = specbos_get_n_a_cals;
+ p->calibrate = specbos_calibrate;
+ p->get_refr_rate = specbos_get_refr_rate;
+ p->set_refr_rate = specbos_set_refr_rate;
+ p->interp_error = specbos_interp_error;
+ p->del = specbos_del;
+
+ p->icom = icom;
+ p->itype = icom->itype;
+ if (p->itype == instSpecbos1201)
+ p->model = 1201;
+
+ amutex_init(p->lock);
+
+ return p;
+}
+