From 0af48f5a29f93c080d86591ddabffab0a7de95bb Mon Sep 17 00:00:00 2001
From: Andrew Johnson
Date: Wed, 6 Jan 2010 16:05:49 -0600
Subject: [PATCH 1/3] Alarm filter changes by Bernd Schoeneburg Algorithm by
Eric Norum. Developed at the 2009 EPICS Codeathon.
---
src/std/rec/aiRecord.c | 140 ++++++++++++++++++++++++++++-----------
src/std/rec/aiRecord.dbd | 10 +++
2 files changed, 110 insertions(+), 40 deletions(-)
diff --git a/src/std/rec/aiRecord.c b/src/std/rec/aiRecord.c
index 158456e88..7f2ce4bde 100644
--- a/src/std/rec/aiRecord.c
+++ b/src/std/rec/aiRecord.c
@@ -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)
diff --git a/src/std/rec/aiRecord.dbd b/src/std/rec/aiRecord.dbd
index a7518ab73..52fd4b05b 100644
--- a/src/std/rec/aiRecord.dbd
+++ b/src/std/rec/aiRecord.dbd
@@ -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)
From 824d378117822298d803975bb4caec71de04d33c Mon Sep 17 00:00:00 2001
From: Kukhee Kim
Date: Fri, 28 May 2010 02:16:45 -0700
Subject: [PATCH 2/3] add the alarm filter for ai, calc, longin, mbbi type
records
---
src/std/rec/calcRecord.c | 142 +++++++++++++++++++++++++----------
src/std/rec/calcRecord.dbd | 10 +++
src/std/rec/longinRecord.c | 135 ++++++++++++++++++++++++---------
src/std/rec/longinRecord.dbd | 10 +++
src/std/rec/mbbiRecord.c | 46 +++++++++++-
src/std/rec/mbbiRecord.dbd | 10 +++
6 files changed, 274 insertions(+), 79 deletions(-)
diff --git a/src/std/rec/calcRecord.c b/src/std/rec/calcRecord.c
index 85e66f538..acbc85668 100644
--- a/src/std/rec/calcRecord.c
+++ b/src/std/rec/calcRecord.c
@@ -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)
diff --git a/src/std/rec/calcRecord.dbd b/src/std/rec/calcRecord.dbd
index 0248f34cd..b4d4cc99b 100644
--- a/src/std/rec/calcRecord.dbd
+++ b/src/std/rec/calcRecord.dbd
@@ -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)
diff --git a/src/std/rec/longinRecord.c b/src/std/rec/longinRecord.c
index b41bbe506..a625b56e4 100644
--- a/src/std/rec/longinRecord.c
+++ b/src/std/rec/longinRecord.c
@@ -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
diff --git a/src/std/rec/longinRecord.dbd b/src/std/rec/longinRecord.dbd
index 4bd734018..ef9506e3d 100644
--- a/src/std/rec/longinRecord.dbd
+++ b/src/std/rec/longinRecord.dbd
@@ -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)
diff --git a/src/std/rec/mbbiRecord.c b/src/std/rec/mbbiRecord.c
index eddc188eb..fd8ebc98a 100644
--- a/src/std/rec/mbbiRecord.c
+++ b/src/std/rec/mbbiRecord.c
@@ -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);
diff --git a/src/std/rec/mbbiRecord.dbd b/src/std/rec/mbbiRecord.dbd
index b0c33ad9e..bbb9fd17a 100644
--- a/src/std/rec/mbbiRecord.dbd
+++ b/src/std/rec/mbbiRecord.dbd
@@ -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)
From d9e066ac29a2b30809a3544a99f5e624aef419e0 Mon Sep 17 00:00:00 2001
From: Andrew Johnson
Date: Fri, 9 Mar 2012 12:22:45 -0600
Subject: [PATCH 3/3] Documentation on alarm filtering (AFTC field)
---
documentation/RELEASE_NOTES.html | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/documentation/RELEASE_NOTES.html b/documentation/RELEASE_NOTES.html
index 100cf023c..1d9fd1f75 100644
--- a/documentation/RELEASE_NOTES.html
+++ b/documentation/RELEASE_NOTES.html
@@ -15,6 +15,23 @@ EPICS Base 3.15.0.x releases are not intended for use in production systems.
Changes between 3.14.x and 3.15.0.x
+Alarm filtering added to input record types
+
+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.
+
+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.
+
Post events on Waveform record's NORD field
When the record type or soft device support modify the NORD field of a