diff --git a/caProvider.md b/caProvider.md index 6a479aa..29a285e 100644 --- a/caProvider.md +++ b/caProvider.md @@ -1,16 +1,20 @@ # pvAccessCPP: ca provider -2018.07.09 +2018.10.05 Editors: * Marty Kraimer -This is a description of channel provider **ca** that is implemented as part of **pvAccessCPP**. +**ca** is a channel provider **ca** that is implemented as part of **pvAccessCPP**. + It uses the **channel access** network protocol to communicate with a server, i. e. the network protocol that has been used to communicate with **EPICS IOCs** since 1990. +A description of **ca** is provided in +[caProvider](https://mrkraimer.github.io/website/caProvider/caProvider.html) + Provider **pva** is another way to connect to a **DBRecord**, But this only works if the IOC has **qsrv** installed. **qsrv**, which is provided with @@ -19,618 +23,4 @@ has full support for communicating with a **DBRecord**. The only advantage of **ca** is that it does require any changes to an existing IOC. -The following are discussed. - -* [Introduction](#S-introduction) -* [client API](#S-client) -* [Mapping DBD data to pvData](#S-dbd_to_pvdata) -* [Developing plugins for ca provider.](#S-plugin) - - - -## Introduction - -The primary purpose of the **ca** provider is to access **DBRecord**s in an EPICS IOC via the -**channel access** network protocol but to use pvData objects for the client. - -Each **DBRecord** instance has a record name that must be unique in the local area network. -A client can access any public field of a **DBRecord**; -Each **DBRecord** instance has a record name that is unique with in the local area network -A channel name is a **recordname.fieldName**. -If the fieldname is not specified then **.VAL** is assumed - -### example database - -The following: - -``` -mrk> pwd -/home/epicsv4/masterCPP/pvAccessCPP/testCa -mrk> softIoc -d testCaProvider.db -``` - -Starts an EPICS IOC that is used for all examples in this document. - -### examples - -``` -mrk> caget -d DBR_TIME_FLOAT DBRdoubleout -DBRdoubleout - Native data type: DBF_DOUBLE - Request type: DBR_TIME_FLOAT - Element count: 1 - Value: 1 - Timestamp: 2018-06-21 06:23:07.939894 - Status: NO_ALARM - Severity: NO_ALARM -mrk> pvget -p ca -r "value,alarm,timeStamp" -i DBRdoubleout -DBRdoubleout -structure - double value 1 - alarm_t alarm - int severity 0 - int status 0 - string message - time_t timeStamp - long secondsPastEpoch 1529576587 - int nanoseconds 939894210 - int userTag 0 -mrk> pvget -p ca -r "value,alarm,timeStamp" DBRdoubleout -DBRdoubleout -structure - double value 1 - alarm_t alarm NO_ALARM NO_STATUS - time_t timeStamp 2018-06-21T06:23:07.940 0 - -mrk> pvget -p ca -r value -i DBRdoubleout.SCAN -DBRdoubleout.SCAN -epics:nt/NTEnum:1.0 - enum_t value - int index 0 - string[] choices [Passive,Event,I/O Intr,10 second,5 second,2 second,1 second,.5 second,.2 second,.1 second] -``` - -### Overview of Channel Access - -**channel access**, which is provided with **epics-base**, defines and implements a protocol -for client/server network communication. - -**epics-base** provides both a client and a server implementation -This document only discusses the client API. - -For details see: - -[EPICS Channel Access 4.13.1 Reference Manual](https://epics.anl.gov/base/R7-0/1-docs/CAref.html) - -**channel access** allows a client to get, put, and monitor monitor data from a server. -The data is defined by various DBD types. - -The following, in **epics-base/include**, are the -main include files that show the **channel access** API: - -``` -cadef.h -db_access.h -``` - -The client requests data via one of the DBR types. - -For example: - -``` -DBR_STS_DOUBLE returns a double status structure (dbr_sts_double) -where -struct dbr_sts_double{ - dbr_short_t status; /* status of value */ - dbr_short_t severity; /* severity of alarm */ - dbr_long_t RISC_pad; /* RISC alignment */ - dbr_double_t value; /* current value */ -}; -``` - -The server converts data between the native type of the field being accessed and the DBR type. - - -### Overview of ca provider - -**ca** is a pvAccess Channel Provider that uses **channel access** to connect a client to a server. - -With **ca**, the client data appears as pvData objects, i. e. -**ca** converts the data provided by **channel access** to/from pvData - -Thus a pvAccess client can communicate with an existing V3 EPICS IOC without making -any changes to existing IOCs. - -For an overview of pvData and pvAccess see: - -[EPICS V4 Developer's Guide](https://mrkraimer.github.io/website/developerGuide/developerGuide.html) - -**ca** requests data from the server with a DBR type that matches the native type. -See the next section for more details. - -All conversion to/from other types must be done by the client. - -## Client API - -**ca** implements the following pvAccess methods : **getField**, **channelGet**, **channelPut** and **monitor**. - -For channelPut the only field that can be accessed is **value**. -For channelPut a client can issue puts with and without a callback from the server. -The default is no callback. If createChannelPut has the option "record[block=true]" then a put callback used. - -All of the other pvAccess methods provide access to fields **alarm** and **timeStamp**. - -Depending on the type associated with the **value** field the following fields may also be available: -**display**, **control** , and **valueAlarm**. - -Thus a client can make requests like: - -``` -pvget -p ca -r "value,alarm,timeStamp,display,control,valueAlarm" names ... -``` - -**ca** will create a structure that has the fields requested but only for fields that are supported -by the server. - -* For puts only value is supported. -* For gets and monitors every channel supports value, alarm, and timeStamp; -* If any of display,control, or valueAlarm are requested then timeStamp is NOT available. - -Lets discuss the various fields. - -### value - -This can be a scalar, scalarArray, or an enumerated structure. - -For a scalar or scalarArray the ScalarType is one of the following: -**pvString**, **pvByte**, **pvShort**, **pvInt**, **pvFloat**, or **pvDouble**. - -Note that **channel access** does not support unsigned integers or 64 bit integers. - -A enumerated structure is created if the native type is **DBR_ENUM**. - -Some examples are: - -``` -pvget -p ca -r value -i DBRlongout -DBRlongout -structure - int value 0 -mrk> pvget -p ca -r value -i DBRdoubleout -DBRdoubleout -structure - double value 0 -mrk> pvget -p ca -r value -i DBRshortArray -DBRshortArray -structure - short[] value [] -mrk> pvget -p ca -r value -i DBRstringArray -DBRstringArray -structure - string[] value [aa,bb,cc] -mrk> pvget -p ca -r value -i DBRmbbin -DBRmbbin -epics:nt/NTEnum:1.0 - enum_t value - int index 1 - string[] choices [zero,one,two,three,four,five,six,seven,eight,nine,ten,eleven,twelve,thirteen,fourteen,fifteen] -mrk> - -``` - - -### alarm,timeStamp,display,control, and valueAlarm - -Each of these is one of the property structures defined in pvData. - -#### Examples - -``` -mrk> pvget -p ca -r alarm -i DBRdoubleout -DBRdoubleout -structure - alarm_t alarm - int severity 2 - int status 3 - string message HIHI - -mrk> pvget -p ca -r timeStamp -i DBRdoubleout -DBRdoubleout -structure - time_t timeStamp - long secondsPastEpoch 1529923341 - int nanoseconds 314916189 - int userTag 0 -mrk> pvget -p ca -r display -i DBRdoubleout -DBRdoubleout -structure - display_t display - double limitLow -10 - double limitHigh 10 - string description - string format F8.2 - string units volts -mrk> pvget -p ca -r control -i DBRdoubleout -DBRdoubleout -structure - control_t control - double limitLow -1e+29 - double limitHigh 1e+29 - double minStep 0 -mrk> pvget -p ca -r valueAlarm -i DBRdoubleout -DBRdoubleout -structure - valueAlarm_t valueAlarm - boolean active false - double lowAlarmLimit -8 - double lowWarningLimit -6 - double highWarningLimit 6 - double highAlarmLimit 8 - int lowAlarmSeverity 0 - int lowWarningSeverity 0 - int highWarningSeverity 0 - int highAlarmSeverity 0 -``` - - - -## DBD to pvData - -### Type Conversion - -Three type systems are involved in accessing data in a **DBRecord** and converting it to/from pvData: - -* DBF The type system used for **DBRecord**s. -* DBR The type system used by **channel access**. -* pvData - -The following gives a summary of the conversions between the type systems: - -``` -rawtype DBF DBR pvData ScalarType - -char[MAX_STRING_SIZE] DBF_STRING DBR_STRING pvString -epicsInt8 DBF_CHAR DBR_CHAR pvByte -epicsUint8 DBF_UCHAR DBR_CHAR pvByte -epicsInt16 DBF_SHORT DBR_SHORT pvShort -epicsUInt16 DBF_USHORT DBR_LONG pvInt -epicsInt32 DBF_LONG DBR_LONG pvInt -epicsUInt32 DBF_ULONG DBR_DOUBLE pvDouble -epicsInt64 DBF_INT64 no support -epicsUInt64 DBF_UINT64 no support -float DBF_FLOAT DBR_FLOAT pvFloat -double DBF_DOUBLE DBR_DOUBLE pvDouble -epicsUInt16 DBF_ENUM DBR_ENUM enum structure -epicsUInt16 DBF_MENU DBR_ENUM enum structure -``` - -Notes: - -* Both DBF_CHAR and DBF_UCHAR go to DBR_CHAR. This is ambigous. -* DBF_USHORT promoted to DBR_LONG -* DBF_ULONG promoted to DBR_DOUBLE -* qsrv provides full access to all DBF types, but the IOC must have qsrv installed. - -### Accessing data in a DBRecord - -An IOC database is a memory resident database of **DBRecord** instances. - -Each **DBRecord** is an instance of one of an extensible set of record types. -Each record type has an associated dbd definition which defines a set of fields for -each record instance. - -For example an aoRecord.dbd has the definition: - -``` -recordtype(ao) { - include "dbCommon.dbd" - field(VAL,DBF_DOUBLE) { - ... - } - field(OVAL,DBF_DOUBLE) { - ... - } - ... many more fields -``` - -In addition each record type has a associated set of support code defined in recSup.h - -``` -/* record support entry table */ -struct typed_rset { - long number; /* number of support routines */ - long (*report)(void *precord); - long (*init)(); - long (*init_record)(struct dbCommon *precord, int pass); - long (*process)(struct dbCommon *precord); - long (*special)(struct dbAddr *paddr, int after); - long (*get_value)(void); /* DEPRECATED set to NULL */ - long (*cvt_dbaddr)(struct dbAddr *paddr); - long (*get_array_info)(struct dbAddr *paddr, long *no_elements, long *offset); - long (*put_array_info)(struct dbAddr *paddr, long nNew); - long (*get_units)(struct dbAddr *paddr, char *units); - long (*get_precision)(const struct dbAddr *paddr, long *precision); - long (*get_enum_str)(const struct dbAddr *paddr, char *pbuffer); - long (*get_enum_strs)(const struct dbAddr *paddr, struct dbr_enumStrs *p); - long (*put_enum_str)(const struct dbAddr *paddr, const char *pbuffer); - long (*get_graphic_double)(struct dbAddr *paddr, struct dbr_grDouble *p); - long (*get_control_double)(struct dbAddr *paddr, struct dbr_ctrlDouble *p); - long (*get_alarm_double)(struct dbAddr *paddr, struct dbr_alDouble *p); -}; -``` - -The methods that support accessing data from the record include: - -``` -cvt_dbaddr Implemented by record types that determine VAL type at record initialization -*array_info Implemented by array record types -get_units Implemented by numeric record types -get_precision Implemented by float and double record types -*_enum_* Implemented by enumerated record types -get_graphic_double NOTE Always returns limits as double -get_control_double NOTE Always returns limits as double -get_alarm_double NOTE Always returns limits as double -``` - -Each of these methods is optional, i. e. record support for a particular record type -only implements methods that make sense for the record type. - -For example the enum methods are only implemented by records that have the definition: - -``` -... - field(VAL,DBF_ENUM) { -... -} -... -``` - - -### Channel Access Data - -A client can access any public field of a **DBRecord**; -Each **DBRecord** instance has a record name that is unique within the local area network. - -A channel name is a **recordname.fieldName**. - -If the fieldname is not specified then **.VAL** is assumed and the record support methods shown -above can also be used to get additional data from the record. - -Any field that is accessable by client code must have a vald DBF_ type. - -A client gets/puts data via a **DBR_*** request. - -The basic DBR types are: -``` -rawtype DBR - -char[MAX_STRING_SIZE] DBR_STRING -epicsInt8 DBR_CHAR -epicsInt16 DBR_SHORT -epicsInt32 DBR_LONG -float DBF_FLOAT -double DBF_DOUBLE -epicsUInt16 DBR_ENUM -``` - -In addition to the DBR basic types the following DBR types provide additional data: - -``` -DBR one of the types above. -DBR_STATUS_* adds status and severity to DBR. -DBR_TIME_* adds epicsTimeStamp to DBR_STATUS. -DBR_GR_* adds display limits to DBR_STATUS. NOTE: no epicsTimeStamp -DBR_CTRL_ adds control limits to DBR_GR. NOTE: no epicsTimeStamp -DBR_CTRL_ENUM This is a special case. -``` - -NOTES: - -* status, severity, and epicsTimeStamp are the same for each DBR type. -* limits have the same types as the correspondng DBR type. -* server converts limits from double to the DBR type. -* GR and CTRL have precision only for DBR_FLOAT and DBR_DOUBLE - - -Some examples: - -``` -DBR_STS_DOUBLE returns a double status structure (dbr_sts_double) -where -struct dbr_sts_double{ - dbr_short_t status; /* status of value */ - dbr_short_t severity; /* severity of alarm */ - dbr_long_t RISC_pad; /* RISC alignment */ - dbr_double_t value; /* current value */ -}; - -DBR_TIME_DOUBLE returns a double time structure (dbr_time_double) -where -struct dbr_time_double{ - dbr_short_t status; /* status of value */ - dbr_short_t severity; /* severity of alarm */ - epicsTimeStamp stamp; /* time stamp */ - dbr_long_t RISC_pad; /* RISC alignment */ - dbr_double_t value; /* current value */ -}; - -DBR_GR_SHORT returns a graphic short structure (dbr_gr_short) -where -struct dbr_gr_short{ - dbr_short_t status; /* status of value */ - dbr_short_t severity; /* severity of alarm */ - char units[MAX_UNITS_SIZE]; /* units of value */ - dbr_short_t upper_disp_limit; /* upper limit of graph */ - dbr_short_t lower_disp_limit; /* lower limit of graph */ - dbr_short_t upper_alarm_limit; - dbr_short_t upper_warning_limit; - dbr_short_t lower_warning_limit; - dbr_short_t lower_alarm_limit; - dbr_short_t value; /* current value */ -}; - -DBR_GR_DOUBLE returns a graphic double structure (dbr_gr_double) -where -struct dbr_gr_double{ - dbr_short_t status; /* status of value */ - dbr_short_t severity; /* severity of alarm */ - dbr_short_t precision; /* number of decimal places */ - dbr_short_t RISC_pad0; /* RISC alignment */ - char units[MAX_UNITS_SIZE]; /* units of value */ - dbr_double_t upper_disp_limit; /* upper limit of graph */ - dbr_double_t lower_disp_limit; /* lower limit of graph */ - dbr_double_t upper_alarm_limit; - dbr_double_t upper_warning_limit; - dbr_double_t lower_warning_limit; - dbr_double_t lower_alarm_limit; - dbr_double_t value; /* current value */ -}; - -DBR_CTRL_DOUBLE returns a control double structure (dbr_ctrl_double) -where -struct dbr_ctrl_double{ - dbr_short_t status; /* status of value */ - dbr_short_t severity; /* severity of alarm */ - dbr_short_t precision; /* number of decimal places */ - dbr_short_t RISC_pad0; /* RISC alignment */ - char units[MAX_UNITS_SIZE]; /* units of value */ - dbr_double_t upper_disp_limit; /* upper limit of graph */ - dbr_double_t lower_disp_limit; /* lower limit of graph */ - dbr_double_t upper_alarm_limit; - dbr_double_t upper_warning_limit; - dbr_double_t lower_warning_limit; - dbr_double_t lower_alarm_limit; - dbr_double_t upper_ctrl_limit; /* upper control limit */ - dbr_double_t lower_ctrl_limit; /* lower control limit */ - dbr_double_t value; /* current value */ -}; - - -DBR_CTRL_ENUM returns a control enum structure (dbr_ctrl_enum) -where -struct dbr_ctrl_enum{ - dbr_short_t status; /* status of value */ - dbr_short_t severity; /* severity of alarm */ - dbr_short_t no_str; /* number of strings */ - char strs[MAX_ENUM_STATES][MAX_ENUM_STRING_SIZE]; - /* state strings */ - dbr_enum_t value; /* current value */ -}; -``` - -### PVData for a DBRrecord via ca provider - -**pvAccessCPP/src/ca** has files **dbdToPv.h** and **dbdToPv.cpp**. -This is the code that converts between DBD data and pvData. - -This code must decide which of the many **DBR_*** types to use. - - -There is a static method: - -``` -static DbdToPvPtr create( - CAChannelPtr const & caChannel, - epics::pvData::PVStructurePtr const & pvRequest, - IOType ioType); // one of getIO, putIO, and monitorIO -``` - - -When this is called the first thing is to determine which fields are requested by the client. -This is from the set **value**, **alarm**, **timeStamp**. **display**, **control** , and **valueAlarm**. - - -* If the ioType is putIO only **value** is valid. -* If the channel type is **DBR_ENUM** then **display**, **control** , and **valueAlarm** are ignored. -* If the channel is an array then **control** , and **valueAlarm** are ignored. -* If the channel type is **DBR_STRING** then **display**, **control** , and **valueAlarm** are ignored. -* If any of **display**, **control** , and **valueAlarm** are still allowed then **timeStamp** is ignored, -because the DBR type selected will not return the timeStamp. - -If ths channel type is **DBR_ENUM** a one time **ca_array_get_callback(DBR_GR_ENUM...** request is issued -to get the choices for the enumerated value. - -Depending or which fields are still valid, the DBR type is obtained via - -* If any of **display**, **control** ,or **valueAlarm** is valid then **dbf_type_to_DBR_CTRL(caValueType)** . -* else If **alarm** or **timeStamp** is valid then **dbf_type_to_DBR_TIME(caValueType)** . -* else **dbf_type_to_DBR(caValueType)** - -Where **caValueType** is one of DBR_STRING, DBR_SHORT, DBR_FLOAT, DBR_ENUM, DBR_CHAR, DBR_LONG, DBR_DOUBLE. - -If **display** is still valid then the following call is made: - -``` -string name(caChannel->getChannelName() + ".DESC"); -int result = ca_create_channel(name.c_str(), -... -``` -When the channel connects a get is issued to get the value for **display.description**. - -## Developing plugins for ca provider - -This section provides guidelines for code developers that use **ca** to connect a client to a server. -This includes plugins for things like MEDM, EDM, caqtDM, etc. -But also means any code that use **ca**: pvget, pvput, pvaClientCPP, exampleCPP/exampleClient, etc. - -The **channel access** reference manual describes channel context: - -[CA Client Contexts and Application Specific Auxiliary Threads](https://epics.anl.gov/base/R7-0/1-docs/CAref.html#Client2) - -A brief summary of channel context is: - - -* Only the thread that calls CAClientFactory::start() and associated auxillary threads -can call **ca_xxx** functions. - -The public access to **ca** is: - -``` -class epicsShareClass CAClientFactory -{ -public: - /** @brief start provider ca - * - */ - static void start(); - /** @brief get the ca_client_context - * - * This can be called by an application specific auxiliary thread. - * See ca documentation. Not for casual use. - */ - static ca_client_context * get_ca_client_context(); - /** @brief stop provider ca - * - * This does nothing since epicsAtExit is used to destroy the instance. - */ - static void stop(); -}; -``` -Any code that uses **ca** must call **CAClientFactory::start()** before making any pvAccess client requests. - -ca_context_create is called for the thread that calls CAClientFactory::start(). - -If the client creates auxillary threads the make pvAccess client requests then the auxillary threads will automatically become -a **ca** auxilary thread. - - -[Deadlock in ca_clear_subscription()](https://bugs.launchpad.net/epics-base/7.0/+bug/1751380) - -Shows a problem with monitor callbacks. -A test was created that shows that the same problem can occur with a combination of rapid get, put and monitor events. - - -In order to prevent this problem **ca** creates the following threads: -**getEventThread**, **putEventThread**, and **monitorEventThread**. - -All client callbacks are made via one of these threads. -For example a call to the requester's **monitorEvent** method is made from the monitorEventThread. - -**Notes** - -* These threads do not call **ca_attach_context**. -* No **ca_xxx** function should be called from the requester's callback method. - - - diff --git a/documentation/release_notes.dox b/documentation/release_notes.dox index ab3e4f7..6683420 100644 --- a/documentation/release_notes.dox +++ b/documentation/release_notes.dox @@ -24,6 +24,7 @@ Release 6.1.0 (UNRELEASED) - PVA Server now handles pipeline=true automatically for all ChannelProviders. It is only necessary to implement epics::pvAccess::Monitor::reportRemoteQueueStatus() to receive notification of initial window size. + - Most locking issues in the 'ca' provider should now be resolved. - Additions - pvput to NTEnum via. string now supported - pvac::* add valid() method and boolean cast shorthand. Also reset() and operator<<(ostream, ...) @@ -31,6 +32,10 @@ Release 6.1.0 (UNRELEASED) - Add pvac::ClientProvider::name() - Add epics::pvAccess::MonitorFIFO utility implementation of epics::pvAccess::Monitor - Add pvas::StaticProvider, pvas::DynamicProvider, and pvas::SharedPV. See @ref pvas + - Support in the 'ca' provider for new pvRequest options: + - New `DBE` record option to select CA events to monitor, containing any combination of the keywords `VALUE`, `ARCHIVE`, `ALARM` and/or `PROPERTY`. + - New `dbtype` field option to represent data types returned by CA as an unsigned or 64-bit integer. + - New `pvtype=pvString` field option to represent an array of chars from CA as a string. Release 6.0.0 (Dec 2017) ======================== diff --git a/src/ca/Makefile b/src/ca/Makefile index 93e6b27..6bed7c3 100644 --- a/src/ca/Makefile +++ b/src/ca/Makefile @@ -11,6 +11,7 @@ LIB_SYS_LIBS_WIN32 += ws2_32 INC += pv/caProvider.h +pvAccessCA_SRCS += channelConnectThread.cpp pvAccessCA_SRCS += monitorEventThread.cpp pvAccessCA_SRCS += getDoneThread.cpp pvAccessCA_SRCS += putDoneThread.cpp diff --git a/src/ca/caChannel.cpp b/src/ca/caChannel.cpp index f1f4e64..6a1cf81 100644 --- a/src/ca/caChannel.cpp +++ b/src/ca/caChannel.cpp @@ -10,6 +10,7 @@ #include #include #include +#include "channelConnectThread.h" #include "monitorEventThread.h" #include "getDoneThread.h" #include "putDoneThread.h" @@ -51,16 +52,43 @@ static void ca_connection_handler(struct connection_handler_args args) CAChannel *channel = static_cast(ca_puser(args.chid)); if (args.op == CA_OP_CONN_UP) { - channel->connected(); + channel->connect(true); } else if (args.op == CA_OP_CONN_DOWN) { - channel->disconnected(); + channel->connect(false); } } -void CAChannel::connected() +void CAChannel::connect(bool isConnected) { if(DEBUG_LEVEL>0) { - cout<< "CAChannel::connected " << channelName << endl; + cout<< "CAChannel::connect " << channelName << endl; + } + { + Lock lock(requestsMutex); + channelConnected = isConnected; + } + channelConnectThread->channelConnected(notifyChannelRequester); +} + +void CAChannel::notifyClient() +{ + if(DEBUG_LEVEL>0) { + cout<< "CAChannel::notifyClient " << channelName << endl; + } + CAChannelProviderPtr provider(channelProvider.lock()); + if(!provider) return; + bool isConnected = false; + { + Lock lock(requestsMutex); + isConnected = channelConnected; + } + if(!isConnected) { + ChannelRequester::shared_pointer req(channelRequester.lock()); + if(req) { + EXCEPTION_GUARD(req->channelStateChange( + shared_from_this(), Channel::DISCONNECTED)); + } + return; } while(!getFieldQueue.empty()) { getFieldQueue.front()->activate(); @@ -87,18 +115,6 @@ void CAChannel::connected() } } -void CAChannel::disconnected() -{ - if(DEBUG_LEVEL>0) { - cout<< "CAChannel::disconnected " << channelName << endl; - } - - ChannelRequester::shared_pointer req(channelRequester.lock()); - if(req) { - EXCEPTION_GUARD(req->channelStateChange( - shared_from_this(), Channel::DISCONNECTED)); - } -} CAChannel::CAChannel(std::string const & channelName, CAChannelProvider::shared_pointer const & channelProvider, @@ -107,7 +123,9 @@ CAChannel::CAChannel(std::string const & channelName, channelProvider(channelProvider), channelRequester(channelRequester), channelID(0), - channelCreated(false) + channelCreated(false), + channelConnected(false), + channelConnectThread(ChannelConnectThread::get()) { if(DEBUG_LEVEL>0) { cout<< "CAChannel::CAChannel " << channelName << endl; @@ -116,11 +134,13 @@ CAChannel::CAChannel(std::string const & channelName, void CAChannel::activate(short priority) { + ChannelRequester::shared_pointer req(channelRequester.lock()); + if(!req) return; if(DEBUG_LEVEL>0) { cout<< "CAChannel::activate " << channelName << endl; } - ChannelRequester::shared_pointer req(channelRequester.lock()); - if(!req) return; + notifyChannelRequester = NotifyChannelRequesterPtr(new NotifyChannelRequester()); + notifyChannelRequester->setChannel(shared_from_this()); attachContext(); int result = ca_create_channel(channelName.c_str(), ca_connection_handler, @@ -382,8 +402,7 @@ void CAChannelGetField::callRequester(CAChannelPtr const & caChannel) if(!requester) return; PVStructurePtr pvRequest(createRequest("")); DbdToPvPtr dbdToPv = DbdToPv::create(caChannel,pvRequest,getIO); - PVStructurePtr pvStructure = dbdToPv->createPVStructure(); - Structure::const_shared_pointer structure(pvStructure->getStructure()); + Structure::const_shared_pointer structure(dbdToPv->getStructure()); Field::const_shared_pointer field = subField.empty() ? std::tr1::static_pointer_cast(structure) : @@ -451,6 +470,7 @@ void CAChannelGet::activate() std::cout << "CAChannelGet::activate " << channel->getChannelName() << endl; } dbdToPv = DbdToPv::create(channel,pvRequest,getIO); + dbdToPv->getChoices(channel); pvStructure = dbdToPv->createPVStructure(); bitSet = BitSetPtr(new BitSet(pvStructure->getStructure()->getNumberFields())); notifyGetRequester = NotifyGetRequesterPtr(new NotifyGetRequester()); @@ -488,6 +508,9 @@ void CAChannelGet::getDone(struct event_handler_args &args) void CAChannelGet::notifyClient() { + if(DEBUG_LEVEL>1) { + std::cout << "CAChannelGet::notifyClient " << channel->getChannelName() << endl; + } ChannelGetRequester::shared_pointer getRequester(channelGetRequester.lock()); if(!getRequester) return; EXCEPTION_GUARD(getRequester->getDone(getStatus, shared_from_this(), pvStructure, bitSet)); @@ -572,6 +595,7 @@ void CAChannelPut::activate() cout << "CAChannelPut::activate " << channel->getChannelName() << endl; } dbdToPv = DbdToPv::create(channel,pvRequest,putIO); + dbdToPv->getChoices(channel); pvStructure = dbdToPv->createPVStructure(); bitSet = BitSetPtr(new BitSet(pvStructure->getStructure()->getNumberFields())); PVStringPtr pvString = pvRequest->getSubField("record._options.block"); @@ -809,7 +833,8 @@ CAChannelMonitor::CAChannelMonitor( pvRequest(pvRequest), isStarted(false), monitorEventThread(MonitorEventThread::get()), - pevid(NULL) + pevid(NULL), + eventMask(DBE_VALUE | DBE_ALARM) {} CAChannelMonitor::~CAChannelMonitor() @@ -831,6 +856,7 @@ void CAChannelMonitor::activate() std::cout << "CAChannelMonitor::activate " << channel->getChannelName() << endl; } dbdToPv = DbdToPv::create(channel,pvRequest,monitorIO); + dbdToPv->getChoices(channel); pvStructure = dbdToPv->createPVStructure(); activeElement = MonitorElementPtr(new MonitorElement(pvStructure)); int32 queueSize = 2; @@ -844,6 +870,15 @@ void CAChannelMonitor::activate() ss >> size; if (size > 1) queueSize = size; } + pvString = pvOptions->getSubField("DBE"); + if(pvString) { + std::string value(pvString->get()); + eventMask = 0; + if(value.find("VALUE")!=std::string::npos) eventMask|=DBE_VALUE; + if(value.find("ARCHIVE")!=std::string::npos) eventMask|=DBE_ARCHIVE; + if(value.find("ALARM")!=std::string::npos) eventMask|=DBE_ALARM; + if(value.find("PROPERTY")!=std::string::npos) eventMask|=DBE_PROPERTY; + } } notifyMonitorRequester = NotifyMonitorRequesterPtr(new NotifyMonitorRequester()); notifyMonitorRequester->setChannelMonitor(shared_from_this()); @@ -916,7 +951,7 @@ Status CAChannelMonitor::start() channel->attachContext(); int result = ca_create_subscription(dbdToPv->getRequestType(), 0, - channel->getChannelID(), DBE_VALUE, + channel->getChannelID(), eventMask, ca_subscription_handler, this, &pevid); if (result == ECA_NORMAL) diff --git a/src/ca/caChannel.h b/src/ca/caChannel.h index d5835cd..cfaf549 100644 --- a/src/ca/caChannel.h +++ b/src/ca/caChannel.h @@ -28,6 +28,15 @@ namespace epics { namespace pvAccess { namespace ca { +class CAChannel; +typedef std::tr1::shared_ptr CAChannelPtr; +typedef std::tr1::weak_ptr CAChannelWPtr; +class ChannelConnectThread; +typedef std::tr1::shared_ptr ChannelConnectThreadPtr; + +class NotifyChannelRequester; +typedef std::tr1::shared_ptr NotifyChannelRequesterPtr; + class NotifyMonitorRequester; typedef std::tr1::shared_ptr NotifyMonitorRequesterPtr; class MonitorEventThread; @@ -88,9 +97,6 @@ public: short priority, ChannelRequester::shared_pointer const & channelRequester); virtual ~CAChannel(); - - void connected(); - void disconnected(); chid getChannelID(); virtual std::tr1::shared_ptr getProvider(); @@ -113,6 +119,8 @@ public: void attachContext(); void disconnectChannel(); + void connect(bool isConnected); + void notifyClient(); private: virtual void destroy() {} CAChannel(std::string const & channelName, @@ -126,6 +134,9 @@ private: ChannelRequester::weak_pointer channelRequester; chid channelID; bool channelCreated; + bool channelConnected; + ChannelConnectThreadPtr channelConnectThread; + NotifyChannelRequesterPtr notifyChannelRequester; epics::pvData::Mutex requestsMutex; std::queue getFieldQueue; @@ -253,6 +264,7 @@ private: bool isStarted; MonitorEventThreadPtr monitorEventThread; evid pevid; + unsigned long eventMask; NotifyMonitorRequesterPtr notifyMonitorRequester; DbdToPvPtr dbdToPv; diff --git a/src/ca/caProvider.cpp b/src/ca/caProvider.cpp index 5c5b22c..09bcb8b 100644 --- a/src/ca/caProvider.cpp +++ b/src/ca/caProvider.cpp @@ -11,6 +11,7 @@ #include #include +#include "channelConnectThread.h" #include "monitorEventThread.h" #include "getDoneThread.h" #include "putDoneThread.h" @@ -39,6 +40,7 @@ CAChannelProvider::CAChannelProvider() CAChannelProvider::CAChannelProvider(const std::tr1::shared_ptr&) : current_context(0), + channelConnectThread(ChannelConnectThread::get()), monitorEventThread(MonitorEventThread::get()), getDoneThread(GetDoneThread::get()), putDoneThread(PutDoneThread::get()) @@ -75,9 +77,10 @@ CAChannelProvider::~CAChannelProvider() channelQ.front()->disconnectChannel(); channelQ.pop(); } - monitorEventThread->stop(); - getDoneThread->stop(); putDoneThread->stop(); + getDoneThread->stop(); + monitorEventThread->stop(); + channelConnectThread->stop(); if(DEBUG_LEVEL>0) { std::cout << "CAChannelProvider::~CAChannelProvider() calling ca_context_destroy\n"; } @@ -174,10 +177,8 @@ void CAChannelProvider::attachContext() { ca_client_context* thread_context = ca_current_context(); if (thread_context == current_context) return; - if (thread_context != NULL) { - throw std::runtime_error("CAChannelProvider::attachContext Foreign CA context in use"); - } int result = ca_attach_context(current_context); + if(result==ECA_ISATTACHED) return; if (result != ECA_NORMAL) { std::string mess("CAChannelProvider::attachContext error calling ca_attach_context "); mess += ca_message(result); diff --git a/src/ca/caProviderPvt.h b/src/ca/caProviderPvt.h index 0d48271..4a84c9d 100644 --- a/src/ca/caProviderPvt.h +++ b/src/ca/caProviderPvt.h @@ -24,6 +24,9 @@ namespace ca { #define DEBUG_LEVEL 0 +class ChannelConnectThread; +typedef std::tr1::shared_ptr ChannelConnectThreadPtr; + class MonitorEventThread; typedef std::tr1::shared_ptr MonitorEventThreadPtr; @@ -86,6 +89,7 @@ private: ca_client_context* current_context; epics::pvData::Mutex channelListMutex; std::vector caChannelList; + ChannelConnectThreadPtr channelConnectThread; MonitorEventThreadPtr monitorEventThread; GetDoneThreadPtr getDoneThread; PutDoneThreadPtr putDoneThread; diff --git a/src/ca/channelConnectThread.cpp b/src/ca/channelConnectThread.cpp new file mode 100644 index 0000000..c378019 --- /dev/null +++ b/src/ca/channelConnectThread.cpp @@ -0,0 +1,115 @@ +/** + * Copyright - See the COPYRIGHT that is included with this distribution. + * pvAccessCPP is distributed subject to a Software License Agreement found + * in file LICENSE that is included with this distribution. + */ +/** + * @author mrk + * @date 2018.07 + */ + +#include "caChannel.h" +#include +#define epicsExportSharedSymbols +#include "channelConnectThread.h" + +using namespace epics::pvData; +using namespace std; + +namespace epics { +namespace pvAccess { +namespace ca { + +ChannelConnectThreadPtr ChannelConnectThread::get() +{ + static ChannelConnectThreadPtr master; + static Mutex mutex; + Lock xx(mutex); + if(!master) { + master = ChannelConnectThreadPtr(new ChannelConnectThread()); + master->start(); + } + return master; +} + +ChannelConnectThread::ChannelConnectThread() +: isStop(false) +{ +} + +ChannelConnectThread::~ChannelConnectThread() +{ +//std::cout << "ChannelConnectThread::~ChannelConnectThread()\n"; +} + + +void ChannelConnectThread::start() +{ + thread = std::tr1::shared_ptr(new epicsThread( + *this, + "channelConnectThread", + epicsThreadGetStackSize(epicsThreadStackSmall), + epicsThreadPriorityLow)); + thread->start(); +} + + +void ChannelConnectThread::stop() +{ + { + Lock xx(mutex); + isStop = true; + } + waitForCommand.signal(); + waitForStop.wait(); +} + +void ChannelConnectThread::channelConnected( + NotifyChannelRequesterPtr const ¬ifyChannelRequester) +{ + { + Lock lock(mutex); + if(notifyChannelRequester->isOnQueue) return; + notifyChannelRequester->isOnQueue = true; + notifyChannelQueue.push(notifyChannelRequester); + } + waitForCommand.signal(); +} + +void ChannelConnectThread::run() +{ + while(true) + { + waitForCommand.wait(); + while(true) { + bool more = false; + NotifyChannelRequester* notifyChannelRequester(NULL); + { + Lock lock(mutex); + if(!notifyChannelQueue.empty()) + { + more = true; + NotifyChannelRequesterWPtr req(notifyChannelQueue.front()); + notifyChannelQueue.pop(); + NotifyChannelRequesterPtr reqPtr(req.lock()); + if(reqPtr) { + notifyChannelRequester = reqPtr.get(); + reqPtr->isOnQueue = false; + } + } + } + if(!more) break; + if(notifyChannelRequester!=NULL) + { + CAChannelPtr channel(notifyChannelRequester->channel.lock()); + if(channel) channel->notifyClient(); + } + } + if(isStop) { + waitForStop.signal(); + break; + } + } +} + +}}} diff --git a/src/ca/channelConnectThread.h b/src/ca/channelConnectThread.h new file mode 100644 index 0000000..7dcb568 --- /dev/null +++ b/src/ca/channelConnectThread.h @@ -0,0 +1,71 @@ +/** + * Copyright - See the COPYRIGHT that is included with this distribution. + * pvAccessCPP is distributed subject to a Software License Agreement found + * in file LICENSE that is included with this distribution. + */ +/** + * @author mrk + * @date 2018.07 + */ +#ifndef ChannelConnectThread_H +#define ChannelConnectThread_H +#include +#include +#include +#include +#include +#include + +namespace epics { +namespace pvAccess { +namespace ca { + +class NotifyChannelRequester; +typedef std::tr1::shared_ptr NotifyChannelRequesterPtr; +typedef std::tr1::weak_ptr NotifyChannelRequesterWPtr; + + +class ChannelConnectThread; +typedef std::tr1::shared_ptr ChannelConnectThreadPtr; + +class CAChannel; +typedef std::tr1::shared_ptr CAChannelPtr; +typedef std::tr1::weak_ptr CAChannelWPtr; + +class NotifyChannelRequester +{ +public: + ChannelRequester::weak_pointer channelRequester; + CAChannelWPtr channel; + bool isOnQueue; + NotifyChannelRequester() : isOnQueue(false) {} + void setChannel(CAChannelPtr const &channel) + { this->channel = channel;} +}; + + +class ChannelConnectThread : + public epicsThreadRunable +{ +public: + static ChannelConnectThreadPtr get(); + ~ChannelConnectThread(); + virtual void run(); + void start(); + void stop(); + void channelConnected(NotifyChannelRequesterPtr const ¬ifyChannelRequester); +private: + ChannelConnectThread(); + + bool isStop; + std::tr1::shared_ptr thread; + epics::pvData::Mutex mutex; + epics::pvData::Event waitForCommand; + epics::pvData::Event waitForStop; + std::queue notifyChannelQueue; +}; + + +}}} + +#endif /* ChannelConnectThread_H */ diff --git a/src/ca/dbdToPv.cpp b/src/ca/dbdToPv.cpp index 09f30f7..254ba7f 100644 --- a/src/ca/dbdToPv.cpp +++ b/src/ca/dbdToPv.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -23,6 +24,7 @@ using namespace epics::pvData; using std::string; using std::ostringstream; +using std::cout; namespace epics { namespace pvAccess { @@ -30,23 +32,7 @@ namespace ca { #define CA_PRIORITY 50 -static void enumChoicesHandler(struct event_handler_args args) -{ - DbdToPv *dbdToPv = static_cast(args.usr); - dbdToPv->getChoicesDone(args); -} -static void description_connection_handler(struct connection_handler_args args) -{ - DbdToPv *dbdToPv = static_cast(ca_puser(args.chid)); - dbdToPv->descriptionConnected(args); -} - -static void descriptionHandler(struct event_handler_args args) -{ - DbdToPv *dbdToPv = static_cast(args.usr); - dbdToPv->getDescriptionDone(args); -} DbdToPvPtr DbdToPv::create( CAChannelPtr const & caChannel, @@ -60,16 +46,20 @@ DbdToPvPtr DbdToPv::create( DbdToPv::DbdToPv(IOType ioType) : ioType(ioType), - fieldRequested(false), + dbfIsUCHAR(false), + dbfIsUSHORT(false), + dbfIsULONG(false), + dbfIsINT64(false), + dbfIsUINT64(false), + valueRequested(false), alarmRequested(false), timeStampRequested(false), displayRequested(false), controlRequested(false), valueAlarmRequested(false), isArray(false), + charArrayIsString(false), firstTime(true), - choicesValid(false), - waitForChoicesValid(false), caValueType(-1), caRequestType(-1), maxElements(0) @@ -95,15 +85,56 @@ static chtype getDbrType(const ScalarType scalarType) { case pvString : return DBR_STRING; case pvByte : return DBR_CHAR; + case pvUByte : return DBR_CHAR; case pvShort : return DBR_SHORT; + case pvUShort : return DBR_SHORT; case pvInt : return DBR_LONG; + case pvUInt : return DBR_LONG; case pvFloat : return DBR_FLOAT; case pvDouble : return DBR_DOUBLE; + case pvLong : return DBR_DOUBLE; + case pvULong : return DBR_DOUBLE; default: break; } throw std::runtime_error("getDbr: illegal scalarType"); } +static dbr_short_t convertDBstatus(dbr_short_t dbStatus) +{ + switch(dbStatus) { + case NO_ALARM: + return noStatus; + case READ_ALARM: + case WRITE_ALARM: + case HIHI_ALARM: + case HIGH_ALARM: + case LOLO_ALARM: + case LOW_ALARM: + case STATE_ALARM: + case COS_ALARM: + case HW_LIMIT_ALARM: + return deviceStatus; + case COMM_ALARM: + case TIMEOUT_ALARM: + return driverStatus; + case CALC_ALARM: + case SCAN_ALARM: + case LINK_ALARM: + case SOFT_ALARM: + case BAD_SUB_ALARM: + return recordStatus; + case DISABLE_ALARM: + case SIMM_ALARM: + case READ_ACCESS_ALARM: + case WRITE_ACCESS_ALARM: + return dbStatus; + case UDF_ALARM: + return undefinedStatus; + default: + return undefinedStatus; // UNDEFINED + } + +} void DbdToPv::activate( CAChannelPtr const & caChannel, @@ -131,14 +162,14 @@ void DbdToPv::activate( } if(fieldPVStructure->getPVFields().size()==0) { - fieldRequested = true; + valueRequested = true; alarmRequested = true; timeStampRequested = true; displayRequested = true; controlRequested = true; valueAlarmRequested = true; } else { - if(fieldPVStructure->getSubField("value")) fieldRequested = true; + if(fieldPVStructure->getSubField("value")) valueRequested = true; if(fieldPVStructure->getSubField("alarm")) alarmRequested = true; if(fieldPVStructure->getSubField("timeStamp")) timeStampRequested = true; if(fieldPVStructure->getSubField("display")) displayRequested = true; @@ -173,39 +204,75 @@ void DbdToPv::activate( } caRequestType = (properties.size()==0 ? DBR_ENUM : DBR_TIME_ENUM); structure = standardField->enumerated(properties); - int result = ca_array_get_callback(DBR_GR_ENUM, - 1, - channelID, enumChoicesHandler, this); - if (result == ECA_NORMAL) result = ca_flush_io(); - if (result != ECA_NORMAL) { - string mess(caChannel->getChannelName()); - mess += " DbdToPv::activate getting enum cnoices "; - mess += ca_message(result); - throw std::runtime_error(mess); - } - // NOTE: we do not wait here, since all subsequent request (over TCP) is serialized - // and will guarantee that enumChoicesHandler is called first + return; } + ScalarType st = dbr2ST[channelType]; + PVStringPtr pvValue = fieldPVStructure->getSubField("value._options.dbtype"); + if(pvValue) + { + std::string value(pvValue->get()); + if(value.find("DBF_UCHAR")!=std::string::npos) { + if(st==pvByte) { + dbfIsUCHAR = true; + st = pvUByte; + caValueType = DBR_CHAR; + } + } else if(value.find("DBF_USHORT")!=std::string::npos) { + if(st==pvInt) { + dbfIsUSHORT = true; + st = pvUShort; + caValueType = DBR_SHORT; + } + } else if(value.find("DBF_ULONG")!=std::string::npos) { + if(st==pvDouble) { + dbfIsULONG = true; + st = pvUInt; + caValueType = DBR_LONG; + } + } else if(value.find("DBF_INT64")!=std::string::npos) { + if(st==pvDouble) { + dbfIsINT64 = true; + st = pvLong; + } + } else if(value.find("DBF_UINT64")!=std::string::npos) { + if(st==pvDouble) { + dbfIsUINT64 = true; + st = pvULong; + } + } + + } + if(st==pvString) { + displayRequested = false; + controlRequested = false; + valueAlarmRequested = false; + } maxElements = ca_element_count(channelID); if(maxElements!=1) isArray = true; if(isArray) { controlRequested = false; valueAlarmRequested = false; + if(channelType==DBR_CHAR && fieldPVStructure) + { + PVStringPtr pvValue = fieldPVStructure->getSubField("value._options.pvtype"); + if(pvValue) { + std::string value(pvValue->get()); + if(value.find("pvString")!=std::string::npos) { + charArrayIsString = true; + st = pvString; + } + } + } } - ScalarType st = dbr2ST[channelType]; - if(st==pvString) { - displayRequested = false; - controlRequested = false; - valueAlarmRequested = false; - } + if(controlRequested || displayRequested || valueAlarmRequested) timeStampRequested = false; FieldCreatePtr fieldCreate(FieldCreate::getFieldCreate()); PVDataCreatePtr pvDataCreate(PVDataCreate::getPVDataCreate()); FieldBuilderPtr fieldBuilder(fieldCreate->createFieldBuilder()); - if(fieldRequested) { - if(isArray) { + if(valueRequested) { + if(isArray && !charArrayIsString) { fieldBuilder->addArray("value",st); } else { fieldBuilder->add("value",st); @@ -219,14 +286,19 @@ void DbdToPv::activate( switch(st) { case pvByte: + case pvUByte: fieldBuilder->add("valueAlarm",standardField->byteAlarm()); break; case pvShort: + case pvUShort: fieldBuilder->add("valueAlarm",standardField->shortAlarm()); break; case pvInt: + case pvUInt: fieldBuilder->add("valueAlarm",standardField->intAlarm()); break; case pvFloat: fieldBuilder->add("valueAlarm",standardField->floatAlarm()); break; case pvDouble: + case pvLong: + case pvULong: fieldBuilder->add("valueAlarm",standardField->doubleAlarm()); break; default: throw std::runtime_error("DbDToPv::activate: bad type"); @@ -242,38 +314,27 @@ void DbdToPv::activate( } else { caRequestType = dbf_type_to_DBR(caValueType); } - if(displayRequested) { - chid channelID; - string name(caChannel->getChannelName() + ".DESC"); - int result = ca_create_channel(name.c_str(), - description_connection_handler, - this, - CA_PRIORITY, // TODO mapping - &channelID); - if (result == ECA_NORMAL) result = ca_flush_io(); - if (result != ECA_NORMAL) { - string mess(caChannel->getChannelName()); - mess += " DbdToPv::activate getting description "; - mess += ca_message(result); - throw std::runtime_error(mess); - } + +} + +chtype DbdToPv::getRequestType() +{ + if(caRequestType<0) { + throw std::runtime_error("DbDToPv::getRequestType: bad type"); } + return caRequestType; } -void DbdToPv::descriptionConnected(struct connection_handler_args args) +Structure::const_shared_pointer DbdToPv::getStructure() { - if (args.op != CA_OP_CONN_UP) return; - ca_array_get_callback(DBR_STRING, - 0, - args.chid, descriptionHandler, this); + return structure; } -void DbdToPv::getDescriptionDone(struct event_handler_args &args) + +static void enumChoicesHandler(struct event_handler_args args) { - if(args.status!=ECA_NORMAL) return; - const dbr_string_t *value = static_cast(dbr_value_ptr(args.dbr,DBR_STRING)); - description = string(*value); - ca_clear_channel(args.chid); + DbdToPv *dbdToPv = static_cast(args.usr); + dbdToPv->getChoicesDone(args); } void DbdToPv::getChoicesDone(struct event_handler_args &args) @@ -288,21 +349,29 @@ void DbdToPv::getChoicesDone(struct event_handler_args &args) size_t num = dbr_enum_p->no_str; choices.reserve(num); for(size_t i=0; istrs[i][0])); - bool signal = false; - { - Lock lock(choicesMutex); - choicesValid = true; - if(waitForChoicesValid) signal = true; - } - if(signal) choicesEvent.signal(); + choicesEvent.signal(); } -chtype DbdToPv::getRequestType() + +void DbdToPv::getChoices(CAChannelPtr const & caChannel) { - if(caRequestType<0) { - throw std::runtime_error("DbDToPv::getRequestType: bad type"); + if(caRequestType==DBR_ENUM||caRequestType==DBR_TIME_ENUM) + { + caChannel->attachContext(); + chid channelID = caChannel->getChannelID(); + int result = ca_array_get_callback(DBR_GR_ENUM, + 1, + channelID, enumChoicesHandler, this); + if (result == ECA_NORMAL) { + result = ca_flush_io(); + choicesEvent.wait(); + } else { + string mess(caChannel->getChannelName()); + mess += " DbdToPv::activate getting enum cnoices "; + mess += ca_message(result); + throw std::runtime_error(mess); + } } - return caRequestType; } PVStructurePtr DbdToPv::createPVStructure() @@ -368,7 +437,7 @@ Status DbdToPv::getFromDBD( Status errorStatus(Status::STATUSTYPE_ERROR, string(ca_message(args.status))); return errorStatus; } - if(fieldRequested) + if(valueRequested) { void * value = dbr_value_ptr(args.dbr,caRequestType); if(isArray) { @@ -386,18 +455,51 @@ Status DbdToPv::getFromDBD( break; } case DBR_CHAR: + if(charArrayIsString) + { + const char * pchar = static_cast(value); + std::string str(pchar); + PVStringPtr pvValue(pvStructure->getSubField("value")); + pvValue->put(str); + break; + } + if(dbfIsUCHAR) + { + copy_DBRScalarArray(value,count,pvValue); + break; + } copy_DBRScalarArray(value,count,pvValue); break; case DBR_SHORT: + if(dbfIsUSHORT) + { + copy_DBRScalarArray(value,count,pvValue); + break; + } copy_DBRScalarArray(value,count,pvValue); break; case DBR_LONG: + if(dbfIsULONG) + { + copy_DBRScalarArray(value,count,pvValue); + break; + } copy_DBRScalarArray(value,count,pvValue); break; case DBR_FLOAT: copy_DBRScalarArray(value,count,pvValue); break; case DBR_DOUBLE: + if(dbfIsINT64) + { + copy_DBRScalarArray(value,count,pvValue); + break; + } + if(dbfIsUINT64) + { + copy_DBRScalarArray(value,count,pvValue); + break; + } copy_DBRScalarArray(value,count,pvValue); break; default: @@ -428,11 +530,40 @@ Status DbdToPv::getFromDBD( break; } case DBR_STRING: copy_DBRScalar(value,pvValue); break; - case DBR_CHAR: copy_DBRScalar(value,pvValue); break; - case DBR_SHORT: copy_DBRScalar(value,pvValue); break; - case DBR_LONG: copy_DBRScalar(value,pvValue); break; + case DBR_CHAR: + if(dbfIsUCHAR) + { + copy_DBRScalar(value,pvValue); + break; + } + copy_DBRScalar(value,pvValue); break; + case DBR_SHORT: + if(dbfIsUSHORT) + { + copy_DBRScalar(value,pvValue); + break; + } + copy_DBRScalar(value,pvValue); break; + case DBR_LONG: + if(dbfIsULONG) + { + copy_DBRScalar(value,pvValue); + break; + } + copy_DBRScalar(value,pvValue); break; case DBR_FLOAT: copy_DBRScalar(value,pvValue); break; - case DBR_DOUBLE: copy_DBRScalar(value,pvValue); break; + case DBR_DOUBLE: + if(dbfIsINT64) + { + copy_DBRScalar(value,pvValue); + break; + } + if(dbfIsUINT64) + { + copy_DBRScalar(value,pvValue); + break; + } + copy_DBRScalar(value,pvValue); break; default: Status errorStatus( Status::STATUSTYPE_ERROR, string("DbdToPv::getFromDBD logic error")); @@ -461,7 +592,7 @@ Status DbdToPv::getFromDBD( PVIntPtr pvStatus(pvAlarm->getSubField("status")); if(caAlarm.status!=status) { caAlarm.status = status; - pvStatus->put(status); + pvStatus->put(convertDBstatus(status)); string message("UNKNOWN STATUS"); if(status<=ALARM_NSTATUS) message = string(epicsAlarmConditionStrings[status]); pvMessage->put(message); @@ -488,7 +619,7 @@ Status DbdToPv::getFromDBD( bitSet->set(pvSeconds->getFieldOffset()); } if(caTimeStamp.nsec!=stamp.nsec) { - caTimeStamp.secPastEpoch = stamp.secPastEpoch; + caTimeStamp.nsec = stamp.nsec; PVIntPtr pvNano(pvTimeStamp->getSubField("nanoseconds")); pvNano->put(stamp.nsec); bitSet->set(pvNano->getFieldOffset()); @@ -590,14 +721,6 @@ Status DbdToPv::getFromDBD( pvString->put(format); bitSet->set(pvString->getFieldOffset()); } - if(!description.empty()) - { - PVStringPtr pvString = pvDisplay->getSubField("description"); - if(description.compare(pvString->get()) !=0) { - pvString->put(description); - bitSet->set(pvString->getFieldOffset()); - } - } } if(valueAlarmRequested) { double upper_alarm_limit = 0.0; @@ -668,6 +791,8 @@ Status DbdToPv::getFromDBD( return Status::Ok; } + + template const void * put_DBRScalar(dbrT *val,PVScalar::shared_pointer const & pvScalar) { @@ -726,18 +851,63 @@ Status DbdToPv::putToDBD( break; } case DBR_CHAR: + if(charArrayIsString) + { + PVStringPtr pvValue(pvStructure->getSubField("value")); + const char * pchar = pvValue->get().c_str(); + pValue = pchar; + count = pvValue->get().length(); + break; + } + if(dbfIsUCHAR) + { + pValue = put_DBRScalarArray(&count,pvValue); + break; + } pValue = put_DBRScalarArray(&count,pvValue); break; case DBR_SHORT: + if(dbfIsUSHORT) + { + pValue = put_DBRScalarArray(&count,pvValue); + break; + } pValue = put_DBRScalarArray(&count,pvValue); break; case DBR_LONG: + if(dbfIsULONG) + { + pValue = put_DBRScalarArray(&count,pvValue); + break; + } pValue = put_DBRScalarArray(&count,pvValue); break; case DBR_FLOAT: pValue = put_DBRScalarArray(&count,pvValue); break; case DBR_DOUBLE: + if(dbfIsINT64) + { + PVLongArrayPtr pvValue(pvStructure->getSubField("value")); + PVLongArray::const_svector sv(pvValue->view()); + pvDoubleArray = PVDoubleArrayPtr(getPVDataCreate()->createPVScalarArray()); + pvDoubleArray->putFrom(sv); + const double * pdouble = pvDoubleArray->view().data(); + count = pvValue->getLength(); + pValue = pdouble; + break; + } + if(dbfIsUINT64) + { + PVULongArrayPtr pvValue(pvStructure->getSubField("value")); + PVULongArray::const_svector sv(pvValue->view()); + pvDoubleArray = PVDoubleArrayPtr(getPVDataCreate()->createPVScalarArray()); + pvDoubleArray->putFrom(sv); + const double * pdouble = pvDoubleArray->view().data(); + count = pvValue->getLength(); + pValue = pdouble; + break; + } pValue = put_DBRScalarArray(&count,pvValue); break; default: @@ -750,36 +920,48 @@ Status DbdToPv::putToDBD( switch(caValueType) { case DBR_ENUM: { - bool wait = false; - { - Lock lock(choicesMutex); - if(!choicesValid) { - wait = true; - waitForChoicesValid = true; - } - } - bool result = true; - if(wait) { - result = choicesEvent.wait(5.0); - } - if(!result) { - Status errorStatus( - Status::STATUSTYPE_ERROR, string("DbdToPv::getFromDBD ")); - return errorStatus; - } dbr_enum_t indexvalue = pvStructure->getSubField("value.index")->get(); pValue = &indexvalue; break; } case DBR_STRING: pValue = pvStructure->getSubField("value")->get().c_str(); break; - case DBR_CHAR: pValue = put_DBRScalar(&bvalue,pvValue); break; - case DBR_SHORT: pValue = put_DBRScalar(&svalue,pvValue); break; - case DBR_LONG: pValue = put_DBRScalar(&lvalue,pvValue); break; + case DBR_CHAR: + if(dbfIsUCHAR) + { + pValue = put_DBRScalar(&bvalue,pvValue); + break; + } + pValue = put_DBRScalar(&bvalue,pvValue); break; + case DBR_SHORT: + if(dbfIsUSHORT) + { + pValue = put_DBRScalar(&svalue,pvValue); + break; + } + pValue = put_DBRScalar(&svalue,pvValue); break; + case DBR_LONG: + if(dbfIsULONG) + { + pValue = put_DBRScalar(&lvalue,pvValue); + break; + } + pValue = put_DBRScalar(&lvalue,pvValue); break; case DBR_FLOAT: pValue = put_DBRScalar(&fvalue,pvValue); break; - case DBR_DOUBLE: pValue = put_DBRScalar(&dvalue,pvValue); break; + case DBR_DOUBLE: + if(dbfIsINT64) + { + pValue = put_DBRScalar(&dvalue,pvValue); + break; + } + if(dbfIsUINT64) + { + pValue = put_DBRScalar(&dvalue,pvValue); + break; + } + pValue = put_DBRScalar(&dvalue,pvValue); break; default: Status errorStatus( - Status::STATUSTYPE_ERROR, string("DbdToPv::getFromDBD logic error")); + Status::STATUSTYPE_ERROR, string("DbdToPv::putToDBD logic error")); return errorStatus; } } diff --git a/src/ca/dbdToPv.h b/src/ca/dbdToPv.h index 5ee77b5..1c9a0fa 100644 --- a/src/ca/dbdToPv.h +++ b/src/ca/dbdToPv.h @@ -89,6 +89,8 @@ public: epics::pvData::PVStructurePtr const & pvRequest, IOType ioType ); + epics::pvData::Structure::const_shared_pointer getStructure(); + void getChoices(CAChannelPtr const & caChannel); epics::pvData::PVStructurePtr createPVStructure(); chtype getRequestType(); epics::pvData::Status getFromDBD( @@ -104,8 +106,6 @@ public: void *userArg ); void getChoicesDone(struct event_handler_args &args); - void descriptionConnected(struct connection_handler_args args); - void getDescriptionDone(struct event_handler_args &args); private: DbdToPv(IOType ioType); void activate( @@ -113,29 +113,32 @@ private: epics::pvData::PVStructurePtr const & pvRequest ); IOType ioType; - bool fieldRequested; + bool dbfIsUCHAR; + bool dbfIsUSHORT; + bool dbfIsULONG; + bool dbfIsINT64; + bool dbfIsUINT64; + bool valueRequested; bool alarmRequested; bool timeStampRequested; bool displayRequested; bool controlRequested; bool valueAlarmRequested; bool isArray; + bool charArrayIsString; bool firstTime; - bool choicesValid; - bool waitForChoicesValid; chtype caValueType; chtype caRequestType; unsigned long maxElements; - epics::pvData::Mutex choicesMutex; epics::pvData::Event choicesEvent; epicsTimeStamp caTimeStamp; CaAlarm caAlarm; CaDisplay caDisplay; CaControl caControl; CaValueAlarm caValueAlarm; - std::string description; epics::pvData::Structure::const_shared_pointer structure; std::vector choices; + epics::pvData::PVDoubleArrayPtr pvDoubleArray; //for dbfIsINT64 and dbfIsUINT64 }; }