diff --git a/documentation/RELEASE_NOTES.html b/documentation/RELEASE_NOTES.html
index a66c0ad30..5ba14b373 100644
--- a/documentation/RELEASE_NOTES.html
+++ b/documentation/RELEASE_NOTES.html
@@ -20,6 +20,12 @@
-->
+
compressRecord buffering order
+
+The compressRecord has a new field BALG which can select between
+FIFO (append) and LIFO (prepend) ordering for insertion of new elements. FIFO
+ordering is the default, matching the behviour of previous versions.
+
Valgrind Instrumentation
Valgrind is a software debugging suite provided by many Linux distributions.
diff --git a/src/std/filters/arr.c b/src/std/filters/arr.c
index 801e69277..9e183ac15 100644
--- a/src/std/filters/arr.c
+++ b/src/std/filters/arr.c
@@ -33,8 +33,7 @@ typedef struct myStruct {
static void *myStructFreeList;
-static const
-chfPluginArgDef opts[] = {
+static const chfPluginArgDef opts[] = {
chfInt32 (myStruct, start, "s", 0, 1),
chfInt32 (myStruct, incr, "i", 0, 1),
chfInt32 (myStruct, end, "e", 0, 1),
@@ -67,13 +66,16 @@ static int parse_ok(void *pvt)
return 0;
}
-static void freeArray(db_field_log *pfl) {
+static void freeArray(db_field_log *pfl)
+{
if (pfl->type == dbfl_type_ref) {
freeListFree(pfl->u.r.pvt, pfl->u.r.field);
}
}
-static long wrapArrayIndices(long *start, const long increment, long *end, const long no_elements) {
+static long wrapArrayIndices(long *start, const long increment, long *end,
+ const long no_elements)
+{
long len = 0;
if (*start < 0) *start = no_elements + *start;
@@ -88,7 +90,8 @@ static long wrapArrayIndices(long *start, const long increment, long *end, const
return len;
}
-static db_field_log* filter(void* pvt, dbChannel *chan, db_field_log *pfl) {
+static db_field_log* filter(void* pvt, dbChannel *chan, db_field_log *pfl)
+{
myStruct *my = (myStruct*) pvt;
struct dbCommon *prec;
struct rset *prset;
@@ -97,13 +100,16 @@ static db_field_log* filter(void* pvt, dbChannel *chan, db_field_log *pfl) {
long nTarget = 0;
long offset = 0;
long nSource = chan->addr.no_elements;
+ long capacity = nSource;
+ void *pdst;
- /* Only array data */
- if (pfl->type == dbfl_type_val) {
- return pfl;
+ switch (pfl->type) {
+ case dbfl_type_val:
+ /* Only filter arrays */
+ break;
- /* Extract from record */
- } else if (pfl->type == dbfl_type_rec) {
+ case dbfl_type_rec:
+ /* Extract from record */
if (chan->addr.special == SPC_DBADDR &&
nSource > 1 &&
(prset = dbGetRset(&chan->addr)) &&
@@ -122,32 +128,36 @@ static db_field_log* filter(void* pvt, dbChannel *chan, db_field_log *pfl) {
pfl->field_size = chan->addr.field_size;
pfl->no_elements = nTarget;
if (nTarget) {
- void *pdst = freeListCalloc(my->arrayFreeList);
+ pdst = freeListCalloc(my->arrayFreeList);
if (pdst) {
pfl->u.r.dtor = freeArray;
pfl->u.r.pvt = my->arrayFreeList;
offset = (offset + start) % chan->addr.no_elements;
- dbExtractArrayFromRec(&chan->addr, pdst, nTarget, nSource, offset, my->incr);
+ dbExtractArrayFromRec(&chan->addr, pdst, nTarget, capacity,
+ offset, my->incr);
pfl->u.r.field = pdst;
}
}
dbScanUnlock(prec);
chan->addr.pfield = pfieldsave;
}
+ break;
/* Extract from buffer */
- } else if (pfl->type == dbfl_type_ref) {
- void *psrc = pfl->u.r.field;
- void *pdst = NULL;
-
+ case dbfl_type_ref:
+ pdst = NULL;
nSource = pfl->no_elements;
nTarget = wrapArrayIndices(&start, my->incr, &end, nSource);
pfl->no_elements = nTarget;
- if (nTarget) { /* Copy the data out */
+ if (nTarget) {
+ /* Copy the data out */
+ void *psrc = pfl->u.r.field;
+
pdst = freeListCalloc(my->arrayFreeList);
- if (!pdst) return pfl;
+ if (!pdst) break;
offset = start;
- dbExtractArrayFromBuf(psrc, pdst, pfl->field_size, pfl->field_type, nTarget, nSource, offset, my->incr);
+ dbExtractArrayFromBuf(psrc, pdst, pfl->field_size, pfl->field_type,
+ nTarget, nSource, offset, my->incr);
}
if (pfl->u.r.dtor) pfl->u.r.dtor(pfl);
if (nTarget) {
@@ -155,21 +165,22 @@ static db_field_log* filter(void* pvt, dbChannel *chan, db_field_log *pfl) {
pfl->u.r.pvt = my->arrayFreeList;
pfl->u.r.field = pdst;
}
+ break;
}
return pfl;
}
static void channelRegisterPost(dbChannel *chan, void *pvt,
- chPostEventFunc **cb_out, void **arg_out, db_field_log *probe)
+ chPostEventFunc **cb_out, void **arg_out, db_field_log *probe)
{
myStruct *my = (myStruct*) pvt;
long start = my->start;
long end = my->end;
long max = 0;
- if (probe->no_elements <= 1) return; /* array data only */
+ if (probe->no_elements <= 1) return; /* array data only */
- max = wrapArrayIndices(&start, my->incr, &end, probe->no_elements); /* wrap indices into array */
+ max = wrapArrayIndices(&start, my->incr, &end, probe->no_elements);
if (max) {
if (!my->arrayFreeList)
freeListInitPvt(&my->arrayFreeList, max * probe->field_size, 2);
@@ -180,7 +191,8 @@ static void channelRegisterPost(dbChannel *chan, void *pvt,
*arg_out = pvt;
}
-static void channel_report(dbChannel *chan, void *pvt, int level, const unsigned short indent)
+static void channel_report(dbChannel *chan, void *pvt, int level,
+ const unsigned short indent)
{
myStruct *my = (myStruct*) pvt;
printf("%*sArray (arr): start=%d, incr=%d, end=%d\n", indent, "",
diff --git a/src/std/rec/compressRecord.c b/src/std/rec/compressRecord.c
index 0ecfbd4e5..c6bdbd869 100644
--- a/src/std/rec/compressRecord.c
+++ b/src/std/rec/compressRecord.c
@@ -89,9 +89,12 @@ static void reset(compressRecord *prec)
prec->cvb = 0.0;
prec->res = 0;
/* allocate memory for the summing buffer for conversions requiring it */
- if (prec->alg == compressALG_Average && prec->sptr == 0){
+ if (prec->alg == compressALG_Average && prec->sptr == NULL) {
prec->sptr = calloc(prec->nsam, sizeof(double));
}
+
+ if (prec->bptr && prec->nsam)
+ memset(prec->bptr, 0, prec->nsam * sizeof(double));
}
static void monitor(compressRecord *prec)
@@ -108,29 +111,32 @@ static void monitor(compressRecord *prec)
static void put_value(compressRecord *prec, double *psource, int n)
{
- epicsInt32 offset = prec->off;
- epicsInt32 nuse = prec->nuse;
- epicsInt32 nsam = prec->nsam;
- double *pdest = prec->bptr + offset;
- int i;
+ int fifo = (prec->balg == bufferingALG_FIFO);
+ epicsUInt32 offset = prec->off;
+ epicsUInt32 nuse = prec->nuse;
+ epicsUInt32 nsam = prec->nsam;
- for (i = 0; i < n; i++) {
- *pdest = *psource++;
- if (++offset >= nsam) {
- pdest = prec->bptr;
- offset = 0;
- }
- else
- pdest++;
- }
nuse += n;
if (nuse > nsam)
nuse = nsam;
+
+ while (n--) {
+ /* for LIFO, decrement before */
+ if (!fifo)
+ offset = (offset - 1) % nsam;
+
+ prec->bptr[offset] = *psource++;
+
+ /* for FIFO, increment after */
+ if (fifo)
+ offset = (offset + 1) % nsam;
+ }
+
prec->off = offset;
prec->nuse = nuse;
- return;
}
-
+
+
/* qsort comparison function (for median calculation) */
static int compare(const void *arg1, const void *arg2)
{
@@ -145,157 +151,167 @@ static int compare(const void *arg1, const void *arg2)
static int compress_array(compressRecord *prec,
double *psource, int no_elements)
{
- epicsInt32 i,j;
- epicsInt32 nnew;
- epicsInt32 nsam=prec->nsam;
- double value;
- epicsInt32 n;
+ epicsInt32 i,j;
+ epicsInt32 n, nnew;
+ epicsInt32 nsam = prec->nsam;
+ double value;
- /* skip out of limit data */
- if (prec->ilil < prec->ihil){
- while (((*psource < prec->ilil) || (*psource > prec->ihil))
- && (no_elements > 0)){
- no_elements--;
- psource++;
- }
- }
- if(prec->n <= 0) prec->n = 1;
- n = prec->n;
- if(no_elementsalg){
- case (compressALG_N_to_1_Low_Value):
- /* compress N to 1 keeping the lowest value */
- for (i = 0; i < nnew; i++){
- value = *psource++;
- for (j = 1; j < n; j++, psource++){
- if (value > *psource) value = *psource;
- }
- put_value(prec,&value,1);
- }
- break;
- case (compressALG_N_to_1_High_Value):
- /* compress N to 1 keeping the highest value */
- for (i = 0; i < nnew; i++){
- value = *psource++;
- for (j = 1; j < n; j++, psource++){
- if (value < *psource) value = *psource;
- }
- put_value(prec,&value,1);
- }
- break;
- case (compressALG_N_to_1_Average):
- /* compress N to 1 keeping the average value */
- for (i = 0; i < nnew; i++){
- value = 0;
- for (j = 0; j < n; j++, psource++)
- value += *psource;
- value /= n;
- put_value(prec,&value,1);
- }
- break;
- case (compressALG_N_to_1_Median):
- /* compress N to 1 keeping the median value */
- /* note: sorts source array (OK; it's a work pointer) */
- for (i = 0; i < nnew; i++, psource+=nnew){
- qsort(psource,n,sizeof(double),compare);
- value=psource[n/2];
- put_value(prec,&value,1);
- }
- break;
+ /* skip out of limit data */
+ if (prec->ilil < prec->ihil) {
+ while (((*psource < prec->ilil) || (*psource > prec->ihil))
+ && (no_elements > 0)) {
+ no_elements--;
+ psource++;
}
- return(0);
+ }
+ if (prec->n <= 0)
+ prec->n = 1;
+ n = prec->n;
+ if (no_elements < n)
+ return 1; /*dont do anything*/
+
+ /* determine number of samples to take */
+ if (no_elements < nsam * n)
+ nnew = (no_elements / n);
+ else nnew = nsam;
+
+ /* compress according to specified algorithm */
+ switch (prec->alg){
+ case compressALG_N_to_1_Low_Value:
+ /* compress N to 1 keeping the lowest value */
+ for (i = 0; i < nnew; i++) {
+ value = *psource++;
+ for (j = 1; j < n; j++, psource++) {
+ if (value > *psource)
+ value = *psource;
+ }
+ put_value(prec, &value, 1);
+ }
+ break;
+ case compressALG_N_to_1_High_Value:
+ /* compress N to 1 keeping the highest value */
+ for (i = 0; i < nnew; i++){
+ value = *psource++;
+ for (j = 1; j < n; j++, psource++) {
+ if (value < *psource)
+ value = *psource;
+ }
+ put_value(prec, &value, 1);
+ }
+ break;
+ case compressALG_N_to_1_Average:
+ /* compress N to 1 keeping the average value */
+ for (i = 0; i < nnew; i++) {
+ value = 0;
+ for (j = 0; j < n; j++, psource++)
+ value += *psource;
+ value /= n;
+ put_value(prec, &value, 1);
+ }
+ break;
+
+ case compressALG_N_to_1_Median:
+ /* compress N to 1 keeping the median value */
+ /* note: sorts source array (OK; it's a work pointer) */
+ for (i = 0; i < nnew; i++, psource += nnew) {
+ qsort(psource, n, sizeof(double), compare);
+ value = psource[n / 2];
+ put_value(prec, &value, 1);
+ }
+ break;
+ }
+ return 0;
}
-
+
static int array_average(compressRecord *prec,
- double *psource,epicsInt32 no_elements)
+ double *psource, epicsInt32 no_elements)
{
- epicsInt32 i;
- epicsInt32 nnow;
- epicsInt32 nsam=prec->nsam;
- double *psum;
- double multiplier;
- epicsInt32 inx=prec->inx;
- epicsInt32 nuse,n;
+ epicsInt32 i;
+ epicsInt32 nnow;
+ epicsInt32 nsam=prec->nsam;
+ double *psum;
+ double multiplier;
+ epicsInt32 inx = prec->inx;
+ epicsInt32 nuse, n;
- nuse = nsam;
- if(nuse>no_elements) nuse = no_elements;
- nnow=nuse;
- if(nnow>no_elements) nnow=no_elements;
- psum = (double *)prec->sptr;
+ nuse = nsam;
+ if (nuse > no_elements)
+ nuse = no_elements;
+ nnow = nuse;
+ if (nnow > no_elements)
+ nnow=no_elements;
+ psum = (double *)prec->sptr;
- /* add in the new waveform */
- if (inx == 0){
- for (i = 0; i < nnow; i++)
- *psum++ = *psource++;
- for(i=nnow; in<=0)prec->n=1;
- n = prec->n;
- if (inxinx = inx;
- return(1);
- }
- if(n>1) {
- psum = (double *)prec->sptr;
- multiplier = 1.0/((double)n);
- for (i = 0; i < nuse; i++, psum++)
- *psum = *psum * multiplier;
- }
- put_value(prec,prec->sptr,nuse);
- prec->inx = 0;
- return(0);
+ /* do we need to calculate the result */
+ inx++;
+ if (prec->n <= 0)
+ prec->n = 1;
+ n = prec->n;
+ if (inx < n) {
+ prec->inx = inx;
+ return 1;
+ }
+ if (n > 1) {
+ psum = (double *)prec->sptr;
+ multiplier = 1.0 / n;
+ for (i = 0; i < nuse; i++, psum++)
+ *psum = *psum * multiplier;
+ }
+ put_value(prec, prec->sptr, nuse);
+ prec->inx = 0;
+ return 0;
}
static int compress_scalar(struct compressRecord *prec,double *psource)
{
- double value = *psource;
- double *pdest=&prec->cvb;
- epicsInt32 inx = prec->inx;
+ double value = *psource;
+ double *pdest=&prec->cvb;
+ epicsInt32 inx = prec->inx;
- /* compress according to specified algorithm */
- switch (prec->alg){
- case (compressALG_N_to_1_Low_Value):
- if ((value < *pdest) || (inx == 0))
- *pdest = value;
- break;
- case (compressALG_N_to_1_High_Value):
- if ((value > *pdest) || (inx == 0))
- *pdest = value;
- break;
- /* for scalars, Median not implemented => use average */
- case (compressALG_N_to_1_Average):
- case (compressALG_N_to_1_Median):
- if (inx == 0)
- *pdest = value;
- else {
- *pdest += value;
- if(inx+1>=(prec->n)) *pdest = *pdest/(inx+1);
- }
- break;
- }
- inx++;
- if(inx>=prec->n) {
- put_value(prec,pdest,1);
- prec->inx = 0;
- return(0);
- } else {
- prec->inx = inx;
- return(1);
- }
+ /* compress according to specified algorithm */
+ switch (prec->alg) {
+ case (compressALG_N_to_1_Low_Value):
+ if ((value < *pdest) || (inx == 0))
+ *pdest = value;
+ break;
+ case (compressALG_N_to_1_High_Value):
+ if ((value > *pdest) || (inx == 0))
+ *pdest = value;
+ break;
+ /* for scalars, Median not implemented => use average */
+ case (compressALG_N_to_1_Average):
+ case (compressALG_N_to_1_Median):
+ if (inx == 0)
+ *pdest = value;
+ else {
+ *pdest += value;
+ if (inx + 1 >= prec->n)
+ *pdest = *pdest / (inx + 1);
+ }
+ break;
+ }
+ inx++;
+ if (inx >= prec->n) {
+ put_value(prec,pdest,1);
+ prec->inx = 0;
+ return 0;
+ } else {
+ prec->inx = inx;
+ return 1;
+ }
}
-
+
/*Beginning of record support routines*/
static long init_record(compressRecord *prec, int pass)
{
@@ -305,6 +321,7 @@ static long init_record(compressRecord *prec, int pass)
prec->bptr = calloc(prec->nsam, sizeof(double));
reset(prec);
}
+
return 0;
}
@@ -350,6 +367,7 @@ static long process(compressRecord *prec)
else
status = 1;
}
+
/* check event list */
if (status != 1) {
prec->udf = FALSE;
@@ -358,10 +376,11 @@ static long process(compressRecord *prec)
/* process the forward scan link record */
recGblFwdLink(prec);
}
+
prec->pact = FALSE;
return 0;
}
-
+
static long special(DBADDR *paddr, int after)
{
compressRecord *prec = (compressRecord *) paddr->precord;
@@ -388,18 +407,31 @@ static long cvt_dbaddr(DBADDR *paddr)
paddr->field_type = DBF_DOUBLE;
paddr->field_size = sizeof(double);
paddr->dbr_field_type = DBF_DOUBLE;
+
+ if (prec->balg == bufferingALG_LIFO)
+ paddr->special = SPC_NOMOD;
return 0;
}
static long get_array_info(DBADDR *paddr, long *no_elements, long *offset)
{
+ /* offset indicates the next element which would be written.
+ * In FIFO mode offset-1 is the last valid element
+ * In LIFO mode offset is the first valid element
+ * (*offset) should be set to the index of the first valid element
+ */
compressRecord *prec = (compressRecord *) paddr->precord;
+ epicsUInt32 off = prec->off;
+ epicsUInt32 nuse = prec->nuse;
+ epicsUInt32 nsam = prec->nsam;
+
+ *no_elements = nuse;
+ if (prec->balg == bufferingALG_FIFO) {
+ *offset = (off - nuse) % nsam;
+ } else {
+ *offset = off;
+ }
- *no_elements = prec->nuse;
- if (prec->nuse == prec->nsam)
- *offset = prec->off;
- else
- *offset = 0;
return 0;
}
@@ -407,7 +439,8 @@ static long put_array_info(DBADDR *paddr, long nNew)
{
compressRecord *prec = (compressRecord *) paddr->precord;
- prec->off = (prec->off + nNew) % prec->nsam;
+ if (prec->balg == bufferingALG_FIFO)
+ prec->off = (prec->off + nNew) % prec->nsam;
prec->nuse += nNew;
if (prec->nuse > prec->nsam)
prec->nuse = prec->nsam;
@@ -440,14 +473,14 @@ static long get_graphic_double(DBADDR *paddr, struct dbr_grDouble *pgd)
compressRecord *prec = (compressRecord *) paddr->precord;
switch (dbGetFieldIndex(paddr)) {
- case indexof(VAL):
- case indexof(IHIL):
- case indexof(ILIL):
- pgd->upper_disp_limit = prec->hopr;
- pgd->lower_disp_limit = prec->lopr;
- break;
- default:
- recGblGetGraphicDouble(paddr,pgd);
+ case indexof(VAL):
+ case indexof(IHIL):
+ case indexof(ILIL):
+ pgd->upper_disp_limit = prec->hopr;
+ pgd->lower_disp_limit = prec->lopr;
+ break;
+ default:
+ recGblGetGraphicDouble(paddr,pgd);
}
return 0;
}
@@ -457,14 +490,14 @@ static long get_control_double(DBADDR *paddr, struct dbr_ctrlDouble *pcd)
compressRecord *prec = (compressRecord *) paddr->precord;
switch (dbGetFieldIndex(paddr)) {
- case indexof(VAL):
- case indexof(IHIL):
- case indexof(ILIL):
- pcd->upper_ctrl_limit = prec->hopr;
- pcd->lower_ctrl_limit = prec->lopr;
- break;
- default:
- recGblGetControlDouble(paddr, pcd);
+ case indexof(VAL):
+ case indexof(IHIL):
+ case indexof(ILIL):
+ pcd->upper_ctrl_limit = prec->hopr;
+ pcd->lower_ctrl_limit = prec->lopr;
+ break;
+ default:
+ recGblGetControlDouble(paddr, pcd);
}
return 0;
}
diff --git a/src/std/rec/compressRecord.dbd.pod b/src/std/rec/compressRecord.dbd.pod
index 97b84a9a0..93d1e63bf 100644
--- a/src/std/rec/compressRecord.dbd.pod
+++ b/src/std/rec/compressRecord.dbd.pod
@@ -9,19 +9,34 @@
=title Compress Record (compress)
-...
+The data compression record is used to collect and compress data from arrays.
+When the INP field references a data array field, it immediately compresses the
+entire array into an element of an array using one of several algorithms,
+overwriting the previous element. If the INP field obtains its value from a
+scalar-value field, the compression record will collect a new sample each time
+the record is processed and add it to the compressed data array as a circular
+buffer.
+
+The INP link can also specify a constant; however, if this is the case, the
+compression algorithms are ignored, and the record support routines merely
+return after checking the FLNK field.
=head2 Record-specific Menus
=head3 Menu compressALG
-The ALG field which uses this menu controls the compression algorithm used.
-
-...
+The ALG field which uses this menu controls the compression algorithm used by
+the record.
=menu compressALG
-...
+=head3 Menu bufferingALG
+
+The BALG field which uses this menu controls whether new values are inserted at
+the beginning or the end of the VAL array.
+
+=menu bufferingALG
+
=head2 Parameter Fields
@@ -41,6 +56,10 @@ menu(compressALG) {
choice(compressALG_Circular_Buffer,"Circular Buffer")
choice(compressALG_N_to_1_Median,"N to 1 Median")
}
+menu(bufferingALG) {
+ choice(bufferingALG_FIFO, "FIFO Buffer")
+ choice(bufferingALG_LIFO, "LIFO Buffer")
+}
recordtype(compress) {
=fields VAL
@@ -76,6 +95,13 @@ recordtype(compress) {
interest(1)
menu(compressALG)
}
+ field(BALG,DBF_MENU) {
+ prompt("Buffering Algorithm")
+ promptgroup(GUI_ALARMS)
+ special(SPC_RESET)
+ interest(1)
+ menu(bufferingALG)
+ }
field(NSAM,DBF_ULONG) {
prompt("Number of Values")
promptgroup(GUI_COMPRESS)
diff --git a/src/std/rec/test/Makefile b/src/std/rec/test/Makefile
index 5e7a8fb29..f5a60a751 100644
--- a/src/std/rec/test/Makefile
+++ b/src/std/rec/test/Makefile
@@ -38,6 +38,13 @@ testHarness_SRCS += linkRetargetLinkTest.c
TESTFILES += ../linkRetargetLink.db
TESTS += linkRetargetLinkTest
+TESTPROD_HOST += compressTest
+compressTest_SRCS += compressTest.c
+compressTest_SRCS += recTestIoc_registerRecordDeviceDriver.cpp
+testHarness_SRCS += compressTest.c
+TESTFILES += ../compressTest.db
+TESTS += compressTest
+
TARGETS += $(COMMON_DIR)/asTestIoc.dbd
asTestIoc_DBD += base.dbd
asTestIoc_DBD += asTest.dbd
diff --git a/src/std/rec/test/compressTest.c b/src/std/rec/test/compressTest.c
new file mode 100644
index 000000000..99a937115
--- /dev/null
+++ b/src/std/rec/test/compressTest.c
@@ -0,0 +1,354 @@
+/*************************************************************************\
+* Copyright (c) 2016 Michael Davidsaver
+* EPICS BASE is distributed subject to a Software License Agreement found
+* in file LICENSE that is included with this distribution.
+\*************************************************************************/
+
+#include "dbUnitTest.h"
+#include "testMain.h"
+#include "dbLock.h"
+#include "errlog.h"
+#include "dbAccess.h"
+#include "epicsMath.h"
+
+#include "aiRecord.h"
+#include "compressRecord.h"
+
+#define testDEq(A,B,D) testOk(fabs((A)-(B))<(D), #A " (%f) ~= " #B " (%f)", A, B)
+
+void recTestIoc_registerRecordDeviceDriver(struct dbBase *);
+
+static
+void checkArrD(const char *pv, long elen, double a, double b, double c, double d)
+{
+ double buf[4];
+ double expect[4];
+ long nReq = NELEMENTS(buf), i;
+ unsigned match;
+ DBADDR addr;
+
+ expect[0] = a;
+ expect[1] = b;
+ expect[2] = c;
+ expect[3] = d;
+
+ if (dbNameToAddr(pv, &addr))
+ testAbort("Unknown PV '%s'", pv);
+
+ if (dbGet(&addr, DBR_DOUBLE, buf, NULL, &nReq, NULL))
+ testAbort("Failed to get '%s'", pv);
+
+ match = elen==nReq;
+ for (i=0; i=0.01)
+ testDiag("[%ld] -> %f != %f", i, expect[i], buf[i]);
+ }
+}
+
+static
+void checkArrI(const char *pv, long elen, epicsInt32 a, epicsInt32 b, epicsInt32 c, epicsInt32 d)
+{
+ epicsInt32 buf[4];
+ epicsInt32 expect[4];
+ long nReq = NELEMENTS(buf), i;
+ unsigned match;
+ DBADDR addr;
+
+ expect[0] = a;
+ expect[1] = b;
+ expect[2] = c;
+ expect[3] = d;
+
+ if (dbNameToAddr(pv, &addr))
+ testAbort("Unknown PV '%s'", pv);
+
+ if (dbGet(&addr, DBR_LONG, buf, NULL, &nReq, NULL))
+ testAbort("Failed to get '%s'", pv);
+
+ match = elen==nReq;
+ for (i=0; i %d != %d", i, (int)expect[i], (int)buf[i]);
+ }
+}
+
+static
+void testFIFOCirc(void)
+{
+ aiRecord *vrec;
+ compressRecord *crec;
+ double *cbuf;
+
+ testDiag("Test FIFO");
+
+ testdbPrepare();
+
+ testdbReadDatabase("recTestIoc.dbd", NULL, NULL);
+
+ recTestIoc_registerRecordDeviceDriver(pdbbase);
+
+ testdbReadDatabase("compressTest.db", NULL, "ALG=Circular Buffer,BALG=FIFO Buffer,NSAM=4");
+
+ vrec = (aiRecord*)testdbRecordPtr("val");
+ crec = (compressRecord*)testdbRecordPtr("comp");
+
+ eltc(0);
+ testIocInitOk();
+ eltc(1);
+
+ dbScanLock((dbCommon*)crec);
+ cbuf = crec->bptr;
+
+ testOk1(crec->off==0);
+ testOk1(crec->inx==0);
+ testOk1(crec->nuse==0);
+
+ testDiag("Push 1.1");
+ vrec->val = 1.1;
+ dbProcess((dbCommon*)crec);
+
+ /* In FIFO mode the valid elements are
+ * cbuf[(off-nuse-1) % size] through cbuf[(off-1) % size]
+ */
+ testOk1(crec->off==1);
+ testOk1(crec->inx==0);
+ testOk1(crec->nuse==1);
+ testDEq(cbuf[0], 1.1, 0.1);
+ testDEq(cbuf[1], 0.0, 0.1);
+ testDEq(cbuf[2], 0.0, 0.1);
+ testDEq(cbuf[3], 0.0, 0.1);
+ checkArrD("comp", 1, 1.1, 0, 0, 0);
+
+ testDiag("Push 2.1");
+ vrec->val = 2.1;
+ dbProcess((dbCommon*)crec);
+
+ testOk1(crec->off==2);
+ testOk1(crec->inx==0);
+ testOk1(crec->nuse==2);
+ testDEq(cbuf[0], 1.1, 0.1);
+ testDEq(cbuf[1], 2.1, 0.1);
+ testDEq(cbuf[2], 0.0, 0.1);
+ testDEq(cbuf[3], 0.0, 0.1);
+ checkArrD("comp", 2, 1.1, 2.1, 0, 0);
+
+ testDiag("Push 3.1");
+ vrec->val = 3.1;
+ dbProcess((dbCommon*)crec);
+
+ testOk1(crec->off==3);
+ testOk1(crec->inx==0);
+ testOk1(crec->nuse==3);
+ testDEq(cbuf[0], 1.1, 0.1);
+ testDEq(cbuf[1], 2.1, 0.1);
+ testDEq(cbuf[2], 3.1, 0.1);
+ testDEq(cbuf[3], 0.0, 0.1);
+ checkArrD("comp", 3, 1.1, 2.1, 3.1, 0);
+
+ testDiag("Push 4.1");
+ vrec->val = 4.1;
+ dbProcess((dbCommon*)crec);
+
+ testOk1(crec->off==0);
+ testOk1(crec->inx==0);
+ testOk1(crec->nuse==4);
+ testDEq(cbuf[0], 1.1, 0.1);
+ testDEq(cbuf[1], 2.1, 0.1);
+ testDEq(cbuf[2], 3.1, 0.1);
+ testDEq(cbuf[3], 4.1, 0.1);
+ checkArrD("comp", 4, 1.1, 2.1, 3.1, 4.1);
+
+ testDiag("Push 5.1");
+ vrec->val = 5.1;
+ dbProcess((dbCommon*)crec);
+
+ testOk1(crec->off==1);
+ testOk1(crec->inx==0);
+ testOk1(crec->nuse==4);
+ testDEq(cbuf[0], 5.1, 0.1);
+ testDEq(cbuf[1], 2.1, 0.1);
+ testDEq(cbuf[2], 3.1, 0.1);
+ testDEq(cbuf[3], 4.1, 0.1);
+ checkArrD("comp", 4, 2.1, 3.1, 4.1, 5.1);
+
+ testDiag("Push 6.1");
+ vrec->val = 6.1;
+ dbProcess((dbCommon*)crec);
+
+ testOk1(crec->off==2);
+ testOk1(crec->inx==0);
+ testOk1(crec->nuse==4);
+ testDEq(cbuf[0], 5.1, 0.1);
+ testDEq(cbuf[1], 6.1, 0.1);
+ testDEq(cbuf[2], 3.1, 0.1);
+ testDEq(cbuf[3], 4.1, 0.1);
+ checkArrD("comp", 4, 3.1, 4.1, 5.1, 6.1);
+
+ dbScanUnlock((dbCommon*)crec);
+
+ testDiag("Reset");
+ testdbPutFieldOk("comp.RES", DBF_LONG, 0);
+
+ dbScanLock((dbCommon*)crec);
+ testOk1(crec->off==0);
+ testOk1(crec->inx==0);
+ testOk1(crec->nuse==0);
+ checkArrD("comp", 0, 0, 0, 0, 0);
+ dbScanUnlock((dbCommon*)crec);
+
+ testIocShutdownOk();
+
+ testdbCleanup();
+}
+
+static
+void testLIFOCirc(void)
+{
+ aiRecord *vrec;
+ compressRecord *crec;
+ double *cbuf;
+
+ testDiag("Test LIFO");
+
+ testdbPrepare();
+
+ testdbReadDatabase("recTestIoc.dbd", NULL, NULL);
+
+ recTestIoc_registerRecordDeviceDriver(pdbbase);
+
+ testdbReadDatabase("compressTest.db", NULL,
+ "ALG=Circular Buffer,BALG=LIFO Buffer,NSAM=4");
+
+ vrec = (aiRecord*)testdbRecordPtr("val");
+ crec = (compressRecord*)testdbRecordPtr("comp");
+
+ eltc(0);
+ testIocInitOk();
+ eltc(1);
+
+ dbScanLock((dbCommon*)crec);
+ cbuf = crec->bptr;
+
+ testOk1(crec->off==0);
+ testOk1(crec->inx==0);
+ testOk1(crec->nuse==0);
+
+ testDiag("Push 1.1");
+ vrec->val = 1.1;
+ dbProcess((dbCommon*)crec);
+
+ testDiag("off %u", crec->off);
+ testOk1(crec->off==3);
+ testOk1(crec->inx==0);
+ testOk1(crec->nuse==1);
+ testDEq(cbuf[0], 0.0, 0.1);
+ testDEq(cbuf[1], 0.0, 0.1);
+ testDEq(cbuf[2], 0.0, 0.1);
+ testDEq(cbuf[3], 1.1, 0.1);
+ checkArrD("comp", 1, 1.1, 0, 0, 0);
+
+ testDiag("Push 2.1");
+ vrec->val = 2.1;
+ dbProcess((dbCommon*)crec);
+
+ testOk1(crec->off==2);
+ testOk1(crec->inx==0);
+ testOk1(crec->nuse==2);
+ testDEq(cbuf[0], 0.0, 0.1);
+ testDEq(cbuf[1], 0.0, 0.1);
+ testDEq(cbuf[2], 2.1, 0.1);
+ testDEq(cbuf[3], 1.1, 0.1);
+ checkArrD("comp", 2, 2.1, 1.1, 0, 0);
+ checkArrI("comp", 2, 2, 1, 0, 0);
+
+ testDiag("Push 3.1");
+ vrec->val = 3.1;
+ dbProcess((dbCommon*)crec);
+
+ testOk1(crec->off==1);
+ testOk1(crec->inx==0);
+ testOk1(crec->nuse==3);
+ testDEq(cbuf[0], 0.0, 0.1);
+ testDEq(cbuf[1], 3.1, 0.1);
+ testDEq(cbuf[2], 2.1, 0.1);
+ testDEq(cbuf[3], 1.1, 0.1);
+ checkArrD("comp", 3, 3.1, 2.1, 1.1, 0);
+ checkArrI("comp", 3, 3, 2, 1, 0);
+
+ testDiag("Push 4.1");
+ vrec->val = 4.1;
+ dbProcess((dbCommon*)crec);
+
+ testOk1(crec->off==0);
+ testOk1(crec->inx==0);
+ testOk1(crec->nuse==4);
+ testDEq(cbuf[0], 4.1, 0.1);
+ testDEq(cbuf[1], 3.1, 0.1);
+ testDEq(cbuf[2], 2.1, 0.1);
+ testDEq(cbuf[3], 1.1, 0.1);
+ checkArrD("comp", 4, 4.1, 3.1, 2.1, 1.1);
+ checkArrI("comp", 4, 4, 3, 2, 1);
+
+ testDiag("Push 5.1");
+ vrec->val = 5.1;
+ dbProcess((dbCommon*)crec);
+
+ testOk1(crec->off==3);
+ testOk1(crec->inx==0);
+ testOk1(crec->nuse==4);
+ testDEq(cbuf[0], 4.1, 0.1);
+ testDEq(cbuf[1], 3.1, 0.1);
+ testDEq(cbuf[2], 2.1, 0.1);
+ testDEq(cbuf[3], 5.1, 0.1);
+ checkArrD("comp", 4, 5.1, 4.1, 3.1, 2.1);
+ checkArrI("comp", 4, 5, 4, 3, 2);
+
+ testDiag("Push 6.1");
+ vrec->val = 6.1;
+ dbProcess((dbCommon*)crec);
+
+ testOk1(crec->off==2);
+ testOk1(crec->inx==0);
+ testOk1(crec->nuse==4);
+ testDEq(cbuf[0], 4.1, 0.1);
+ testDEq(cbuf[1], 3.1, 0.1);
+ testDEq(cbuf[2], 6.1, 0.1);
+ testDEq(cbuf[3], 5.1, 0.1);
+ checkArrD("comp", 4, 6.1, 5.1, 4.1, 3.1);
+
+ dbScanUnlock((dbCommon*)crec);
+
+ testDiag("Reset");
+ testdbPutFieldOk("comp.RES", DBF_LONG, 0);
+
+ dbScanLock((dbCommon*)crec);
+ testOk1(crec->off==0);
+ testOk1(crec->inx==0);
+ testOk1(crec->nuse==0);
+ checkArrD("comp", 0, 0, 0, 0, 0);
+ dbScanUnlock((dbCommon*)crec);
+
+ testIocShutdownOk();
+
+ testdbCleanup();
+}
+
+MAIN(compressTest)
+{
+ testPlan(116);
+ testFIFOCirc();
+ testLIFOCirc();
+ return testDone();
+}
diff --git a/src/std/rec/test/compressTest.db b/src/std/rec/test/compressTest.db
new file mode 100644
index 000000000..59fc620ba
--- /dev/null
+++ b/src/std/rec/test/compressTest.db
@@ -0,0 +1,7 @@
+record(ai, "val") {}
+record(compress, "comp") {
+ field(INP, "val NPP")
+ field(ALG, "$(ALG)")
+ field(BALG,"$(BALG)")
+ field(NSAM,"$(NSAM)")
+}
diff --git a/src/std/rec/test/epicsRunRecordTests.c b/src/std/rec/test/epicsRunRecordTests.c
index 3476d2138..5612917cc 100644
--- a/src/std/rec/test/epicsRunRecordTests.c
+++ b/src/std/rec/test/epicsRunRecordTests.c
@@ -13,6 +13,7 @@
#include "epicsExit.h"
int analogMonitorTest(void);
+int compressTest(void);
int arrayOpTest(void);
int asTest(void);
int linkRetargetLinkTest(void);
@@ -23,6 +24,8 @@ void epicsRunRecordTests(void)
runTest(analogMonitorTest);
+ runTest(compressTest);
+
runTest(arrayOpTest);
runTest(asTest);