Merge changes from 3.15 into 3.16
This commit is contained in:
@@ -79,16 +79,20 @@ CPP = cl -nologo -C -E
|
||||
|
||||
# Configure OS vendor C++ compiler
|
||||
#
|
||||
# __STDC__=0 gives us both:
|
||||
# 1) define STDC for code (pretend ANSI conformance)
|
||||
# 2) set it to 0 to use MS C "extensions" (open for _open etc.)
|
||||
# because MS uses: if __STDC__ ... disable many nice things
|
||||
#
|
||||
# -EHsc - generate code for exceptions
|
||||
# -GR - generate code for run time type identification
|
||||
#
|
||||
CCC = cl -EHsc -GR
|
||||
CODE_CPPFLAGS += -nologo -D__STDC__=0
|
||||
|
||||
# Other compiler flags, used for CPP, C and C++
|
||||
#
|
||||
# -FC - Show absolute path of source file in diagnostics
|
||||
# -D__STDC__=0 gives us both:
|
||||
# 1) define STDC for code (pretend ANSI conformance)
|
||||
# 2) set it to 0 to use MS C "extensions" (open for _open etc.)
|
||||
# because MS uses: if __STDC__ ... disable many nice things
|
||||
#
|
||||
CODE_CPPFLAGS += -nologo -FC -D__STDC__=0
|
||||
CODE_CPPFLAGS += -D_CRT_SECURE_NO_DEPRECATE -D_CRT_NONSTDC_NO_DEPRECATE
|
||||
|
||||
|
||||
|
||||
@@ -662,6 +662,14 @@ of its CALLBACK objects.</p>
|
||||
|
||||
<!-- Insert inherited items immediately below here ... -->
|
||||
|
||||
<h3>Fix problem with numeric soft events</h3>
|
||||
<p>Changing from numeric to named soft events introduced an incompatibility
|
||||
when a numeric event 1-255 is converted from a DOUBLE, e.g. from a calc record.
|
||||
The <tt>post_event()</tt> API is not marked deprecated any more.
|
||||
|
||||
<p>Also <code>scanpel</code> has been modified to accept a glob pattern for
|
||||
event name filtering and to show events with no connected records as well.</p>
|
||||
|
||||
<h3>Add osiSockOptMcastLoop_t and osiSockTest</h3>
|
||||
|
||||
<p>Added a new OS-independent typedef for multicast socket options, and a test
|
||||
|
||||
+57
-36
@@ -1021,7 +1021,7 @@ d:/user/epics/base-3.15/lib/win32-x86/Com.lib</code></p>
|
||||
<h2><a name="CommandUtils">Command Line Utilities</a></h2>
|
||||
|
||||
<h3><a name="acctst">acctst</a></h3>
|
||||
<pre>acctst <PV name> [progress logging level] [channel duplication count]
|
||||
<pre>acctst <PV name> [progress logging level] [channel duplication count]
|
||||
[test repetition count] [enable preemptive callback]</pre>
|
||||
|
||||
<h4>Description</h4>
|
||||
@@ -2091,7 +2091,7 @@ example, be beneficial when tuning an archiver installation.</p>
|
||||
<p>Significant performance gains can be realized when the CA client library
|
||||
doesn't wait for a response to return from the server after each request. All
|
||||
requests which require interaction with a CA server are accumulated (buffered)
|
||||
and not forwarded to the IOC until one of <code>ca_flush_io()</code>,
|
||||
and not forwarded to the IOC until one of <code>ca_flush_io()</code>,
|
||||
<code>ca_pend_io()</code>, <code>ca_pend_event()</code>, or
|
||||
<code>ca_sg_block()</code> are called allowing several operations to be
|
||||
efficiently sent over the network together. Any process variable values written
|
||||
@@ -2115,16 +2115,16 @@ shouldn't test the success of a CA function call by checking to see if the
|
||||
returned value is zero as is the UNIX convention. Below are several methods to
|
||||
test CA function returns. See <a href="#ca_signal"><code>ca_signal()</code> and
|
||||
<code>SEVCHK()</code></a> for more information on this topic.</p>
|
||||
<pre>status = ca_XXXX();
|
||||
SEVCHK( status, "ca_XXXX() returned failure status");
|
||||
<pre>status = ca_XXXX();
|
||||
SEVCHK( status, "ca_XXXX() returned failure status");
|
||||
|
||||
if ( status & CA_M_SUCCESS ) {
|
||||
printf ( "The requested ca_XXXX() operation didn't complete successfully");
|
||||
}
|
||||
if ( status & CA_M_SUCCESS ) {
|
||||
printf ( "The requested ca_XXXX() operation didn't complete successfully");
|
||||
}
|
||||
|
||||
if ( status != ECA_NORMAL ) {
|
||||
if ( status != ECA_NORMAL ) {
|
||||
printf("The requested ca_XXXX() operation didn't complete successfully because \"%s\"\n",
|
||||
ca_message ( status ) );
|
||||
ca_message ( status ) );
|
||||
}</pre>
|
||||
|
||||
<h3><a name="Channel">Channel Access Data Types</a></h3>
|
||||
@@ -2297,7 +2297,7 @@ int main ( int argc, char ** argv )
|
||||
unsigned nBytes;
|
||||
unsigned elementCount;
|
||||
char timeString[32];
|
||||
unsigned i;
|
||||
unsigned i;
|
||||
chid chan;
|
||||
double sum;
|
||||
int status;
|
||||
@@ -2340,7 +2340,7 @@ int main ( int argc, char ** argv )
|
||||
epicsTimeToStrftime ( timeString, sizeof ( timeString ),
|
||||
"%a %b %d %Y %H:%M:%S.%f", & pTD->stamp );
|
||||
|
||||
printf ( "The sum of elements in %s at %s was %f\n",
|
||||
printf ( "The sum of elements in %s at %s was %f\n",
|
||||
argv[1], timeString, sum );
|
||||
|
||||
ca_clear_channel ( chan );
|
||||
@@ -2371,7 +2371,7 @@ executing within the user's callback function.</p>
|
||||
<pre>typedef struct event_handler_args {
|
||||
void *usr; /* user argument supplied with request */
|
||||
chanId chid; /* channel id */
|
||||
long type; /* the type of the item returned */
|
||||
long type; /* the type of the item returned */
|
||||
long count; /* the element count of the item returned */
|
||||
const void *dbr; /* a pointer to the item returned */
|
||||
int status; /* ECA_XXX status of the requested op from the server */
|
||||
@@ -2394,7 +2394,7 @@ attached to the request, an exception handler is executed in the client. The
|
||||
default exception handler prints a message on the console and exits if the
|
||||
exception condition is severe. Certain internal exceptions within the CA client
|
||||
library, and failures detected by the SEVCHK macro may also cause the exception
|
||||
handler to be invoked. To modify this behavior see
|
||||
handler to be invoked. To modify this behavior see
|
||||
<code><a href="#ca_add_exception_event">ca_add_exception_event</a>()</code>.</p>
|
||||
|
||||
<h3><a name="Server">Server and Client Share the Same Address Space on The Same
|
||||
@@ -2686,6 +2686,14 @@ automatically released by the system when the process exits and
|
||||
vxWorks or RTEMS no cleanup occurs unless the application calls
|
||||
<code>ca_context_destroy()</code>.</p>
|
||||
|
||||
<p>Note: This operation blocks until any user callbacks for any channel
|
||||
created in the current context have run to completion. If callbacks take a
|
||||
lock (mutex) then it is the user's responsibility to ensure that this lock
|
||||
is not held when <code>ca_clear_context()</code> is called, otherwise a
|
||||
deadlock may ensue. (See also
|
||||
<code><a href="#ca_clear_channel">ca_clear_channel</a>()</code> and
|
||||
<code><a href="#ca_clear_event">ca_clear_subscription</a>()</code>.)</p>
|
||||
|
||||
<h4>Returns</h4>
|
||||
|
||||
<p>ECA_NORMAL - Normal successful completion</p>
|
||||
@@ -2831,6 +2839,12 @@ efficiently sent over the network in one message.</p>
|
||||
clearing a channel does shutdown and reclaim any channel state change event
|
||||
subscriptions (monitors) registered with the channel.</p>
|
||||
|
||||
<p>Note: This operation blocks until any user callbacks for this channel
|
||||
have run to completion. If callbacks take a lock (mutex) then it is the
|
||||
user's responsibility to ensure that this lock is not held when
|
||||
<code>ca_clear_channel()</code> is called, otherwise a deadlock may ensue.
|
||||
(See also <code><a href="#ca_clear_event">ca_clear_subscription</a>()</code>.)</p>
|
||||
|
||||
<h4>Arguments</h4>
|
||||
<dl>
|
||||
<dt><code>CHID</code></dt>
|
||||
@@ -2845,16 +2859,16 @@ subscriptions (monitors) registered with the channel.</p>
|
||||
|
||||
<h3><code><a name="ca_put">ca_put()</a></code></h3>
|
||||
<pre>#include <cadef.h>
|
||||
int ca_put ( chtype TYPE,
|
||||
chid CHID, void *PVALUE );
|
||||
int ca_array_put ( chtype TYPE, unsigned long COUNT,
|
||||
int ca_put ( chtype TYPE,
|
||||
chid CHID, void *PVALUE );
|
||||
int ca_array_put ( chtype TYPE, unsigned long COUNT,
|
||||
chid CHID, const void *PVALUE);
|
||||
typedef void ( caEventCallBackFunc ) (struct event_handler_args);
|
||||
int ca_put_callback ( chtype TYPE,
|
||||
chid CHID, const void *PVALUE,
|
||||
caEventCallBackFunc PFUNC, void *USERARG );
|
||||
int ca_array_put_callback ( chtype TYPE, unsigned long COUNT,
|
||||
chid CHID, const void *PVALUE,
|
||||
int ca_put_callback ( chtype TYPE,
|
||||
chid CHID, const void *PVALUE,
|
||||
caEventCallBackFunc PFUNC, void *USERARG );
|
||||
int ca_array_put_callback ( chtype TYPE, unsigned long COUNT,
|
||||
chid CHID, const void *PVALUE,
|
||||
caEventCallBackFunc PFUNC, void *USERARG );</pre>
|
||||
|
||||
<h4>Description</h4>
|
||||
@@ -3081,7 +3095,7 @@ when a CA get request is initiated.</p>
|
||||
typedef void ( caEventCallBackFunc ) (struct event_handler_args);
|
||||
int ca_create_subscription ( chtype TYPE, unsigned long COUNT,
|
||||
chid CHID, unsigned long MASK,
|
||||
caEventCallBackFunc USERFUNC, void *USERARG,
|
||||
caEventCallBackFunc USERFUNC, void *USERARG,
|
||||
evid *PEVID );</pre>
|
||||
|
||||
<h4>Description</h4>
|
||||
@@ -3165,7 +3179,7 @@ indicating the current state of the channel.</p>
|
||||
<dt><code>MASK</code></dt>
|
||||
<dd>A mask with bits set for each of the event trigger types requested. The
|
||||
event trigger mask must be a <em>bitwise or</em> of one or more of the
|
||||
following constants.
|
||||
following constants.
|
||||
<ul>
|
||||
<li>DBE_VALUE - Trigger events when the channel value exceeds the
|
||||
monitor dead band</li>
|
||||
@@ -3212,6 +3226,13 @@ and not forwarded to the server until one of <code>ca_flush_io()</code>, <code>c
|
||||
<code>ca_pend_event()</code>, or <code>ca_sg_block()</code> are called. This allows several requests to be
|
||||
efficiently sent together in one message.</p>
|
||||
|
||||
<p>Note: This operation blocks until any user callbacks for this channel
|
||||
have run to completion. If callbacks take a lock (mutex) then it is the
|
||||
user's responsibility to ensure that this lock is not held when
|
||||
<code>ca_clear_subscription()</code> is called, otherwise a deadlock may
|
||||
ensue. (See also <code><a
|
||||
href="#ca_clear_channel">ca_clear_channel</a>()</code>.)</p>
|
||||
|
||||
<h4>Arguments</h4>
|
||||
<dl>
|
||||
<dt>EVID</dt>
|
||||
@@ -3376,7 +3397,7 @@ becomes full.</p>
|
||||
|
||||
<h3><code><a name="ca_signal">ca_signal()</a></code></h3>
|
||||
<pre>#include <cadef.h>
|
||||
int ca_signal ( long CA_STATUS, const char * CONTEXT_STRING );
|
||||
int ca_signal ( long CA_STATUS, const char * CONTEXT_STRING );
|
||||
void SEVCHK( CA_STATUS, CONTEXT_STRING );</pre>
|
||||
|
||||
<h4>Description</h4>
|
||||
@@ -3393,7 +3414,7 @@ recommended error handler for simple applications which do not wish to write
|
||||
code testing the status returned from each channel access call.</p>
|
||||
|
||||
<h4>Examples</h4>
|
||||
<pre>status = ca_context_create (...);
|
||||
<pre>status = ca_context_create (...);
|
||||
SEVCHK ( status, "Unable to create a CA client context" );</pre>
|
||||
|
||||
<p>If the application only wishes to print the message associated with an error
|
||||
@@ -3417,7 +3438,7 @@ this purpose.</p>
|
||||
|
||||
<h3><code><a
|
||||
name="ca_add_exception_event">ca_add_exception_event()</a></code></h3>
|
||||
<pre>#include <cadef.h>
|
||||
<pre>#include <cadef.h>
|
||||
typedef void (*pCallback) ( struct exception_handler_args HANDLERARGS );
|
||||
int ca_add_exception_event ( pCallback USERFUNC, void *USERARG );</pre>
|
||||
|
||||
@@ -3626,7 +3647,7 @@ specified channel.</p>
|
||||
<dt><code>PFUNC</code></dt>
|
||||
<dd>Pointer to a user supplied callback function. A null pointer uninstalls
|
||||
the current handler. The following arguments are passed <em>by value</em>
|
||||
to the supplied callback handler.
|
||||
to the supplied callback handler.
|
||||
<pre>typedef struct ca_access_rights {
|
||||
unsigned read_access:1;
|
||||
unsigned write_access:1;
|
||||
@@ -3966,8 +3987,8 @@ type.</p>
|
||||
prints diagnostics to standard out.</p>
|
||||
|
||||
<h4>Examples</h4>
|
||||
<pre>void ca_test_event ();
|
||||
status = ca_create_subscription ( type, chid, ca_test_event, NULL, NULL );
|
||||
<pre>void ca_test_event ();
|
||||
status = ca_create_subscription ( type, chid, ca_test_event, NULL, NULL );
|
||||
SEVCHK ( status, .... );</pre>
|
||||
|
||||
<h4>See Also</h4>
|
||||
@@ -4001,8 +4022,8 @@ outstanding within them at any given time.</p>
|
||||
</dl>
|
||||
|
||||
<h4>Examples</h4>
|
||||
<pre>CA_SYNC_GID gid;
|
||||
status = ca_sg_create ( &gid );
|
||||
<pre>CA_SYNC_GID gid;
|
||||
status = ca_sg_create ( &gid );
|
||||
SEVCHK ( status, Sync group create failed );</pre>
|
||||
|
||||
<h4>Returns</h4>
|
||||
@@ -4040,8 +4061,8 @@ int ca_sg_delete ( CA_SYNC_GID GID );</pre>
|
||||
</dl>
|
||||
|
||||
<h4>Examples</h4>
|
||||
<pre>CA_SYNC_GID gid;
|
||||
status = ca_sg_delete ( gid );
|
||||
<pre>CA_SYNC_GID gid;
|
||||
status = ca_sg_delete ( gid );
|
||||
SEVCHK ( status, Sync group delete failed );</pre>
|
||||
|
||||
<h4>Returns</h4>
|
||||
@@ -4152,7 +4173,7 @@ will not block unless additional subsequent requests are made.</p>
|
||||
</dl>
|
||||
|
||||
<h4>Examples</h4>
|
||||
<pre>CA_SYNC_GID gid;
|
||||
<pre>CA_SYNC_GID gid;
|
||||
status = ca_sg_reset(gid);</pre>
|
||||
|
||||
<h4>Returns</h4>
|
||||
@@ -4165,7 +4186,7 @@ status = ca_sg_reset(gid);</pre>
|
||||
<pre>#include <cadef.h>
|
||||
int ca_sg_put ( CA_SYNC_GID GID, chtype TYPE,
|
||||
chid CHID, void *PVALUE );
|
||||
int ca_sg_array_put ( CA_SYNC_GID GID, chtype TYPE,
|
||||
int ca_sg_array_put ( CA_SYNC_GID GID, chtype TYPE,
|
||||
unsigned long COUNT, chid CHID, void *PVALUE );</pre>
|
||||
|
||||
<p>Write a value, or array of values, to a channel and increment the outstanding
|
||||
@@ -4306,7 +4327,7 @@ reissued.</p>
|
||||
|
||||
<h3><code><a name="ca_client_status">ca_client_status()</a></code></h3>
|
||||
<pre>int ca_client_status ( unsigned level );
|
||||
int ca_context_status ( struct ca_client_context *CONTEXT,
|
||||
int ca_context_status ( struct ca_client_context *CONTEXT,
|
||||
unsigned LEVEL );</pre>
|
||||
|
||||
<h4>Description</h4>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* Copyright (c) 2002 The Regents of the University of California, as
|
||||
* Operator of Los Alamos National Laboratory.
|
||||
* EPICS BASE is distributed subject to a Software License Agreement found
|
||||
* in file LICENSE that is included with this distribution.
|
||||
* in file LICENSE that is included with this distribution.
|
||||
\*************************************************************************/
|
||||
/*
|
||||
* Author Jeffrey O. Hill
|
||||
@@ -26,33 +26,39 @@
|
||||
#include "casAsyncIOI.h"
|
||||
#include "casMonitor.h"
|
||||
|
||||
casPVI::casPVI ( casPV & intf ) :
|
||||
pCAS ( NULL ), pPV ( & intf ), nMonAttached ( 0u ),
|
||||
|
||||
// Use casErrMessage instead of errMessage to show PV name
|
||||
#define casErrMessage(S, PM) \
|
||||
errPrintf(S, __FILE__, __LINE__, ", %s, %s", getName(), PM)
|
||||
|
||||
casPVI::casPVI ( casPV & intf ) :
|
||||
pCAS ( NULL ), pPV ( & intf ), nMonAttached ( 0u ),
|
||||
nIOAttached ( 0u ), deletePending ( false ) {}
|
||||
|
||||
casPVI::~casPVI ()
|
||||
{
|
||||
//
|
||||
// all channels should have been destroyed
|
||||
// (otherwise the server tool is yanking the
|
||||
//
|
||||
// all channels should have been destroyed
|
||||
// (otherwise the server tool is yanking the
|
||||
// PV out from under the server)
|
||||
//
|
||||
casVerify ( this->chanList.count() == 0u );
|
||||
//
|
||||
casVerify ( this->chanList.count() == 0u );
|
||||
|
||||
//
|
||||
// all outstanding IO should have been deleted
|
||||
// when we destroyed the channels
|
||||
//
|
||||
casVerify ( this->nIOAttached == 0u );
|
||||
//
|
||||
// all outstanding IO should have been deleted
|
||||
// when we destroyed the channels
|
||||
//
|
||||
casVerify ( this->nIOAttached == 0u );
|
||||
if ( this->nIOAttached ) {
|
||||
errlogPrintf ( "The number of IO objected attached is %u\n", this->nIOAttached );
|
||||
errlogPrintf ( "%u IO objects still attached in destructor\n",
|
||||
this->nIOAttached );
|
||||
}
|
||||
|
||||
//
|
||||
// all monitors should have been deleted
|
||||
// when we destroyed the channels
|
||||
//
|
||||
casVerify ( this->nMonAttached == 0u );
|
||||
//
|
||||
// all monitors should have been deleted
|
||||
// when we destroyed the channels
|
||||
//
|
||||
casVerify ( this->nMonAttached == 0u );
|
||||
|
||||
{
|
||||
epicsGuard < epicsMutex > guard ( this->mutex );
|
||||
@@ -139,26 +145,26 @@ caStatus casPVI::attachToServer ( caServerI & cas )
|
||||
caStatus casPVI::updateEnumStringTable ( casCtx & ctxIn )
|
||||
{
|
||||
epicsGuard < epicsMutex > guard ( this->mutex );
|
||||
|
||||
|
||||
//
|
||||
// create a gdd with the "enum string table" application type
|
||||
//
|
||||
// gddArray(int app, aitEnum prim, int dimen, ...);
|
||||
gdd * pTmp = new gddScalar ( gddAppType_enums );
|
||||
if ( pTmp == NULL ) {
|
||||
errMessage ( S_cas_noMemory,
|
||||
casErrMessage ( S_cas_noMemory,
|
||||
"unable to create gdd for read of application type \"enums\" string"
|
||||
" conversion table for enumerated PV" );
|
||||
return S_cas_noMemory;
|
||||
}
|
||||
|
||||
caStatus status = convertContainerMemberToAtomic ( *pTmp,
|
||||
caStatus status = convertContainerMemberToAtomic ( *pTmp,
|
||||
gddAppType_enums, MAX_ENUM_STATES );
|
||||
if ( status != S_cas_success ) {
|
||||
pTmp->unreference ();
|
||||
errMessage ( status,
|
||||
"unable to to config gdd for read of application type \"enums\" string"
|
||||
" conversion table for enumerated PV");
|
||||
casErrMessage ( status,
|
||||
"unable to config gdd for read of application type \"enums\" string"
|
||||
" conversion table for enumerated PV" );
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -169,12 +175,11 @@ caStatus casPVI::updateEnumStringTable ( casCtx & ctxIn )
|
||||
if ( status == S_cas_success ) {
|
||||
updateEnumStringTableAsyncCompletion ( *pTmp );
|
||||
}
|
||||
else if ( status != S_casApp_asyncCompletion &&
|
||||
else if ( status != S_casApp_asyncCompletion &&
|
||||
status != S_casApp_postponeAsyncIO ) {
|
||||
errPrintf ( status, __FILE__, __LINE__,
|
||||
"- unable to read application type \"enums\" "
|
||||
" (string conversion table) from enumerated native type PV \"%s\"",
|
||||
this->getName() );
|
||||
casErrMessage ( status,
|
||||
"unable to read application type \"enums\" "
|
||||
" (string conversion table) from enumerated native type PV" );
|
||||
}
|
||||
|
||||
pTmp->unreference ();
|
||||
@@ -185,40 +190,39 @@ caStatus casPVI::updateEnumStringTable ( casCtx & ctxIn )
|
||||
void casPVI::updateEnumStringTableAsyncCompletion ( const gdd & resp )
|
||||
{
|
||||
epicsGuard < epicsMutex > guard ( this->mutex );
|
||||
|
||||
|
||||
if ( resp.isContainer() ) {
|
||||
errMessage ( S_cas_badType,
|
||||
"application type \"enums\" string conversion table for"
|
||||
" enumerated PV was a container (expected vector of strings)" );
|
||||
casErrMessage ( S_cas_badType,
|
||||
"Invalid \"enums\" string conversion table for"
|
||||
" enumerated PV (container instead of vector of strings)" );
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if ( resp.dimension() == 0 ) {
|
||||
if ( resp.primitiveType() == aitEnumString ) {
|
||||
aitString *pStr = (aitString *) resp.dataVoid ();
|
||||
if ( ! this->enumStrTbl.setString ( 0, pStr->string() ) ) {
|
||||
errMessage ( S_cas_noMemory,
|
||||
casErrMessage ( S_cas_noMemory,
|
||||
"no memory to set enumerated PV string cache" );
|
||||
}
|
||||
}
|
||||
else if ( resp.primitiveType() == aitEnumFixedString ) {
|
||||
aitFixedString *pStr = (aitFixedString *) resp.dataVoid ();
|
||||
if ( ! this->enumStrTbl.setString ( 0, pStr->fixed_string ) ) {
|
||||
errMessage ( S_cas_noMemory,
|
||||
casErrMessage ( S_cas_noMemory,
|
||||
"no memory to set enumerated PV string cache" );
|
||||
}
|
||||
}
|
||||
else {
|
||||
errPrintf ( S_cas_badType, __FILE__, __LINE__,
|
||||
casErrMessage ( S_cas_badType,
|
||||
"application type \"enums\" string conversion"
|
||||
" table for enumerated PV \"%s\" isnt a string type?",
|
||||
getName() );
|
||||
" table for enumerated PV isnt a string type?" );
|
||||
}
|
||||
}
|
||||
else if ( resp.dimension() == 1 ) {
|
||||
gddStatus gdd_status;
|
||||
aitIndex index, first, count;
|
||||
|
||||
|
||||
gdd_status = resp.getBound ( 0, first, count );
|
||||
assert ( gdd_status == 0 );
|
||||
|
||||
@@ -232,7 +236,7 @@ void casPVI::updateEnumStringTableAsyncCompletion ( const gdd & resp )
|
||||
aitString *pStr = (aitString *) resp.dataVoid ();
|
||||
for ( index = 0; index<count; index++ ) {
|
||||
if ( ! this->enumStrTbl.setString ( index, pStr[index].string() ) ) {
|
||||
errMessage ( S_cas_noMemory,
|
||||
casErrMessage ( S_cas_noMemory,
|
||||
"no memory to set enumerated PV string cache" );
|
||||
}
|
||||
}
|
||||
@@ -241,19 +245,18 @@ void casPVI::updateEnumStringTableAsyncCompletion ( const gdd & resp )
|
||||
aitFixedString *pStr = (aitFixedString *) resp.dataVoid ();
|
||||
for ( index = 0; index < count; index++ ) {
|
||||
if ( ! this->enumStrTbl.setString ( index, pStr[index].fixed_string ) ) {
|
||||
errMessage ( S_cas_noMemory,
|
||||
casErrMessage ( S_cas_noMemory,
|
||||
"no memory to set enumerated PV string cache" );
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
errMessage ( S_cas_badType,
|
||||
"application type \"enums\" string conversion"
|
||||
" table for enumerated PV isnt a string type?" );
|
||||
casErrMessage( S_cas_badType,
|
||||
"bad \"enums\" string conversion table for enumerated PV" );
|
||||
}
|
||||
}
|
||||
else {
|
||||
errMessage ( S_cas_badType,
|
||||
casErrMessage ( S_cas_badType,
|
||||
"application type \"enums\" string conversion table"
|
||||
" for enumerated PV was multi-dimensional"
|
||||
" (expected vector of strings)" );
|
||||
@@ -287,7 +290,7 @@ void casPVI::postEvent ( const casEventMask & select, const gdd & event )
|
||||
}
|
||||
}
|
||||
|
||||
caStatus casPVI::installMonitor (
|
||||
caStatus casPVI::installMonitor (
|
||||
casMonitor & mon, tsDLList < casMonitor > & monitorList )
|
||||
{
|
||||
epicsGuard < epicsMutex > guard ( this->mutex );
|
||||
@@ -303,14 +306,14 @@ caStatus casPVI::installMonitor (
|
||||
}
|
||||
}
|
||||
|
||||
casMonitor * casPVI::removeMonitor (
|
||||
casMonitor * casPVI::removeMonitor (
|
||||
tsDLList < casMonitor > & list, ca_uint32_t clientIdIn )
|
||||
{
|
||||
epicsGuard < epicsMutex > guard ( this->mutex );
|
||||
casMonitor * pMon = 0;
|
||||
//
|
||||
// (it is reasonable to do a linear search here because
|
||||
// sane clients will require only one or two monitors
|
||||
// sane clients will require only one or two monitors
|
||||
// per channel)
|
||||
//
|
||||
tsDLIter < casMonitor > iter = list.firstIter ();
|
||||
@@ -359,9 +362,9 @@ void casPVI::installChannel ( chanIntfForPV & chan )
|
||||
epicsGuard < epicsMutex > guard ( this->mutex );
|
||||
this->chanList.add ( chan );
|
||||
}
|
||||
|
||||
void casPVI::removeChannel (
|
||||
chanIntfForPV & chan, tsDLList < casMonitor > & src,
|
||||
|
||||
void casPVI::removeChannel (
|
||||
chanIntfForPV & chan, tsDLList < casMonitor > & src,
|
||||
tsDLList < casMonitor > & dest )
|
||||
{
|
||||
epicsGuard < epicsMutex > guard ( this->mutex );
|
||||
@@ -380,8 +383,8 @@ void casPVI::clearOutstandingReads ( tsDLList < casAsyncIOI > & ioList )
|
||||
{
|
||||
epicsGuard < epicsMutex > guard ( this->mutex );
|
||||
|
||||
// cancel any pending asynchronous IO
|
||||
tsDLIter < casAsyncIOI > iterIO =
|
||||
// cancel any pending asynchronous IO
|
||||
tsDLIter < casAsyncIOI > iterIO =
|
||||
ioList.firstIter ();
|
||||
while ( iterIO.valid () ) {
|
||||
tsDLIter < casAsyncIOI > tmp = iterIO;
|
||||
@@ -407,7 +410,7 @@ void casPVI::destroyAllIO ( tsDLList < casAsyncIOI > & ioList )
|
||||
}
|
||||
}
|
||||
|
||||
void casPVI::installIO (
|
||||
void casPVI::installIO (
|
||||
tsDLList < casAsyncIOI > & ioList, casAsyncIOI & io )
|
||||
{
|
||||
epicsGuard < epicsMutex > guard ( this->mutex );
|
||||
@@ -416,7 +419,7 @@ void casPVI::installIO (
|
||||
this->nIOAttached++;
|
||||
}
|
||||
|
||||
void casPVI::uninstallIO (
|
||||
void casPVI::uninstallIO (
|
||||
tsDLList < casAsyncIOI > & ioList, casAsyncIOI & io )
|
||||
{
|
||||
{
|
||||
@@ -517,8 +520,8 @@ aitEnum casPVI::bestExternalType () const
|
||||
}
|
||||
}
|
||||
|
||||
// CA only does 1D arrays for now
|
||||
aitIndex casPVI::nativeCount ()
|
||||
// CA only does 1D arrays for now
|
||||
aitIndex casPVI::nativeCount ()
|
||||
{
|
||||
epicsGuard < epicsMutex > guard ( this->mutex );
|
||||
if ( this->pPV ) {
|
||||
@@ -542,4 +545,3 @@ const char * casPVI::getName () const
|
||||
return "<disconnected>";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+46
-39
@@ -6,7 +6,7 @@
|
||||
* Copyright (c) 2013 Helmholtz-Zentrum Berlin
|
||||
* für Materialien und Energie GmbH.
|
||||
* EPICS BASE is distributed subject to a Software License Agreement found
|
||||
* in file LICENSE that is included with this distribution.
|
||||
* in file LICENSE that is included with this distribution.
|
||||
\*************************************************************************/
|
||||
/* dbScan.c */
|
||||
/* tasks and subroutines to scan the database */
|
||||
@@ -109,8 +109,8 @@ static char *priorityName[NUM_CALLBACK_PRIORITIES] = {
|
||||
typedef struct event_list {
|
||||
CALLBACK callback[NUM_CALLBACK_PRIORITIES];
|
||||
scan_list scan_list[NUM_CALLBACK_PRIORITIES];
|
||||
struct event_list *next;
|
||||
char event_name[MAX_STRING_SIZE];
|
||||
struct event_list *next;
|
||||
char eventname[1]; /* actually arbitrary size */
|
||||
} event_list;
|
||||
static event_list * volatile pevent_list[256];
|
||||
static epicsMutexId event_lock;
|
||||
@@ -247,11 +247,6 @@ void scanAdd(struct dbCommon *precord)
|
||||
event_list *pel;
|
||||
|
||||
eventname = precord->evnt;
|
||||
if (strlen(eventname) >= MAX_STRING_SIZE) {
|
||||
recGblRecordError(S_db_badField, (void *)precord,
|
||||
"scanAdd: too long EVNT value");
|
||||
return;
|
||||
}
|
||||
prio = precord->prio;
|
||||
if (prio < 0 || prio >= NUM_CALLBACK_PRIORITIES) {
|
||||
recGblRecordError(-1, (void *)precord,
|
||||
@@ -315,24 +310,17 @@ void scanDelete(struct dbCommon *precord)
|
||||
recGblRecordError(-1, (void *)precord,
|
||||
"scanDelete detected illegal SCAN value");
|
||||
} else if (scan == menuScanEvent) {
|
||||
char* eventname;
|
||||
int prio;
|
||||
event_list *pel;
|
||||
scan_list *psl = 0;
|
||||
|
||||
eventname = precord->evnt;
|
||||
prio = precord->prio;
|
||||
if (prio < 0 || prio >= NUM_CALLBACK_PRIORITIES) {
|
||||
recGblRecordError(-1, (void *)precord,
|
||||
"scanDelete detected illegal PRIO field");
|
||||
return;
|
||||
}
|
||||
do /* multithreading: make sure pel is consistent */
|
||||
pel = pevent_list[0];
|
||||
while (pel != pevent_list[0]);
|
||||
for (; pel; pel=pel->next) {
|
||||
if (strcmp(pel->event_name, eventname) == 0) break;
|
||||
}
|
||||
pel = eventNameToHandle(precord->evnt);
|
||||
if (pel && (psl = &pel->scan_list[prio]))
|
||||
deleteFromList(precord, psl);
|
||||
} else if (scan == menuScanI_O_Intr) {
|
||||
@@ -420,14 +408,12 @@ int scanpel(const char* eventname) /* print event list */
|
||||
int prio;
|
||||
event_list *pel;
|
||||
|
||||
do /* multithreading: make sure pel is consistent */
|
||||
pel = pevent_list[0];
|
||||
while (pel != pevent_list[0]);
|
||||
for (; pel; pel = pel->next) {
|
||||
if (!eventname || strcmp(pel->event_name, eventname) == 0) {
|
||||
for (pel = pevent_list[0]; pel; pel = pel->next) {
|
||||
if (!eventname || epicsStrGlobMatch(pel->eventname, eventname)) {
|
||||
printf("Event \"%s\"\n", pel->eventname);
|
||||
for (prio = 0; prio < NUM_CALLBACK_PRIORITIES; prio++) {
|
||||
if (ellCount(&pel->scan_list[prio].list) == 0) continue;
|
||||
sprintf(message, "Event \"%s\" Priority %s", pel->event_name, priorityName[prio]);
|
||||
sprintf(message, " Priority %s", priorityName[prio]);
|
||||
printList(&pel->scan_list[prio], message);
|
||||
}
|
||||
}
|
||||
@@ -478,20 +464,52 @@ event_list *eventNameToHandle(const char *eventname)
|
||||
int prio;
|
||||
event_list *pel;
|
||||
static epicsThreadOnceId onceId = EPICS_THREAD_ONCE_INIT;
|
||||
double eventnumber = 0;
|
||||
size_t namelength;
|
||||
|
||||
if (!eventname || eventname[0] == 0)
|
||||
return NULL;
|
||||
if (!eventname) return NULL;
|
||||
while (isspace((int) eventname[0])) eventname++;
|
||||
if (!eventname[0]) return NULL;
|
||||
namelength = strlen(eventname);
|
||||
while (isspace((int) eventname[namelength-1])) namelength--;
|
||||
|
||||
/* Backward compatibility with numeric events:
|
||||
Treat any string that represents a double with an
|
||||
integer part between 0 and 255 the same as the integer
|
||||
because it is most probably a conversion from double
|
||||
like from a calc record.
|
||||
*/
|
||||
if (epicsParseDouble(eventname, &eventnumber, NULL) == 0)
|
||||
{
|
||||
if (eventnumber >= 0 && eventnumber < 256)
|
||||
{
|
||||
if (eventnumber < 1)
|
||||
return NULL; /* 0 is no event */
|
||||
if ((pel = pevent_list[(int)eventnumber]) != NULL)
|
||||
return pel;
|
||||
}
|
||||
else
|
||||
eventnumber = 0; /* not a numeric event between 1 and 255 */
|
||||
}
|
||||
|
||||
epicsThreadOnce(&onceId, eventOnce, NULL);
|
||||
epicsMutexMustLock(event_lock);
|
||||
for (pel = pevent_list[0]; pel; pel=pel->next) {
|
||||
if (strcmp(pel->event_name, eventname) == 0) break;
|
||||
if (strncmp(pel->eventname, eventname, namelength) == 0
|
||||
&& pel->eventname[namelength] == 0)
|
||||
break;
|
||||
}
|
||||
if (pel == NULL) {
|
||||
pel = calloc(1, sizeof(event_list));
|
||||
pel = calloc(1, sizeof(event_list) + namelength);
|
||||
if (!pel)
|
||||
goto done;
|
||||
strcpy(pel->event_name, eventname);
|
||||
if (eventnumber > 0) {
|
||||
/* backward compatibility: make all numeric events look like integers */
|
||||
sprintf(pel->eventname, "%i", (int)eventnumber);
|
||||
pevent_list[(int)eventnumber] = pel;
|
||||
}
|
||||
else
|
||||
strncpy(pel->eventname, eventname, namelength);
|
||||
for (prio = 0; prio < NUM_CALLBACK_PRIORITIES; prio++) {
|
||||
callbackSetUser(&pel->scan_list[prio], &pel->callback[prio]);
|
||||
callbackSetPriority(prio, &pel->callback[prio]);
|
||||
@@ -501,12 +519,6 @@ event_list *eventNameToHandle(const char *eventname)
|
||||
}
|
||||
pel->next=pevent_list[0];
|
||||
pevent_list[0]=pel;
|
||||
{ /* backward compatibility */
|
||||
char* p;
|
||||
long e = strtol(eventname, &p, 0);
|
||||
if (*p == 0 && e > 0 && e <= 255)
|
||||
pevent_list[e] = pel;
|
||||
}
|
||||
}
|
||||
done:
|
||||
epicsMutexUnlock(event_lock);
|
||||
@@ -528,13 +540,8 @@ void postEvent(event_list *pel)
|
||||
/* backward compatibility */
|
||||
void post_event(int event)
|
||||
{
|
||||
event_list* pel;
|
||||
|
||||
if (event <= 0 || event > 255) return;
|
||||
do { /* multithreading: make sure pel is consistent */
|
||||
pel = pevent_list[event];
|
||||
} while (pel != pevent_list[event]);
|
||||
postEvent(pel);
|
||||
postEvent(pevent_list[event]);
|
||||
}
|
||||
|
||||
static void ioscanOnce(void *arg)
|
||||
|
||||
+1
-1
@@ -51,7 +51,7 @@ epicsShareFunc void scanCleanup(void);
|
||||
|
||||
epicsShareFunc EVENTPVT eventNameToHandle(const char* event);
|
||||
epicsShareFunc void postEvent(EVENTPVT epvt);
|
||||
epicsShareFunc void post_event(int event) EPICS_DEPRECATED;
|
||||
epicsShareFunc void post_event(int event);
|
||||
epicsShareFunc void scanAdd(struct dbCommon *);
|
||||
epicsShareFunc void scanDelete(struct dbCommon *);
|
||||
epicsShareFunc double scanPeriod(int scan);
|
||||
|
||||
@@ -1064,32 +1064,39 @@ static void dbRecordHead(char *recordType, char *name, int visible)
|
||||
|
||||
static void dbRecordField(char *name,char *value)
|
||||
{
|
||||
DBENTRY *pdbentry;
|
||||
tempListNode *ptempListNode;
|
||||
long status;
|
||||
DBENTRY *pdbentry;
|
||||
tempListNode *ptempListNode;
|
||||
long status;
|
||||
|
||||
if(duplicate) return;
|
||||
if (duplicate) return;
|
||||
ptempListNode = (tempListNode *)ellFirst(&tempList);
|
||||
pdbentry = ptempListNode->item;
|
||||
status = dbFindField(pdbentry,name);
|
||||
if(status) {
|
||||
epicsPrintf("Record \"%s\" does not have a field \"%s\"\n",
|
||||
dbGetRecordName(pdbentry), name);
|
||||
yyerror(NULL);
|
||||
return;
|
||||
if (status) {
|
||||
epicsPrintf("Record \"%s\" does not have a field \"%s\"\n",
|
||||
dbGetRecordName(pdbentry), name);
|
||||
yyerror(NULL);
|
||||
return;
|
||||
}
|
||||
if (pdbentry->indfield == 0) {
|
||||
epicsPrintf("Can't set \"NAME\" field of record \"%s\"\n",
|
||||
dbGetRecordName(pdbentry));
|
||||
yyerror(NULL);
|
||||
return;
|
||||
}
|
||||
if (*value == '"') {
|
||||
/* jsonSTRING values still have their quotes */
|
||||
/* jsonSTRING values still have their quotes */
|
||||
value++;
|
||||
value[strlen(value) - 1] = 0;
|
||||
value[strlen(value) - 1] = 0;
|
||||
}
|
||||
dbTranslateEscape(value, value); /* in-place; safe & legal */
|
||||
status = dbPutString(pdbentry,value);
|
||||
if(status) {
|
||||
if (status) {
|
||||
char msg[128];
|
||||
|
||||
errSymLookup(status, msg, sizeof(msg));
|
||||
epicsPrintf("Can't set \"%s.%s\" to \"%s\" %s\n",
|
||||
dbGetRecordName(pdbentry), name, value, msg);
|
||||
dbGetRecordName(pdbentry), name, value, msg);
|
||||
yyerror(NULL);
|
||||
return;
|
||||
}
|
||||
@@ -1097,39 +1104,39 @@ static void dbRecordField(char *name,char *value)
|
||||
|
||||
static void dbRecordInfo(char *name, char *value)
|
||||
{
|
||||
DBENTRY *pdbentry;
|
||||
tempListNode *ptempListNode;
|
||||
long status;
|
||||
DBENTRY *pdbentry;
|
||||
tempListNode *ptempListNode;
|
||||
long status;
|
||||
|
||||
if(duplicate) return;
|
||||
if (duplicate) return;
|
||||
ptempListNode = (tempListNode *)ellFirst(&tempList);
|
||||
pdbentry = ptempListNode->item;
|
||||
if (*value == '"') {
|
||||
/* jsonSTRING values still have their quotes */
|
||||
/* jsonSTRING values still have their quotes */
|
||||
value++;
|
||||
value[strlen(value) - 1] = 0;
|
||||
value[strlen(value) - 1] = 0;
|
||||
}
|
||||
dbTranslateEscape(value, value); /* yuck: in-place, but safe */
|
||||
status = dbPutInfo(pdbentry,name,value);
|
||||
if(status) {
|
||||
epicsPrintf("Can't set \"%s\" info \"%s\" to \"%s\"\n",
|
||||
if (status) {
|
||||
epicsPrintf("Can't set \"%s\" info \"%s\" to \"%s\"\n",
|
||||
dbGetRecordName(pdbentry), name, value);
|
||||
yyerror(NULL);
|
||||
return;
|
||||
yyerror(NULL);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void dbRecordAlias(char *name)
|
||||
{
|
||||
DBENTRY *pdbentry;
|
||||
tempListNode *ptempListNode;
|
||||
long status;
|
||||
DBENTRY *pdbentry;
|
||||
tempListNode *ptempListNode;
|
||||
long status;
|
||||
|
||||
if(duplicate) return;
|
||||
if (duplicate) return;
|
||||
ptempListNode = (tempListNode *)ellFirst(&tempList);
|
||||
pdbentry = ptempListNode->item;
|
||||
status = dbCreateAlias(pdbentry, name);
|
||||
if(status) {
|
||||
if (status) {
|
||||
epicsPrintf("Can't create alias \"%s\" for \"%s\"\n",
|
||||
name, dbGetRecordName(pdbentry));
|
||||
yyerror(NULL);
|
||||
@@ -1139,15 +1146,16 @@ static void dbRecordAlias(char *name)
|
||||
|
||||
static void dbAlias(char *name, char *alias)
|
||||
{
|
||||
DBENTRY dbEntry;
|
||||
DBENTRY *pdbEntry = &dbEntry;
|
||||
DBENTRY dbEntry;
|
||||
DBENTRY *pdbEntry = &dbEntry;
|
||||
|
||||
dbInitEntry(pdbbase, pdbEntry);
|
||||
if (dbFindRecord(pdbEntry, name)) {
|
||||
epicsPrintf("Alias \"%s\" refers to unknown record \"%s\"\n",
|
||||
alias, name);
|
||||
yyerror(NULL);
|
||||
} else if (dbCreateAlias(pdbEntry, alias)) {
|
||||
}
|
||||
else if (dbCreateAlias(pdbEntry, alias)) {
|
||||
epicsPrintf("Can't create alias \"%s\" referring to \"%s\"\n",
|
||||
alias, name);
|
||||
yyerror(NULL);
|
||||
@@ -1157,14 +1165,14 @@ static void dbAlias(char *name, char *alias)
|
||||
|
||||
static void dbRecordBody(void)
|
||||
{
|
||||
DBENTRY *pdbentry;
|
||||
DBENTRY *pdbentry;
|
||||
|
||||
if(duplicate) {
|
||||
duplicate = FALSE;
|
||||
return;
|
||||
if (duplicate) {
|
||||
duplicate = FALSE;
|
||||
return;
|
||||
}
|
||||
pdbentry = (DBENTRY *)popFirstTemp();
|
||||
if(ellCount(&tempList))
|
||||
yyerrorAbort("dbRecordBody: tempList not empty");
|
||||
if (ellCount(&tempList))
|
||||
yyerrorAbort("dbRecordBody: tempList not empty");
|
||||
dbFreeEntry(pdbentry);
|
||||
}
|
||||
|
||||
@@ -146,9 +146,9 @@ Note: Negative index numbers address from the end of the array, with C<-1> being
|
||||
|
||||
=item Square bracket notation C<[start:increment:end]> (shorthand)
|
||||
|
||||
The common square bracket notation with can be used in place of JSON.
|
||||
The common square bracket notation which can be used in place of JSON.
|
||||
Any parameter may be omitted (keeping the colons) to use the default value.
|
||||
If only one colon is included, this means C<[start:end]> with a increment of 1.
|
||||
If only one colon is included, this means C<[start:end]> with an increment of 1.
|
||||
If only a single parameter is used C<[index]> the filter returns one element.
|
||||
|
||||
=item Start index C<"s">
|
||||
|
||||
@@ -96,6 +96,17 @@ testHarness_SRCS += analogMonitorTest_registerRecordDeviceDriver.cpp
|
||||
TESTFILES += $(COMMON_DIR)/analogMonitorTest.dbd ../analogMonitorTest.db
|
||||
TESTS += analogMonitorTest
|
||||
|
||||
TARGETS += $(COMMON_DIR)/scanEventTest.dbd
|
||||
DBDDEPENDS_FILES += scanEventTest.dbd$(DEP)
|
||||
scanEventTest_DBD += base.dbd
|
||||
TESTPROD_HOST += scanEventTest
|
||||
scanEventTest_SRCS += scanEventTest.c
|
||||
scanEventTest_SRCS += scanEventTest_registerRecordDeviceDriver.cpp
|
||||
testHarness_SRCS += scanEventTest.c
|
||||
testHarness_SRCS += scanEventTest_registerRecordDeviceDriver.cpp
|
||||
TESTFILES += $(COMMON_DIR)/scanEventTest.dbd ../scanEventTest.db
|
||||
TESTS += scanEventTest
|
||||
|
||||
TARGETS += $(COMMON_DIR)/regressTest.dbd
|
||||
DBDDEPENDS_FILES += regressTest.dbd$(DEP)
|
||||
regressTest_DBD += base.dbd
|
||||
|
||||
@@ -20,6 +20,7 @@ int asTest(void);
|
||||
int linkRetargetLinkTest(void);
|
||||
int linkInitTest(void);
|
||||
int asyncSoftTest(void);
|
||||
int scanEventTest(void);
|
||||
|
||||
void epicsRunRecordTests(void)
|
||||
{
|
||||
@@ -41,5 +42,7 @@ void epicsRunRecordTests(void)
|
||||
|
||||
runTest(asyncSoftTest);
|
||||
|
||||
runTest(scanEventTest);
|
||||
|
||||
epicsExit(0); /* Trigger test harness */
|
||||
}
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
/*************************************************************************\
|
||||
* Copyright (c) 2018 Paul Scherrer Institut
|
||||
* EPICS BASE is distributed subject to a Software License Agreement found
|
||||
* in file LICENSE that is included with this distribution.
|
||||
\*************************************************************************/
|
||||
|
||||
/*
|
||||
* Author: Dirk Zimoch <dirk.zimoch@psi.ch>
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include "dbStaticLib.h"
|
||||
#include "dbAccessDefs.h"
|
||||
#include "dbUnitTest.h"
|
||||
#include "testMain.h"
|
||||
#include "osiFileName.h"
|
||||
#include "epicsThread.h"
|
||||
#include "dbScan.h"
|
||||
|
||||
void scanEventTest_registerRecordDeviceDriver(struct dbBase *);
|
||||
|
||||
/* test name to event number:
|
||||
num = 0 is no event,
|
||||
num > 0 is numeric event
|
||||
num < 0 is string event (use same unique number for aliases)
|
||||
*/
|
||||
const struct {char* name; int num;} events[] = {
|
||||
/* No events */
|
||||
{NULL, 0},
|
||||
{"", 0},
|
||||
{" ", 0},
|
||||
{"0", 0},
|
||||
{"0.000000", 0},
|
||||
{"-0.00000", 0},
|
||||
{"0.9", 0},
|
||||
/* Numeric events */
|
||||
{"2", 2},
|
||||
{"2.000000", 2},
|
||||
{"2.5", 2},
|
||||
{" 2.5 ", 2},
|
||||
{"+0x02", 2},
|
||||
{"3", 3},
|
||||
/* Named events */
|
||||
{"info 1", -1},
|
||||
{" info 1 ", -1},
|
||||
{"-0.9", -2},
|
||||
{"-2", -3},
|
||||
{"-2.000000", -4},
|
||||
{"-2.5", -5},
|
||||
{" -2.5 ", -5},
|
||||
{"nan", -6},
|
||||
{"NaN", -7},
|
||||
{"-NAN", -8},
|
||||
{"-inf", -9},
|
||||
{"inf", -10},
|
||||
};
|
||||
|
||||
MAIN(scanEventTest)
|
||||
{
|
||||
int i, e;
|
||||
int aliases[512] ;
|
||||
int expected_count[512];
|
||||
#define INDX(i) 256-events[i].num
|
||||
#define MAXEV 5
|
||||
|
||||
testPlan(NELEMENTS(events)*2+(MAXEV+1)*5);
|
||||
|
||||
memset(aliases, 0, sizeof(aliases));
|
||||
memset(expected_count, 0, sizeof(expected_count));
|
||||
|
||||
if (dbReadDatabase(&pdbbase, "scanEventTest.dbd",
|
||||
"." OSI_PATH_LIST_SEPARATOR ".." OSI_PATH_LIST_SEPARATOR
|
||||
"../O.Common" OSI_PATH_LIST_SEPARATOR "O.Common", NULL))
|
||||
testAbort("Database description 'scanEventTest.dbd' not found");
|
||||
|
||||
scanEventTest_registerRecordDeviceDriver(pdbbase);
|
||||
for (i = 0; i < NELEMENTS(events); i++) {
|
||||
char substitutions[256];
|
||||
sprintf(substitutions, "N=%d,EVENT=%s", i, events[i].name);
|
||||
if (dbReadDatabase(&pdbbase, "scanEventTest.db",
|
||||
"." OSI_PATH_LIST_SEPARATOR "..", substitutions))
|
||||
testAbort("Error reading test database 'scanEventTest.db'");
|
||||
}
|
||||
testIocInitOk();
|
||||
testDiag("Test if eventNameToHandle() strips spaces and handles numeric events");
|
||||
for (i = 0; i < NELEMENTS(events); i++) {
|
||||
EVENTPVT pev = eventNameToHandle(events[i].name);
|
||||
/* test that some names are not events (num=0) */
|
||||
if (events[i].num == 0)
|
||||
testOk(pev == NULL, "\"%s\" -> no event", events[i].name);
|
||||
else
|
||||
{
|
||||
expected_count[INDX(i)]++; /* +1 for postEvent */
|
||||
if (events[i].num > 0)
|
||||
{
|
||||
testOk(pev != NULL, "\"%s\" -> numeric event %d", events[i].name, events[i].num);
|
||||
expected_count[INDX(i)]++; /* +1 for post_event */
|
||||
}
|
||||
else
|
||||
{
|
||||
/* test that some strings resolve the same event (num!=0) */
|
||||
if (!aliases[INDX(i)])
|
||||
{
|
||||
aliases[INDX(i)] = i;
|
||||
testOk(pev != NULL, "\"%s\" -> new named event", events[i].name);
|
||||
}
|
||||
else
|
||||
{
|
||||
testOk(pev == eventNameToHandle(events[aliases[INDX(i)]].name),
|
||||
"\"%s\" alias for \"%s\"", events[i].name, events[aliases[INDX(i)]].name);
|
||||
}
|
||||
}
|
||||
}
|
||||
post_event(events[i].num); /* triggers numeric events only */
|
||||
postEvent(pev);
|
||||
}
|
||||
|
||||
testDiag("Check calculated numeric events (backward compatibility)");
|
||||
for (e = 0; e <= MAXEV; e++) {
|
||||
testdbPutFieldOk("eventnum", DBR_LONG, e);
|
||||
testdbGetFieldEqual("e1", DBR_LONG, e);
|
||||
testdbGetFieldEqual("e2", DBR_LONG, e);
|
||||
testdbPutFieldOk("e3", DBR_LONG, e);
|
||||
testdbPutFieldOk("e3.PROC", DBR_LONG, 1);
|
||||
for (i = 0; i < NELEMENTS(events); i++)
|
||||
if (e > 0 && e < 256 && events[i].num == e) { /* numeric events */
|
||||
expected_count[INDX(i)]+=3; /* +1 for eventnum->e1, +1 for e2<-eventnum, +1 for e3 */
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* Allow records to finish processing */
|
||||
epicsThreadSleep(0.1);
|
||||
testDiag("Check if events have been processed the expected number of times");
|
||||
for (i = 0; i < NELEMENTS(events); i++) {
|
||||
char pvname[100];
|
||||
sprintf(pvname, "c%d", i);
|
||||
testDiag("Event \"%s\" expected %d times", events[i].name, expected_count[INDX(i)]);
|
||||
testdbGetFieldEqual(pvname, DBR_LONG, expected_count[INDX(i)]);
|
||||
}
|
||||
|
||||
return testDone();
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
record(calc, "c$(N)") {
|
||||
field(SCAN, "Event")
|
||||
field(EVNT, "$(EVENT)")
|
||||
field(CALC, "VAL+1")
|
||||
}
|
||||
record(dfanout, "eventnum") {
|
||||
field(OUTA, "e1 PP")
|
||||
field(FLNK, "e2")
|
||||
}
|
||||
record(event, "e1") {
|
||||
}
|
||||
record(event, "e2") {
|
||||
field(INP, "eventnum")
|
||||
}
|
||||
record(event, "e3") {
|
||||
}
|
||||
Reference in New Issue
Block a user