This is Unofficial EPICS BASE Doxygen Site
aiRecord.c
Go to the documentation of this file.
1 /*************************************************************************\
2 * Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne
3 * National Laboratory.
4 * Copyright (c) 2002 The Regents of the University of California, as
5 * Operator of Los Alamos National Laboratory.
6 * EPICS BASE is distributed subject to a Software License Agreement found
7 * in file LICENSE that is included with this distribution.
8 \*************************************************************************/
9 
10 /* aiRecord.c - Record Support Routines for Analog Input records */
11 /*
12  * Original Author: Bob Dalesio
13  * Date: 7-14-89
14  *
15  */
16 
17 #include <stddef.h>
18 #include <stdlib.h>
19 #include <stdarg.h>
20 #include <stdio.h>
21 #include <string.h>
22 
23 #include "dbDefs.h"
24 #include "errlog.h"
25 #include "epicsMath.h"
26 #include "alarm.h"
27 #include "callback.h"
28 #include "cvtTable.h"
29 #include "dbAccess.h"
30 #include "dbScan.h"
31 #include "dbEvent.h"
32 #include "dbFldTypes.h"
33 #include "devSup.h"
34 #include "menuSimm.h"
35 #include "recSup.h"
36 #include "recGbl.h"
37 #include "special.h"
38 #include "menuConvert.h"
39 
40 #define GEN_SIZE_OFFSET
41 #include "aiRecord.h"
42 #undef GEN_SIZE_OFFSET
43 #include "epicsExport.h"
44 
45 /* Hysterisis for alarm filtering: 1-1/e */
46 #define THRESHOLD 0.6321
47 
48 /* Create RSET - Record Support Entry Table*/
49 #define report NULL
50 #define initialize NULL
51 static long init_record(struct dbCommon *, int);
52 static long process(struct dbCommon *);
53 static long special(DBADDR *, int);
54 #define get_value NULL
55 #define cvt_dbaddr NULL
56 #define get_array_info NULL
57 #define put_array_info NULL
58 static long get_units(DBADDR *, char *);
59 static long get_precision(const DBADDR *, long *);
60 #define get_enum_str NULL
61 #define get_enum_strs NULL
62 #define put_enum_str NULL
63 static long get_graphic_double(DBADDR *, struct dbr_grDouble *);
64 static long get_control_double(DBADDR *, struct dbr_ctrlDouble *);
65 static long get_alarm_double(DBADDR *, struct dbr_alDouble *);
66 
68  RSETNUMBER,
69  report,
70  initialize,
72  process,
73  special,
74  get_value,
75  cvt_dbaddr,
78  get_units,
86 };
87 epicsExportAddress(rset,aiRSET);
88 
89 static void checkAlarms(aiRecord *prec, epicsTimeStamp *lastTime);
90 static void convert(aiRecord *prec);
91 static void monitor(aiRecord *prec);
92 static long readValue(aiRecord *prec);
93 
94 static long init_record(struct dbCommon *pcommon, int pass)
95 {
96  struct aiRecord *prec = (struct aiRecord *)pcommon;
97  aidset *pdset;
98  double eoff = prec->eoff, eslo = prec->eslo;
99 
100  if (pass == 0) return 0;
101 
102  recGblInitSimm(pcommon, &prec->sscn, &prec->oldsimm, &prec->simm, &prec->siml);
103  recGblInitConstantLink(&prec->siol, DBF_DOUBLE, &prec->sval);
104 
105  if(!(pdset = (aidset *)(prec->dset))) {
106  recGblRecordError(S_dev_noDSET,(void *)prec,"ai: init_record");
107  return(S_dev_noDSET);
108  }
109  /* must have read_ai function defined */
110  if ((pdset->common.number < 6) || (pdset->read_ai == NULL)) {
111  recGblRecordError(S_dev_missingSup,(void *)prec,"ai: init_record");
112  return(S_dev_missingSup);
113  }
114  prec->init = TRUE;
115  /*The following is for old device support that doesnt know about eoff*/
116  if ((prec->eslo==1.0) && (prec->eoff==0.0)) {
117  prec->eoff = prec->egul;
118  }
119 
120  if (pdset->common.init_record) {
121  long status = pdset->common.init_record(pcommon);
122  if (prec->linr == menuConvertSLOPE) {
123  prec->eoff = eoff;
124  prec->eslo = eslo;
125  }
126  return (status);
127  }
128  prec->mlst = prec->val;
129  prec->alst = prec->val;
130  prec->lalm = prec->val;
131  prec->oraw = prec->rval;
132  return(0);
133 }
134 
135 static long process(struct dbCommon *pcommon)
136 {
137  struct aiRecord *prec = (struct aiRecord *)pcommon;
138  aidset *pdset = (aidset *)(prec->dset);
139  long status;
140  unsigned char pact=prec->pact;
141  epicsTimeStamp timeLast;
142 
143  if( (pdset==NULL) || (pdset->read_ai==NULL) ) {
144  prec->pact=TRUE;
145  recGblRecordError(S_dev_missingSup,(void *)prec,"read_ai");
146  return(S_dev_missingSup);
147  }
148  timeLast = prec->time;
149 
150  status=readValue(prec); /* read the new value */
151  /* check if device support set pact */
152  if ( !pact && prec->pact ) return(0);
153  prec->pact = TRUE;
154 
155  recGblGetTimeStampSimm(prec, prec->simm, &prec->siol);
156 
157  if (status==0) convert(prec);
158  else if (status==2) status=0;
159 
160  if (status == 0) prec->udf = isnan(prec->val);
161 
162  /* check for alarms */
163  checkAlarms(prec,&timeLast);
164  /* check event list */
165  monitor(prec);
166  /* process the forward scan link record */
167  recGblFwdLink(prec);
168 
169  prec->init=FALSE;
170  prec->pact=FALSE;
171  return(status);
172 }
173 
174 static long special(DBADDR *paddr,int after)
175 {
176  aiRecord *prec = (aiRecord *)(paddr->precord);
177  aidset *pdset = (aidset *) (prec->dset);
178  int special_type = paddr->special;
179 
180  switch(special_type) {
181  case(SPC_LINCONV):
182  if (pdset->common.number < 6) {
183  recGblDbaddrError(S_db_noMod,paddr,"ai: special");
184  return(S_db_noMod);
185  }
186  prec->init=TRUE;
187  if ((prec->linr == menuConvertLINEAR) && pdset->special_linconv) {
188  double eoff = prec->eoff;
189  double eslo = prec->eslo;
190  long status;
191  prec->eoff = prec->egul;
192  status = (*pdset->special_linconv)(prec,after);
193  if (eoff != prec->eoff)
194  db_post_events(prec, &prec->eoff, DBE_VALUE|DBE_LOG);
195  if (eslo != prec->eslo)
196  db_post_events(prec, &prec->eslo, DBE_VALUE|DBE_LOG);
197  return(status);
198  }
199  return(0);
200  case(SPC_MOD):
201  if (dbGetFieldIndex(paddr) == aiRecordSIMM) {
202  if (!after)
203  recGblSaveSimm(prec->sscn, &prec->oldsimm, prec->simm);
204  else
205  recGblCheckSimm((dbCommon *)prec, &prec->sscn, prec->oldsimm, prec->simm);
206  return(0);
207  }
208  default:
209  recGblDbaddrError(S_db_badChoice,paddr,"ai: special");
210  return(S_db_badChoice);
211  }
212 }
213 
214 #define indexof(field) aiRecord##field
215 
216 static long get_units(DBADDR *paddr, char *units)
217 {
218  aiRecord *prec=(aiRecord *)paddr->precord;
219 
220  if(paddr->pfldDes->field_type == DBF_DOUBLE) {
221  switch (dbGetFieldIndex(paddr)) {
222  case indexof(ASLO):
223  case indexof(AOFF):
224  case indexof(SMOO):
225  break;
226  default:
227  strncpy(units,prec->egu,DB_UNITS_SIZE);
228  }
229  }
230  return(0);
231 }
232 
233 static long get_precision(const DBADDR *paddr, long *precision)
234 {
235  aiRecord *prec=(aiRecord *)paddr->precord;
236 
237  *precision = prec->prec;
238  if (dbGetFieldIndex(paddr) == indexof(VAL)) return(0);
239  recGblGetPrec(paddr,precision);
240  return(0);
241 }
242 
243 static long get_graphic_double(DBADDR *paddr,struct dbr_grDouble *pgd)
244 {
245  aiRecord *prec=(aiRecord *)paddr->precord;
246 
247  switch (dbGetFieldIndex(paddr)) {
248  case indexof(VAL):
249  case indexof(HIHI):
250  case indexof(HIGH):
251  case indexof(LOW):
252  case indexof(LOLO):
253  case indexof(LALM):
254  case indexof(ALST):
255  case indexof(MLST):
256  case indexof(SVAL):
257  pgd->upper_disp_limit = prec->hopr;
258  pgd->lower_disp_limit = prec->lopr;
259  break;
260  default:
261  recGblGetGraphicDouble(paddr,pgd);
262  }
263  return(0);
264 }
265 
266 static long get_control_double(DBADDR *paddr,struct dbr_ctrlDouble *pcd)
267 {
268  aiRecord *prec=(aiRecord *)paddr->precord;
269 
270  switch (dbGetFieldIndex(paddr)) {
271  case indexof(VAL):
272  case indexof(HIHI):
273  case indexof(HIGH):
274  case indexof(LOW):
275  case indexof(LOLO):
276  case indexof(LALM):
277  case indexof(ALST):
278  case indexof(MLST):
279  case indexof(SVAL):
280  pcd->upper_ctrl_limit = prec->hopr;
281  pcd->lower_ctrl_limit = prec->lopr;
282  break;
283  default:
284  recGblGetControlDouble(paddr,pcd);
285  }
286  return(0);
287 }
288 
289 static long get_alarm_double(DBADDR *paddr,struct dbr_alDouble *pad)
290 {
291  aiRecord *prec=(aiRecord *)paddr->precord;
292 
293  if (dbGetFieldIndex(paddr) == indexof(VAL)) {
294  pad->upper_alarm_limit = prec->hhsv ? prec->hihi : epicsNAN;
295  pad->upper_warning_limit = prec->hsv ? prec->high : epicsNAN;
296  pad->lower_warning_limit = prec->lsv ? prec->low : epicsNAN;
297  pad->lower_alarm_limit = prec->llsv ? prec->lolo : epicsNAN;
298  } else recGblGetAlarmDouble(paddr,pad);
299  return(0);
300 }
301 
302 static void checkAlarms(aiRecord *prec, epicsTimeStamp *lastTime)
303 {
304  enum {
305  range_Lolo = 1,
306  range_Low,
307  range_Normal,
308  range_High,
309  range_Hihi
310  } alarmRange;
311  static const epicsEnum16 range_stat[] = {
314  };
315  double val, hyst, lalm, alev, aftc, afvl;
316  epicsEnum16 asev;
317 
318  if (prec->udf) {
319  recGblSetSevr(prec, UDF_ALARM, prec->udfs);
320  prec->afvl = 0;
321  return;
322  }
323 
324  val = prec->val;
325  hyst = prec->hyst;
326  lalm = prec->lalm;
327 
328  /* check VAL against alarm limits */
329  if ((asev = prec->hhsv) &&
330  (val >= (alev = prec->hihi) ||
331  ((lalm == alev) && (val >= alev - hyst))))
332  alarmRange = range_Hihi;
333  else
334  if ((asev = prec->llsv) &&
335  (val <= (alev = prec->lolo) ||
336  ((lalm == alev) && (val <= alev + hyst))))
337  alarmRange = range_Lolo;
338  else
339  if ((asev = prec->hsv) &&
340  (val >= (alev = prec->high) ||
341  ((lalm == alev) && (val >= alev - hyst))))
342  alarmRange = range_High;
343  else
344  if ((asev = prec->lsv) &&
345  (val <= (alev = prec->low) ||
346  ((lalm == alev) && (val <= alev + hyst))))
347  alarmRange = range_Low;
348  else {
349  alev = val;
350  asev = NO_ALARM;
351  alarmRange = range_Normal;
352  }
353 
354  aftc = prec->aftc;
355  afvl = 0;
356 
357  if (aftc > 0) {
358  /* Apply level filtering */
359  afvl = prec->afvl;
360  if (afvl == 0) {
361  afvl = (double)alarmRange;
362  } else {
363  double t = epicsTimeDiffInSeconds(&prec->time, lastTime);
364  double alpha = aftc / (t + aftc);
365 
366  /* The sign of afvl indicates whether the result should be
367  * rounded up or down. This gives the filter hysteresis.
368  * If afvl > 0 the floor() function rounds to a lower alarm
369  * level, otherwise to a higher.
370  */
371  afvl = alpha * afvl +
372  ((afvl > 0) ? (1 - alpha) : (alpha - 1)) * alarmRange;
373  if (afvl - floor(afvl) > THRESHOLD)
374  afvl = -afvl; /* reverse rounding */
375 
376  alarmRange = abs((int)floor(afvl));
377  switch (alarmRange) {
378  case range_Hihi:
379  asev = prec->hhsv;
380  alev = prec->hihi;
381  break;
382  case range_High:
383  asev = prec->hsv;
384  alev = prec->high;
385  break;
386  case range_Normal:
387  asev = NO_ALARM;
388  break;
389  case range_Low:
390  asev = prec->lsv;
391  alev = prec->low;
392  break;
393  case range_Lolo:
394  asev = prec->llsv;
395  alev = prec->lolo;
396  break;
397  }
398  }
399  }
400  prec->afvl = afvl;
401 
402  if (asev) {
403  /* Report alarm condition, store LALM for future HYST calculations */
404  if (recGblSetSevr(prec, range_stat[alarmRange], asev))
405  prec->lalm = alev;
406  } else {
407  /* No alarm condition, reset LALM */
408  prec->lalm = val;
409  }
410 }
411 
412 static void convert(aiRecord *prec)
413 {
414  double val;
415 
416 
417  val = (double)prec->rval + (double)prec->roff;
418  /* adjust slope and offset */
419  if(prec->aslo!=0.0) val*=prec->aslo;
420  val+=prec->aoff;
421 
422  /* convert raw to engineering units and signal units */
423  switch (prec->linr) {
424  case menuConvertNO_CONVERSION:
425  break; /* do nothing*/
426 
427  case menuConvertLINEAR:
428  case menuConvertSLOPE:
429  val = (val * prec->eslo) + prec->eoff;
430  break;
431 
432  default: /* must use breakpoint table */
433  if (cvtRawToEngBpt(&val,prec->linr,prec->init,(void *)&prec->pbrk,&prec->lbrk)!=0) {
434  recGblSetSevr(prec,SOFT_ALARM,MAJOR_ALARM);
435  }
436  }
437 
438  /* apply smoothing algorithm */
439  if (prec->smoo != 0.0 && finite(prec->val)){
440  if (prec->init) prec->val = val; /* initial condition */
441  prec->val = val * (1.00 - prec->smoo) + (prec->val * prec->smoo);
442  }else{
443  prec->val = val;
444  }
445  return;
446 }
447 
448 static void monitor(aiRecord *prec)
449 {
450  unsigned monitor_mask = recGblResetAlarms(prec);
451 
452  /* check for value change */
453  recGblCheckDeadband(&prec->mlst, prec->val, prec->mdel, &monitor_mask, DBE_VALUE);
454 
455  /* check for archive change */
456  recGblCheckDeadband(&prec->alst, prec->val, prec->adel, &monitor_mask, DBE_ARCHIVE);
457 
458  /* send out monitors connected to the value field */
459  if (monitor_mask){
460  db_post_events(prec,&prec->val,monitor_mask);
461  if(prec->oraw != prec->rval) {
462  db_post_events(prec,&prec->rval,monitor_mask);
463  prec->oraw = prec->rval;
464  }
465  }
466  return;
467 }
468 
469 static long readValue(aiRecord *prec)
470 {
471  aidset *pdset = (aidset *)prec->dset;
472  long status = 0;
473 
474  if (!prec->pact) {
475  status = recGblGetSimm((dbCommon *)prec, &prec->sscn, &prec->oldsimm, &prec->simm, &prec->siml);
476  if (status) return status;
477  }
478 
479  switch (prec->simm) {
480  case menuSimmNO:
481  status = pdset->read_ai(prec);
482  break;
483 
484  case menuSimmYES:
485  case menuSimmRAW: {
486  recGblSetSevr(prec, SIMM_ALARM, prec->sims);
487  if (prec->pact || (prec->sdly < 0.)) {
488  status = dbGetLink(&prec->siol, DBR_DOUBLE, &prec->sval, 0, 0);
489  if (status == 0) {
490  if (prec->simm == menuSimmYES) {
491  prec->val = prec->sval;
492  status = 2; /* don't convert */
493  } else {
494  prec->rval = (long)floor(prec->sval);
495  status = 0; /* convert RVAL */
496  }
497  }
498  prec->pact = FALSE;
499  } else { /* !prec->pact && delay >= 0. */
500  epicsCallback *pvt = prec->simpvt;
501  if (!pvt) {
502  pvt = calloc(1, sizeof(epicsCallback)); /* very lazy allocation of callback structure */
503  prec->simpvt = pvt;
504  }
505  if (pvt) callbackRequestProcessCallbackDelayed(pvt, prec->prio, prec, prec->sdly);
506  prec->pact = TRUE;
507  }
508  break;
509  }
510 
511  default:
512  recGblSetSevr(prec, SOFT_ALARM, INVALID_ALARM);
513  status = -1;
514  }
515 
516  return status;
517 }
rset aiRSET
Definition: aiRecord.c:67
#define HIHI_ALARM
Definition: alarm.h:94
#define RSETNUMBER
Definition: recSup.h:92
#define report
Definition: aiRecord.c:49
#define FALSE
Definition: dbDefs.h:32
pvd::Status status
#define get_enum_strs
Definition: aiRecord.c:61
#define MAJOR_ALARM
Definition: alarm.h:52
#define initialize
Definition: aiRecord.c:50
#define init_record
#define S_dev_missingSup
Definition: devSup.h:170
#define SPC_LINCONV
Definition: special.h:37
#define put_enum_str
Definition: aiRecord.c:62
#define SOFT_ALARM
Definition: alarm.h:106
#define NULL
Definition: catime.c:38
#define indexof(field)
Definition: aiRecord.c:214
Miscellaneous macro definitions.
#define DBE_ARCHIVE
Definition: caeventmask.h:39
#define get_control_double
Definition: biRecord.c:58
#define DBE_VALUE
Definition: caeventmask.h:38
#define HIGH_ALARM
Definition: alarm.h:95
#define get_array_info
Definition: aiRecord.c:56
Device support routines.
#define isnan(x)
Definition: epicsMath.h:21
#define DBE_LOG
Definition: caeventmask.h:40
#define get_units
Definition: biRecord.c:52
#define SIMM_ALARM
Definition: alarm.h:110
#define DBR_DOUBLE
Definition: db_access.h:76
#define NO_ALARM
The NO_ALARM value can be used as both a severity and a status.
Definition: alarm.h:32
epicsShareFunc long cvtRawToEngBpt(double *pval, short linr, short init, void **ppbrk, short *plbrk)
#define get_enum_str
Definition: aiRecord.c:60
float epicsNAN
Definition: epicsMath.cpp:35
#define THRESHOLD
Definition: aiRecord.c:46
#define get_value
Definition: aiRecord.c:54
epicsUInt16 epicsEnum16
Definition: epicsTypes.h:47
#define get_precision
Definition: biRecord.c:53
#define put_array_info
Definition: aiRecord.c:57
EPICS time stamp, for use from C code.
Definition: epicsTime.h:33
#define TRUE
Definition: dbDefs.h:27
if(yy_init)
Definition: scan.c:972
Definition: recSup.h:67
LIBCOM_API double epicsStdCall epicsTimeDiffInSeconds(const epicsTimeStamp *pLeft, const epicsTimeStamp *pRight)
Time difference between left and right in seconds.
Definition: epicsTime.cpp:1048
#define SPC_MOD
Definition: special.h:33
#define finite(x)
Definition: epicsMath.h:16
int prec
Definition: reader.c:29
epicsExportAddress(rset, aiRSET)
#define INVALID_ALARM
Definition: alarm.h:53
#define LOLO_ALARM
Definition: alarm.h:96
#define LOW_ALARM
Definition: alarm.h:97
#define get_graphic_double
Definition: biRecord.c:57
#define cvt_dbaddr
Definition: aiRecord.c:55
#define special
Definition: dfanoutRecord.c:50
#define S_dev_noDSET
Definition: devSup.h:169
#define get_alarm_double
Definition: aaiRecord.c:69
#define UDF_ALARM
Definition: alarm.h:108
Exporting IOC objects.