1264 lines
30 KiB
C
1264 lines
30 KiB
C
|
|
|
|
/*
|
|
* Argyll Color Management System
|
|
*
|
|
* Hughski ColorHug related functions
|
|
*
|
|
* Author: Richard Hughes
|
|
* Date: 30/11/2011
|
|
*
|
|
* Copyright 2006 - 2014, Graeme W. Gill
|
|
* Copyright 2011, Richard Hughes
|
|
* All rights reserved.
|
|
*
|
|
* (Based on huey.c)
|
|
*
|
|
* This material is licenced under the GNU GENERAL PUBLIC LICENSE Version 2 or later :-
|
|
* see the License2.txt file for licencing details.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <ctype.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <stdarg.h>
|
|
#include <math.h>
|
|
#ifndef SALONEINSTLIB
|
|
#include "copyright.h"
|
|
#include "aconfig.h"
|
|
#include "numlib.h"
|
|
#else /* SALONEINSTLIB */
|
|
#include "sa_config.h"
|
|
#include "numsup.h"
|
|
#endif /* SALONEINSTLIB */
|
|
#include "cgats.h"
|
|
#include "xspect.h"
|
|
#include "insttypes.h"
|
|
#include "conv.h"
|
|
#include "icoms.h"
|
|
#include "colorhug.h"
|
|
|
|
static inst_code colorhug_interp_code(inst *pp, int ec);
|
|
|
|
/* Interpret an icoms error into a ColorHug error */
|
|
static int icoms2colorhug_err(int se) {
|
|
if (se != ICOM_OK)
|
|
return COLORHUG_COMS_FAIL;
|
|
return COLORHUG_OK;
|
|
}
|
|
|
|
/* ColorHug commands that we care about */
|
|
typedef enum {
|
|
ch_set_mult = 0x04, /* Set multiplier value */
|
|
ch_set_integral = 0x06, /* Set integral time */
|
|
ch_get_firmware_version = 0x07, /* Get the Firmware version number */
|
|
ch_get_serial = 0x0b, /* Gets the serial number */
|
|
ch_set_leds = 0x0e, /* Sets the LEDs */
|
|
ch_take_reading = 0x22, /* Takes a raw reading minus dark offset */
|
|
ch_take_reading_xyz = 0x23, /* Takes an XYZ reading using the current matrix */
|
|
ch_get_post_scale = 0x2a /* Get the post scaling factor */
|
|
} ColorHugCmd;
|
|
|
|
/* Diagnostic - return a description given the instruction code */
|
|
static char *inst_desc(int cc) {
|
|
static char buf[40];
|
|
switch(cc) {
|
|
case 0x04:
|
|
return "SetMultiplier";
|
|
case 0x06:
|
|
return "SetIntegral";
|
|
case 0x07:
|
|
return "GetFirmwareVersion";
|
|
case 0x0b:
|
|
return "GetSerial";
|
|
case 0x0e:
|
|
return "SetLeds";
|
|
case 0x22:
|
|
return "TakeReading";
|
|
case 0x23:
|
|
return "TakeReadingXYZ";
|
|
case 0x2a:
|
|
return "GetPostScale";
|
|
}
|
|
sprintf(buf,"Unknown %02x",cc);
|
|
return buf;
|
|
}
|
|
|
|
/* Error codes interpretation */
|
|
static char *
|
|
colorhug_interp_error(inst *pp, int ec) {
|
|
ec &= inst_imask;
|
|
switch (ec) {
|
|
case COLORHUG_INTERNAL_ERROR:
|
|
return "Internal software error";
|
|
case COLORHUG_COMS_FAIL:
|
|
return "Communications failure";
|
|
case COLORHUG_UNKNOWN_MODEL:
|
|
return "Not a known ColorHug Model";
|
|
|
|
case COLORHUG_OK:
|
|
return "OK";
|
|
case COLORHUG_UNKNOWN_CMD:
|
|
return "Unknown command";
|
|
case COLORHUG_WRONG_UNLOCK_CODE:
|
|
return "Wrong unlock code";
|
|
case COLORHUG_NOT_IMPLEMENTED:
|
|
return "Not implemented";
|
|
case COLORHUG_UNDERFLOW_SENSOR:
|
|
return "Sensor underflow";
|
|
case COLORHUG_NO_SERIAL:
|
|
return "No serial";
|
|
case COLORHUG_WATCHDOG:
|
|
return "Watchdog";
|
|
case COLORHUG_INVALID_ADDRESS:
|
|
return "Invalid address";
|
|
case COLORHUG_INVALID_LENGTH:
|
|
return "Invalid length";
|
|
case COLORHUG_INVALID_CHECKSUM:
|
|
return "Invlid checksum";
|
|
case COLORHUG_INVALID_VALUE:
|
|
return "Invalid value";
|
|
case COLORHUG_UNKNOWN_CMD_FOR_BOOTLOADER:
|
|
return "Unknown command for bootloader";
|
|
case COLORHUG_NO_CALIBRATION:
|
|
return "No calibration";
|
|
case COLORHUG_OVERFLOW_MULTIPLY:
|
|
return "Multiply overflow";
|
|
case COLORHUG_OVERFLOW_ADDITION:
|
|
return "Addition overflow";
|
|
case COLORHUG_OVERFLOW_SENSOR:
|
|
return "Sensor overflow";
|
|
case COLORHUG_OVERFLOW_STACK:
|
|
return "Stack overflow";
|
|
case COLORHUG_DEVICE_DEACTIVATED:
|
|
return "Device deactivated";
|
|
case COLORHUG_INCOMPLETE_REQUEST:
|
|
return "Incomplete request";
|
|
|
|
/* Internal errors */
|
|
case COLORHUG_NO_COMS:
|
|
return "Communications hasn't been established";
|
|
case COLORHUG_NOT_INITED:
|
|
return "Instrument hasn't been initialised";
|
|
case COLORHUG_WRONG_MODEL:
|
|
return "Attempt to use wrong command for model";
|
|
default:
|
|
return "Unknown error code";
|
|
}
|
|
}
|
|
|
|
/* Do a command/response exchange with the colorhug */
|
|
static inst_code
|
|
colorhug_command(colorhug *p,
|
|
ColorHugCmd cmd,
|
|
unsigned char *in, unsigned int in_size,
|
|
unsigned char *out, unsigned int out_size,
|
|
double timeout)
|
|
{
|
|
int i;
|
|
unsigned char buf[64];
|
|
int xwbytes, wbytes;
|
|
int xrbytes, xrbytes2, rbytes;
|
|
int se, ua = 0, rv = inst_ok;
|
|
int ishid = p->icom->port_type(p->icom) == icomt_hid;
|
|
|
|
a1logd(p->log,5,"colorhg_command: sending cmd '%s' args '%s'\n",
|
|
inst_desc(cmd), icoms_tohex(in, in_size));
|
|
|
|
/* Send the command with any specified data */
|
|
memset(buf, 0, 64);
|
|
buf[0] = cmd;
|
|
if (in != NULL)
|
|
memcpy(buf + 1, in, in_size);
|
|
if (ishid) {
|
|
xwbytes = 64;
|
|
se = p->icom->hid_write(p->icom, buf, xwbytes, &wbytes, timeout, 0);
|
|
} else {
|
|
// xwbytes = in_size + 1; /* cmd + arguments */
|
|
xwbytes = 64;
|
|
se = p->icom->usb_write(p->icom, NULL, 0x01, buf, xwbytes, &wbytes, timeout);
|
|
}
|
|
a1logd(p->log,8,"colorhug_command: Send %d bytes and %d sent\n",xwbytes,wbytes);
|
|
if (se != 0) {
|
|
a1logd(p->log,1,"colorhug_command: command send failed with ICOM err 0x%x\n",se);
|
|
return colorhug_interp_code((inst *)p, COLORHUG_COMS_FAIL);
|
|
}
|
|
rv = colorhug_interp_code((inst *)p, icoms2colorhug_err(ua));
|
|
if (rv == inst_ok && wbytes != xwbytes)
|
|
rv = colorhug_interp_code((inst *)p, COLORHUG_BAD_WR_LENGTH);
|
|
a1logd(p->log,6,"colorhug_command: got inst code \n",rv);
|
|
|
|
if (rv != inst_ok) {
|
|
/* Flush any response if write failed */
|
|
if (ishid)
|
|
p->icom->hid_read(p->icom, buf, 64, &rbytes, timeout, 0);
|
|
else
|
|
p->icom->usb_read(p->icom, NULL, 0x81, buf, out_size + 2, &rbytes, timeout);
|
|
return rv;
|
|
}
|
|
|
|
/* Now fetch the response */
|
|
a1logd(p->log,6,"colorhug_command: Reading response\n");
|
|
|
|
if (ishid) {
|
|
xrbytes = xrbytes2 = 64;
|
|
se = p->icom->hid_read(p->icom, buf, xrbytes, &rbytes, timeout, 0);
|
|
} else {
|
|
xrbytes = 64;
|
|
xrbytes2 = out_size + 2; /* For backwards compatibility with fw <= 1.1.8 */
|
|
se = p->icom->usb_read(p->icom, NULL, 0x81, buf, xrbytes, &rbytes, timeout);
|
|
}
|
|
|
|
a1logd(p->log,8,"colorhug_command: Read %d bytes and %d read\n",xrbytes,rbytes);
|
|
if (rbytes >= 2) {
|
|
a1logd(p->log,1,"colorhug_command: received cmd '%s' error '%s' args '%s'\n",
|
|
inst_desc(buf[1]),
|
|
colorhug_interp_error((inst *) p, buf[0]),
|
|
icoms_tohex(buf, rbytes - 2));
|
|
}
|
|
|
|
if (se != 0 || buf[0] != COLORHUG_OK) {
|
|
|
|
/* deal with command error */
|
|
if (buf[0] != COLORHUG_OK) {
|
|
a1logd(p->log,1,"colorhug_command: Got Colorhug !OK\n");
|
|
rv = colorhug_interp_code((inst *)p, buf[0]);
|
|
return rv;
|
|
}
|
|
|
|
/* deal with underrun or overrun */
|
|
if (rbytes != xrbytes
|
|
&& rbytes != xrbytes2) {
|
|
a1logd(p->log,1,"colorhug_command: got underrun or overrun\n");
|
|
rv = colorhug_interp_code((inst *)p, COLORHUG_BAD_RD_LENGTH);
|
|
return rv;
|
|
}
|
|
|
|
if (se != ICOM_SHORT) { /* Allow short read for firware compatibility */
|
|
/* there's another reason it failed */
|
|
a1logd(p->log,1,"colorhug_command: read failed with ICOM err 0x%x\n",se);
|
|
return colorhug_interp_code((inst *)p, COLORHUG_COMS_FAIL);
|
|
}
|
|
}
|
|
rv = colorhug_interp_code((inst *)p, icoms2colorhug_err(ua));
|
|
|
|
/* check the command was the same */
|
|
if (rv == inst_ok && buf[1] != cmd) {
|
|
a1logd(p->log,1,"colorhug_command: command wasn't echo'd\n");
|
|
rv = colorhug_interp_code((inst *)p, COLORHUG_BAD_RET_CMD);
|
|
return rv;
|
|
}
|
|
if (rv == inst_ok && out != NULL)
|
|
memcpy(out, buf + 2, out_size);
|
|
|
|
a1logd(p->log,5,"colorhg_command: returning '%s' ICOM err 0x%x\n",
|
|
icoms_tohex(buf + 2, out_size),ua);
|
|
return rv;
|
|
}
|
|
|
|
/* --------------------------------------------- */
|
|
/* Little endian wire format conversion routines */
|
|
|
|
/* Take an int, and convert it into a byte buffer */
|
|
static void int2buf_le(unsigned char *buf, int inv) {
|
|
buf[0] = (inv >> 0) & 0xff;
|
|
buf[1] = (inv >> 8) & 0xff;
|
|
buf[2] = (inv >> 16) & 0xff;
|
|
buf[3] = (inv >> 24) & 0xff;
|
|
}
|
|
|
|
/* Take an unsigned int, and convert it into a byte buffer */
|
|
static void uint2buf_le(unsigned char *buf, unsigned int inv) {
|
|
buf[0] = (inv >> 0) & 0xff;
|
|
buf[1] = (inv >> 8) & 0xff;
|
|
buf[2] = (inv >> 16) & 0xff;
|
|
buf[3] = (inv >> 24) & 0xff;
|
|
}
|
|
|
|
/* Take a short, and convert it into a byte buffer */
|
|
static void short2buf_le(unsigned char *buf, int inv) {
|
|
buf[0] = (inv >> 0) & 0xff;
|
|
buf[1] = (inv >> 8) & 0xff;
|
|
}
|
|
|
|
/* Take an unsigned short, and convert it into a byte buffer */
|
|
static void ushort2buf_le(unsigned char *buf, unsigned int inv) {
|
|
buf[0] = (inv >> 0) & 0xff;
|
|
buf[1] = (inv >> 8) & 0xff;
|
|
}
|
|
|
|
/* Take a word sized buffer, and convert it to an int */
|
|
static int buf2int_le(unsigned char *buf) {
|
|
int val;
|
|
val = buf[3];
|
|
val = ((val << 8) + buf[2]);
|
|
val = ((val << 8) + buf[1]);
|
|
val = ((val << 8) + buf[0]);
|
|
return val;
|
|
}
|
|
|
|
/* Take a word sized buffer, and convert it to an unsigned int */
|
|
static unsigned int buf2uint_le(unsigned char *buf) {
|
|
unsigned int val;
|
|
val = buf[3];
|
|
val = ((val << 8) + buf[2]);
|
|
val = ((val << 8) + buf[1]);
|
|
val = ((val << 8) + buf[0]);
|
|
return val;
|
|
}
|
|
|
|
/* Take a short sized buffer, and convert it to an int */
|
|
static int buf2short_le(unsigned char *buf) {
|
|
int val;
|
|
val = buf[1];
|
|
val = ((val << 8) + buf[0]);
|
|
return val;
|
|
}
|
|
|
|
/* Take an unsigned short sized buffer, and convert it to an int */
|
|
static unsigned int buf2ushort_le(unsigned char *buf) {
|
|
unsigned int val;
|
|
val = buf[1];
|
|
val = ((val << 8) + buf[0]);
|
|
return val;
|
|
}
|
|
|
|
/* --------------------------------------------- */
|
|
|
|
|
|
/* Converts 4 bytes of packed float into a double */
|
|
static double buf2pfdouble(unsigned char *buf)
|
|
{
|
|
return (double) buf2int_le(buf) / (double) 0x10000;
|
|
}
|
|
|
|
/* Set the device LED state */
|
|
static inst_code
|
|
colorhug_set_LEDs(colorhug *p, int mask)
|
|
{
|
|
int i;
|
|
unsigned char ibuf[4];
|
|
inst_code ev;
|
|
|
|
mask &= 0x3;
|
|
p->led_state = mask;
|
|
|
|
ibuf[0] = mask;
|
|
ibuf[1] = 0; /* repeat */
|
|
ibuf[2] = 0; /* on */
|
|
ibuf[3] = 0; /* off */
|
|
|
|
/* Do command */
|
|
ev = colorhug_command(p, ch_set_leds,
|
|
ibuf, sizeof (ibuf), /* input */
|
|
NULL, 0, /* output */
|
|
2.0);
|
|
return ev;
|
|
}
|
|
|
|
/* Take a measurement from the device */
|
|
/* There are 64 calibration matricies, index 0..63 */
|
|
/* 0 is the factory calibration, while 1..63 are */
|
|
/* applied on top of the factory calibration as corrections. */
|
|
/* Index 64..70 are mapped via the mapping table */
|
|
/* to an index between 0 and 63, and notionaly correspond */
|
|
/* as follows: */
|
|
/* LCD = 0 */
|
|
/* CRT = 1 */
|
|
/* Projector = 2 */
|
|
/* LED = 3 */
|
|
/* Custom1 = 4 */
|
|
/* Custom2 = 5 */
|
|
static inst_code
|
|
colorhug_take_measurement(colorhug *p, double XYZ[3])
|
|
{
|
|
inst_code ev;
|
|
int i;
|
|
ORD8 ibuf[2];
|
|
|
|
if (!p->inited)
|
|
return colorhug_interp_code((inst *)p, COLORHUG_NOT_INITED);
|
|
|
|
if (p->icx == 11) { /* Raw */
|
|
unsigned char obuf[3 * 4];
|
|
|
|
/* Do the measurement, and return the values */
|
|
ev = colorhug_command(p, ch_take_reading,
|
|
NULL, 0,
|
|
obuf, 3 * 4,
|
|
30.0);
|
|
if (ev != inst_ok)
|
|
return ev;
|
|
|
|
/* Convert to doubles */
|
|
for (i = 0; i < 3; i++) {
|
|
//printf("%d raw = 0x%x\n",i,buf2int_le(obuf + i * 4));
|
|
XYZ[i] = p->postscale * buf2pfdouble(obuf + i * 4);
|
|
}
|
|
} else {
|
|
int icx = 64 + p->icx;
|
|
unsigned char obuf[3 * 4];
|
|
|
|
if (p->icx == 10) /* Factory */
|
|
icx = 0;
|
|
|
|
/* Choose the calibration matrix */
|
|
short2buf_le(ibuf + 0, icx);
|
|
|
|
/* Do the measurement, and return the values */
|
|
ev = colorhug_command(p, ch_take_reading_xyz,
|
|
ibuf, sizeof (ibuf),
|
|
obuf, 3 * 4,
|
|
30.0);
|
|
if (ev != inst_ok)
|
|
return ev;
|
|
|
|
/* Convert to doubles */
|
|
for (i = 0; i < 3; i++) {
|
|
//printf("%d raw = 0x%x\n",i,buf2int_le(obuf + i * 4));
|
|
XYZ[i] = buf2pfdouble(obuf + i * 4);
|
|
}
|
|
}
|
|
|
|
/* Apply the colorimeter correction matrix */
|
|
icmMulBy3x3(XYZ, p->ccmat, XYZ);
|
|
|
|
a1logd(p->log,3,"colorhug_take_measurement: XYZ = %f %f %f\n",XYZ[0],XYZ[1],XYZ[2]);
|
|
|
|
return inst_ok;
|
|
}
|
|
|
|
/* Establish communications with a ColorHug */
|
|
static inst_code
|
|
colorhug_init_coms(inst *pp, baud_rate br, flow_control fc, double tout) {
|
|
int se;
|
|
colorhug *p = (colorhug *) pp;
|
|
icomuflags usbflags = icomuf_none;
|
|
|
|
a1logd(p->log, 2, "colorhug_init_coms: About to init coms\n");
|
|
|
|
/* Open as an HID if available */
|
|
if (p->icom->port_type(p->icom) == icomt_hid) {
|
|
|
|
a1logd(p->log, 3, "colorhug_init_coms: About to init HID\n");
|
|
|
|
/* Set config, interface */
|
|
if ((se = p->icom->set_hid_port(p->icom, usbflags, 0, NULL)) != ICOM_OK) {
|
|
a1logd(p->log, 1, "colorhug_init_coms: set_hid_port failed ICOM err 0x%x\n",se);
|
|
return colorhug_interp_code((inst *)p, icoms2colorhug_err(se));
|
|
}
|
|
|
|
} else if (p->icom->port_type(p->icom) == icomt_usb) {
|
|
|
|
a1logd(p->log, 3, "colorhug_init_coms: About to init USB\n");
|
|
|
|
#if defined(UNIX_X11)
|
|
usbflags |= icomuf_detach;
|
|
/* Some Linux drivers can't open the device a second time, so */
|
|
/* use the reset on close workaround. */
|
|
usbflags |= icomuf_reset_before_close;
|
|
#endif
|
|
|
|
/* Set config, interface, write end point, read end point */
|
|
// ~~ does Linux need icomuf_reset_before_close ? Why ?
|
|
if ((se = p->icom->set_usb_port(p->icom, 1, 0x00, 0x00, usbflags, 0, NULL))
|
|
!= ICOM_OK) {
|
|
a1logd(p->log, 1, "colorhug_init_coms: set_usb_port failed ICOM err 0x%x\n",se);
|
|
return colorhug_interp_code((inst *)p, icoms2colorhug_err(se));
|
|
}
|
|
|
|
} else {
|
|
a1logd(p->log, 1, "colorhug_init_coms: wrong communications type for device!\n");
|
|
return inst_internal_error;
|
|
}
|
|
|
|
a1logd(p->log, 2, "colorhug_init_coms: inited coms OK\n");
|
|
|
|
p->gotcoms = 1;
|
|
return inst_ok;
|
|
}
|
|
|
|
/* Get the firmware version */
|
|
static inst_code
|
|
colorhug_get_firmwareversion (colorhug *p)
|
|
{
|
|
inst_code ev;
|
|
unsigned char obuf[6];
|
|
|
|
ev = colorhug_command(p, ch_get_firmware_version,
|
|
NULL, 0,
|
|
obuf, 6,
|
|
2.0);
|
|
if (ev != inst_ok)
|
|
return ev;
|
|
|
|
p->maj = buf2short_le(obuf + 0);
|
|
p->min = buf2short_le(obuf + 2);
|
|
p->uro = buf2short_le(obuf + 4);
|
|
|
|
a1logd(p->log,2,"colorhug: Firware version = %d.%d.%d\n",p->maj,p->min,p->uro);
|
|
|
|
return ev;
|
|
}
|
|
|
|
/* Get the serial number */
|
|
static inst_code
|
|
colorhug_get_serialnumber (colorhug *p)
|
|
{
|
|
inst_code ev;
|
|
unsigned char obuf[6];
|
|
|
|
ev = colorhug_command(p, ch_get_serial,
|
|
NULL, 0,
|
|
obuf, 4,
|
|
2.0);
|
|
if (ev != inst_ok)
|
|
return ev;
|
|
|
|
p->ser_no = buf2uint_le(obuf + 0);
|
|
sprintf(p->serno, "%u", p->ser_no);
|
|
|
|
a1logd(p->log,2,"colorhug: Serial number = %d\n",p->ser_no);
|
|
|
|
return ev;
|
|
}
|
|
|
|
/* Set the device multiplier */
|
|
static inst_code
|
|
colorhug_set_multiplier (colorhug *p, int multiplier)
|
|
{
|
|
inst_code ev;
|
|
unsigned char ibuf[1];
|
|
|
|
if (p->stype != ch_one)
|
|
return colorhug_interp_code((inst *)p, COLORHUG_WRONG_MODEL);
|
|
|
|
/* Set the desired multiplier */
|
|
ibuf[0] = multiplier;
|
|
ev = colorhug_command(p, ch_set_mult,
|
|
ibuf, sizeof (ibuf),
|
|
NULL, 0,
|
|
2.0);
|
|
return ev;
|
|
}
|
|
|
|
/* Set the device integral time */
|
|
static inst_code
|
|
colorhug_set_integral (colorhug *p, int integral)
|
|
{
|
|
inst_code ev;
|
|
unsigned char ibuf[2];
|
|
|
|
if (p->stype != ch_one)
|
|
return colorhug_interp_code((inst *)p, COLORHUG_WRONG_MODEL);
|
|
|
|
/* Set the desired integral time */
|
|
short2buf_le(ibuf + 0, integral);
|
|
ev = colorhug_command(p, ch_set_integral,
|
|
ibuf, sizeof (ibuf),
|
|
NULL, 0,
|
|
2.0);
|
|
return ev;
|
|
}
|
|
|
|
/* Get the post scale factor */
|
|
static inst_code
|
|
colorhug_get_postscale (colorhug *p, double *postscale)
|
|
{
|
|
inst_code ev;
|
|
unsigned char obuf[4];
|
|
|
|
/* Hmm. The post scale is in the 2nd short returned */
|
|
ev = colorhug_command(p, ch_get_post_scale,
|
|
NULL, 0,
|
|
obuf, 4,
|
|
2.0);
|
|
*postscale = buf2pfdouble(obuf);
|
|
return ev;
|
|
}
|
|
|
|
static inst_code set_default_disp_type(colorhug *p);
|
|
|
|
/* Initialise the ColorHug */
|
|
static inst_code
|
|
colorhug_init_inst(inst *pp)
|
|
{
|
|
colorhug *p = (colorhug *)pp;
|
|
inst_code ev;
|
|
int i;
|
|
|
|
a1logd(p->log, 2, "colorhug_init_coms: About to init coms\n");
|
|
|
|
/* Must establish coms first */
|
|
if (p->gotcoms == 0)
|
|
return colorhug_interp_code((inst *)p, COLORHUG_NO_COMS);
|
|
|
|
/* Get the firmware version */
|
|
ev = colorhug_get_firmwareversion(p);
|
|
if (ev != inst_ok)
|
|
return ev;
|
|
|
|
/* Get the serial number */
|
|
ev = colorhug_get_serialnumber(p);
|
|
if (ev != inst_ok)
|
|
return ev;
|
|
|
|
/* Turn the LEDs off */
|
|
ev = colorhug_set_LEDs(p, 0x0);
|
|
if (ev != inst_ok)
|
|
return ev;
|
|
|
|
if (p->stype == ch_one) {
|
|
|
|
/* Turn the sensor on */
|
|
ev = colorhug_set_multiplier(p, 0x03);
|
|
if (ev != inst_ok)
|
|
return ev;
|
|
|
|
/* Set the integral time to maximum precision */
|
|
ev = colorhug_set_integral(p, 0xffff);
|
|
if (ev != inst_ok)
|
|
return ev;
|
|
}
|
|
|
|
if (p->maj <= 1 && p->min <= 1 && p->uro <= 4) {
|
|
|
|
/* Get the post scale factor */
|
|
ev = colorhug_get_postscale(p, &p->postscale);
|
|
if (ev != inst_ok)
|
|
return ev;
|
|
|
|
|
|
/* In firmware >= 1.1.5, the postscale is done in the firmware */
|
|
} else {
|
|
p->postscale = 1.0;
|
|
}
|
|
|
|
p->trig = inst_opt_trig_user;
|
|
|
|
/* Setup the default display type */
|
|
if ((ev = set_default_disp_type(p)) != inst_ok) {
|
|
return ev;
|
|
}
|
|
|
|
p->inited = 1;
|
|
a1logd(p->log, 2, "colorhug_init: inited coms OK\n");
|
|
|
|
a1logv(p->log,1,"Serial Number: %06u\n"
|
|
"Firmware Version: %d.%d.%d\n"
|
|
,p->ser_no,p->maj,p->min,p->uro);
|
|
|
|
/* Flash the LEDs */
|
|
ev = colorhug_set_LEDs(p, 0x1);
|
|
if (ev != inst_ok)
|
|
return ev;
|
|
msec_sleep(50);
|
|
ev = colorhug_set_LEDs(p, 0x2);
|
|
if (ev != inst_ok)
|
|
return ev;
|
|
msec_sleep(50);
|
|
ev = colorhug_set_LEDs(p, 0x1);
|
|
if (ev != inst_ok)
|
|
return ev;
|
|
msec_sleep(50);
|
|
ev = colorhug_set_LEDs(p, 0x0);
|
|
if (ev != inst_ok)
|
|
return ev;
|
|
|
|
return inst_ok;
|
|
}
|
|
|
|
static char *colorhug_get_serial_no(inst *pp) {
|
|
colorhug *p = (colorhug *)pp;
|
|
|
|
if (!pp->gotcoms)
|
|
return "";
|
|
if (!pp->inited)
|
|
return "";
|
|
|
|
return p->serno;
|
|
}
|
|
|
|
/* Read a single sample */
|
|
static inst_code
|
|
colorhug_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 */
|
|
colorhug *p = (colorhug *)pp;
|
|
int user_trig = 0;
|
|
int rv = inst_protocol_error;
|
|
|
|
if (!p->gotcoms)
|
|
return inst_no_coms;
|
|
if (!p->inited)
|
|
return inst_no_init;
|
|
|
|
if (p->trig == inst_opt_trig_user) {
|
|
|
|
if (p->uicallback == NULL) {
|
|
a1logd(p->log, 1, "colorhug: 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);
|
|
|
|
/* Progromatic Trigger */
|
|
} else {
|
|
/* Check for abort */
|
|
if (p->uicallback != NULL
|
|
&& (rv = p->uicallback(p->uic_cntx, inst_armed)) == inst_user_abort)
|
|
return rv; /* Abort */
|
|
}
|
|
|
|
/* Read the XYZ value */
|
|
rv = colorhug_take_measurement(p, val->XYZ);
|
|
|
|
if (rv != inst_ok)
|
|
return rv;
|
|
|
|
/* This may not change anything since instrument may clamp */
|
|
if (clamp)
|
|
icmClamp3(val->XYZ, val->XYZ);
|
|
|
|
val->mtype = inst_mrt_emission;
|
|
val->mcond = inst_mrc_none;
|
|
val->XYZ_v = 1; /* These are absolute XYZ readings ? */
|
|
val->sp.spec_n = 0;
|
|
val->duration = 0.0;
|
|
|
|
|
|
if (user_trig)
|
|
return inst_user_trig;
|
|
return rv;
|
|
}
|
|
|
|
static inst_code set_base_disp_type(colorhug *p, int cbid);
|
|
|
|
/* Insert a colorimetric correction matrix */
|
|
inst_code colorhug_col_cor_mat(
|
|
inst *pp,
|
|
disptech dtech, /* Use disptech_unknown if not known */
|
|
int cbid, /* Calibration display type base ID required, 1 if unknown */
|
|
double mtx[3][3]
|
|
) {
|
|
colorhug *p = (colorhug *)pp;
|
|
inst_code ev;
|
|
|
|
if (!p->gotcoms)
|
|
return inst_no_coms;
|
|
if (!p->inited)
|
|
return inst_no_init;
|
|
|
|
if ((ev = set_base_disp_type(p, cbid)) != inst_ok)
|
|
return ev;
|
|
if (mtx == NULL)
|
|
icmSetUnity3x3(p->ccmat);
|
|
else
|
|
icmCpy3x3(p->ccmat, mtx);
|
|
|
|
p->dtech = dtech;
|
|
p->refrmode = disptech_get_id(dtech)->refr;
|
|
p->cbid = 0; /* Base has been overwitten */
|
|
|
|
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;
|
|
}
|
|
|
|
/* Convert a machine specific error code into an abstract dtp code */
|
|
static inst_code
|
|
colorhug_interp_code(inst *pp, int ec) {
|
|
ec &= inst_imask;
|
|
switch (ec) {
|
|
|
|
case COLORHUG_OK:
|
|
return inst_ok;
|
|
|
|
case COLORHUG_INTERNAL_ERROR:
|
|
case COLORHUG_NO_COMS:
|
|
case COLORHUG_NOT_INITED:
|
|
case COLORHUG_WRONG_MODEL:
|
|
return inst_internal_error | ec;
|
|
|
|
case COLORHUG_COMS_FAIL:
|
|
return inst_coms_fail | ec;
|
|
|
|
case COLORHUG_UNKNOWN_MODEL:
|
|
return inst_unknown_model | ec;
|
|
|
|
case COLORHUG_UNKNOWN_CMD:
|
|
case COLORHUG_WRONG_UNLOCK_CODE:
|
|
case COLORHUG_NOT_IMPLEMENTED:
|
|
case COLORHUG_NO_SERIAL:
|
|
case COLORHUG_WATCHDOG:
|
|
case COLORHUG_INVALID_ADDRESS:
|
|
case COLORHUG_INVALID_LENGTH:
|
|
case COLORHUG_INVALID_CHECKSUM:
|
|
case COLORHUG_INVALID_VALUE:
|
|
case COLORHUG_UNKNOWN_CMD_FOR_BOOTLOADER:
|
|
case COLORHUG_OVERFLOW_MULTIPLY:
|
|
case COLORHUG_OVERFLOW_ADDITION:
|
|
case COLORHUG_OVERFLOW_SENSOR:
|
|
case COLORHUG_OVERFLOW_STACK:
|
|
case COLORHUG_INCOMPLETE_REQUEST:
|
|
case COLORHUG_BAD_WR_LENGTH:
|
|
case COLORHUG_BAD_RD_LENGTH:
|
|
case COLORHUG_BAD_RET_CMD:
|
|
case COLORHUG_BAD_RET_STAT:
|
|
return inst_protocol_error | ec;
|
|
|
|
case COLORHUG_UNDERFLOW_SENSOR:
|
|
return inst_misread | ec;
|
|
|
|
case COLORHUG_DEVICE_DEACTIVATED:
|
|
case COLORHUG_NO_CALIBRATION:
|
|
return inst_hardware_fail | ec;
|
|
}
|
|
return inst_other_error | ec;
|
|
}
|
|
|
|
/* Destroy ourselves */
|
|
static void
|
|
colorhug_del(inst *pp) {
|
|
colorhug *p = (colorhug *)pp;
|
|
if (p != NULL) {
|
|
if (p->icom != NULL)
|
|
p->icom->del(p->icom);
|
|
inst_del_disptype_list(p->dtlist, p->ndtlist);
|
|
p->vdel(pp);
|
|
free(p);
|
|
}
|
|
}
|
|
|
|
/* Return the instrument mode capabilities */
|
|
void colorhug_capabilities(inst *pp,
|
|
inst_mode *pcap1,
|
|
inst2_capability *pcap2,
|
|
inst3_capability *pcap3) {
|
|
colorhug *p = (colorhug *)pp;
|
|
inst_mode cap = 0;
|
|
inst2_capability cap2 = 0;
|
|
|
|
cap |= inst_mode_emis_spot
|
|
| inst_mode_colorimeter
|
|
;
|
|
|
|
cap2 |= inst2_prog_trig
|
|
| inst2_user_trig
|
|
| inst2_has_leds
|
|
| inst2_disptype
|
|
| inst2_ccmx
|
|
;
|
|
|
|
if (pcap1 != NULL)
|
|
*pcap1 = cap;
|
|
if (pcap2 != NULL)
|
|
*pcap2 = cap2;
|
|
if (pcap3 != NULL)
|
|
*pcap3 = inst3_none;
|
|
}
|
|
|
|
/* Check device measurement mode */
|
|
inst_code colorhug_check_mode(inst *pp, inst_mode m) {
|
|
colorhug *p = (colorhug *)pp;
|
|
inst_mode cap;
|
|
|
|
if (!p->gotcoms)
|
|
return inst_no_coms;
|
|
if (!p->inited)
|
|
return inst_no_init;
|
|
|
|
pp->capabilities(pp, &cap, NULL, NULL);
|
|
|
|
/* Simple test */
|
|
if (m & ~cap)
|
|
return inst_unsupported;
|
|
|
|
/* only display emission mode and ambient supported */
|
|
if (!IMODETST(m, inst_mode_emis_spot)
|
|
&& !IMODETST(m, inst_mode_emis_ambient)) {
|
|
return inst_unsupported;
|
|
}
|
|
|
|
return inst_ok;
|
|
}
|
|
|
|
/* Set device measurement mode */
|
|
inst_code colorhug_set_mode(inst *pp, inst_mode m) {
|
|
colorhug *p = (colorhug *)pp;
|
|
inst_code ev;
|
|
|
|
if ((ev = colorhug_check_mode(pp, m)) != inst_ok)
|
|
return ev;
|
|
|
|
p->mode = m;
|
|
|
|
return inst_ok;
|
|
}
|
|
|
|
/* The HW handles up to 6, + 2 special */
|
|
static inst_disptypesel colorhug_disptypesel[7] = {
|
|
{
|
|
inst_dtflags_default, /* flags */
|
|
0, /* cbix */
|
|
"l", /* sel */
|
|
"LCD, CCFL Backlight", /* desc */
|
|
0, /* refr */
|
|
disptech_lcd_ccfl, /* disptype */
|
|
0 /* ix */
|
|
},
|
|
{
|
|
inst_dtflags_none,
|
|
0,
|
|
"c",
|
|
"CRT display",
|
|
0,
|
|
disptech_crt,
|
|
1
|
|
},
|
|
{
|
|
inst_dtflags_none,
|
|
0,
|
|
"p",
|
|
"Projector",
|
|
0,
|
|
disptech_dlp,
|
|
2
|
|
},
|
|
{
|
|
inst_dtflags_none,
|
|
0,
|
|
"e",
|
|
"LCD, White LED Backlight",
|
|
0,
|
|
disptech_lcd_wled,
|
|
3
|
|
},
|
|
{
|
|
inst_dtflags_none,
|
|
1,
|
|
"F",
|
|
"Factory matrix (For Calibration)",
|
|
0,
|
|
disptech_unknown,
|
|
10
|
|
},
|
|
{
|
|
inst_dtflags_none,
|
|
2,
|
|
"R",
|
|
"Raw Reading (For Factory matrix Calibration)",
|
|
0,
|
|
disptech_unknown,
|
|
11
|
|
},
|
|
{
|
|
inst_dtflags_end,
|
|
0,
|
|
"",
|
|
"",
|
|
0,
|
|
disptech_none,
|
|
0
|
|
}
|
|
};
|
|
|
|
/* Get mode and option details */
|
|
static inst_code colorhug_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 */
|
|
) {
|
|
colorhug *p = (colorhug *)pp;
|
|
inst_code rv = inst_ok;
|
|
|
|
/* Create/Re-create a current list of abailable display types */
|
|
if (p->dtlist == NULL || recreate) {
|
|
if ((rv = inst_creat_disptype_list(pp, &p->ndtlist, &p->dtlist,
|
|
colorhug_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 implementation from inst_disptypesel */
|
|
static inst_code set_disp_type(colorhug *p, inst_disptypesel *dentry) {
|
|
int ix;
|
|
|
|
if (dentry->flags & inst_dtflags_ccmx) {
|
|
inst_code ev;
|
|
if ((ev = set_base_disp_type(p, dentry->cc_cbid)) != inst_ok)
|
|
return ev;
|
|
icmCpy3x3(p->ccmat, dentry->mat);
|
|
p->dtech = dentry->dtech;
|
|
p->cbid = 0; /* Can't be a base type */
|
|
|
|
} else {
|
|
|
|
/* The HW handles up to 6 calibrations */
|
|
ix = dentry->ix;
|
|
if (ix != 10 && ix != 11 && (ix < 0 || ix > 3))
|
|
return inst_unsupported;
|
|
|
|
p->icx = ix;
|
|
p->dtech = dentry->dtech;
|
|
p->cbid = dentry->cbid;
|
|
p->ucbid = dentry->cbid; /* This is underying base if dentry is base selection */
|
|
icmSetUnity3x3(p->ccmat);
|
|
}
|
|
p->refrmode = dentry->refr;
|
|
|
|
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 the display type */
|
|
static inst_code colorhug_set_disptype(inst *pp, int ix) {
|
|
colorhug *p = (colorhug *)pp;
|
|
inst_code ev;
|
|
inst_disptypesel *dentry;
|
|
|
|
if (p->dtlist == NULL) {
|
|
if ((ev = inst_creat_disptype_list(pp, &p->ndtlist, &p->dtlist,
|
|
colorhug_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;
|
|
}
|
|
|
|
/* Setup the default display type */
|
|
static inst_code set_default_disp_type(colorhug *p) {
|
|
inst_code ev;
|
|
int i;
|
|
|
|
if (p->dtlist == NULL) {
|
|
if ((ev = inst_creat_disptype_list((inst *)p, &p->ndtlist, &p->dtlist,
|
|
colorhug_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;
|
|
}
|
|
|
|
/* Setup the display type to the given base type */
|
|
static inst_code set_base_disp_type(colorhug *p, int cbid) {
|
|
inst_code ev;
|
|
int i;
|
|
|
|
if (cbid == 0) {
|
|
a1loge(p->log, 1, "colorhug set_base_disp_type: can't set base display type of 0\n");
|
|
return inst_wrong_setup;
|
|
}
|
|
if (p->dtlist == NULL) {
|
|
if ((ev = inst_creat_disptype_list((inst *)p, &p->ndtlist, &p->dtlist,
|
|
colorhug_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_ccmx) /* Prevent infinite recursion */
|
|
&& p->dtlist[i].cbid == cbid)
|
|
break;
|
|
}
|
|
if (p->dtlist[i].flags & inst_dtflags_end) {
|
|
a1loge(p->log, 1, "set_base_disp_type: failed to find cbid %d!\n",cbid);
|
|
return inst_wrong_setup;
|
|
}
|
|
if ((ev = set_disp_type(p, &p->dtlist[i])) != 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 colorhug_get_disptechi(
|
|
inst *pp,
|
|
disptech *dtech,
|
|
int *refrmode,
|
|
int *cbid) {
|
|
colorhug *p = (colorhug *)pp;
|
|
if (dtech != NULL)
|
|
*dtech = p->dtech;
|
|
if (refrmode != NULL)
|
|
*refrmode = p->refrmode;
|
|
if (cbid != NULL)
|
|
*cbid = p->cbid;
|
|
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
|
|
colorhug_get_set_opt(inst *pp, inst_opt_type m, ...) {
|
|
colorhug *p = (colorhug *)pp;
|
|
inst_code ev = inst_ok;
|
|
|
|
/* Record the trigger mode */
|
|
if (m == inst_opt_trig_prog
|
|
|| m == inst_opt_trig_user) {
|
|
p->trig = m;
|
|
return inst_ok;
|
|
}
|
|
|
|
if (!p->gotcoms)
|
|
return inst_no_coms;
|
|
if (!p->inited)
|
|
return inst_no_init;
|
|
|
|
/* Operate the LEDs */
|
|
if (m == inst_opt_get_gen_ledmask) {
|
|
va_list args;
|
|
int *mask = NULL;
|
|
|
|
va_start(args, m);
|
|
mask = va_arg(args, int *);
|
|
va_end(args);
|
|
*mask = 0x3; /* Two general LEDs */
|
|
return inst_ok;
|
|
} else if (m == inst_opt_get_led_state) {
|
|
va_list args;
|
|
int *mask = NULL;
|
|
|
|
va_start(args, m);
|
|
mask = va_arg(args, int *);
|
|
va_end(args);
|
|
*mask = p->led_state;
|
|
return inst_ok;
|
|
} else if (m == inst_opt_set_led_state) {
|
|
va_list args;
|
|
int mask = 0;
|
|
|
|
va_start(args, m);
|
|
mask = va_arg(args, int);
|
|
va_end(args);
|
|
return colorhug_set_LEDs(p, mask);
|
|
}
|
|
|
|
/* Use default implementation of other inst_opt_type's */
|
|
{
|
|
inst_code rv;
|
|
va_list args;
|
|
|
|
va_start(args, m);
|
|
rv = inst_get_set_opt_def(pp, m, args);
|
|
va_end(args);
|
|
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
/* Constructor */
|
|
extern colorhug *new_colorhug(icoms *icom, instType dtype) {
|
|
colorhug *p;
|
|
int i;
|
|
|
|
if ((p = (colorhug *)calloc(sizeof(colorhug),1)) == NULL) {
|
|
a1loge(icom->log, 1, "new_colorhug: malloc failed!\n");
|
|
return NULL;
|
|
}
|
|
|
|
p->log = new_a1log_d(icom->log);
|
|
|
|
p->init_coms = colorhug_init_coms;
|
|
p->init_inst = colorhug_init_inst;
|
|
p->capabilities = colorhug_capabilities;
|
|
p->get_serial_no = colorhug_get_serial_no;
|
|
p->check_mode = colorhug_check_mode;
|
|
p->set_mode = colorhug_set_mode;
|
|
p->get_disptypesel = colorhug_get_disptypesel;
|
|
p->set_disptype = colorhug_set_disptype;
|
|
p->get_disptechi = colorhug_get_disptechi;
|
|
p->get_set_opt = colorhug_get_set_opt;
|
|
p->read_sample = colorhug_read_sample;
|
|
p->col_cor_mat = colorhug_col_cor_mat;
|
|
p->interp_error = colorhug_interp_error;
|
|
p->del = colorhug_del;
|
|
|
|
p->icom = icom;
|
|
p->dtype = dtype;
|
|
|
|
if (dtype == instColorHug2)
|
|
p->stype = ch_two;
|
|
|
|
icmSetUnity3x3(p->ccmat);
|
|
p->dtech = disptech_unknown;
|
|
|
|
return p;
|
|
}
|
|
|