This is Unofficial EPICS BASE Doxygen Site
calcRecord.c
Go to the documentation of this file.
1 /*************************************************************************\
2 * Copyright (c) 2007 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 /* Record Support Routines for Calculation records */
11 /*
12  * Original Author: Julie Sander and Bob Dalesio
13  * Date: 7-27-87
14  */
15 
16 #include <stddef.h>
17 #include <stdlib.h>
18 #include <stdarg.h>
19 #include <stdio.h>
20 #include <string.h>
21 
22 #include "dbDefs.h"
23 #include "errlog.h"
24 #include "alarm.h"
25 #include "dbAccess.h"
26 #include "dbEvent.h"
27 #include "dbFldTypes.h"
28 #include "epicsMath.h"
29 #include "errMdef.h"
30 #include "recSup.h"
31 #include "recGbl.h"
32 #include "special.h"
33 
34 #define GEN_SIZE_OFFSET
35 #include "calcRecord.h"
36 #undef GEN_SIZE_OFFSET
37 #include "epicsExport.h"
38 
39 /* Hysterisis for alarm filtering: 1-1/e */
40 #define THRESHOLD 0.6321
41 
42 /* Create RSET - Record Support Entry Table */
43 
44 #define report NULL
45 #define initialize NULL
46 static long init_record(struct dbCommon *pcommon, int pass);
47 static long process(struct dbCommon *prec);
48 static long special(DBADDR *paddr, int after);
49 #define get_value NULL
50 #define cvt_dbaddr NULL
51 #define get_array_info NULL
52 #define put_array_info NULL
53 static long get_units(DBADDR *paddr, char *units);
54 static long get_precision(const DBADDR *paddr, long *precision);
55 #define get_enum_str NULL
56 #define get_enum_strs NULL
57 #define put_enum_str NULL
58 static long get_graphic_double(DBADDR *paddr, struct dbr_grDouble *pgd);
59 static long get_control_double(DBADDR *paddr, struct dbr_ctrlDouble *pcd);
60 static long get_alarm_double(DBADDR *paddr, struct dbr_alDouble *pad);
61 
63  RSETNUMBER,
64  report,
65  initialize,
67  process,
68  special,
69  get_value,
70  cvt_dbaddr,
73  get_units,
81 };
82 epicsExportAddress(rset, calcRSET);
83 
84 static void checkAlarms(calcRecord *prec, epicsTimeStamp *timeLast);
85 static void monitor(calcRecord *prec);
86 static int fetch_values(calcRecord *prec);
87 
88 
89 static long init_record(struct dbCommon *pcommon, int pass)
90 {
91  struct calcRecord *prec = (struct calcRecord *)pcommon;
92  struct link *plink;
93  double *pvalue;
94  int i;
95  short error_number;
96 
97  if (pass==0) return(0);
98 
99  plink = &prec->inpa;
100  pvalue = &prec->a;
101  for (i = 0; i < CALCPERFORM_NARGS; i++, plink++, pvalue++) {
102  recGblInitConstantLink(plink, DBF_DOUBLE, pvalue);
103  }
104  if (postfix(prec->calc, prec->rpcl, &error_number)) {
105  recGblRecordError(S_db_badField, (void *)prec,
106  "calc: init_record: Illegal CALC field");
107  errlogPrintf("%s.CALC: %s in expression \"%s\"\n",
108  prec->name, calcErrorStr(error_number), prec->calc);
109  }
110  return 0;
111 }
112 
113 static long process(struct dbCommon *pcommon)
114 {
115  struct calcRecord *prec = (struct calcRecord *)pcommon;
116  epicsTimeStamp timeLast;
117 
118  prec->pact = TRUE;
119  if (fetch_values(prec) == 0) {
120  if (calcPerform(&prec->a, &prec->val, prec->rpcl)) {
121  recGblSetSevr(prec, CALC_ALARM, INVALID_ALARM);
122  } else
123  prec->udf = isnan(prec->val);
124  }
125 
126  timeLast = prec->time;
127  recGblGetTimeStamp(prec);
128  /* check for alarms */
129  checkAlarms(prec, &timeLast);
130  /* check event list */
131  monitor(prec);
132  /* process the forward scan link record */
133  recGblFwdLink(prec);
134  prec->pact = FALSE;
135  return 0;
136 }
137 
138 static long special(DBADDR *paddr, int after)
139 {
140  calcRecord *prec = (calcRecord *)paddr->precord;
141  short error_number;
142 
143  if (!after) return 0;
144  if (paddr->special == SPC_CALC) {
145  if (postfix(prec->calc, prec->rpcl, &error_number)) {
146  recGblRecordError(S_db_badField, (void *)prec,
147  "calc: Illegal CALC field");
148  errlogPrintf("%s.CALC: %s in expression \"%s\"\n",
149  prec->name, calcErrorStr(error_number), prec->calc);
150  return S_db_badField;
151  }
152  return 0;
153  }
154  recGblDbaddrError(S_db_badChoice, paddr, "calc::special - bad special value!");
155  return S_db_badChoice;
156 }
157 
158 #define indexof(field) calcRecord##field
159 
160 static long get_linkNumber(int fieldIndex) {
161  if (fieldIndex >= indexof(A) && fieldIndex <= indexof(L))
162  return fieldIndex - indexof(A);
163  if (fieldIndex >= indexof(LA) && fieldIndex <= indexof(LL))
164  return fieldIndex - indexof(LA);
165  return -1;
166 }
167 
168 static long get_units(DBADDR *paddr, char *units)
169 {
170  calcRecord *prec = (calcRecord *)paddr->precord;
171  int linkNumber;
172 
173  if(paddr->pfldDes->field_type == DBF_DOUBLE) {
174  linkNumber = get_linkNumber(dbGetFieldIndex(paddr));
175  if (linkNumber >= 0)
176  dbGetUnits(&prec->inpa + linkNumber, units, DB_UNITS_SIZE);
177  else
178  strncpy(units,prec->egu,DB_UNITS_SIZE);
179  }
180  return 0;
181 }
182 
183 static long get_precision(const DBADDR *paddr, long *pprecision)
184 {
185  calcRecord *prec = (calcRecord *)paddr->precord;
186  int fieldIndex = dbGetFieldIndex(paddr);
187  int linkNumber;
188 
189  *pprecision = prec->prec;
190  if (fieldIndex == indexof(VAL))
191  return 0;
192 
193  linkNumber = get_linkNumber(fieldIndex);
194  if (linkNumber >= 0) {
195  short precision;
196 
197  if (dbGetPrecision(&prec->inpa + linkNumber, &precision) == 0)
198  *pprecision = precision;
199  } else
200  recGblGetPrec(paddr, pprecision);
201  return 0;
202 }
203 
204 static long get_graphic_double(DBADDR *paddr, struct dbr_grDouble *pgd)
205 {
206  calcRecord *prec = (calcRecord *)paddr->precord;
207  int fieldIndex = dbGetFieldIndex(paddr);
208  int linkNumber;
209 
210  switch (fieldIndex) {
211  case indexof(VAL):
212  case indexof(HIHI):
213  case indexof(HIGH):
214  case indexof(LOW):
215  case indexof(LOLO):
216  case indexof(LALM):
217  case indexof(ALST):
218  case indexof(MLST):
219  pgd->lower_disp_limit = prec->lopr;
220  pgd->upper_disp_limit = prec->hopr;
221  break;
222  default:
223  linkNumber = get_linkNumber(fieldIndex);
224  if (linkNumber >= 0) {
225  dbGetGraphicLimits(&prec->inpa + linkNumber,
226  &pgd->lower_disp_limit,
227  &pgd->upper_disp_limit);
228  } else
229  recGblGetGraphicDouble(paddr,pgd);
230  }
231  return 0;
232 }
233 
234 static long get_control_double(DBADDR *paddr, struct dbr_ctrlDouble *pcd)
235 {
236  calcRecord *prec = (calcRecord *)paddr->precord;
237 
238  switch (dbGetFieldIndex(paddr)) {
239  case indexof(VAL):
240  case indexof(HIHI):
241  case indexof(HIGH):
242  case indexof(LOW):
243  case indexof(LOLO):
244  case indexof(LALM):
245  case indexof(ALST):
246  case indexof(MLST):
247  pcd->lower_ctrl_limit = prec->lopr;
248  pcd->upper_ctrl_limit = prec->hopr;
249  break;
250  default:
251  recGblGetControlDouble(paddr,pcd);
252  }
253  return 0;
254 }
255 
256 static long get_alarm_double(DBADDR *paddr, struct dbr_alDouble *pad)
257 {
258  calcRecord *prec = (calcRecord *)paddr->precord;
259  int fieldIndex = dbGetFieldIndex(paddr);
260  int linkNumber;
261 
262  if (fieldIndex == indexof(VAL)) {
263  pad->lower_alarm_limit = prec->llsv ? prec->lolo : epicsNAN;
264  pad->lower_warning_limit = prec->lsv ? prec->low : epicsNAN;
265  pad->upper_warning_limit = prec->hsv ? prec->high : epicsNAN;
266  pad->upper_alarm_limit = prec->hhsv ? prec->hihi : epicsNAN;
267  } else {
268  linkNumber = get_linkNumber(fieldIndex);
269  if (linkNumber >= 0) {
270  dbGetAlarmLimits(&prec->inpa + linkNumber,
271  &pad->lower_alarm_limit,
272  &pad->lower_warning_limit,
273  &pad->upper_warning_limit,
274  &pad->upper_alarm_limit);
275  } else
276  recGblGetAlarmDouble(paddr, pad);
277  }
278  return 0;
279 }
280 
281 static void checkAlarms(calcRecord *prec, epicsTimeStamp *timeLast)
282 {
283 
284  enum {
285  range_Lolo = 1,
286  range_Low,
287  range_Normal,
288  range_High,
289  range_Hihi
290  } alarmRange;
291  static const epicsEnum16 range_stat[] = {
294  };
295 
296  double val, hyst, lalm, alev, aftc, afvl;
297  epicsEnum16 asev;
298 
299  if (prec->udf) {
300  recGblSetSevr(prec, UDF_ALARM, prec->udfs);
301  prec->afvl = 0;
302  return;
303  }
304 
305  val = prec->val;
306  hyst = prec->hyst;
307  lalm = prec->lalm;
308 
309  /* check VAL against alarm limits */
310  if ((asev = prec->hhsv) &&
311  (val >= (alev = prec->hihi) ||
312  ((lalm == alev) && (val >= alev - hyst))))
313  alarmRange = range_Hihi;
314  else
315  if ((asev = prec->llsv) &&
316  (val <= (alev = prec->lolo) ||
317  ((lalm == alev) && (val <= alev + hyst))))
318  alarmRange = range_Lolo;
319  else
320  if ((asev = prec->hsv) &&
321  (val >= (alev = prec->high) ||
322  ((lalm == alev) && (val >= alev - hyst))))
323  alarmRange = range_High;
324  else
325  if ((asev = prec->lsv) &&
326  (val <= (alev = prec->low) ||
327  ((lalm == alev) && (val <= alev + hyst))))
328  alarmRange = range_Low;
329  else {
330  alev = val;
331  asev = NO_ALARM;
332  alarmRange = range_Normal;
333  }
334 
335  aftc = prec->aftc;
336  afvl = 0;
337 
338  if (aftc > 0) {
339  /* Apply level filtering */
340  afvl = prec->afvl;
341  if (afvl == 0) {
342  afvl = (double)alarmRange;
343  } else {
344  double t = epicsTimeDiffInSeconds(&prec->time, timeLast);
345  double alpha = aftc / (t + aftc);
346 
347  /* The sign of afvl indicates whether the result should be
348  * rounded up or down. This gives the filter hysteresis.
349  * If afvl > 0 the floor() function rounds to a lower alarm
350  * level, otherwise to a higher.
351  */
352  afvl = alpha * afvl +
353  ((afvl > 0) ? (1 - alpha) : (alpha - 1)) * alarmRange;
354  if (afvl - floor(afvl) > THRESHOLD)
355  afvl = -afvl; /* reverse rounding */
356 
357  alarmRange = abs((int)floor(afvl));
358  switch (alarmRange) {
359  case range_Hihi:
360  asev = prec->hhsv;
361  alev = prec->hihi;
362  break;
363  case range_High:
364  asev = prec->hsv;
365  alev = prec->high;
366  break;
367  case range_Normal:
368  asev = NO_ALARM;
369  break;
370  case range_Low:
371  asev = prec->lsv;
372  alev = prec->low;
373  break;
374  case range_Lolo:
375  asev = prec->llsv;
376  alev = prec->lolo;
377  break;
378  }
379  }
380  }
381  prec->afvl = afvl;
382 
383  if (asev) {
384  /* Report alarm condition, store LALM for future HYST calculations */
385  if (recGblSetSevr(prec, range_stat[alarmRange], asev))
386  prec->lalm = alev;
387  } else {
388  /* No alarm condition, reset LALM */
389  prec->lalm = val;
390  }
391 
392 }
393 
394 static void monitor(calcRecord *prec)
395 {
396  unsigned monitor_mask;
397  double *pnew, *pprev;
398  int i;
399 
400  monitor_mask = recGblResetAlarms(prec);
401 
402  /* check for value change */
403  recGblCheckDeadband(&prec->mlst, prec->val, prec->mdel, &monitor_mask, DBE_VALUE);
404 
405  /* check for archive change */
406  recGblCheckDeadband(&prec->alst, prec->val, prec->adel, &monitor_mask, DBE_ARCHIVE);
407 
408  /* send out monitors connected to the value field */
409  if (monitor_mask){
410  db_post_events(prec, &prec->val, monitor_mask);
411  }
412 
413  /* check all input fields for changes*/
414  pnew = &prec->a;
415  pprev = &prec->la;
416  for (i = 0; i < CALCPERFORM_NARGS; i++, pnew++, pprev++) {
417  if (*pnew != *pprev ||
418  monitor_mask & DBE_ALARM) {
419  db_post_events(prec, pnew, monitor_mask | DBE_VALUE | DBE_LOG);
420  *pprev = *pnew;
421  }
422  }
423  return;
424 }
425 
426 static int fetch_values(calcRecord *prec)
427 {
428  struct link *plink;
429  double *pvalue;
430  long status = 0;
431  int i;
432 
433  plink = &prec->inpa;
434  pvalue = &prec->a;
435  for(i = 0; i < CALCPERFORM_NARGS; i++, plink++, pvalue++) {
436  int newStatus;
437 
438  newStatus = dbGetLink(plink, DBR_DOUBLE, pvalue, 0, 0);
439  if (status == 0) status = newStatus;
440  }
441  return status;
442 }
#define HIHI_ALARM
Definition: alarm.h:94
#define RSETNUMBER
Definition: recSup.h:92
#define THRESHOLD
Definition: calcRecord.c:40
LIBCOM_API const char * calcErrorStr(short error)
Convert an error code to a string.
Definition: postfix.c:493
#define FALSE
Definition: dbDefs.h:32
#define get_array_info
Definition: calcRecord.c:51
unsigned * LA
Definition: lalr.c:22
pvd::Status status
int i
Definition: scan.c:967
#define cvt_dbaddr
Definition: calcRecord.c:50
#define get_enum_str
Definition: calcRecord.c:55
#define init_record
#define SPC_CALC
Definition: special.h:39
#define SOFT_ALARM
Definition: alarm.h:106
#define DBE_ALARM
Definition: caeventmask.h:41
#define indexof(field)
Definition: calcRecord.c:158
#define CALCPERFORM_NARGS
Number of input arguments to a calc expression (A-L)
Definition: postfix.h:25
Miscellaneous macro definitions.
#define DBE_ARCHIVE
Definition: caeventmask.h:39
#define get_control_double
Definition: biRecord.c:58
#define put_enum_str
Definition: calcRecord.c:57
#define DBE_VALUE
Definition: caeventmask.h:38
#define HIGH_ALARM
Definition: alarm.h:95
LIBCOM_API long calcPerform(double *parg, double *presult, const char *pinst)
Run the calculation engine.
Definition: calcPerform.c:45
#define CALC_ALARM
Definition: alarm.h:103
#define isnan(x)
Definition: epicsMath.h:21
#define DBE_LOG
Definition: caeventmask.h:40
#define initialize
Definition: calcRecord.c:45
#define get_units
Definition: biRecord.c:52
#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
#define report
Definition: calcRecord.c:44
#define get_enum_strs
Definition: calcRecord.c:56
int errlogPrintf(const char *pFormat,...)
Definition: errlog.c:105
float epicsNAN
Definition: epicsMath.cpp:35
epicsUInt16 epicsEnum16
Definition: epicsTypes.h:47
#define get_precision
Definition: biRecord.c:53
EPICS time stamp, for use from C code.
Definition: epicsTime.h:33
#define TRUE
Definition: dbDefs.h:27
epicsExportAddress(rset, calcRSET)
if(yy_init)
Definition: scan.c:972
Definition: recSup.h:67
#define put_array_info
Definition: calcRecord.c:52
LIBCOM_API double epicsStdCall epicsTimeDiffInSeconds(const epicsTimeStamp *pLeft, const epicsTimeStamp *pRight)
Time difference between left and right in seconds.
Definition: epicsTime.cpp:1048
LIBCOM_API long postfix(const char *psrc, char *pout, short *perror)
Compile an infix expression into postfix byte-code.
Definition: postfix.c:209
int prec
Definition: reader.c:29
#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
rset calcRSET
Definition: calcRecord.c:62
#define special
Definition: dfanoutRecord.c:50
#define get_value
Definition: calcRecord.c:49
#define get_alarm_double
Definition: aaiRecord.c:69
#define UDF_ALARM
Definition: alarm.h:108
Exporting IOC objects.