Merged alarm-filter branch.

This commit is contained in:
Andrew Johnson
2012-03-09 12:25:59 -06:00
9 changed files with 401 additions and 119 deletions
+17
View File
@@ -15,6 +15,23 @@ EPICS Base 3.15.0.x releases are not intended for use in production systems.</p>
<h2 align="center">Changes between 3.14.x and 3.15.0.x</h2>
<!-- Insert new items immediately below here ... -->
<h3>Alarm filtering added to input record types</h3>
<p>The record types ai, calc, longin and mbbi have a new alarm filter added to
them. This provides a low-pass filter that can be used to delay the reporting of
alarms caused by the input level passing the HIGH, HIHI, LOW or LOLO values. The
filter is controlled with a new AFTC field that sets the filter's time constant.
The default value for this field is zero, which keeps the record's original
alarm behaviour.</p>
<p>The record must be scanned often enough for the filtering action to work
effectively and the alarm severity can only change when the record is processed,
but that processing does not have to be regular; the filter uses the time since
the record last processed in its calculation. Setting AFTC to a positive number
of seconds will delay the record going into or out of a minor alarm severity or
from minor to major severity until the input signal has been in that range for
that number of seconds.</p>
<h3>Post events on Waveform record's NORD field</h3>
<p>When the record type or soft device support modify the NORD field of a
+100 -40
View File
@@ -41,6 +41,9 @@
#undef GEN_SIZE_OFFSET
#include "epicsExport.h"
/* Hysterisis for alarm filtering: 1-1/e */
#define THRESHOLD 0.6321
/* Create RSET - Record Support Entry Table*/
#define report NULL
#define initialize NULL
@@ -99,7 +102,7 @@ typedef struct aidset { /* analog input dset */
extern unsigned int gts_trigger_counter;
*/
static void checkAlarms(aiRecord *prec);
static void checkAlarms(aiRecord *prec, epicsTimeStamp *lastTime);
static void convert(aiRecord *prec);
static void monitor(aiRecord *prec);
static long readValue(aiRecord *prec);
@@ -158,12 +161,15 @@ static long process(void *precord)
aidset *pdset = (aidset *)(prec->dset);
long status;
unsigned char pact=prec->pact;
epicsTimeStamp timeLast;
if( (pdset==NULL) || (pdset->read_ai==NULL) ) {
prec->pact=TRUE;
recGblRecordError(S_dev_missingSup,(void *)prec,"read_ai");
return(S_dev_missingSup);
}
timeLast = prec->time;
status=readValue(prec); /* read the new value */
/* check if device support set pact */
if ( !pact && prec->pact ) return(0);
@@ -174,7 +180,7 @@ static long process(void *precord)
else if (status==2) status=0;
/* check for alarms */
checkAlarms(prec);
checkAlarms(prec,&timeLast);
/* check event list */
monitor(prec);
/* process the forward scan link record */
@@ -305,60 +311,114 @@ static long get_alarm_double(DBADDR *paddr,struct dbr_alDouble *pad)
return(0);
}
static void checkAlarms(aiRecord *prec)
static void checkAlarms(aiRecord *prec, epicsTimeStamp *lastTime)
{
double val, hyst, lalm;
double alev;
enum {
range_Lolo = 1,
range_Low,
range_Normal,
range_High,
range_Hihi
} alarmRange;
static const epicsEnum16 range_stat[] = {
SOFT_ALARM, LOLO_ALARM, LOW_ALARM,
NO_ALARM, HIGH_ALARM, HIHI_ALARM
};
double val, hyst, lalm, alev, aftc, afvl;
epicsEnum16 asev;
if (prec->udf) {
recGblSetSevr(prec, UDF_ALARM, INVALID_ALARM);
prec->afvl = 0;
return;
}
val = prec->val;
val = prec->val;
hyst = prec->hyst;
lalm = prec->lalm;
/* alarm condition hihi */
asev = prec->hhsv;
alev = prec->hihi;
if (asev && (val >= alev || ((lalm == alev) && (val >= alev - hyst)))) {
if (recGblSetSevr(prec, HIHI_ALARM, asev))
prec->lalm = alev;
return;
/* check VAL against alarm limits */
if ((asev = prec->hhsv) &&
(val >= (alev = prec->hihi) ||
((lalm == alev) && (val >= alev - hyst))))
alarmRange = range_Hihi;
else
if ((asev = prec->llsv) &&
(val <= (alev = prec->lolo) ||
((lalm == alev) && (val <= alev + hyst))))
alarmRange = range_Lolo;
else
if ((asev = prec->hsv) &&
(val >= (alev = prec->high) ||
((lalm == alev) && (val >= alev - hyst))))
alarmRange = range_High;
else
if ((asev = prec->lsv) &&
(val <= (alev = prec->low) ||
((lalm == alev) && (val <= alev + hyst))))
alarmRange = range_Low;
else {
alev = val;
asev = NO_ALARM;
alarmRange = range_Normal;
}
/* alarm condition lolo */
asev = prec->llsv;
alev = prec->lolo;
if (asev && (val <= alev || ((lalm == alev) && (val <= alev + hyst)))) {
if (recGblSetSevr(prec, LOLO_ALARM, asev))
prec->lalm = alev;
return;
}
aftc = prec->aftc;
afvl = 0;
/* alarm condition high */
asev = prec->hsv;
alev = prec->high;
if (asev && (val >= alev || ((lalm == alev) && (val >= alev - hyst)))) {
if (recGblSetSevr(prec, HIGH_ALARM, asev))
prec->lalm = alev;
return;
}
if (aftc > 0) {
/* Apply level filtering */
afvl = prec->afvl;
if (afvl == 0) {
afvl = (double)alarmRange;
} else {
double t = epicsTimeDiffInSeconds(&prec->time, lastTime);
double alpha = aftc / (t + aftc);
/* alarm condition low */
asev = prec->lsv;
alev = prec->low;
if (asev && (val <= alev || ((lalm == alev) && (val <= alev + hyst)))) {
if (recGblSetSevr(prec, LOW_ALARM, asev))
prec->lalm = alev;
return;
}
/* The sign of afvl indicates whether the result should be
* rounded up or down. This gives the filter hysteresis.
* If afvl > 0 the floor() function rounds to a lower alarm
* level, otherwise to a higher.
*/
afvl = alpha * afvl +
((afvl > 0) ? (1 - alpha) : (alpha - 1)) * alarmRange;
if (afvl - floor(afvl) > THRESHOLD)
afvl = -afvl; /* reverse rounding */
/* we get here only if val is out of alarm by at least hyst */
prec->lalm = val;
return;
alarmRange = abs((int)floor(afvl));
switch (alarmRange) {
case range_Hihi:
asev = prec->hhsv;
alev = prec->hihi;
break;
case range_High:
asev = prec->hsv;
alev = prec->high;
break;
case range_Normal:
asev = NO_ALARM;
break;
case range_Low:
asev = prec->lsv;
alev = prec->low;
break;
case range_Lolo:
asev = prec->llsv;
alev = prec->lolo;
break;
}
}
}
prec->afvl = afvl;
if (asev) {
/* Report alarm condition, store LALM for future HYST calculations */
if (recGblSetSevr(prec, range_stat[alarmRange], asev))
prec->lalm = alev;
} else {
/* No alarm condition, reset LALM */
prec->lalm = val;
}
}
static void convert(aiRecord *prec)
+10
View File
@@ -138,6 +138,11 @@ recordtype(ai) {
promptgroup(GUI_ALARMS)
interest(1)
}
field(AFTC,DBF_DOUBLE) {
prompt("Alarm Filter Time Constant")
promptgroup(GUI_ALARMS)
interest(1)
}
field(ADEL,DBF_DOUBLE) {
prompt("Archive Deadband")
promptgroup(GUI_DISPLAY)
@@ -153,6 +158,11 @@ recordtype(ai) {
special(SPC_NOMOD)
interest(3)
}
field(AFVL,DBF_DOUBLE) {
prompt("Alarm Filter Value")
special(SPC_NOMOD)
interest(3)
}
field(ALST,DBF_DOUBLE) {
prompt("Last Value Archived")
special(SPC_NOMOD)
+103 -39
View File
@@ -37,6 +37,9 @@
#undef GEN_SIZE_OFFSET
#include "epicsExport.h"
/* Hysterisis for alarm filtering: 1-1/e */
#define THRESHOLD 0.6321
/* Create RSET - Record Support Entry Table */
#define report NULL
@@ -79,7 +82,7 @@ rset calcRSET={
};
epicsExportAddress(rset, calcRSET);
static void checkAlarms(calcRecord *prec);
static void checkAlarms(calcRecord *prec, epicsTimeStamp *timeLast);
static void monitor(calcRecord *prec);
static int fetch_values(calcRecord *prec);
@@ -111,6 +114,8 @@ static long init_record(calcRecord *prec, int pass)
static long process(calcRecord *prec)
{
epicsTimeStamp timeLast;
prec->pact = TRUE;
if (fetch_values(prec) == 0) {
if (calcPerform(&prec->a, &prec->val, prec->rpcl)) {
@@ -118,9 +123,11 @@ static long process(calcRecord *prec)
} else
prec->udf = isnan(prec->val);
}
timeLast = prec->time;
recGblGetTimeStamp(prec);
/* check for alarms */
checkAlarms(prec);
checkAlarms(prec, &timeLast);
/* check event list */
monitor(prec);
/* process the forward scan link record */
@@ -273,14 +280,27 @@ static long get_alarm_double(DBADDR *paddr, struct dbr_alDouble *pad)
return 0;
}
static void checkAlarms(calcRecord *prec)
static void checkAlarms(calcRecord *prec, epicsTimeStamp *timeLast)
{
double val, hyst, lalm;
double alev;
enum {
range_Lolo = 1,
range_Low,
range_Normal,
range_High,
range_Hihi
} alarmRange;
static const epicsEnum16 range_stat[] = {
SOFT_ALARM, LOLO_ALARM, LOW_ALARM,
NO_ALARM, HIGH_ALARM, HIHI_ALARM
};
double val, hyst, lalm, alev, aftc, afvl;
epicsEnum16 asev;
if (prec->udf) {
recGblSetSevr(prec, UDF_ALARM, INVALID_ALARM);
prec->afvl = 0;
return;
}
@@ -288,45 +308,89 @@ static void checkAlarms(calcRecord *prec)
hyst = prec->hyst;
lalm = prec->lalm;
/* alarm condition hihi */
asev = prec->hhsv;
alev = prec->hihi;
if (asev && (val >= alev || ((lalm == alev) && (val >= alev - hyst)))) {
if (recGblSetSevr(prec, HIHI_ALARM, asev))
prec->lalm = alev;
return;
/* check VAL against alarm limits */
if ((asev = prec->hhsv) &&
(val >= (alev = prec->hihi) ||
((lalm == alev) && (val >= alev - hyst))))
alarmRange = range_Hihi;
else
if ((asev = prec->llsv) &&
(val <= (alev = prec->lolo) ||
((lalm == alev) && (val <= alev + hyst))))
alarmRange = range_Lolo;
else
if ((asev = prec->hsv) &&
(val >= (alev = prec->high) ||
((lalm == alev) && (val >= alev - hyst))))
alarmRange = range_High;
else
if ((asev = prec->lsv) &&
(val <= (alev = prec->low) ||
((lalm == alev) && (val <= alev + hyst))))
alarmRange = range_Low;
else {
alev = val;
asev = NO_ALARM;
alarmRange = range_Normal;
}
/* alarm condition lolo */
asev = prec->llsv;
alev = prec->lolo;
if (asev && (val <= alev || ((lalm == alev) && (val <= alev + hyst)))) {
if (recGblSetSevr(prec, LOLO_ALARM, asev))
aftc = prec->aftc;
afvl = 0;
if (aftc > 0) {
/* Apply level filtering */
afvl = prec->afvl;
if (afvl == 0) {
afvl = (double)alarmRange;
} else {
double t = epicsTimeDiffInSeconds(&prec->time, timeLast);
double alpha = aftc / (t + aftc);
/* The sign of afvl indicates whether the result should be
* rounded up or down. This gives the filter hysteresis.
* If afvl > 0 the floor() function rounds to a lower alarm
* level, otherwise to a higher.
*/
afvl = alpha * afvl +
((afvl > 0) ? (1 - alpha) : (alpha - 1)) * alarmRange;
if (afvl - floor(afvl) > THRESHOLD)
afvl = -afvl; /* reverse rounding */
alarmRange = abs((int)floor(afvl));
switch (alarmRange) {
case range_Hihi:
asev = prec->hhsv;
alev = prec->hihi;
break;
case range_High:
asev = prec->hsv;
alev = prec->high;
break;
case range_Normal:
asev = NO_ALARM;
break;
case range_Low:
asev = prec->lsv;
alev = prec->low;
break;
case range_Lolo:
asev = prec->llsv;
alev = prec->lolo;
break;
}
}
}
prec->afvl = afvl;
if (asev) {
/* Report alarm condition, store LALM for future HYST calculations */
if (recGblSetSevr(prec, range_stat[alarmRange], asev))
prec->lalm = alev;
return;
} else {
/* No alarm condition, reset LALM */
prec->lalm = val;
}
/* alarm condition high */
asev = prec->hsv;
alev = prec->high;
if (asev && (val >= alev || ((lalm == alev) && (val >= alev - hyst)))) {
if (recGblSetSevr(prec, HIGH_ALARM, asev))
prec->lalm = alev;
return;
}
/* alarm condition low */
asev = prec->lsv;
alev = prec->low;
if (asev && (val <= alev || ((lalm == alev) && (val <= alev + hyst)))) {
if (recGblSetSevr(prec, LOW_ALARM, asev))
prec->lalm = alev;
return;
}
/* we get here only if val is out of alarm by at least hyst */
prec->lalm = val;
return;
}
static void monitor(calcRecord *prec)
+10
View File
@@ -153,6 +153,16 @@ recordtype(calc) {
interest(1)
menu(menuAlarmSevr)
}
field(AFTC, DBF_DOUBLE) {
prompt("Alarm Filter Time Constant")
promptgroup(GUI_ALARMS)
interest(1)
}
field(AFVL, DBF_DOUBLE) {
prompt("Alarm Filter Value")
special(SPC_NOMOD)
interest(3)
}
field(HYST,DBF_DOUBLE) {
prompt("Alarm Deadband")
promptgroup(GUI_ALARMS)
+98 -37
View File
@@ -37,6 +37,8 @@
#undef GEN_SIZE_OFFSET
#include "epicsExport.h"
/* Hysterisis for alarm filtering: 1-1/e */
#define THRESHOLD 0.6321
/* Create RSET - Record Support Entry Table*/
#define report NULL
#define initialize NULL
@@ -87,7 +89,7 @@ struct longindset { /* longin input dset */
DEVSUPFUN get_ioint_info;
DEVSUPFUN read_longin; /*returns: (-1,0)=>(failure,success)*/
};
static void checkAlarms(longinRecord *prec);
static void checkAlarms(longinRecord *prec, epicsTimeStamp *timeLast);
static void monitor(longinRecord *prec);
static long readValue(longinRecord *prec);
@@ -132,12 +134,14 @@ static long process(longinRecord *prec)
struct longindset *pdset = (struct longindset *)(prec->dset);
long status;
unsigned char pact=prec->pact;
epicsTimeStamp timeLast;
if( (pdset==NULL) || (pdset->read_longin==NULL) ) {
prec->pact=TRUE;
recGblRecordError(S_dev_missingSup,(void *)prec,"read_longin");
return(S_dev_missingSup);
}
timeLast = prec->time;
status=readValue(prec); /* read the new value */
/* check if device support set pact */
@@ -148,7 +152,7 @@ static long process(longinRecord *prec)
if (status==0) prec->udf = FALSE;
/* check for alarms */
checkAlarms(prec);
checkAlarms(prec, &timeLast);
/* check event list */
monitor(prec);
/* process the forward scan link record */
@@ -230,14 +234,28 @@ static long get_alarm_double(DBADDR *paddr, struct dbr_alDouble *pad)
return(0);
}
static void checkAlarms(longinRecord *prec)
static void checkAlarms(longinRecord *prec, epicsTimeStamp *timeLast)
{
enum {
range_Lolo = 1,
range_Low,
range_Normal,
range_High,
range_Hihi
} alarmRange;
static const epicsEnum16 range_stat[] = {
SOFT_ALARM, LOLO_ALARM, LOW_ALARM,
NO_ALARM, HIGH_ALARM, HIHI_ALARM
};
double aftc, afvl;
epicsInt32 val, hyst, lalm;
epicsInt32 alev;
epicsEnum16 asev;
if (prec->udf) {
recGblSetSevr(prec, UDF_ALARM, INVALID_ALARM);
prec->afvl = 0;
return;
}
@@ -245,45 +263,88 @@ static void checkAlarms(longinRecord *prec)
hyst = prec->hyst;
lalm = prec->lalm;
/* alarm condition hihi */
asev = prec->hhsv;
alev = prec->hihi;
if (asev && (val >= alev || ((lalm == alev) && (val >= alev - hyst)))) {
if (recGblSetSevr(prec, HIHI_ALARM, asev))
prec->lalm = alev;
return;
/* check VAL against alarm limits */
if ((asev = prec->hhsv) &&
(val >= (alev = prec->hihi) ||
((lalm == alev) && (val >= alev - hyst))))
alarmRange = range_Hihi;
else
if ((asev = prec->llsv) &&
(val <= (alev = prec->lolo) ||
((lalm == alev) && (val <= alev + hyst))))
alarmRange = range_Lolo;
else
if ((asev = prec->hsv) &&
(val >= (alev = prec->high) ||
((lalm == alev) && (val >= alev - hyst))))
alarmRange = range_High;
else
if ((asev = prec->lsv) &&
(val <= (alev = prec->low) ||
((lalm == alev) && (val <= alev + hyst))))
alarmRange = range_Low;
else {
alev = val;
asev = NO_ALARM;
alarmRange = range_Normal;
}
/* alarm condition lolo */
asev = prec->llsv;
alev = prec->lolo;
if (asev && (val <= alev || ((lalm == alev) && (val <= alev + hyst)))) {
if (recGblSetSevr(prec, LOLO_ALARM, asev))
prec->lalm = alev;
return;
}
aftc = prec->aftc;
afvl = 0;
/* alarm condition high */
asev = prec->hsv;
alev = prec->high;
if (asev && (val >= alev || ((lalm == alev) && (val >= alev - hyst)))) {
if (recGblSetSevr(prec, HIGH_ALARM, asev))
prec->lalm = alev;
return;
}
if (aftc > 0) {
/* Apply level filtering */
afvl = prec->afvl;
if (afvl == 0) {
afvl = (double)alarmRange;
} else {
double t = epicsTimeDiffInSeconds(&prec->time, timeLast);
double alpha = aftc / (t + aftc);
/* alarm condition low */
asev = prec->lsv;
alev = prec->low;
if (asev && (val <= alev || ((lalm == alev) && (val <= alev + hyst)))) {
if (recGblSetSevr(prec, LOW_ALARM, asev))
prec->lalm = alev;
return;
}
/* The sign of afvl indicates whether the result should be
* rounded up or down. This gives the filter hysteresis.
* If afvl > 0 the floor() function rounds to a lower alarm
* level, otherwise to a higher.
*/
afvl = alpha * afvl +
((afvl > 0) ? (1 - alpha) : (alpha - 1)) * alarmRange;
if (afvl - floor(afvl) > THRESHOLD)
afvl = -afvl; /* reverse rounding */
/* we get here only if val is out of alarm by at least hyst */
prec->lalm = val;
return;
alarmRange = abs((int)floor(afvl));
switch (alarmRange) {
case range_Hihi:
asev = prec->hhsv;
alev = prec->hihi;
break;
case range_High:
asev = prec->hsv;
alev = prec->high;
break;
case range_Normal:
asev = NO_ALARM;
break;
case range_Low:
asev = prec->lsv;
alev = prec->low;
break;
case range_Lolo:
asev = prec->llsv;
alev = prec->lolo;
break;
}
}
}
prec->afvl = afvl;
if (asev) {
/* Report alarm condition, store LALM for future HYST calculations */
if (recGblSetSevr(prec, range_stat[alarmRange], asev))
prec->lalm = alev;
} else {
/* No alarm condition, reset LALM */
prec->lalm = val;
}
}
/* DELTA calculates the absolute difference between its arguments
+10
View File
@@ -93,6 +93,16 @@ recordtype(longin) {
promptgroup(GUI_ALARMS)
interest(1)
}
field(AFTC, DBF_DOUBLE) {
prompt("Alarm Filter Time Constant")
promptgroup(GUI_ALARMS)
interest(1)
}
field(AFVL, DBF_DOUBLE) {
prompt("Alarm Filter Value")
special(SPC_NOMOD)
interest(3)
}
field(ADEL,DBF_LONG) {
prompt("Archive Deadband")
promptgroup(GUI_DISPLAY)
+43 -3
View File
@@ -36,6 +36,9 @@
#include "mbbiRecord.h"
#undef GEN_SIZE_OFFSET
#include "epicsExport.h"
/* Hysterisis for alarm filtering: 1-1/e */
#define THRESHOLD 0.6321
/* Create RSET - Record Support Entry Table*/
#define report NULL
#define initialize NULL
@@ -84,7 +87,7 @@ struct mbbidset { /* multi bit binary input dset */
DEVSUPFUN get_ioint_info;
DEVSUPFUN read_mbbi;/*(0,2)=>(success, success no convert)*/
};
static void checkAlarms(mbbiRecord *);
static void checkAlarms(mbbiRecord *, epicsTimeStamp *);
static void monitor(mbbiRecord *);
static long readValue(mbbiRecord *);
@@ -146,6 +149,7 @@ static long process(mbbiRecord *prec)
struct mbbidset *pdset = (struct mbbidset *)(prec->dset);
long status;
unsigned char pact=prec->pact;
epicsTimeStamp timeLast;
if( (pdset==NULL) || (pdset->read_mbbi==NULL) ) {
prec->pact=TRUE;
@@ -153,6 +157,8 @@ static long process(mbbiRecord *prec)
return(S_dev_missingSup);
}
timeLast = prec->time;
status=readValue(prec); /* read the new value */
/* check if device support set pact */
if ( !pact && prec->pact ) return(0);
@@ -184,7 +190,7 @@ static long process(mbbiRecord *prec)
else if(status == 2) status = 0;
/* check for alarms */
checkAlarms(prec);
checkAlarms(prec, &timeLast);
/* check event list */
monitor(prec);
@@ -279,14 +285,20 @@ static long put_enum_str(DBADDR *paddr, char *pstring)
return(S_db_badChoice);
}
static void checkAlarms(mbbiRecord *prec)
static void checkAlarms(mbbiRecord *prec, epicsTimeStamp *timeLast)
{
double aftc, afvl;
unsigned short alarm;
epicsEnum16 asev;
unsigned short *severities;
unsigned short val=prec->val;
/* check for udf alarm */
if(prec->udf == TRUE ){
recGblSetSevr(prec,UDF_ALARM,INVALID_ALARM);
prec->afvl = 0;
}
/* check for state alarm */
@@ -296,9 +308,37 @@ static void checkAlarms(mbbiRecord *prec)
} else {
/* in a state which is an error */
severities = (unsigned short *)&(prec->zrsv);
/*
recGblSetSevr(prec,STATE_ALARM,severities[prec->val]);
*/
alarm = severities[prec->val];
}
aftc = prec->aftc;
afvl = 0.;
if(aftc > 0.) {
afvl = prec->afvl;
if(afvl == 0.) {
afvl = (double) alarm;
} else {
double t = epicsTimeDiffInSeconds(&prec->time, timeLast);
double alpha = aftc / (t + aftc);
afvl = alpha * afvl +
((afvl>0.)?(1.-alpha):(alpha-1.)) * alarm;
if(afvl - floor(afvl) > THRESHOLD)
afvl = -afvl;
alarm = abs((int)floor(afvl));
}
}
asev = alarm;
recGblSetSevr(prec, STATE_ALARM, asev);
/* check for cos alarm */
if(val == prec->lalm) return;
recGblSetSevr(prec,COS_ALARM,prec->cosv);
+10
View File
@@ -394,6 +394,16 @@ recordtype(mbbi) {
interest(1)
menu(menuAlarmSevr)
}
field(AFTC, DBF_DOUBLE) {
prompt("Alarm Filter Time Constant")
promptgroup(GUI_ALARMS)
interest(1)
}
field(AFVL, DBF_DOUBLE) {
prompt("Alarm Filter Value")
special(SPC_NOMOD)
interest(3)
}
field(UNSV,DBF_MENU) {
prompt("Unknown State Severity")
promptgroup(GUI_MBB)