cvtRecord.c 23.8 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
#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"

34
35
36
37
38
39
/* Warning: these must correspond exactly to the size property of the
   fields in the dbd file */
#define BDIR_SIZE 40
#define TDIR_SIZE 40
#define SPEC_SIZE 40

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
/* 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)

/*
 * 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
87
88
89
90
91
#define DRTY_BDIR 0x04
#define DRTY_TDIR 0x08
#define DRTY_ISTA 0x10
#define DRTY_X    0x20
#define DRTY_Y    0x40
92

93
static void checkAlarms(struct cvtRecord *pcvt);
94
static long readInputs(struct cvtRecord *pcvt);
95
96
97
98
static long convert(struct cvtRecord *pcvt);
static void monitor(struct cvtRecord *pcvt);
static long checkInit(struct cvtRecord *pcvt);
static long reinitConversion(struct cvtRecord *pcvt);
99
static long initConversion(const char *name, const char *bdir, const char *tdir,
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
    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).
 */

Franksen, Benjamin's avatar
Franksen, Benjamin committed
119
static long init_record(struct dbCommon *prec, int pass)
120
{
Franksen, Benjamin's avatar
Franksen, Benjamin committed
121
    struct cvtRecord *pcvt = (struct cvtRecord *)prec;
122
123
124
125
126
    void *sub;

    if (pass == 0) {
        /* set new conversion parameters equal to to configured ones */
        pcvt->nmet = pcvt->meth;
127
128
129
        strncpy(pcvt->nbdi, pcvt->bdir, BDIR_SIZE);
        strncpy(pcvt->ntdi, pcvt->tdir, TDIR_SIZE);
        strncpy(pcvt->nspe, pcvt->spec, SPEC_SIZE);
130
131
132
133
        return 0;
    }

    /* initialize input links */
134
135
136
137
138
139
140
141
142
143
144
145
    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);
    }
146
147

    /* try to initialize conversion as specified */
148
    if (initConversion(pcvt->name, pcvt->nbdi, pcvt->ntdi, pcvt->nmet, pcvt->nspe, &sub)) {
149
150
151
152
153
154
155
156
        pcvt->ista = menuCvtInitStateError;
        pcvt->drty |= DRTY_ISTA;
        return -1;
    }
    pcvt->csub = sub;
    return 0;
}

Franksen, Benjamin's avatar
Franksen, Benjamin committed
157
static long process(struct dbCommon *prec)
158
{
Franksen, Benjamin's avatar
Franksen, Benjamin committed
159
    struct cvtRecord *pcvt = (struct cvtRecord *)prec;
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
    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;
178
    status = readInputs(pcvt);
179
180
181
182
183
184
    pcvt->pact = FALSE;

    if (status) {
        goto error;
    }

185
    status = convert(pcvt);
186
187

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

    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;
    }
250
    checkAlarms(pcvt);
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
    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;
}

Franksen, Benjamin's avatar
Franksen, Benjamin committed
287
static long get_precision(const struct dbAddr *paddr, long *precision)
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
{
    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;
}

358
static void checkAlarms(struct cvtRecord *pcvt)
359
360
{
    double hyst, lalm, val;
361
362
    double hihi, high, low, lolo;
    epicsEnum16 hhsv, llsv, hsv, lsv;
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

    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;
}

421
static long readInputs(struct cvtRecord *pcvt)
422
423
424
425
426
427
428
429
430
431
432
{
    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) {
433
        pcvt->drty |= DRTY_X;
434
435
436
437
438
439
440
441
442
    }

    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) {
443
        pcvt->drty |= DRTY_Y;
444
445
    }

446
447
448
449
    status = dbGetLink(&pcvt->iaml, DBR_ENUM, &pcvt->iaom, 0, 0);
    if (status) {
        recGblSetSevr(pcvt, LINK_ALARM, INVALID_ALARM);
        return status;
450
    }
451
452
453

    if (pcvt->iaom) {
        status = dbGetLink(&pcvt->iavl, DBR_DOUBLE, &pcvt->iaov, 0, 0);
454
455
456
457
        if (status) {
            recGblSetSevr(pcvt, LINK_ALARM, INVALID_ALARM);
            return status;
        }
458
        pcvt->val = pcvt->iaov;
459
460
461
462
463
464
    }

    return 0;
}

static long initConversion(
465
    const char *name, const char *bdir, const char *tdir,
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
    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;
495
            char temp[BDIR_SIZE+TDIR_SIZE+SPEC_SIZE+2];
496
497
498
499
500
501

            csub = csm_new_function();
            if (!csub) {
                nerrmsg(name, "csm_new_function failed");
                return -1;
            }
502
            sprintf(temp, "%s/%s/%s", bdir, tdir, spec);
503
504
505
506
507
508
509
510
511
512
513
514
            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;
515
            char temp[BDIR_SIZE+TDIR_SIZE+SPEC_SIZE+2];
516
517
518
519
520
521

            csub = csm_new_function();
            if (!csub) {
                nerrmsg(name, "csm_new_function failed");
                return -1;
            }
522
            sprintf(temp, "%s/%s/%s", bdir, tdir, spec);
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
            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;

540
541
542
543
544
545
546
    if (pcvt->iaom) {
        value = pcvt->iaov;
    } else {
        switch (pcvt->meth) {
            case menuCvtMethodLinear: {
                value = pcvt->x * pcvt->xslo + pcvt->y * pcvt->yslo + pcvt->voff;
                break;
547
            }
548
549
550
551
552
553
554
            case menuCvtMethodSubroutine: {
                cvt_subroutine *csub = (cvt_subroutine *)pcvt->csub;
                if (!csub) {
                    goto error;
                }
                value = csub(pcvt->x, pcvt->y, &pcvt->dpvt);
                break;
555
            }
556
557
558
559
560
561
562
            case menuCvtMethod1DTable: {
                csm_function *csub = (csm_function *)pcvt->csub;
                if (!csub) {
                    goto error;
                }
                value = csm_y(csub, pcvt->x);
                break;
563
            }
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
            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");
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
                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);
    }
641
642
643
    if (pcvt->drty & DRTY_BDIR) {
        db_post_events(pcvt, &pcvt->bdir, DBE_VALUE|DBE_LOG);
    }
644
645
646
647
648
649
    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);
    }
650
651
652
653
654
655
    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);
    }
656
657
658
659
660
661
    pcvt->drty = DRTY_NONE;
    return;
}

struct reinitMsg {
    struct cvtRecord *record;
662
663
664
    char spec[SPEC_SIZE];
    char bdir[TDIR_SIZE];
    char tdir[TDIR_SIZE];
665
666
667
668
669
670
671
672
673
674
675
676
677
    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) {
678
679
680
        strncpy(msg.spec, pcvt->nspe, SPEC_SIZE);
        strncpy(msg.bdir, pcvt->nbdi, BDIR_SIZE);
        strncpy(msg.tdir, pcvt->ntdi, TDIR_SIZE);
681
    }
682
683
    qstatus = epicsMessageQueueSend(
        initConversionQ, (void*)&msg, REINIT_MSG_SIZE);
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
    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) {
700
701
        qstatus = epicsMessageQueueReceive(
            initConversionQ, (void*)&msg, REINIT_MSG_SIZE);
702
703
704
705
706
        if (qstatus == -1) {
            nerrmsg("", "msgQReceive failed");
            continue;
        }
        pcvt = msg.record;
707
        status = initConversion(pcvt->name, msg.bdir, msg.tdir, msg.meth, msg.spec, &sub);
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
        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;
734
                strncpy(pcvt->spec, msg.spec, SPEC_SIZE);
735
                pcvt->drty |= DRTY_SPEC;
736
737
738
                strncpy(pcvt->bdir, msg.bdir, BDIR_SIZE);
                pcvt->drty |= DRTY_BDIR;
                strncpy(pcvt->tdir, msg.tdir, TDIR_SIZE);
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
                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;
            }
        }
770
        checkAlarms(pcvt);
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
        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;
795
    unsigned long cvtRecCnt = countCvtRecords();
796

797
798
    if (!initConversionQ && cvtRecCnt > 0) {
        initConversionQ = epicsMessageQueueCreate((unsigned)cvtRecCnt,
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
            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;
}
Franksen, Benjamin's avatar
Franksen, Benjamin committed
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
845
846
847
848

/* Create RSET - Record Support Entry Table*/
#define report NULL
#define get_value NULL
#define cvt_dbaddr NULL
#define get_array_info NULL
#define put_array_info NULL
#define get_enum_str NULL
#define get_enum_strs NULL
#define put_enum_str NULL
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);