1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472
|
Instlib API
-----------
Although instlib is writen in the C language, it uses an
object oriented style of API.
The instlib API is declared in the inst.h file, with suplimental
definitions in other .h files such as insttypes.h, disptechs.h,
icoms.h, xspect.h etc.
1) Instrument discovery
--------------------
A list of potential instruments is returned by a call to new_icompaths().
The diagnostic log argument should be set to the default global log g_log, if
it is not being overridden to use a different log.
The list of instruments can then be itterated through, to select which
instrument to use:
icompaths *icmps;
icmps = new_icompaths(g_log);
if (icmps != NULL && icmps->paths != NULL) {
int i;
for (i = 0; ;i++) {
if (icmps->paths[i] == NULL)
break;
printf(" %d = '%s'\n",i+1,icmps->paths[i]->name);
}
}
The chosen instrument device path can be retrieved using the "comport"
index 1..N, which corresponds to the index in the icompaths list (see above):
icompath *ipath;
if ((ipath = icmps->get_path(icmps, comport)) == NULL)
... error ...
2) User interface callback function
--------------------------------
There is the option of setting a user interface callback function,
either at the creation of the instrument object, or afterwards.
The callback is used for one of four different purposes:
To poll for a user abort during communication setup.
To signal that the instrument measurement has been triggered.
To poll for an abort while waiting to trigger.
To poll for a user abort during measurement.
The callback function will have the purpose paramater set appropriately.
For inst_negcoms, the return value of inst_user_abort
will abort the communication negotiation.
For inst_armed return value should be one of:
inst_ok to do nothing, inst_user_abort to abort the measurement,
or inst_user_trig to trigger the measurement.
For inst_triggered, the return value of the callback is ignored.
For inst_measuring the return value should be one of:
inst_ok to do nothing, or inst_user_abort to abort the measurement.
typedef struct _uicontext {
... your context here ...
} uicontext;
uicontext cb_cntx;
inst_code ui_cb_func(void *cntx, inst_ui_purp purp) {
uicontext *p = (uicontext *)cntx;
/* Negotiating communications with instrument. */
if (purp == inst_triggered) {
// Can abort by returning inst_user_abort;
return inst_ok;
/* Instrument is armed and ready for measurement */
} else if (purp == inst_armed) {
// Can trigger by returning inst_user_trig;
// Can abort by returning inst_user_abort;
return inst_ok; // Wait some more
/* Instrument measurement has been triggerted
} else if (purp == inst_triggered) {
... show in user interface that the instrument measurement
has triggered ...
return inst_ok;
/* Instrument is busy measuring */
} else if (purp == inst_measuring) {
// Can abort by returning inst_user_abort;
return inst_ok;
}
return inst_ok;
}
3) Opening instrument
------------------
Given the instrument device path ipath (see above), first create an instrument
object. A user interface callback function can be provided (see above).
inst *it;
if ((it = new_inst(ipath, 0, g_log, ui_cb_func, cb_cntx)) == NULL)
... error ...
and establish communications:
baud_rate br = baud_38400; /* Target baud rate - ignored for i1pro */
flow_control fc = fc_nc; /* flow control - ignored for i1pro */
if ((rv = it->init_coms(it, br, fc, 15.0)) != inst_ok)
... error ...
and initialise the instrument:
if ((rv = it->init_inst(it)) != inst_ok)
... error ...
4) Getting instrument capabilities and configuration
-------------------------------------------------
Because instlib handles many different types of instruments,
it is very useful to be able to determine what capabililities
an instrument has, to determine what sort of measurements it is
capable of making, and how to best configure and operate it.
Capabilities are indicated by three 32 bit flag values, and are
retrieved by the capabilities() function:
inst_mode cap;
inst2_capability cap2;
inst3_capability cap3;
it->capabilities(it, &cap, &cap2, &cap3);
The first set of flags correspond to the measurement mode capabilities.
Modes consist of basic mode combinations (light measurement mode + action),
+ dependent modes + independent modes.
It is generaly best to test and set for mode combinations rather than independent
flags, and to use the IMODETST(flags, mode) macro and check_mode() function
to test for a particular mode combination capability. (see the inst_mode enum).
For instance, if we are intending to do strip reflectance measurement, we
should test if it has this capability:
if (!IMODETST(cap, inst_mode_ref_strip)
|| it->check_mode(it, inst_mode_ref_strip) != inst_ok)
... error ...
Configuration consists of two aspects: setting the operating mode and setting options.
If we want to configure for reflective strip reading with spectral:
inst_mode mode = inst_mode_ref_strip;
if (!IMODETST(cap, inst_mode_spectral))
... error, no spectral support ...
mode |= inst_mode_spectral;
if ((rv = it->set_mode(it, mode)) != inst_ok)
... error ...
The exact capabilities may change with mode, so they should be re-fetched
if they are needed again:
it->capabilities(it, &cap, &cap2, &cap3);
For reflectance measurement with an X-Rite type instrument, we may want to
set the XRGA handling. i.e. to get xrga standard measurements:
if ((rv = it->get_set_opt(it, inst_opt_set_xcalstd, xcalstd_xrga)) != inst_ok)
... warning, XRGA not supported ...
Many instruments normally need a calibration every time they
are opened, but if several measurements are being done in quick
succession the calibration after opening the instrument
can be supressed by setting the inst_opt_noinitcalib option:
if ((rv = it->get_set_opt(it, inst_opt_noinitcalib, 0)) != inst_ok) {
... warning, not supported ...
6) Asynchronous callback handler
-----------------------------
To handle certain asynchronouos events, you can register
a callback handler. This function is typically called from
a different thread from the one making calls to inst functions.
Events are:
inst_event_switch: Instrument measure/calibrate/user trigger switch pressed.
inst_event_mconf: The measurement configuration has changed (ie. sensor position)
inst_event_scan_ready: Ready for manual strip scan (i.e. issue audible prompt to user)
Which events are issued will depend on the type of instrument and what mode and state
it is in.
void event_callback_handler(void *cntx, inst_event_type event) {
if (event == inst_event_scan_ready)
normal_beep();
}
it->set_event_callback(it, event_callback_handler, (void *)it);
If no event handler is registered, then the inst_event_scan_ready event will
generate a "beep" using operating system facilities.
7) Dealing with calibration
------------------------
Often it is better to do a proactive calibration, rather than waiting for
a calibration error to be returned by a measurement request. A need for
calibration can be determined by calling needs_calibration():
if (it->needs_calibration(it) & inst_calt_n_dfrble_mask)
... do needed calibration ...
Performing a calibration involves (usually) repeated calls to
calibrate(), with arguments *calt containing the mask of calibrations
to be performed, and *calc set to the current calibration condition.
Alternately, one of the psudo-calibration types inst_calt_all,
inst_calt_needed or inst_calt_available can be used,
and/or the *calc of inst_calc_none to get calibrate()
to determine the required calibration types and conditions.
(The corresponding calibration types will be used & returned.)
If no error is returned to the first call to calibrate(),
then the instrument was capable of calibrating without user or
application intervention. If on the other hand calibrate() returns
inst_cal_setup, then the appropriate action indicated by the value
returned in *calc should be taken by the user or application,
before retrying calibration() with the current setup indicated
by *calc. If more than one calibration type is requested, then
several retries may be needed with different calibration conditions.
Each call to calibrate() will update *calt to reflect the remaining
calibration to be performed. calibrate() returns inst_ok when no
more calibrations remain.
If the calc has the inst_calc_optional_flag flag set,
then the user should be offered the option of skipping the
calibration. If they decide to skip it, return a calc with
inst_calc_optional_flag set, and if they want to proceed,
make sure the inst_calc_optional_flag is cleared in the returned
calc.
The id parameter allows passing back messages to the user, such
as the serial number of the calibration plaque, or messages,
while the idtype parameter indicates the nature of what is
passed back in id, to allow for internationalization of
any messages.
The following is a cut down version of inst_handle_calibrate()
in the file instappsup.c.
inst_cal_type calt = inst_calt_needed;
inst_cal_cond calc = inst_calc_none;
inst_code ev;
int usermes = 0; /* User was given a message */
inst_calc_id_type idtype; /* Condition identifier type */
char id[200]; /* Condition identifier */
/* Untill we're done with the calibration */
for (;;) {
ev = p->calibrate(p, &calt, &calc, &idtype, id);
/* We're done */
if ((ev & inst_mask) == inst_ok) {
if ((calc & inst_calc_cond_mask) == inst_calc_message)
... display message id to user, or substitute our
own message based on the value of idtype ...
... done calibration ...
}
/* User aborted */
if ((ev & inst_mask) == inst_user_abort)
... done calibration with user abort ...
/* Retry on an error */
if ((ev & inst_mask) != inst_cal_setup) {
if ((ev & inst_mask) == inst_unsupported)
... requested calibration type is unsupported ...
... ask user whether to rety or abort,
and don't continue on abort ...
} else {
/* Ask user to do/setup calibration */
switch (calc & inst_calc_cond_mask) {
case inst_calc_man_ref_white:
... ask user to place instrument on its reflective white
reference S/N id string, and continue when done ...
break;
... cases not needed for i1pro have been omitted ...
default:
... we're not handling some calc ...
}
if (calc & inst_calc_optional_flag)
... offer user option to skip or abort calibration & measurement ...
else
... offer user option to abort calibration & measurement ...
... get user response ...
/* If optional calib. and user wants to skip it */
/* Loop back to calibrate() with inst_calc_optional_flag still set */
if ((calc & inst_calc_optional_flag) != 0 && ... user says skip ...)
goto oloop;
if (... user says abort ...)
... abort calibration & measurement ...
/* Remove any skip flag and continue with the calibration */
calc &= inst_calc_cond_mask;
}
oloop:;
}
8) Taking a single measurement
---------------------------
We may want to set a particular trigger mode before doing this. The appropriate
trigger mode depends on the instrument capabilities, as well as the way we
want to use it.
We can check what trigger modes are available by looking at the cap2 values
and comparing against the inst2_prog_trig, inst2_user_trig, inst2_switch_trig &
inst2_user_switch_trig flags, and then choose which mode to use. i.e. if we
want the user to initiate the measurement we might use:
/* Enable switch or user via uicallback trigger if possible */
if (cap2 & inst2_user_switch_trig) {
if ((rv = it->get_set_opt(it, inst_opt_trig_user_switch)) != inst_ok)
... error ...
/* Or go for user via uicallback trigger */
} else if (cap2 & inst2_user_trig) {
if ((rv = it->get_set_opt(it, inst_opt_trig_user)) != inst_ok)
... error ...
} else
... error, no usable trigger mode ...
or if we wanted to trigger progromatically:
if (cap2 & inst2_prog_trig) {
if ((rv = it->get_set_opt(it, inst_opt_trig_prog)) != inst_ok)
... error ...
} else
... error, no usable trigger mode ...
To measure one patch:
ipatch val;
rv = it->read_sample(it, "SPOT", &val, 1);
if (rv == inst_ok
|| (rv & inst_mask) == inst_user_trig)
... OK ...
else if ((rv & inst_mask) == inst_user_abort)
... abort measurements ...
else if ((rv & inst_mask) == inst_needs_cal)
... do calibration ...
else if ((rv & inst_mask) == inst_misread)
... deal with misread, i.e. retry ? ...
else
... some other error, probably fatal ...
ipatch values are in XYZ % (0..100) for reflective & transmissive,
cd/m^2 for emissive, and Lux for ambient.
Spectral will be analogous to the XYZ (see inst_meas_type).
By default values may be -ve due to noise (depending on instrument),
unless the clamp flag is set.
9) Taking strip measurements
-------------------------
We may want to set a particular trigger mode before doing this (see above).
Each strip may need certain parameters, depending on the type of instrument
used. For instance, a DTP51 displays a 7 character strip name,
and a 3 character pass name and a guide number, whereas an i1pro instrument
will ignore these parameters.
Similarly, the patch width, gap width and trailer width are only
needed by a DTP20 and DTP41.
To measure a strip:
#define NPATCHES 21
ipatch vals[NPATCHES];
rv = it->read_strip(it, "STRIP", NPATCHES, "001", 1, 7.366, 2.032, 18.8, vals);
if (rv == inst_ok
|| (rv & inst_mask) == inst_user_trig)
... OK ...
else if ((rv & inst_mask) == inst_user_abort)
... abort measurements ...
else if ((rv & inst_mask) == inst_needs_cal)
... do calibration ...
else if if ((rv & inst_mask) == inst_wrong_config)
... deal with bad sensor position, i.e. ColorMunki ...
else if ((rv & inst_mask) == inst_misread)
... deal with misread, i.e. retry ? ...
else
... some other error, probably fatal ...
10) Closing instrument
------------------
To close an instrument and the communication port, call the del() function:
it->del(it);
12) Error codes
-----------
The inst_code return codes generally consist of two parts; an instrument specific
16 bit error code in the lower 16 bits, and an abstract error code in the upper 8 bits.
These two parts can be separated using the inst_code inst_mask and inst_imask values.
An English language interpretation of the 16 bit instrument specific error code
can be obtained using the inst_interp_error() function:
it->inst_interp_error(it, rv)
and an English language interpretation of the 8 bit abstract error code
can be obtained using the interp_error() function:
it->interp_error(it, rv));
|