feat(spectro): add instlist utility for JSON instrument enumeration #9

Merged
gronod merged 2 commits from feature/usb-enumeration into main 2026-08-21 21:51:50 +01:00
5 changed files with 180 additions and 1 deletions
+1
View File
@@ -52,6 +52,7 @@ Test 1D Brent solver
<br>xicc/xcolorantslu.exe
<br>xicc/xfbview.exe
<br>spectro/dispwin.exe
<br>spectro/instlist.exe
<br>target/ifarp.exe
<br>target/ppoint.exe
<br>target/qrand.exe
+39
View File
@@ -0,0 +1,39 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>instlist</title>
<meta http-equiv="content-type" content="text/html;
charset=windows-1252">
<meta name="author" content="Graeme Gill">
</head>
<body>
<h2><b>spectro/instlist</b></h2>
<br>
Enumerate connected instrument communication ports (USB and Serial) and output the list in JSON format.<br>
<h3>Usage</h3>
<small><span style="font-family: monospace;">&nbsp;instlist [-options]</span><br style="font-family: monospace;">
<span style="font-family: monospace;">&nbsp;-h, -?, --help&nbsp; Show help information</span><br style="font-family: monospace;">
<span style="font-family: monospace;">&nbsp;-v&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Verbose diagnostics to stderr</span><br style="font-family: monospace;">
<span style="font-family: monospace;">&nbsp;-D [level]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Print debug diagnostics to stderr</span><br style="font-family: monospace;">
</small><br>
<h3><b>Output</b></h3>
The output is emitted to <code>stdout</code> as a JSON object containing an array of discovered device objects:<br>
<pre>
{
"event": "instruments",
"devices": [
{
"port": 1,
"name": "usb:/bus0/dev1 (X-Rite i1Pro)",
"type": "i1Pro"
}
]
}
</pre>
<h3>Comments</h3>
This tool is designed to allow graphical user interfaces and external automation scripts to discover available spectrophotometers and colorimeters across Windows, macOS, and Linux without needing to parse interactive usage output.<br>
<br>
The <code>port</code> number directly corresponds to the <code>-c</code> parameter accepted by tools such as <a href="chartread.html">chartread</a>, <a href="spotread.html">spotread</a>, and <a href="dispread.html">dispread</a>.<br>
<br>
</body>
</html>
+1
View File
@@ -42,6 +42,7 @@ LINKLIBS = ../numlib/libnum ;
# GenFile source.c : program args ; make custom file
# Generate all the kernel files
GenFileND imdi_k.h : imdi_make $(IMDI_MAKE_OPT) -d [ NormPaths $(DOT) ] ;
Clean clean : imdi_k.c ;
# imdi library
Library libimdi : imdi.c imdi_tab.c ;
+5 -1
View File
@@ -51,7 +51,7 @@ if $(UNIX) {
#Products
Libraries = libinsttypes libdisptechs libinst libdisp libconv libinstapp ;
Executables = dispwin synthcal dispread dispcal fakeread synthread
chartread spotread illumread ccxxmake average oeminst ;
chartread spotread instlist illumread ccxxmake average oeminst ;
Headers = inst.h ;
Samples = SOtele.sp linear.cal strange.cal ccxx.ti1 ;
@@ -198,6 +198,10 @@ Main illumread : illumread.c : : : ../target : : ;
# Printed target spot reader utility
Main spotread : spotread.c : : : : : ;
# Connected instrument enumeration utility
Main instlist : instlist.c : : : : : ;
Clean clean : instlist$(SUFEXE) instlist.o ;
echo "HOME = " $(HOME) ;
echo "PWD = " $(PWD) ;
+134
View File
@@ -0,0 +1,134 @@
/*
* Argyll Color Management System
* USB & Serial Instrument Enumeration Utility.
* Outputs connected instruments in structured JSON format.
*
* Author: Graeme W. Gill / Gordon
*
* Copyright 2026 Graeme W. Gill / Gordon
* All rights reserved.
*
* This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
* see the License.txt file for licencing details.
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include "copyright.h"
#include "aconfig.h"
#include "numlib.h"
#include "cgats.h"
#include "conv.h"
#include "xicc.h"
#include "insttypes.h"
#include "icoms.h"
#include "inst.h"
static void usage(char *diag, ...) {
fprintf(stderr,"Enumerate Instrument Ports, Version %s\n",ARGYLL_VERSION_STR);
if (diag != NULL) {
va_list args;
fprintf(stderr," Diagnostic: ");
va_start(args, diag);
vfprintf(stderr, diag, args);
va_end(args);
fprintf(stderr,"\n");
}
fprintf(stderr,"Author: Graeme W. Gill / Gordon, licensed under the AGPL Version 3\n");
fprintf(stderr,"usage: instlist [-options]\n");
fprintf(stderr," -h, -?, --help Show this help information\n");
fprintf(stderr," -v Verbose diagnostics to stderr\n");
fprintf(stderr," -D [level] Print debug diagnostics to stderr\n");
exit(1);
}
int main(int argc, char *argv[]) {
int fa, nfa;
int verb = 0;
int debug = 0;
icompaths *icmps = NULL;
icompath **paths = NULL;
int i;
int count = 0;
/* Set executable path for logger */
set_exe_path(argv[0]);
/* Process command-line options */
for (fa = 1; fa < argc; fa++) {
nfa = fa + 1;
if (argv[fa][0] == '-') {
char *na = NULL;
if (nfa < argc && argv[nfa][0] != '-')
na = argv[nfa];
if (strcmp(argv[fa], "-h") == 0 || strcmp(argv[fa], "-?") == 0 ||
strcmp(argv[fa], "--help") == 0 || strcmp(argv[fa], "-help") == 0) {
usage(NULL);
} else if (argv[fa][1] == 'v') {
verb = 1;
g_log->verb = verb;
} else if (argv[fa][1] == 'D') {
debug = 1;
if (na != NULL && na[0] >= '0' && na[0] <= '9') {
debug = atoi(na);
fa = nfa;
}
g_log->debug = debug;
} else {
usage("Unknown option '%s'", argv[fa]);
}
} else {
usage("Unexpected argument '%s'", argv[fa]);
}
}
if ((icmps = new_icompaths(g_log)) == NULL) {
printf("{\n \"event\": \"instruments\",\n \"devices\": []\n}\n");
fflush(stdout);
return 1;
}
paths = icmps->paths;
printf("{\n \"event\": \"instruments\",\n \"devices\": [\n");
if (paths != NULL) {
for (i = 0; paths[i] != NULL; i++) {
char *tname = inst_name(paths[i]->dtype);
char *name = paths[i]->name ? paths[i]->name : "Unknown";
if (count > 0) {
printf(",\n");
}
printf(" {\n");
printf(" \"port\": %d,\n", i + 1);
printf(" \"name\": \"");
/* Escape quotes and backslashes in name */
{
char *p;
for (p = name; *p != '\0'; p++) {
if (*p == '"' || *p == '\\') {
putchar('\\');
}
putchar(*p);
}
}
printf("\",\n");
printf(" \"type\": \"%s\"\n", (tname != NULL && tname[0] != '\0') ? tname : "Unknown");
printf(" }");
count++;
}
}
printf("\n ]\n}\n");
fflush(stdout);
if (icmps != NULL)
icmps->del(icmps);
return 0;
}