cvtRecord.c 23.4 KB
Newer Older
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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <epicsMessageQueue.h>
#include <epicsThread.h>
#include <epicsStdlib.h>

#include <registryFunction.h>

#include <alarm.h>
#include <dbAccess.h>
#include <dbStaticLib.h>
#include <dbEvent.h>
#include <dbFldTypes.h>
#include <devSup.h>
#include <errMdef.h>
#include <errlog.h>
#include <special.h>
#include <recSup.h>
#include <recGbl.h>
#define GEN_SIZE_OFFSET
#include <cvtRecord.h>
#undef  GEN_SIZE_OFFSET
#include <menuIvoa.h>

#include <epicsExport.h>

#include <csmbase.h>

#include "menuCvtMethod.h"
#include "menuCvtInitState.h"

/* error message macros */

#define genmsg(sevr,name,msg,args...)\
    errlogSevPrintf(sevr,"%s(%s): " msg "\n", __FUNCTION__, name, ## args)
#define nerrmsg(name,msg,args...) genmsg(errlogFatal,name,msg, ## args)
#define errmsg(msg,args...) genmsg(errlogFatal,pcvt->name,msg, ## args)

/* standard EPICS record support stuff */

/* Create RSET - Record Support Entry Table*/
#define report NULL
static long initialize();
static long init_record();
static long process();
static long special();

#define get_value NULL
#define cvt_dbaddr NULL
#define get_array_info NULL
#define put_array_info NULL
static long get_units();
static long get_precision();

#define get_enum_str NULL
#define get_enum_strs NULL
#define put_enum_str NULL
static long get_graphic_double();
static long get_control_double();
static long get_alarm_double();

struct rset cvtRSET = {
    RSETNUMBER,
    report,
    initialize,
    init_record,
    process,
    special,
    get_value,
    cvt_dbaddr,
    get_array_info,
    put_array_info,
    get_units,
    get_precision,
    get_enum_str,
    get_enum_strs,
    put_enum_str,
    get_graphic_double,
    get_control_double,
    get_alarm_double
};

epicsExportAddress(rset,cvtRSET);

/*
 * General Remarks
 * ===============
 * 
 * This record type supports conversion of one or two floating point values
 * into one resulting floating point value. The field METH specifies the kind
 * of conversion: LINEAR, via a custom SUBROUTINE, or via one- (1D) or
 * two-dimensional (2D) conversion TABLE. More specifically, if METH is
 * 
 * menuCvtMethodLinear:
 *     VAL = XSLO * X + YSLO * Y + VOFF
 * 
 * menuCvtMethodSubroutine:
 *     SPEC should be the name of a global subroutine. CSUB is set to the
 *     address of the subroutine.
 *     VAL = CSUB(X, Y, &DPVT)
 *
 * menuCvtMethod1DTable:
 * menuCvtMethod1DTableInverted:
 * menuCvtMethod2DTable:
 *     Conversion uses the csm module. SPEC should be the filename
 *     of the table. CSUB is set to the csm_function handle returned by csm.
 *     VAL = csm_y(CSUB, X)         for one-dimensional tables
 *     VAL = csm_x(CSUB, Y)         for one-dimensional inverted tables
 *     VAL = csm_z(CSUB, X, Y)      two-dimensional tables
 */

/*
 * If METH==menuCvtMethodSubroutine, then SPEC should be the name of a
 * conversion subroutine with the following type. First two args are
 * INPX and INPY, the 3rd arg is a pointer to the record's DPVT field.
 * The subroutine may allocate a private structure and store its address
 * in the supplied pointer in order to store data between separate calls.
 */
typedef double cvt_subroutine(double,double,void**);

/* Values for field DRTY (dirty bits) */
#define DRTY_NONE 0x00
#define DRTY_METH 0x01
#define DRTY_SPEC 0x02
#define DRTY_TDIR 0x04
#define DRTY_ISTA 0x08
129
130
#define DRTY_X    0x10
#define DRTY_Y    0x20
131

132
static void checkAlarms(struct cvtRecord *pcvt);
133
static long readInputs(struct cvtRecord *pcvt);
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
static long convert(struct cvtRecord *pcvt);
static void monitor(struct cvtRecord *pcvt);
static long checkInit(struct cvtRecord *pcvt);
static long reinitConversion(struct cvtRecord *pcvt);
static long initConversion(const char *name, const char *tdir,
    menuCvtMethod meth, const char *spec, void **psub);

static epicsMessageQueueId initConversionQ;

/*
 * Invariants:
 *
 *  o METH and SPEC specify the currently active conversion.
 *  o Writing any non-zero value to INIT re-initializes
 *    conversion with the values of NMET and NSPE at that moment.
 *  o Each record is enqueued in initConversionQ at most once.
 *  o New conversion values are written back if and only if
 *    initConversion succeeds and state!=Again.
 *    => If conversion re-init either fails (for whatever reason)
 *    or must be repeated (state==Again), values are *not* written back.
 *  o NMET and NSPE are written by record support only once
 *    during record initialization (init_record).
 */

static long init_record(struct cvtRecord *pcvt, int pass)
{
    void *sub;

    if (pass == 0) {
        /* set new conversion parameters equal to to configured ones */
        pcvt->nmet = pcvt->meth;
        strncpy(pcvt->nspe, pcvt->spec, MAX_STRING_SIZE);
        strncpy(pcvt->ntdi, pcvt->tdir, MAX_STRING_SIZE);
        return 0;
    }

    /* initialize input links */
171
172
173
174
175
176
177
178
179
180
181
182
    if (pcvt->inpx.type == CONSTANT) {
        recGblInitConstantLink(&pcvt->inpx, DBF_DOUBLE, &pcvt->x);
    }
    if (pcvt->inpy.type == CONSTANT) {
        recGblInitConstantLink(&pcvt->inpy, DBF_DOUBLE, &pcvt->y);
    }
    if (pcvt->iaml.type == CONSTANT) {
        recGblInitConstantLink(&pcvt->iaml, DBF_UCHAR, &pcvt->iaom);
    }
    if (pcvt->iavl.type == CONSTANT) {
        recGblInitConstantLink(&pcvt->iavl, DBF_DOUBLE, &pcvt->iaov);
    }
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

    /* try to initialize conversion as specified */
    if (initConversion(pcvt->name, pcvt->ntdi, pcvt->nmet, pcvt->nspe, &sub)) {
        pcvt->ista = menuCvtInitStateError;
        pcvt->drty |= DRTY_ISTA;
        return -1;
    }
    pcvt->csub = sub;
    return 0;
}

static long process(struct cvtRecord *pcvt)
{
    long status = 0;

    pcvt->pact = TRUE;
    status = dbGetLink(&pcvt->inil, DBR_UCHAR, &pcvt->init, 0, 0);
    pcvt->pact = FALSE;

    if (status) {
        recGblSetSevr(pcvt, LINK_ALARM, INVALID_ALARM);
        goto error;
    }
    if (checkInit(pcvt)) {
        recGblSetSevr(pcvt, SOFT_ALARM, INVALID_ALARM);
        recGblResetAlarms(pcvt);
        pcvt->pact = TRUE;
        return -1;
    }

    pcvt->pact = TRUE;
214
    status = readInputs(pcvt);
215
216
217
218
219
220
    pcvt->pact = FALSE;

    if (status) {
        goto error;
    }

221
    status = convert(pcvt);
222
223

error:
224
    checkAlarms(pcvt);
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

    pcvt->pact = TRUE;
    if (pcvt->nsev < INVALID_ALARM) {
        status = dbPutLink(&pcvt->out, DBR_DOUBLE, &pcvt->val, 1);
    }
    else {
        switch (pcvt->ivoa) {
        case (menuIvoaSet_output_to_IVOV):
            pcvt->val = pcvt->ivov;
            /* note: this falls through to the case below */
        case (menuIvoaContinue_normally):
            status = dbPutLink(&pcvt->out, DBR_DOUBLE, &pcvt->val, 1);
        case (menuIvoaDon_t_drive_outputs):
            break;
        default:
            status = S_db_badField;
            errmsg("internal error: Illegal value in IVOA field");
            recGblSetSevr(pcvt, SOFT_ALARM, INVALID_ALARM);
            recGblResetAlarms(pcvt);
            return status;
        }
    }

    recGblGetTimeStamp(pcvt);
    monitor(pcvt);
    recGblFwdLink(pcvt);

    pcvt->pact = FALSE;

    return status;
}

static long checkInit(struct cvtRecord *pcvt)
{
    if (!pcvt->init) {
        return 0;
    }
    pcvt->init = 0;
    switch (pcvt->ista) {
    case menuCvtInitStateDone:
    case menuCvtInitStateError:
        pcvt->ista = menuCvtInitStateInProgress;
        pcvt->drty |= DRTY_ISTA;
        if (reinitConversion(pcvt)) {
            /* this is fatal */
            pcvt->ista = menuCvtInitStateError;
            pcvt->pact = TRUE;
            return -1;
        }
        break;
    case menuCvtInitStateInProgress:
        pcvt->ista = menuCvtInitStateAgain;
        pcvt->drty |= DRTY_ISTA;
        break;
    case menuCvtInitStateAgain:
        break;
    default:
        errmsg("internal error: illegal value %d in field ISTA", pcvt->ista);
        pcvt->pact = TRUE;
        return -1;
    }
286
    checkAlarms(pcvt);
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
    monitor(pcvt);
    return 0;
}

static long special(struct dbAddr *paddr, int after)
{
    struct cvtRecord *pcvt = (struct cvtRecord *)(paddr->precord);
    int fieldIndex = dbGetFieldIndex(paddr);

    if (!after) return 0;
    if (paddr->special==SPC_MOD) {
        switch (fieldIndex) {
        case cvtRecordINIT:
            if (checkInit(pcvt)) {
                return -1;
            }
            return 0;
        default:
            errmsg("internal error: special called for wrong field");
            pcvt->pact = TRUE;
            return -1;
        }
    }
    errmsg("internal error: special called with wrong special type");
    pcvt->pact = TRUE;
    return -1;
}

static long get_units(struct dbAddr *paddr, char *units)
{
    struct cvtRecord *pcvt = (struct cvtRecord *)paddr->precord;

    strncpy(units, pcvt->egu, DB_UNITS_SIZE);
    return 0;
}

static long get_precision(struct dbAddr *paddr, long *precision)
{
    struct cvtRecord *pcvt = (struct cvtRecord *)paddr->precord;
    int fieldIndex = dbGetFieldIndex(paddr);

    switch (fieldIndex) {
    case cvtRecordVAL:
        *precision = pcvt->prec;
        break;
    default:
        recGblGetPrec(paddr, precision);
    }
    return 0;
}

static long get_graphic_double(struct dbAddr *paddr, struct dbr_grDouble *pgd)
{
    struct cvtRecord *pcvt = (struct cvtRecord *)paddr->precord;
    int fieldIndex = dbGetFieldIndex(paddr);

    switch (fieldIndex) {
    case cvtRecordVAL:
    case cvtRecordHIHI:
    case cvtRecordHIGH:
    case cvtRecordLOW:
    case cvtRecordLOLO:
        pgd->upper_disp_limit = pcvt->hopr;
        pgd->lower_disp_limit = pcvt->lopr;
        break;
    default:
        recGblGetGraphicDouble(paddr, pgd);
    }
    return 0;
}

static long get_control_double(struct dbAddr *paddr, struct dbr_ctrlDouble *pcd)
{
    struct cvtRecord *pcvt = (struct cvtRecord *)paddr->precord;
    int fieldIndex = dbGetFieldIndex(paddr);

    switch (fieldIndex) {
    case cvtRecordVAL:
    case cvtRecordHIHI:
    case cvtRecordHIGH:
    case cvtRecordLOW:
    case cvtRecordLOLO:
        pcd->upper_ctrl_limit = pcvt->drvh;
        pcd->lower_ctrl_limit = pcvt->drvl;
        break;
    default:
        recGblGetControlDouble(paddr, pcd);
    }
    return 0;
}

static long get_alarm_double(struct dbAddr *paddr, struct dbr_alDouble *pad)
{
    struct cvtRecord *pcvt = (struct cvtRecord *)paddr->precord;
    int fieldIndex = dbGetFieldIndex(paddr);

    if (fieldIndex == cvtRecordVAL) {
        pad->upper_alarm_limit = pcvt->hihi;
        pad->upper_warning_limit = pcvt->high;
        pad->lower_warning_limit = pcvt->low;
        pad->lower_alarm_limit = pcvt->lolo;
    }
    else
        recGblGetAlarmDouble(paddr, pad);
    return 0;
}

394
static void checkAlarms(struct cvtRecord *pcvt)
395
396
{
    double hyst, lalm, val;
397
398
    double hihi, high, low, lolo;
    epicsEnum16 hhsv, llsv, hsv, lsv;
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

    if (pcvt->udf == TRUE) {
        recGblSetSevr(pcvt, UDF_ALARM, INVALID_ALARM);
        return;
    }

    if (pcvt->ista == menuCvtInitStateInProgress
        || pcvt->ista == menuCvtInitStateAgain) {
        recGblSetSevr(pcvt, SOFT_ALARM, MINOR_ALARM);
    }
    else if (pcvt->ista == menuCvtInitStateError) {
        recGblSetSevr(pcvt, SOFT_ALARM, MAJOR_ALARM);
    }

    hihi = pcvt->hihi;
    lolo = pcvt->lolo;
    high = pcvt->high;
    low = pcvt->low;
    hhsv = pcvt->hhsv;
    llsv = pcvt->llsv;
    hsv = pcvt->hsv;
    lsv = pcvt->lsv;
    val = pcvt->val;
    hyst = pcvt->hyst;
    lalm = pcvt->lalm;

    /* alarm condition hihi */
    if (hhsv && (val >= hihi || ((lalm == hihi) && (val >= hihi - hyst)))) {
        if (recGblSetSevr(pcvt, HIHI_ALARM, pcvt->hhsv))
            pcvt->lalm = hihi;
        return;
    }

    /* alarm condition lolo */
    if (llsv && (val <= lolo || ((lalm == lolo) && (val <= lolo + hyst)))) {
        if (recGblSetSevr(pcvt, LOLO_ALARM, pcvt->llsv))
            pcvt->lalm = lolo;
        return;
    }

    /* alarm condition high */
    if (hsv && (val >= high || ((lalm == high) && (val >= high - hyst)))) {
        if (recGblSetSevr(pcvt, HIGH_ALARM, pcvt->hsv))
            pcvt->lalm = high;
        return;
    }

    /* alarm condition low */
    if (lsv && (val <= low || ((lalm == low) && (val <= low + hyst)))) {
        if (recGblSetSevr(pcvt, LOW_ALARM, pcvt->lsv))
            pcvt->lalm = low;
        return;
    }

    /* we get here only if val is out of alarm by at least hyst */
    pcvt->lalm = val;
}

457
static long readInputs(struct cvtRecord *pcvt)
458
459
460
461
462
463
464
465
466
467
468
{
    long status;
    double old;

    old = pcvt->x;
    status = dbGetLink(&pcvt->inpx, DBR_DOUBLE, &pcvt->x, 0, 0);
    if (status) {
        recGblSetSevr(pcvt, LINK_ALARM, INVALID_ALARM);
        return status;
    }
    if (old != pcvt->x) {
469
        pcvt->drty |= DRTY_X;
470
471
472
473
474
475
476
477
478
    }

    old = pcvt->y;
    status = dbGetLink(&pcvt->inpy, DBR_DOUBLE, &pcvt->y, 0, 0);
    if (status) {
        recGblSetSevr(pcvt, LINK_ALARM, INVALID_ALARM);
        return status;
    }
    if (old != pcvt->y) {
479
        pcvt->drty |= DRTY_Y;
480
481
    }

482
483
484
485
    status = dbGetLink(&pcvt->iaml, DBR_ENUM, &pcvt->iaom, 0, 0);
    if (status) {
        recGblSetSevr(pcvt, LINK_ALARM, INVALID_ALARM);
        return status;
486
    }
487
488
489

    if (pcvt->iaom) {
        status = dbGetLink(&pcvt->iavl, DBR_DOUBLE, &pcvt->iaov, 0, 0);
490
491
492
493
        if (status) {
            recGblSetSevr(pcvt, LINK_ALARM, INVALID_ALARM);
            return status;
        }
494
        pcvt->val = pcvt->iaov;
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
    }

    return 0;
}

static long initConversion(
    const char *name, const char *tdir,
    menuCvtMethod meth, const char *spec, void **psub)
{
    *psub = 0;
    switch (meth) {
        case menuCvtMethodLinear:
        {
            break;
        }
        case menuCvtMethodSubroutine:
        {
            REGISTRYFUNCTION csub;

            if(spec[0]==0) {
                nerrmsg(name, "configuration error: SPEC not specified");
                return -1;
            }
            csub = registryFunctionFind(spec);
            if (!csub) {
                nerrmsg(name, "configuration error: "
                    "SPEC is not the name of a registered subroutine");
                return -1;
            }
            *psub = csub;
            break;
        }
        case menuCvtMethod1DTable:
        case menuCvtMethod1DTableInverted:
        {
            csm_function *csub;
            char temp[2*MAX_STRING_SIZE];

            csub = csm_new_function();
            if (!csub) {
                nerrmsg(name, "csm_new_function failed");
                return -1;
            }
            sprintf(temp, "%s/%s", tdir, spec);
            if (!csm_read_1d_table(temp, csub)) {
                nerrmsg(name, "configuration error: "
                    "File %s is not a valid 1-parameter table", temp);
                csm_free(csub);
                return -1;
            }
            *psub = csub;
            break;
        }
        case menuCvtMethod2DTable:
        {
            csm_function *csub;
            char temp[2*MAX_STRING_SIZE];

            csub = csm_new_function();
            if (!csub) {
                nerrmsg(name, "csm_new_function failed");
                return -1;
            }
            sprintf(temp, "%s/%s", tdir, spec);
            if (!csm_read_2d_table(temp, csub)) {
                nerrmsg(name, "configuration error: "
                    "File %s is not a valid 2-parameter table", temp);
                csm_free(csub);
                return -1;
            }
            *psub = csub;
            break;
        }
    }
    return 0;
}

static long convert(struct cvtRecord *pcvt)
{
    double value;

576
577
578
579
580
581
582
    if (pcvt->iaom) {
        value = pcvt->iaov;
    } else {
        switch (pcvt->meth) {
            case menuCvtMethodLinear: {
                value = pcvt->x * pcvt->xslo + pcvt->y * pcvt->yslo + pcvt->voff;
                break;
583
            }
584
585
586
587
588
589
590
            case menuCvtMethodSubroutine: {
                cvt_subroutine *csub = (cvt_subroutine *)pcvt->csub;
                if (!csub) {
                    goto error;
                }
                value = csub(pcvt->x, pcvt->y, &pcvt->dpvt);
                break;
591
            }
592
593
594
595
596
597
598
            case menuCvtMethod1DTable: {
                csm_function *csub = (csm_function *)pcvt->csub;
                if (!csub) {
                    goto error;
                }
                value = csm_y(csub, pcvt->x);
                break;
599
            }
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
            case menuCvtMethod1DTableInverted: {
                csm_function *csub = (csm_function *)pcvt->csub;
                if (!csub) {
                    goto error;
                }
                value = csm_x(csub, pcvt->y);
                break;
            }
            case menuCvtMethod2DTable: {
                csm_function *csub = (csm_function *)pcvt->csub;
                if (!csub) {
                    goto error;
                }
                value = csm_z(csub, pcvt->x, pcvt->y);
                break;
            }
            default: {
                errmsg("internal error: METH is not a member of menuCvtMethod");
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
                goto error;
            }
        }
    }

    /* check drive limits */
    if (pcvt->drvh > pcvt->drvl) {
        if (value > pcvt->drvh)
            value = pcvt->drvh;
        else if (value < pcvt->drvl)
            value = pcvt->drvl;
    }

    pcvt->val = value;
    pcvt->udf = FALSE;
    return 0;

error:
    recGblSetSevr(pcvt, SOFT_ALARM, INVALID_ALARM);
    return -1;
}

static void monitor(struct cvtRecord *pcvt)
{
    unsigned short monitor_mask;
    double delta;

    monitor_mask = recGblResetAlarms(pcvt);
    /* check for value change */
    delta = pcvt->mlst - pcvt->val;
    if (delta < 0.0)
        delta = -delta;
    if (delta > pcvt->mdel) {
        /* post events for value change */
        monitor_mask |= DBE_VALUE;
        /* update last value monitored */
        pcvt->mlst = pcvt->val;
    }
    /* check for archive change */
    delta = pcvt->alst - pcvt->val;
    if (delta < 0.0)
        delta = -delta;
    if (delta > pcvt->adel) {
        /* post events on value field for archive change */
        monitor_mask |= DBE_LOG;
        /* update last archive value monitored */
        pcvt->alst = pcvt->val;
    }

    /* send out monitors connected to the value field */
    if (monitor_mask) {
        db_post_events(pcvt, &pcvt->val, monitor_mask);
    }
    if (pcvt->drty & DRTY_METH) {
        db_post_events(pcvt, &pcvt->meth, DBE_VALUE|DBE_LOG);
    }
    if (pcvt->drty & DRTY_SPEC) {
        db_post_events(pcvt, &pcvt->spec, DBE_VALUE|DBE_LOG);
    }
    if (pcvt->drty & DRTY_TDIR) {
        db_post_events(pcvt, &pcvt->tdir, DBE_VALUE|DBE_LOG);
    }
    if (pcvt->drty & DRTY_ISTA) {
        db_post_events(pcvt, &pcvt->ista, DBE_VALUE|DBE_LOG|DBE_ALARM);
    }
683
684
685
686
687
688
    if (pcvt->drty & DRTY_X) {
        db_post_events(pcvt, &pcvt->x, DBE_VALUE|DBE_LOG);
    }
    if (pcvt->drty & DRTY_Y) {
        db_post_events(pcvt, &pcvt->y, DBE_VALUE|DBE_LOG);
    }
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
    pcvt->drty = DRTY_NONE;
    return;
}

struct reinitMsg {
    struct cvtRecord *record;
    char spec[MAX_STRING_SIZE];
    char tdir[MAX_STRING_SIZE];
    menuCvtMethod meth;
};

#define REINIT_MSG_SIZE sizeof(struct reinitMsg)

static long reinitConversion(struct cvtRecord *pcvt)
{
    long qstatus;
    struct reinitMsg msg;

    msg.record = pcvt;
    msg.meth = pcvt->nmet;
    if (pcvt->nmet != menuCvtMethodLinear) {
        strncpy(msg.spec, pcvt->nspe, MAX_STRING_SIZE);
        strncpy(msg.tdir, pcvt->ntdi, MAX_STRING_SIZE);
    }
713
714
    qstatus = epicsMessageQueueSend(
        initConversionQ, (void*)&msg, REINIT_MSG_SIZE);
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
    if (qstatus == -1) {
        errmsg("internal error: msgQ overrun");
        return -1;
    }
    return 0;
}

static void initConversionTask(void* parm)
{
    int qstatus;
    void *sub;
    struct reinitMsg msg;
    struct cvtRecord *pcvt;
    long status;

    while (TRUE) {
731
732
        qstatus = epicsMessageQueueReceive(
            initConversionQ, (void*)&msg, REINIT_MSG_SIZE);
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
        if (qstatus == -1) {
            nerrmsg("", "msgQReceive failed");
            continue;
        }
        pcvt = msg.record;
        status = initConversion(pcvt->name, msg.tdir, msg.meth, msg.spec, &sub);
        dbScanLock((struct dbCommon *)pcvt);
        if (status && pcvt->ista != menuCvtInitStateAgain) {
            if (pcvt->ista != menuCvtInitStateError) {
                pcvt->ista = menuCvtInitStateError;
                pcvt->drty |= DRTY_ISTA;
            }
        }
        else {
            switch (pcvt->ista) {
            case menuCvtInitStateInProgress:
            case menuCvtInitStateError:
                pcvt->ista = menuCvtInitStateDone;
                pcvt->drty |= DRTY_ISTA;
                /* free old csub if it was a csm_function... */
                if (pcvt->meth == menuCvtMethod1DTable
                    || pcvt->meth == menuCvtMethod1DTableInverted
                    || pcvt->meth == menuCvtMethod2DTable) {
                    csm_function *csub = (csm_function *)pcvt->csub;
                    if (csub) {
                        /* check because it might have never been created */
                        csm_free(csub);
                    }
                }
                /* ...and write the new values back into the record */
                pcvt->meth = msg.meth;
                pcvt->drty |= DRTY_METH;
                strncpy(pcvt->spec, msg.spec, MAX_STRING_SIZE);
                pcvt->drty |= DRTY_SPEC;
                strncpy(pcvt->tdir, msg.tdir, MAX_STRING_SIZE);
                pcvt->drty |= DRTY_TDIR;
                pcvt->csub = sub;
                break;
            case menuCvtInitStateAgain:
                if (!status && sub && (
                    pcvt->meth == menuCvtMethod1DTable
                    || pcvt->meth == menuCvtMethod1DTableInverted
                    || pcvt->meth == menuCvtMethod2DTable)) {
                    csm_free((csm_function *)sub);
                }
                /* even if initConversion(...) above failed, we go here */
                if (reinitConversion(pcvt)) {
                    /* this is fatal */
                    pcvt->ista = menuCvtInitStateError;
                    pcvt->drty |= DRTY_ISTA;
                    pcvt->pact = TRUE;
                    break;
                }
                pcvt->ista = menuCvtInitStateInProgress;
                pcvt->drty |= DRTY_ISTA;
                break;
            case menuCvtInitStateDone:
                errmsg("internal error: unexpected "
                    "value <menuCvtInitStateDone> in field ISTA");
                pcvt->pact = TRUE;
                break;
            default:
                errmsg("internal error: ISTA is not a member of menuCvtMethod");
                pcvt->pact = TRUE;
            }
        }
799
        checkAlarms(pcvt);
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
        monitor(pcvt);
        dbScanUnlock((struct dbCommon *)pcvt);
    }
}

static int countCvtRecords(void)
{
    DBENTRY dbentry;
    extern DBBASE *pdbbase;
    int result = 0;
    long status;

    dbInitEntry(pdbbase, &dbentry);
    status = dbFindRecordType(&dbentry,"cvt");
    if (!status) {
        result = dbGetNRecords(&dbentry);
    }
    dbFinishEntry(&dbentry);
    return result;
}

static long initialize()
{
    epicsThreadId tid;

    if (!initConversionQ) {
        initConversionQ = epicsMessageQueueCreate(countCvtRecords(),
            REINIT_MSG_SIZE);
        if (!initConversionQ) {
            nerrmsg("", "msgQCreate failed");
            goto error;
        }
        tid = epicsThreadCreate("initCvt", epicsThreadPriorityLow, 20000,
            (EPICSTHREADFUNC)initConversionTask, 0);
        if (!tid) {
            nerrmsg("", "taskSpawn failed");
            goto error;
        }
    }
    return 0;

error:
    if (initConversionQ) epicsMessageQueueDestroy(initConversionQ);
    return -1;
}