summaryrefslogtreecommitdiff
path: root/spectro/vtpglut.c
blob: b558bf4cccc3a6dbaf16e8296056abc2e7d6f003 (plain)
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
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580

/* Abstract Video Test Patch Generator and/or 3dLUT class implemenation */

/* 
 * Argyll Color Correction System
 *
 * Author: Graeme W. Gill
 * Date:   10/3/2001
 *
 * Copyright 2001 - 2013 Graeme W. Gill
 * All rights reserved.
 *
 * This material is licenced under the GNU GENERAL PUBLIC LICENSE Version 2 or later :-
 * see the License2.txt file for licencing details.
 */

/*
	TTBD:

	Note that there is some support for HW 3dLUTs in X11 xrandr
	in a patch for xf86-videeo-amdgpu drmmode_display.c
	submitted on 3 May 2018. 
	See <https://lists.freedesktop.org/archives/amd-gfx/2018-May/022007.html>

*/

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <ctype.h>
#include <string.h>
#include <time.h>
#ifndef SALONEINSTLIB
#include "copyright.h"
#include "aconfig.h"
#else
#include "sa_config.h"
#endif /* !SALONEINSTLIB */
#include "numsup.h"
#include "cgats.h"
#include "xspect.h"
#include "conv.h"

#include "insttypes.h"
#include "icoms.h"
#include "vtpglut.h"
#include "rspec.h"
#include "vtpgluttypes.h"
#include "sort.h"

icom_type dev_category(instType itype);

/* --------------------------------------------------------- */

#if defined(ENABLE_FAST_SERIAL)
extern devType fast_ser_dev_type(icoms *p, int tryhard, 
       inst_code (*uicallback)(void *cntx, inst_ui_purp purp), void *cntx);
# if defined(ENABLE_SERIAL)
static devType ser_vtpglut_type(icoms *p, 
       vtpglut_code (*uicallback)(void *cntx, vtpglut_ui_purp purp), void *cntx);

/* Translate vtpglut uicallback into inst callback */
typedef struct {
    void *cntx;
    vtpglut_code (*uicallback)(void *cntx, vtpglut_ui_purp purp);
} vtpglut_callback_cntx;

static inst_code ser_uicallback(void *cntx, inst_ui_purp purp) {
	vtpglut_callback_cntx *p = (vtpglut_callback_cntx *)cntx;
	vtpglut_ui_purp ser_purp;

	if (purp == vtpglut_negcoms)
		ser_purp = inst_negcoms;
	else
		return inst_internal_error; 
	return p->uicallback(p->cntx, ser_purp);
}

# endif /* ENABLE_SERIAL */
#endif /* ENABLE_FAST_SERIAL */


/* ------------------------------------ */
/* Default methods for instrument class */

/* Establish communications at the indicated baud rate. */
/* Timout in to seconds, and return non-zero error code */
static vtpglut_code init_coms(
vtpglut *p) {		/* Timeout */
	return vtpglut_unsupported;
}

/* Initialise or re-initialise the INST */
/* return non-zero on an error, with inst error code */
static vtpglut_code init_vtpglut(  
vtpglut *p) {
	return vtpglut_unsupported;
}

/* Return the device type */
static devType get_dtype(vtpglut *p) {
	if (p != NULL)
		return p->dtype;
	return devUnknown;
}

/* Return the device serial number. */
/* (This will be an empty string if there is no serial no) */
static char *get_serial_no(vtpglut *p) {
	return "";
}

/* Return the device mode/capabilities */
static void capabilities(vtpglut *p, vtpglut_mode *cap1,
                         vtpglut_capability *cap2) {
	if (cap1 != NULL)
		*cap1 = vtpglut_mode_none;
	if (cap2 != NULL)
		*cap2 = vtpglut2_none;
}

/* Check the device mode */                                       
static vtpglut_code check_mode(
vtpglut *p,
vtpglut_mode m) {		/* Requested mode */
	return vtpglut_unsupported;
}

/* Set the device mode */                                       
static vtpglut_code set_mode(
vtpglut *p,
vtpglut_mode m) {		/* Requested mode */
	return vtpglut_unsupported;
}

/* Get a status or set or get an option (default implementation) */
vtpglut_code vtpglut_get_set_opt_def(
vtpglut *p,
vtpglut_opt_type m,	/* Requested option type */
va_list args) {		/* Option parameters */                             
	return vtpglut_unsupported;
}

/* Get a status or set or get an option */
static vtpglut_code get_set_opt(
vtpglut *p,
vtpglut_opt_type m,	/* Requested status type */
...) {				/* Status parameters */                             
	vtpglut_code rv;
	va_list args;

	va_start(args, m);
	rv = vtpglut_get_set_opt_def(p, m, args);	/* Call the above */
	va_end(args);

	return rv;
}

/* Supply a user interaction callback function. */
static void set_uicallback(
vtpglut *pp,
vtpglut_code (*uicallback)(void *cntx, vtpglut_ui_purp purp),
void *cntx)	 {
	pp->uicallback = uicallback;
	pp->uic_cntx = cntx;
}

/* Supply an asynchronous event callback function. */
static void set_event_callback(
vtpglut *pp,
void (*eventcallback)(void *cntx, vtpglut_event_type event),
void *cntx)	 {
	pp->eventcallback = eventcallback;
	pp->event_cntx = cntx;
}

/* Generic inst error codes interpretation */
static char *vtpglut_interp_error(vtpglut *p, vtpglut_code ec) {
	switch(ec & vtpglut_mask) {
		case vtpglut_ok:
			return "No error";
		case vtpglut_notify:
			return "Notification";
		case vtpglut_warning:
			return "Warning";
		case vtpglut_no_coms:
			return "Internal error - communications needed but not established";
		case vtpglut_no_init:
			return "Internal error - initialisation needed but not done";
		case vtpglut_unsupported:
			return "Unsupported function";
		case vtpglut_internal_error:
			return "Internal software error";
		case vtpglut_coms_fail:
			return "Communications failure";
		case vtpglut_unknown_model:
			return "Not expected device model";
		case vtpglut_protocol_error:
			return "Communication protocol breakdown";
		case vtpglut_user_abort:
			return "User hit Abort Key";
		case vtpglut_user_trig:
			return "User hit Trigger Key";
		case vtpglut_unexpected_reply:
			return "Unexpected Reply";
		case vtpglut_wrong_setup:
			return "Wrong or conflicting setup";
		case vtpglut_hardware_fail:
			return "Hardware Failure";
		case vtpglut_system_error:
			return "Operating System Error";
		case vtpglut_bad_parameter:
			return "Bad Parameter Value";
		case vtpglut_other_error:
			return "Non-specific error";
	}
	return "Unknown inst error code";
}

/* Instrument specific error codes interpretation */
static char *interp_error(vtpglut *p, int ec) {
	return "interp_error is uninplemented in base class!";
}

/* Return the last serial communication error code */
/* (This is used for deciding fallback/retry strategies) */
static int last_scomerr(vtpglut *p) {
	return p->icom->lserr;
}

/* ---------------------------------------------- */

/* Delete things set/done by new_vtpglut() */
static vtpglut_code virtual_del(vtpglut *p) {

#if defined(UNIX_APPLE)
	osx_latencycritical_end();
#endif

	return vtpglut_ok;
}


/* Virtual constructor. */
/* Return NULL for unknown instrument, */
/* or serial instrument if nocoms == 0. */
extern vtpglut *new_vtpglut(
icompath *path,		/* Device path to instrument */
int nocoms,			/* Don't open if communications are needed to establish inst type */
a1log *log,			/* log to use */
vtpglut_code (*uicallback)(void *cntx, vtpglut_ui_purp purp),	/* optional uicallback for abort */
void *cntx			/* Context for callback */
) {
	devType itype = devUnknown;		/* Actual type */
	icoms *icom;
	vtpglut *p = NULL;

	if (path == NULL) {
		a1logd(log, 2, "new_vtpglut: got NULL path\n");
		return NULL;
	}

	a1logd(log, 2, "new_vtpglut: called with path '%s' type '%s'\n",path->name,inst_sname(path->itype));

	if ((icom = new_icoms(path, log)) == NULL) {
		a1logd(log, 2, "new_vtpglut: new_icoms failed to open it\n");
		return NULL;
	}



	/* Set device type from USB port, if not specified */
	itype = icom->itype;		/* Instrument type if its known from usb/hid */

#if defined(ENABLE_FAST_SERIAL)
	if (itype == instUnknown && !nocoms && (icom->dctype & icomt_fastserial)) {
		vtpglut_callback_cntx wcntx;

		/* Wrap vtpglut callback in inst callback */
		wcntx.cntx = cntx;
		wcntx.uicallback = uicallback;

		itype = fast_ser_dev_type(icom, 1, ser_uicallback, &wcntx);		/* Else type from serial */
		icom->dctype = (icom->dctype & ~icomt_cat_mask) | dev_category(itype);
		a1logd(log, 8, "new_vtpglut: fast set '%s' dctype 0x%x\n",icom->name,icom->dctype);
	}
#endif /* ENABLE_FAST_SERIAL */

#if defined(ENABLE_SERIAL)
	if (itype == instUnknown && !nocoms) {
		itype = ser_vtpglut_type(icom, uicallback, cntx);		/* Else type from serial */
		icom->dctype = (icom->dctype & ~icomt_cat_mask) | dev_category(itype);
		a1logd(log, 8, "new_vtpglut: set '%s' dctype 0x%x\n",icom->name,icom->dctype);
	}
#endif /* ENABLE_SERIAL */


#ifdef ENABLE_SERIAL
//	if (itype == devRadiance)
//		p = (vtpglut *)new_radiance(icom, itype);
#endif /* ENABLE_SERIAL */

#ifdef ENABLE_USB
//	if (itype == instXXXX)
//		p = (vtpglut *)new_XXXX(icom, itype);
#endif /* ENABLE_USB */


	/* Nothing matched */
	if (p == NULL) {
		a1logd(log, 2, "new_vtpglut: device type not recognised\n");
		icom->del(icom);
		return NULL;
	}

	p->vdel = virtual_del;

	/* Add default methods if constructor did not supply them */
	if (p->init_coms == NULL)
		p->init_coms = init_coms;
	if (p->init_vtpglut == NULL)
		p->init_vtpglut = init_vtpglut;
	if (p->get_dtype == NULL)
		p->get_dtype = get_dtype;
	if (p->get_serial_no == NULL)
		p->get_serial_no = get_serial_no;
	if (p->capabilities == NULL)
		p->capabilities = capabilities;
	if (p->check_mode == NULL)
		p->check_mode = check_mode;
	if (p->set_mode == NULL)
		p->set_mode = set_mode;
	if (p->get_set_opt == NULL)
		p->get_set_opt = get_set_opt;
	if (p->set_uicallback == NULL)
		p->set_uicallback = set_uicallback;
	if (p->set_event_callback == NULL)
		p->set_event_callback = set_event_callback;
	if (p->vtpglut_interp_error == NULL)
		p->vtpglut_interp_error = vtpglut_interp_error;
	if (p->interp_error == NULL)
		p->interp_error = interp_error;
	if (p->last_scomerr == NULL)
		p->last_scomerr = last_scomerr;

	/* Set the provided user interaction callback */
	p->set_uicallback(p, uicallback, cntx);

#if defined(UNIX_APPLE)
	osx_latencycritical_start();
#endif

	return p;
}

/* ============================================================= */
/* Detect serial device */

#ifdef ENABLE_SERIAL
static void hex2bin(char *buf, int len);

/* Heuristicly determine the device type for */
/* a serial connection, and devUnknown if not serial. */
/* Set it in icoms and also return it. */
static devType ser_vtpglut_type(
	icoms *p,
	vtpglut_code (*uicallback)(void *cntx, vtpglut_ui_purp purp),		/* optional uicallback */
	void *cntx			/* Context for callback */
) {
	devType rv = instUnknown;
#define BUFSZ (128 + 10)
	char buf[BUFSZ];
	baud_rate brt[] = { baud_9600, baud_57600, baud_115200, baud_230400, baud_nc };
	unsigned int etime;
	unsigned int bi, i;
	int se, len, bread;
	int lumagen = 0, lgchsum = 0;
	
#ifdef ENABLE_USB
	if (p->usbd != NULL || p->hidd != NULL)
		return p->itype;
#endif /* ENABLE_USB */

	bi = 0;

	/* The tick to give up on */
	etime = msec_time() + (long)(20.0 * 1000.0 + 0.5);

	a1logd(p->log, 1, "ser_vtpglut_type: Trying different baud rates (%u msec to go)\n",etime - msec_time());

	/* Until we time out, find the correct baud rate */
	for (i = bi; msec_time() < etime; i++) {
		lgchsum = 0;

		if (brt[i] == baud_nc)
			i = 0;
		if ((se = p->set_ser_port(p, fc_None, brt[i], parity_none,
			                         stop_1, length_8)) != ICOM_OK) { 
			a1logd(p->log, 5, "ser_vtpglut_type: set_ser_port failed with 0x%x\n",se);
			return instUnknown;		/* Give up */
		}

		a1logd(p->log, 5, "Trying %s baud\n",baud_rate_to_str(brt[i]));
		bread = 0;

		/* If Lumagen baud rate */
		if (brt[i] == baud_9600
		 || brt[i] == baud_57600
		 || brt[i] == baud_115200
		 || brt[i] == baud_230400) {

			/* Try a spectrolino/spectroscan command first, so as not to upset it, */
			/* but do first character only and see if there is an echo */
			p->write_read_ex(p, ";", 1, buf, BUFSZ-1, &bread, "\r", 1, 0.2, 1);
			if (bread == 1 && buf[0] == ';')
				goto check_lumagen;		/* It may be a Lumagen, so skip it. */
	
			/* Send the rest of the spectrolino command */
			p->write_read_ex(p, "D024\r\n", 0, buf, BUFSZ-1, &bread, "\r", 1, 0.5, 1);


			if (bread == 0) {
				char *bp;
				a1logd(p->log, 5, "ser_vtpglut_type: Spectrolino command returned nothing\n");

				/* It could be a Lumagen Radiance with echo off, */
				/* so poke it and see if it responds. */
				/* (Unfortunately the Lumagen delimeters modes aren't */
				/*  backwards compatible, so we may have to poke it twice...) */

				/* Send "X" first, to get it out of menu mode ? */
				p->write_read_ex(p, "X", 1, buf, BUFSZ, NULL, "\n", 1, 0.1, 1);		// Menu off

				a1logd(p->log, 5, "ser_vtpglut_type: Checking for Lumagen Radiance\n");
				p->write_read_ex(p, "#ZQS00\r", 0, buf, BUFSZ, &bread, "\n", 1, 0.5, 1);
				if (bread == 0) {
					p->write_read_ex(p, "#0ZQS008E\r", 0, buf, BUFSZ, &bread, "\n", 1, 0.5, 1);
					lgchsum = 1;
				}

				if (bread > 0
				 && (bp = strrchr(buf, '!')) != NULL
				 && strlen(bp) >= 4
				 && strncmp(bp,"!S00",4) == 0) {
					goto check_lumagen;
				}

				/* Nope - look for something at a different baud rate */
				goto continue_looking;
			}
			buf[bread] = '\000';
			len = strlen(buf);

			/* Is this a Lumagen Radiance with echo on, it responds with ";D024..." */
			if ((len >= 4 && strncmp(buf, ";D024", 4) == 0)
			 || (len >= 4 && strncmp(buf, "!N\n\r", 4) == 0)) {
				char *bp;

			  check_lumagen:;

				/* Get the Lumagen device information */
				p->write_read_ex(p, lgchsum ? "#0ZQS018F\r" : "#ZQS01\r",
					                    0, buf, BUFSZ, &bread, "\n", 1, 0.5, 1);

				/* Might have echo with checksum, so lgchsum not set correctly */
				if (!lgchsum && bread > 0 && strstr(buf, "!N") != NULL) {
					p->write_read_ex(p, "#0ZQS018F\r", 0, buf, BUFSZ, &bread, "\n", 1, 0.5, 1);
					if (bread >= 11 && strncmp(buf, "#0ZQS018F!Y", 11) == 0)
						lgchsum = 1;
				}
			
				/* returns something like "ZQS01!S01,Radiance2020,030115,1016,001309\r\m" */
				if ((bp = strrchr(buf, '!')) != NULL && strlen(bp) >= 13) {
				    if (strncmp(bp,"!S01,Radiance",13) == 0) {
						a1logd(p->log, 5, "ser_vtpglut_type: Found Lumagen Radiance\n");
						lumagen = 1;
						break;
					}
				}
				a1logd(p->log, 5, "ser_vtpglut_type: Not Lumagen Radiance\n");
				goto continue_looking;
			}
		}	/* Possibly Lumagen */

	  continue_looking:;

		/* Check for user abort */
		if (uicallback != NULL) {
			vtpglut_code ev;
			if ((ev = uicallback(cntx, vtpglut_negcoms)) == vtpglut_user_abort) {
				a1logd(p->log, 5, "ser_vtpglut_type: User aborted\n");
				return instUnknown;
			}
		}
	}	/* next baud */

	if (rv == instUnknown
	 && lumagen == 0
	 && msec_time() >= etime) {		/* We haven't established comms */
		a1logd(p->log, 5, "ser_vtpglut_type: Failed to establish coms\n");
		return instUnknown;
	}

	a1logd(p->log, 5, "ser_vtpglut_type: Got coms with device\n");

	if (lumagen) {
		char *bp;

		/* Get the Lumagen device information */
		if ((se = p->write_read_ex(p, "ZQS01", 0, buf, BUFSZ, NULL, "\n", 1, 2.5, 1)) != 0)
			return instUnknown;
	
		/* returns something like "ZQS01!S01,Radiance2020,030115,1016,001309\r\m" */
		if ((bp = strstr(buf, "!")) != NULL && strlen(bp) >= 13) {
			if (strncmp(bp,"!S01,Radiance",13) == 0) {
				rv = devRadiance;
			}
		}
	}

	a1logd(p->log, 5, "ser_vtpglut_type: Device type is '%s'\n", inst_name(rv));

	p->close_port(p);	/* Or should we leave it open ?? */

	p->itype = rv;

	return rv;
}
#undef BUFSZ

#endif /* ENABLE_SERIAL */

#if defined(ENABLE_SERIAL) || defined(ENABLE_FAST_SERIAL)

/* Convert an ASCII Hex character to an integer. */
static int h2b(char c) {
	if (c >= '0' && c <= '9')
		return (c-(int)'0');
	if (c >= 'A' && c <= 'F')
		return (10 + c-(int)'A');
	if (c >= 'a' && c <= 'f')
		return (10 + c-(int)'a');
	return 0;
}

/* Convert a Hex encoded buffer into binary. */
/* len is number of bytes out */
static void hex2bin(char *buf, int len) {
	int i;

	for (i = 0; i < len; i++) {
		buf[i] = (char)((h2b(buf[2 * i + 0]) << 4)
		              | (h2b(buf[2 * i + 1]) << 0));
	}
}

#endif /* ENABLE_SERIAL */