From 70111c42721e9b7843154d9c8709d924f78ee1ab Mon Sep 17 00:00:00 2001 From: Gronod Date: Sat, 5 Sep 2026 18:13:31 +0100 Subject: [PATCH] feat(i1pro2): implement visual LED feedback in chartread (fixes #37) - Add inst_led_state enum, INST_CAP_LED_STATUS capability, and set_led_state to inst.h / inst.c - Implement i1pro2_indLEDset, worker thread i1pro2_led_thread, and i1pro_imp_set_led_state in i1pro_imp.c / i1pro_imp.h - Wire up i1pro_set_led_state and determine_capabilities in i1pro.c for i1Pro 2 (Rev E) - Integrate -Y l option and lifecycle hooks in chartread.c for calibration, readiness, success, failure, and clean exit --- spectro/chartread.c | 50 +++++++++++- spectro/i1pro.c | 19 ++++- spectro/i1pro_imp.c | 190 +++++++++++++++++++++++++++++++++++++++++++- spectro/i1pro_imp.h | 9 +++ spectro/inst.c | 7 ++ spectro/inst.h | 15 ++++ 6 files changed, 286 insertions(+), 4 deletions(-) diff --git a/spectro/chartread.c b/spectro/chartread.c index 6158912..4d3e5b3 100644 --- a/spectro/chartread.c +++ b/spectro/chartread.c @@ -204,6 +204,17 @@ typedef struct { extern int json_ui_out; int json_ui_out = 0; +static int g_use_leds = 0; /* Enable i1Pro 2 visual LED feedback */ + +/* Update instrument LED state safely */ +static void update_led_state(inst *it, inst_led_state state) { + if (!g_use_leds || it == NULL) + return; + if (it->set_led_state != NULL) { + it->set_led_state(it, state); + } +} + static void compute_patch_metrics(chcol *scb, double *eLab, double *mLab) { /* Expected Lab */ if (scb->eXYZ[0] != 0.0 || scb->eXYZ[1] != 0.0 || scb->eXYZ[2] != 0.0) { @@ -1486,8 +1497,10 @@ a1log *log /* verb, debug & error log */ /* Do any needed calibration before the user places the instrument on a desired spot */ if (it->needs_calibration(it) & inst_calt_n_dfrble_mask) { + update_led_state(it, inst_led_cal_wait); if ((rv = inst_handle_calibrate(it, inst_calt_needed, inst_calc_none, NULL, NULL, 0)) != inst_ok) { + update_led_state(it, inst_led_off); printf("\nCalibration failed with error :'%s' (%s)\n", it->inst_interp_error(it, rv), it->interp_error(it, rv)); it->del(it); @@ -1495,6 +1508,7 @@ a1log *log /* verb, debug & error log */ free(pfname); return -1; } + update_led_state(it, inst_led_off); } /* Enable (switch or user) via uicallback trigger if possible */ @@ -1637,6 +1651,7 @@ a1log *log /* verb, debug & error log */ printf("Press any other key to start:%s",fl_end); } do_fflush(); + update_led_state(it, inst_led_row_ready); if ((rv = it->read_strip(it, "STRIP", stipa+nextrap, nn, guide, plen, glen, tlen, vals)) != inst_ok && (rv & inst_mask) != inst_user_trig) { @@ -1698,6 +1713,7 @@ a1log *log /* verb, debug & error log */ printf("Hit Esc or 'q' to give up, any other key to retry:%s",fl_end); do_fflush(); if ((ch = next_con_char()) == 0x1b || ch == 0x3 || ch == 'q' || ch == 'Q') { + update_led_state(it, inst_led_off); printf("\n"); if (nn != NULL) free(nn); free(vals); @@ -1717,7 +1733,9 @@ a1log *log /* verb, debug & error log */ if (cap2 & inst2_no_feedback) bad_beep(); printf("\nStrip read failed because instruments needs calibration\n"); + update_led_state(it, inst_led_cal_wait); ev = inst_handle_calibrate(it, inst_calt_needed, inst_calc_none, NULL, NULL, 0); + update_led_state(it, inst_led_off); if (ev != inst_ok) { /* Abort or fatal error */ if (nn != NULL) free(nn); free(vals); @@ -1735,12 +1753,14 @@ a1log *log /* verb, debug & error log */ /* Deal with a misread */ } else if ((rv & inst_mask) == inst_misread) { + update_led_state(it, inst_led_row_fail); if (cap2 & inst2_no_feedback) bad_beep(); empty_con_chars(); printf("\nStrip read failed due to misread (%s)\n",it->interp_error(it, rv)); printf("Hit Esc to give up, any other key to retry%s:",fl_end); do_fflush(); if ((ch = next_con_char()) == 0x1b || ch == 0x3 || ch == 'q' || ch == 'Q') { + update_led_state(it, inst_led_off); printf("\n"); if (nn != NULL) free(nn); free(vals); @@ -1754,12 +1774,14 @@ a1log *log /* verb, debug & error log */ /* Deal with a communications error */ } else if ((rv & inst_mask) == inst_coms_fail) { + update_led_state(it, inst_led_row_fail); if (cap2 & inst2_no_feedback) bad_beep(); empty_con_chars(); printf("\nStrip read failed due to communication problem.\n"); printf("Hit Esc or 'q' to give up, any other key to retry:%s",fl_end); do_fflush(); if ((ch = next_con_char()) == 0x1b || ch == 0x3 || ch == 'q' || ch == 'Q') { + update_led_state(it, inst_led_off); printf("\n"); if (nn != NULL) free(nn); free(vals); @@ -1798,12 +1820,14 @@ a1log *log /* verb, debug & error log */ /* Some other error. Treat it as fatal */ } else { + update_led_state(it, inst_led_row_fail); if (cap2 & inst2_no_feedback) bad_beep(); printf("\nStrip read failed due unexpected error :'%s' (%s)\n", it->inst_interp_error(it, rv), it->interp_error(it, rv)); printf("Hit Esc or 'q' to give up, any other key to retry:%s",fl_end); do_fflush(); if ((ch = next_con_char()) == 0x1b || ch == 0x3 || ch == 'q' || ch == 'Q') { + update_led_state(it, inst_led_off); printf("\n"); if (nn != NULL) free(nn); free(vals); @@ -1953,6 +1977,7 @@ a1log *log /* verb, debug & error log */ return -1; } if (ch != 0x0d && ch != 0x0a) { /* !(CR or LF) */ + update_led_state(it, inst_led_row_fail); printf("\n"); continue; /* Try again */ } @@ -1976,6 +2001,7 @@ a1log *log /* verb, debug & error log */ printf("\nThere is at least one patch with an very unexpected response! (DeltaE %f)\n",werror); printf("Hit Return to use it anyway, any other key to retry, Esc or 'q' to give up:%s",fl_end); do_fflush(); if ((ch = next_con_char()) == 0x1b || ch == 0x3 || ch == 'q' || ch == 'Q') { + update_led_state(it, inst_led_off); printf("\n"); if (nn != NULL) free(nn); free(vals); @@ -1985,6 +2011,7 @@ a1log *log /* verb, debug & error log */ return -1; } if (ch != 0x0d && ch != 0x0a) { /* !Cr */ + update_led_state(it, inst_led_row_fail); printf("\n"); continue; } @@ -1995,6 +2022,7 @@ a1log *log /* verb, debug & error log */ /* Must be OK - save the readings */ if (cap2 & inst2_no_feedback) good_beep(); + update_led_state(it, inst_led_row_success); printf(" Strip read OK"); if (boff != 0) printf(" (DTP51 offset fix of %d applied)",boff); @@ -2048,6 +2076,7 @@ a1log *log /* verb, debug & error log */ } /* Go around to read another row */ + update_led_state(it, inst_led_off); free(vals); /* -------------------------------------------------- */ @@ -2065,8 +2094,10 @@ a1log *log /* verb, debug & error log */ /* Do any needed calibration before the user places the instrument on a desired spot */ if (it->needs_calibration(it) & inst_calt_n_dfrble_mask) { + update_led_state(it, inst_led_cal_wait); if ((rv = inst_handle_calibrate(it, inst_calt_needed, inst_calc_none, NULL, NULL, 0)) != inst_ok) { + update_led_state(it, inst_led_off); printf("\nCalibration failed with error :'%s' (%s)\n", it->inst_interp_error(it, rv), it->interp_error(it, rv)); it->del(it); @@ -2074,6 +2105,7 @@ a1log *log /* verb, debug & error log */ free(pfname); return -1; } + update_led_state(it, inst_led_off); } /* Enable (switch or user) via uicallback trigger if possible */ @@ -2302,7 +2334,9 @@ a1log *log /* verb, debug & error log */ if (cap2 & inst2_no_feedback) bad_beep(); printf("\nSpot read failed because instruments needs calibration\n"); + update_led_state(it, inst_led_cal_wait); ev = inst_handle_calibrate(it, inst_calt_needed, inst_calc_none, NULL, NULL, 0); + update_led_state(it, inst_led_off); if (ev != inst_ok) { /* Abort or fatal error */ it->del(it); if (pfname != NULL) @@ -2312,12 +2346,14 @@ a1log *log /* verb, debug & error log */ continue; /* Deal with a misread */ } else if ((rv & inst_mask) == inst_misread) { + update_led_state(it, inst_led_row_fail); if (cap2 & inst2_no_feedback) bad_beep(); empty_con_chars(); printf("\nStrip read failed due to misread (%s)\n",it->interp_error(it, rv)); printf("Hit Esc or 'q' to give up, any other key to retry:%s",fl_end); do_fflush(); if ((ch = next_con_char()) == 0x1b || ch == 0x3 || ch == 'q' || ch == 'Q') { + update_led_state(it, inst_led_off); printf("\n"); it->del(it); if (pfname != NULL) @@ -2328,12 +2364,14 @@ a1log *log /* verb, debug & error log */ continue; /* Deal with a communications error */ } else if ((rv & inst_mask) == inst_coms_fail) { + update_led_state(it, inst_led_row_fail); if (cap2 & inst2_no_feedback) bad_beep(); empty_con_chars(); printf("\nStrip read failed due to communication problem.\n"); printf("Hit Esc or 'q' to give up, any other key to retry:%s",fl_end); do_fflush(); if ((ch = next_con_char()) == 0x1b || ch == 0x3 || ch == 'q' || ch == 'Q') { + update_led_state(it, inst_led_off); printf("\n"); it->del(it); if (pfname != NULL) @@ -2369,12 +2407,14 @@ a1log *log /* verb, debug & error log */ } else { /* Some other error. Treat it as fatal */ + update_led_state(it, inst_led_row_fail); if (cap2 & inst2_no_feedback) bad_beep(); printf("\nPatch read failed due unexpected error :'%s' (%s)\n", it->inst_interp_error(it, rv), it->interp_error(it, rv)); printf("Hit Esc or 'q' to give up, any other key to retry:%s",fl_end); do_fflush(); if ((ch = next_con_char()) == 0x1b || ch == 0x3 || ch == 'q' || ch == 'Q') { + update_led_state(it, inst_led_off); printf("\n"); it->del(it); if (pfname != NULL) @@ -2390,6 +2430,7 @@ a1log *log /* verb, debug & error log */ empty_con_chars(); printf("\nAbort ? - Are you sure ? [y/n]:%s",fl_end); do_fflush(); if ((ch = next_con_char()) == 'y' || ch == 'Y') { + update_led_state(it, inst_led_off); printf("\n"); it->del(it); if (pfname != NULL) @@ -2401,7 +2442,9 @@ a1log *log /* verb, debug & error log */ } else if (ch == 'k') { inst_code ev; + update_led_state(it, inst_led_cal_wait); ev = inst_handle_calibrate(it, inst_calt_available, inst_calc_none, NULL, NULL, 0); + update_led_state(it, inst_led_off); if (ev != inst_ok) { /* Abort or fatal error */ it->del(it); if (pfname != NULL) @@ -2556,6 +2599,7 @@ a1log *log /* verb, debug & error log */ /* -------------------------------------------------- */ /* clean up */ + update_led_state(it, inst_led_off); if (it != NULL) it->del(it); if (pfname != NULL) @@ -2621,7 +2665,7 @@ usage() { } fprintf(stderr," -T ratio Modify strip patch consistency tolerance by ratio\n"); fprintf(stderr," -S Suppress wrong strip & unexpected value warnings\n"); -// fprintf(stderr," -Y U Test i1pro2 UV measurement mode\n"); + fprintf(stderr," -Y l Enable i1Pro 2 visual LED feedback\n"); fprintf(stderr," -W n|h|x Override serial port flow control: n = none, h = HW, x = Xon/Xoff\n"); #ifndef SALONEINSTLIB fprintf(stderr," -P Plot spectral if patch by patch\n"); @@ -2902,7 +2946,9 @@ int main(int argc, char *argv[]) { if (na == NULL) usage(); /* "Parameter expected after -Y" */ - { + if (na[0] == 'l' || na[0] == 'L') { + g_use_leds = 1; + } else { usage(); /* "-Y parameter '%c' not recognised",na[0] */ } } diff --git a/spectro/i1pro.c b/spectro/i1pro.c index ca7fca4..c1c814a 100644 --- a/spectro/i1pro.c +++ b/spectro/i1pro.c @@ -170,9 +170,14 @@ i1pro_determine_capabilities(i1pro *p) { | inst2_user_switch_trig | inst2_bidi_scan | inst2_has_scan_toll - | inst2_no_feedback ; + if (p->dtype == instI1Pro2) { + p->cap2 |= inst2_has_leds; + } else { + p->cap2 |= inst2_no_feedback; + } + if (p->m != NULL) { i1proimp *m = (i1proimp *)p->m; i1pro_state *s = &m->ms[m->mmode]; @@ -943,6 +948,17 @@ i1pro_get_set_opt(inst *pp, inst_opt_type m, ...) { } } +/* Set device indicator LED status */ +static inst_code +i1pro_set_led_state(inst *pp, inst_led_state state) { + i1pro *p = (i1pro *)pp; + + if (p->dtype != instI1Pro2) + return inst_unsupported; + + return i1pro_interp_code(p, i1pro_imp_set_led_state(p, state)); +} + /* Destroy ourselves */ static void i1pro_del(inst *pp) { @@ -984,6 +1000,7 @@ extern i1pro *new_i1pro(icoms *icom, instType dtype) { p->meas_delay = i1pro_meas_delay; p->white_change = i1pro_white_change; p->interp_error = i1pro_interp_error; + p->set_led_state = i1pro_set_led_state; p->del = i1pro_del; p->icom = icom; diff --git a/spectro/i1pro_imp.c b/spectro/i1pro_imp.c index 7bb0b32..993161e 100644 --- a/spectro/i1pro_imp.c +++ b/spectro/i1pro_imp.c @@ -443,6 +443,21 @@ i1pro_stop_threads(i1pro *p) { m->trig_thread = NULL; a1logd(p->log,5,"i1pro trigger thread terminated\n"); } + + /* Stop the LED status feedback thread if running */ + if (m->led_th != NULL) { + m->led_th_run = 0; + if (m->led_th->wait != NULL) + m->led_th->wait(m->led_th); + if (m->led_th->del != NULL) + m->led_th->del(m->led_th); + m->led_th = NULL; + a1logd(p->log,5,"i1pro led thread terminated\n"); + } + if (m->led_th_init) { + amutex_del(m->led_lock); + m->led_th_init = 0; + } } /* Stop any pending I/O, shut down the instrument hardware, and close the */ @@ -12407,11 +12422,17 @@ i1pro2_stop_ruler(void *pp, int parm) { static int i1pro2_indLEDseq(void *pp, unsigned char *buf, int size) { i1pro *p = (i1pro *)pp; - i1proimp *m = (i1proimp *)p->m; + i1proimp *m; int rwbytes; /* Data bytes written */ unsigned char pbuf[4]; /* Number of bytes being send */ int se, rv = I1PRO_OK; + if (p == NULL || p->m == NULL || p->icom == NULL || !p->icom->is_open + || p->icom->usb_control == NULL || p->icom->usb_write == NULL) { + return I1PRO_INT_NO_COMS; + } + m = (i1proimp *)p->m; + int2buf(pbuf, size); a1logd(p->log,2,"i1pro2_indLEDseq: length %d bytes\n", size); @@ -12465,6 +12486,173 @@ i1pro2_indLEDoff(void *pp) { return rv; } +/* Set indicator LEDs to a solid color mask (or off if mask is 0) */ +static int +i1pro2_indLEDset(void *pp, unsigned char mask) { + i1pro *p = (i1pro *)pp; + int rv = I1PRO_OK; + unsigned char seq[] = { + 0x00, 0x00, 0x00, 0x01, + + 0x00, 0x00, 0x00, 0x0a, + 0xff, 0xff, 0xff, 0xff, + 0x00, 0x36, 0x40, + 0x00, 0x00, 0x01 + }; + + if (mask == 0) + return i1pro2_indLEDoff(pp); + + seq[12] = mask; + + a1logd(p->log,3,"i1pro2_indLEDset: called with mask 0x%02x\n", mask); + rv = i1pro2_indLEDseq(p, seq, sizeof(seq)); + a1logd(p->log,3,"i1pro2_indLEDset: returning ICOM err 0x%x\n", rv); + + return rv; +} + +/* Helper to sleep up to ms while checking if state changed or thread stopping */ +static void +led_sleep_ms(i1proimp *m, inst_led_state state, int ms) { + int elapsed = 0; + while (m->led_th_run && elapsed < ms) { + inst_led_state cur; + amutex_lock(m->led_lock); + cur = m->current_led_state; + amutex_unlock(m->led_lock); + if (cur != state) + break; + msec_sleep(20); + elapsed += 20; + } +} + +/* Worker thread for asynchronous LED pulsing / patterns */ +static int +i1pro2_led_thread(void *context) { + i1pro *p = (i1pro *)context; + i1proimp *m; + int phase = 0; + int i; + + if (p == NULL || p->m == NULL) + return 0; + m = (i1proimp *)p->m; + + a1logd(p->log, 3, "i1pro2_led_thread started\n"); + + while (m->led_th_run) { + inst_led_state state; + + amutex_lock(m->led_lock); + state = m->current_led_state; + amutex_unlock(m->led_lock); + + switch (state) { + case inst_led_cal_wait: + /* White flashing: 500ms ON / 500ms OFF (1.0 Hz) */ + if (phase % 2 == 0) + i1pro2_indLEDset(p, 0x3F); /* White = both Red+Green+Blue */ + else + i1pro2_indLEDoff(p); + led_sleep_ms(m, inst_led_cal_wait, 500); + phase++; + break; + + case inst_led_row_ready: + /* Blue pulsing: 300ms ON / 700ms OFF (1.0 Hz) */ + i1pro2_indLEDset(p, 0x24); /* Blue = both Left & Right Blue */ + led_sleep_ms(m, inst_led_row_ready, 300); + i1pro2_indLEDoff(p); + led_sleep_ms(m, inst_led_row_ready, 700); + phase = 0; + break; + + case inst_led_row_fail: + /* Red rapid strobe: 3 short bursts (100ms ON / 100ms OFF, 600ms total) */ + for (i = 0; i < 3 && m->led_th_run; i++) { + i1pro2_indLEDset(p, 0x09); /* Red = both Left & Right Red */ + msec_sleep(100); + i1pro2_indLEDoff(p); + msec_sleep(100); + } + /* Revert to off until next command */ + amutex_lock(m->led_lock); + if (m->current_led_state == inst_led_row_fail) + m->current_led_state = inst_led_off; + amutex_unlock(m->led_lock); + phase = 0; + break; + + case inst_led_row_success: + /* Green pulse: Solid confirmation illumination for 400ms */ + i1pro2_indLEDset(p, 0x12); /* Green = both Left & Right Green */ + msec_sleep(400); + i1pro2_indLEDoff(p); + + /* Revert to off until next command */ + amutex_lock(m->led_lock); + if (m->current_led_state == inst_led_row_success) + m->current_led_state = inst_led_off; + amutex_unlock(m->led_lock); + phase = 0; + break; + + case inst_led_off: + default: + i1pro2_indLEDoff(p); + led_sleep_ms(m, inst_led_off, 100); + phase = 0; + break; + } + } + + /* Extinguish LEDs upon thread exit if port is still open */ + if (p->icom != NULL && p->icom->is_open) + i1pro2_indLEDoff(p); + + a1logd(p->log, 3, "i1pro2_led_thread exiting\n"); + return 0; +} + +/* Set indicator LED status */ +i1pro_code +i1pro_imp_set_led_state(i1pro *p, inst_led_state state) { + i1proimp *m; + + if (p == NULL || p->m == NULL) + return I1PRO_INT_NO_COMS; + m = (i1proimp *)p->m; + + if (p->dtype != instI1Pro2) + return I1PRO_UNSUPPORTED; + + if (!m->led_th_init) { + amutex_init(m->led_lock); + m->led_th_init = 1; + } + + amutex_lock(m->led_lock); + m->current_led_state = state; + amutex_unlock(m->led_lock); + + /* If thread is not yet running and state is not off, start worker thread */ + if (m->led_th == NULL && state != inst_led_off) { + m->led_th_run = 1; + if ((m->led_th = new_athread(i1pro2_led_thread, (void *)p)) == NULL) { + a1logd(p->log, 1, "i1pro_imp_set_led_state: new_athread failed\n"); + return I1PRO_INT_THREADFAILED; + } + } else if (state == inst_led_off && m->led_th == NULL) { + /* Directly extinguish LEDs if port is open */ + if (p->icom != NULL && p->icom->is_open) + i1pro2_indLEDoff(p); + } + + return I1PRO_OK; +} + #ifdef NEVER // ~~99 play with LED settings diff --git a/spectro/i1pro_imp.h b/spectro/i1pro_imp.h index e06f094..5b14a62 100644 --- a/spectro/i1pro_imp.h +++ b/spectro/i1pro_imp.h @@ -340,6 +340,13 @@ struct _i1proimp { volatile double whitestamp; /* meas_delay() white timestamp */ volatile double trigstamp; /* meas_delay() trigger timestamp */ + /* Visual LED feedback thread management */ + athread *led_th; + int led_th_run; + int led_th_init; + amutex led_lock; + inst_led_state current_led_state; + }; typedef struct _i1proimp i1proimp; /* Add an implementation structure */ @@ -535,6 +542,8 @@ i1pro_code i1pro_set_stdres(i1pro *p); /* Modify the scan consistency tollerance */ i1pro_code i1pro_set_scan_toll(i1pro *p, double toll_ratio); +/* Set device indicator LED status */ +i1pro_code i1pro_imp_set_led_state(i1pro *p, inst_led_state state); /* Update the single remission calibration and instrument usage log */ i1pro_code i1pro_update_log(i1pro *p); diff --git a/spectro/inst.c b/spectro/inst.c index 109351b..7c981c0 100644 --- a/spectro/inst.c +++ b/spectro/inst.c @@ -589,6 +589,11 @@ static inst_config config_enum(inst *p, int ec) { return inst_conf_unknown; } +/* Set device indicator LED status (default implementation) */ +static inst_code set_led_state(inst *p, inst_led_state state) { + return inst_unsupported; +} + /* ---------------------------------------------- */ /* Delete things set/done by new_inst() */ @@ -824,6 +829,8 @@ void *cntx /* Context for callback */ p->last_scomerr = last_scomerr; if (p->config_enum == NULL) p->config_enum = config_enum; + if (p->set_led_state == NULL) + p->set_led_state = set_led_state; /* Set the provided user interaction callback */ p->set_uicallback(p, uicallback, cntx); diff --git a/spectro/inst.h b/spectro/inst.h index 111a4f0..e613f0f 100644 --- a/spectro/inst.h +++ b/spectro/inst.h @@ -371,6 +371,9 @@ typedef enum { } inst2_capability; +/* Capability alias for instrument indicator LED status feedback */ +#define INST_CAP_LED_STATUS inst2_has_leds + /* Instrument capabilities 3 (room for expansion) */ /* (Available capabilities may be mode dependent) */ typedef enum { @@ -558,6 +561,15 @@ typedef enum { } inst_opt_type; +/* Generic Instrument LED Status States */ +typedef enum { + inst_led_off = 0, + inst_led_cal_wait = 1, /* Flashing White */ + inst_led_row_ready = 2, /* Flashing Blue */ + inst_led_row_fail = 3, /* Flashing Red */ + inst_led_row_success = 4 /* Solid/Flash Green */ +} inst_led_state; + /* Optional manufacturers instrument filter fitted to instrument (for inst_opt_set_filter) */ /* These could be physical (i.e. Spectrolino, i1Pro3), or they could be virtual (i.e. i1Pro3) */ typedef enum { @@ -1170,6 +1182,9 @@ typedef struct _inst_meascondsel { /* (This is used for deciding fallback/retry strategies) */ \ int (*last_scomerr)(struct _inst *p); \ \ + /* Set device indicator LED status (if supported) */ \ + inst_code (*set_led_state)(struct _inst *p, inst_led_state state); \ + \ /* Destroy ourselves */ \ void (*del)(struct _inst *p); \ -- 2.39.5