/** * 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. */ #include #include #include #include #define epicsExportSharedSymbols #include #include using namespace epics::pvData; using std::string; namespace epics { namespace pvAccess { namespace ca { #define EXCEPTION_GUARD(code) try { code; } \ catch (std::exception &e) { LOG(logLevelError, "Unhandled exception caught from client code at %s:%d: %s", __FILE__, __LINE__, e.what()); } \ catch (...) { LOG(logLevelError, "Unhandled exception caught from client code at %s:%d.", __FILE__, __LINE__); } #define PVACCESS_REFCOUNT_MONITOR_DEFINE(name) #define PVACCESS_REFCOUNT_MONITOR_CONSTRUCT(name) #define PVACCESS_REFCOUNT_MONITOR_DESTRUCT(name) PVACCESS_REFCOUNT_MONITOR_DEFINE(caChannel); CAChannel::shared_pointer CAChannel::create(CAChannelProvider::shared_pointer const & channelProvider, std::string const & channelName, short priority, ChannelRequester::shared_pointer const & channelRequester) { CAChannel::shared_pointer thisPtr(new CAChannel(channelName, channelProvider, channelRequester)); thisPtr->activate(priority); return thisPtr; } 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(); } else if (args.op == CA_OP_CONN_DOWN) { channel->disconnected(); } } static ScalarType dbr2ST[] = { pvString, // DBR_STRING = 0 pvShort, // DBR_SHORT. DBR_INT = 1 pvFloat, // DBR_FLOAT = 2 static_cast(-1), // DBR_ENUM = 3 pvByte, // DBR_CHAR = 4 pvInt, // DBR_LONG = 5 pvDouble // DBR_DOUBLE = 6 }; static Structure::const_shared_pointer createStructure(CAChannel::shared_pointer const & channel, string const & properties) { StandardFieldPtr standardField = getStandardField(); Structure::const_shared_pointer structure; chtype channelType = channel->getNativeType(); if (channelType != DBR_ENUM) { ScalarType st = dbr2ST[channelType]; structure = (channel->getElementCount() > 1) ? standardField->scalarArray(st, properties) : standardField->scalar(st, properties); } else { // NOTE: enum arrays not supported structure = standardField->enumerated(properties); } return structure; } static void ca_get_labels_handler(struct event_handler_args args) { if (args.status == ECA_NORMAL) { const dbr_gr_enum* dbr_enum_p = static_cast(args.dbr); PVStringArray* labelsArray = static_cast(args.usr); if (labelsArray) { PVStringArray::svector labels(labelsArray->reuse()); labels.resize(dbr_enum_p->no_str); std::copy(dbr_enum_p->strs, dbr_enum_p->strs + dbr_enum_p->no_str, labels.begin()); labelsArray->replace(freeze(labels)); } } else { // TODO better error handling std::cerr << "failed to get labels for enum : " << ca_message(args.status) << std::endl; } } // Filter out unrequested fields from a source structure according to a // structure conforming to the format of the "field" field of a pvRequest, // preserving type ids of unchanged structures. If ntTop is true also preserve // type id if none of the deleted top-level subfields are the value field. static StructureConstPtr refineStructure(StructureConstPtr const & source, StructureConstPtr const & requestedFields, bool ntTop) { if (requestedFields.get() == NULL || requestedFields->getNumberFields() == 0) return source; FieldBuilderPtr builder = getFieldCreate()->createFieldBuilder(); bool addId = true; FieldConstPtrArray fields = source->getFields(); StringArray names = source->getFieldNames(); size_t i = 0; for (FieldConstPtrArray::const_iterator it = fields.begin(); it != fields.end(); ++it) { FieldConstPtr field = *it; const std::string & name = names[i++]; FieldConstPtr reqField = requestedFields->getField(name); if (reqField.get()) { if (field->getType() != structure || (reqField->getType() != structure)) builder->add(name,field); else { StructureConstPtr substruct = std::tr1::dynamic_pointer_cast(field); StructureConstPtr reqSubstruct = std::tr1::dynamic_pointer_cast(reqField); StructureConstPtr nested = refineStructure(substruct, reqSubstruct, false); builder->add(name,nested); if (nested->getID() != substruct->getID()) addId = false; } } else if (!ntTop || name == "value") addId = false; } if (addId) builder->setId(source->getID()); return builder->createStructure(); } static PVStructure::shared_pointer createPVStructure(CAChannel::shared_pointer const & channel, string const & properties, PVStructurePtr pvRequest) { StructureConstPtr unrefinedStructure = createStructure(channel, properties); PVStructurePtr fieldPVStructure = pvRequest->getSubField("field"); StructureConstPtr finalStructure = fieldPVStructure.get() ? refineStructure(unrefinedStructure, fieldPVStructure->getStructure(),true) : unrefinedStructure; PVStructure::shared_pointer pvStructure = getPVDataCreate()->createPVStructure(finalStructure); if (channel->getNativeType() == DBR_ENUM) { PVScalarArrayPtr pvScalarArray = pvStructure->getSubField("value.choices"); // TODO avoid getting labels if DBR_GR_ENUM or DBR_CTRL_ENUM is used in subsequent get int result = ca_array_get_callback(DBR_GR_ENUM, 1, channel->getChannelID(), ca_get_labels_handler, pvScalarArray.get()); if (result == ECA_NORMAL) { ca_flush_io(); // NOTE: we do not wait here, since all subsequent request (over TCP) is serialized // and will guarantee that ca_get_labels_handler is called first } else { // TODO better error handling std::cerr << "failed to get labels for enum " << channel->getChannelName() << ": " << ca_message(result) << std::endl; } } return pvStructure; } static PVStructure::shared_pointer createPVStructure(CAChannel::shared_pointer const & channel, chtype dbrType, PVStructurePtr pvRequest) { // Match to closest DBR type // NOTE: value is always there string properties; bool isArray = channel->getElementCount() > 1; if (dbrType >= DBR_CTRL_STRING) // 28 { if (dbrType != DBR_CTRL_STRING && dbrType != DBR_CTRL_ENUM) { if (isArray) properties = "value,alarm,display"; else properties = "value,alarm,display,valueAlarm,control"; } else properties = "value,alarm"; } else if (dbrType >= DBR_GR_STRING) // 21 { if (dbrType != DBR_GR_STRING && dbrType != DBR_GR_ENUM) { if (isArray) properties = "value,alarm,display"; else properties = "value,alarm,display,valueAlarm"; } else properties = "value,alarm"; } else if (dbrType >= DBR_TIME_STRING) // 14 properties = "value,alarm,timeStamp"; else if (dbrType >= DBR_STS_STRING) // 7 properties = "value,alarm"; else properties = "value"; return createPVStructure(channel, properties, pvRequest); } void CAChannel::connected() { { Lock lock(requestsMutex); // we assume array if element count > 1 elementCount = ca_element_count(channelID); channelType = ca_field_type(channelID); bool isArray = elementCount > 1; // no valueAlarm and control,display for non-numeric type // no control,display for numeric arrays string allProperties = (channelType != DBR_STRING && channelType != DBR_ENUM) ? isArray ? "value,timeStamp,alarm,display" : "value,timeStamp,alarm,display,valueAlarm,control" : "value,timeStamp,alarm"; Structure::const_shared_pointer structure = createStructure(shared_from_this(), allProperties); // TODO we need only Structure here this->structure = structure; } while(!putQueue.empty()) { putQueue.front()->activate(); putQueue.pop(); } while(!getQueue.empty()) { getQueue.front()->activate(); getQueue.pop(); } while(!monitorQueue.empty()) { monitorQueue.front()->activate(); monitorQueue.pop(); } std::queue putQ; std::queue getQ; std::queue monitorQ; { Lock lock(requestsMutex); std::vector::const_iterator getiter; for (getiter = getList.begin(); getiter != getList.end(); ++getiter) { CAChannelGetPtr temp = (*getiter).lock(); if(!temp) continue; getQ.push(temp); } std::vector::const_iterator putiter; for (putiter = putList.begin(); putiter != putList.end(); ++putiter) { CAChannelPutPtr temp = (*putiter).lock(); if(!temp) continue; putQ.push(temp); } std::vector::const_iterator monitoriter; for (monitoriter = monitorList.begin(); monitoriter != monitorList.end(); ++monitoriter) { CAChannelMonitorPtr temp = (*monitoriter).lock(); if(!temp) continue; monitorQ.push(temp); } } while(!putQ.empty()) { putQ.front()->channelCreated(Status::Ok,shared_from_this()); putQ.pop(); } while(!getQ.empty()) { getQ.front()->channelCreated(Status::Ok,shared_from_this()); getQ.pop(); } while(!monitorQ.empty()) { monitorQ.front()->channelCreated(Status::Ok,shared_from_this()); monitorQ.pop(); } EXCEPTION_GUARD(channelRequester->channelStateChange(shared_from_this(), Channel::CONNECTED)); } void CAChannel::disconnected() { std::queue putQ; std::queue getQ; std::queue monitorQ; { Lock lock(requestsMutex); std::vector::const_iterator getiter; for (getiter = getList.begin(); getiter != getList.end(); ++getiter) { CAChannelGetPtr temp = (*getiter).lock(); if(!temp) continue; getQ.push(temp); } std::vector::const_iterator putiter; for (putiter = putList.begin(); putiter != putList.end(); ++putiter) { CAChannelPutPtr temp = (*putiter).lock(); if(!temp) continue; putQ.push(temp); } std::vector::const_iterator monitoriter; for (monitoriter = monitorList.begin(); monitoriter != monitorList.end(); ++monitoriter) { CAChannelMonitorPtr temp = (*monitoriter).lock(); if(!temp) continue; monitorQ.push(temp); } } while(!putQ.empty()) { putQ.front()->channelDisconnect(false); putQ.pop(); } while(!getQ.empty()) { getQ.front()->channelDisconnect(false); getQ.pop(); } while(!monitorQ.empty()) { monitorQ.front()->channelDisconnect(false); monitorQ.pop(); } EXCEPTION_GUARD(channelRequester->channelStateChange(shared_from_this(), Channel::DISCONNECTED)); } CAChannel::CAChannel(std::string const & _channelName, CAChannelProvider::shared_pointer const & _channelProvider, ChannelRequester::shared_pointer const & _channelRequester) : channelName(_channelName), channelProvider(_channelProvider), channelRequester(_channelRequester), channelID(0), channelType(0), elementCount(0), destroyed(false) { PVACCESS_REFCOUNT_MONITOR_CONSTRUCT(caChannel); } void CAChannel::activate(short priority) { int result = ca_create_channel(channelName.c_str(), ca_connection_handler, this, priority, // TODO mapping &channelID); if (result == ECA_NORMAL) { channelProvider->registerChannel(shared_from_this()); // TODO be sure that ca_connection_handler is not called before this call channelRequester->channelCreated(Status::Ok, shared_from_this()); } else { Status errorStatus(Status::STATUSTYPE_ERROR, string(ca_message(result))); channelRequester->channelCreated(errorStatus, shared_from_this()); } } void CAChannel::addChannelGet(const CAChannelGetPtr & get) { Lock lock(requestsMutex); for(size_t i=0; i< getList.size(); ++i) { if(!(getList[i].lock())) { getList[i] = get; return; } } getList.push_back(get); } void CAChannel::addChannelPut(const CAChannelPutPtr & put) { Lock lock(requestsMutex); for(size_t i=0; i< putList.size(); ++i) { if(!(putList[i].lock())) { putList[i] = put; return; } } putList.push_back(put); } void CAChannel::addChannelMonitor(const CAChannelMonitorPtr & monitor) { Lock lock(requestsMutex); for(size_t i=0; i< monitorList.size(); ++i) { if(!(monitorList[i].lock())) { monitorList[i] = monitor; return; } } monitorList.push_back(monitor); } CAChannel::~CAChannel() { PVACCESS_REFCOUNT_MONITOR_DESTRUCT(caChannel); { Lock lock(requestsMutex); if (destroyed) return; destroyed = true; } channelProvider->unregisterChannel(this); /* Clear CA Channel */ threadAttach(); ca_clear_channel(channelID); } chid CAChannel::getChannelID() { return channelID; } chtype CAChannel::getNativeType() { return channelType; } unsigned CAChannel::getElementCount() { return elementCount; } Structure::const_shared_pointer CAChannel::getStructure() { return structure; } std::tr1::shared_ptr CAChannel::getProvider() { return channelProvider; } std::string CAChannel::getRemoteAddress() { return std::string(ca_host_name(channelID)); } static Channel::ConnectionState cs2CS[] = { Channel::NEVER_CONNECTED, // cs_never_conn Channel::DISCONNECTED, // cs_prev_conn Channel::CONNECTED, // cs_conn Channel::DESTROYED // cs_closed }; Channel::ConnectionState CAChannel::getConnectionState() { return cs2CS[ca_state(channelID)]; } std::string CAChannel::getChannelName() { return channelName; } std::tr1::shared_ptr CAChannel::getChannelRequester() { return channelRequester; } void CAChannel::getField(GetFieldRequester::shared_pointer const & requester, std::string const & subField) { Field::const_shared_pointer field = subField.empty() ? std::tr1::static_pointer_cast(structure) : structure->getField(subField); if (field) { EXCEPTION_GUARD(requester->getDone(Status::Ok, field)); } else { Status errorStatus(Status::STATUSTYPE_ERROR, "field '" + subField + "' not found"); EXCEPTION_GUARD(requester->getDone(errorStatus, FieldConstPtr())); } } AccessRights CAChannel::getAccessRights(epics::pvData::PVField::shared_pointer const & /*pvField*/) { if (ca_write_access(channelID)) return readWrite; else if (ca_read_access(channelID)) return read; else return none; } ChannelGet::shared_pointer CAChannel::createChannelGet( ChannelGetRequester::shared_pointer const & channelGetRequester, epics::pvData::PVStructure::shared_pointer const & pvRequest) { CAChannelGetPtr channelGet = CAChannelGet::create(shared_from_this(), channelGetRequester, pvRequest);\ if(getConnectionState()==Channel::CONNECTED) { channelGet->activate(); } else { getQueue.push(channelGet); } return channelGet; } ChannelPut::shared_pointer CAChannel::createChannelPut( ChannelPutRequester::shared_pointer const & channelPutRequester, epics::pvData::PVStructure::shared_pointer const & pvRequest) { CAChannelPutPtr channelPut = CAChannelPut::create(shared_from_this(), channelPutRequester, pvRequest);\ if(getConnectionState()==Channel::CONNECTED) { channelPut->activate(); } else { putQueue.push(channelPut); } return channelPut; } Monitor::shared_pointer CAChannel::createMonitor( MonitorRequester::shared_pointer const & monitorRequester, epics::pvData::PVStructure::shared_pointer const & pvRequest) { CAChannelMonitorPtr channelMonitor = CAChannelMonitor::create(shared_from_this(), monitorRequester, pvRequest);\ if(getConnectionState()==Channel::CONNECTED) { channelMonitor->activate(); } else { monitorQueue.push(channelMonitor); } return channelMonitor; } void CAChannel::printInfo(std::ostream& out) { out << "CHANNEL : " << getChannelName() << std::endl; ConnectionState state = getConnectionState(); out << "STATE : " << ConnectionStateNames[state] << std::endl; if (state == CONNECTED) { out << "ADDRESS : " << getRemoteAddress() << std::endl; //out << "RIGHTS : " << getAccessRights() << std::endl; } } /* --------------- Destroyable --------------- */ void CAChannel::destroy() { Lock lock(requestsMutex); { if (destroyed) return; destroyed = true; } channelProvider->unregisterChannel(shared_from_this()); /* Clear CA Channel */ threadAttach(); ca_clear_channel(channelID); } /* ---------------------------------------------------------- */ void CAChannel::threadAttach() { std::tr1::static_pointer_cast(channelProvider)->threadAttach(); } CAChannelGetPtr CAChannelGet::create( CAChannel::shared_pointer const & channel, ChannelGetRequester::shared_pointer const & channelGetRequester, epics::pvData::PVStructure::shared_pointer const & pvRequest) { return CAChannelGetPtr(new CAChannelGet(channel, channelGetRequester, pvRequest)); } static chtype getDBRType(PVStructure::shared_pointer const & pvRequest, chtype nativeType) { // get "field" sub-structure PVStructure::shared_pointer fieldSubField = std::tr1::dynamic_pointer_cast(pvRequest->getSubField("field")); if (!fieldSubField) fieldSubField = pvRequest; Structure::const_shared_pointer fieldStructure = fieldSubField->getStructure(); // no fields or control -> DBR_CTRL_ if (fieldStructure->getNumberFields() == 0 || fieldStructure->getField("control")) return static_cast(static_cast(nativeType) + DBR_CTRL_STRING); // display/valueAlarm -> DBR_GR_ if (fieldStructure->getField("display") || fieldStructure->getField("valueAlarm")) return static_cast(static_cast(nativeType) + DBR_GR_STRING); // timeStamp -> DBR_TIME_ // NOTE: that only DBR_TIME_ type holds timestamp, therefore if you request for // the fields above, you will never get timestamp if (fieldStructure->getField("timeStamp")) return static_cast(static_cast(nativeType) + DBR_TIME_STRING); // alarm -> DBR_STS_ if (fieldStructure->getField("alarm")) return static_cast(static_cast(nativeType) + DBR_STS_STRING); return nativeType; } CAChannelGet::CAChannelGet(CAChannel::shared_pointer const & channel, ChannelGetRequester::shared_pointer const & channelGetRequester, epics::pvData::PVStructure::shared_pointer const & pvRequest) : channel(channel), channelGetRequester(channelGetRequester), pvRequest(pvRequest), lastRequestFlag(false) { } CAChannelGet::~CAChannelGet() { } void CAChannelGet::activate() { if(pvStructure) throw std::runtime_error("CAChannelGet::activate() was called twice"); getType = getDBRType(pvRequest, channel->getNativeType()); pvStructure = createPVStructure(channel, getType, pvRequest); bitSet = BitSetPtr(new BitSet(pvStructure->getStructure()->getNumberFields())); bitSet->set(0); channel->addChannelGet(shared_from_this()); EXCEPTION_GUARD(channelGetRequester->channelGetConnect(Status::Ok, shared_from_this(), pvStructure->getStructure())); } void CAChannelGet::channelCreated(const Status& status,Channel::shared_pointer const & cl) { chtype newType = getDBRType(pvRequest, channel->getNativeType()); if(newType!=getType) { pvStructure.reset(); activate(); } } void CAChannelGet::channelStateChange( Channel::shared_pointer const & channel, Channel::ConnectionState connectionState) { if(connectionState==Channel::DISCONNECTED || connectionState==Channel::DESTROYED) { EXCEPTION_GUARD(channelGetRequester->channelDisconnect(connectionState==Channel::DESTROYED);) } } void CAChannelGet::channelDisconnect(bool destroy) { EXCEPTION_GUARD(channelGetRequester->channelDisconnect(destroy);) } /* --------------- epics::pvAccess::ChannelGet --------------- */ namespace { static void ca_get_handler(struct event_handler_args args) { CAChannelGet *channelGet = static_cast(args.usr); channelGet->getDone(args); } typedef void (*copyDBRtoPVStructure)(const void * from, unsigned count, PVStructure::shared_pointer const & to); // template template void copy_DBR(const void * dbr, unsigned count, PVStructure::shared_pointer const & pvStructure) { if (count == 1) { std::tr1::shared_ptr value = pvStructure->getSubField("value"); if (value.get()) value->put(static_cast(dbr)[0]); } else { std::tr1::shared_ptr value = pvStructure->getSubField("value"); if (value.get()) { std::tr1::shared_ptr value = pvStructure->getSubField("value"); typename aF::svector temp(value->reuse()); temp.resize(count); std::copy(static_cast(dbr), static_cast(dbr) + count, temp.begin()); value->replace(freeze(temp)); } } } #if defined(__vxworks) || defined(__rtems__) // dbr_long_t is defined as "int", pvData uses int32 which can be defined as "long int" (32-bit) // template template<> void copy_DBR(const void * dbr, unsigned count, PVStructure::shared_pointer const & pvStructure) { if (count == 1) { std::tr1::shared_ptr value = pvStructure->getSubField("value"); if (value.get()) value->put(static_cast(dbr)[0]); } else { std::tr1::shared_ptr value = pvStructure->getSubField("value"); if (value.get()) { PVIntArray::svector temp(value->reuse()); temp.resize(count); std::copy(static_cast(dbr), static_cast(dbr) + count, temp.begin()); value->replace(freeze(temp)); } } } #endif // string specialization template<> void copy_DBR(const void * dbr, unsigned count, PVStructure::shared_pointer const & pvStructure) { if (count == 1) { std::tr1::shared_ptr value = pvStructure->getSubField("value"); if (value.get()) value->put(std::string(static_cast(dbr))); } else { std::tr1::shared_ptr value = pvStructure->getSubField("value"); if (value.get()) { const dbr_string_t* dbrStrings = static_cast(dbr); PVStringArray::svector sA(value->reuse()); sA.resize(count); std::copy(dbrStrings, dbrStrings + count, sA.begin()); value->replace(freeze(sA)); } } } // enum specialization template<> void copy_DBR(const void * dbr, unsigned count, PVStructure::shared_pointer const & pvStructure) { if (count == 1) { PVIntPtr value = pvStructure->getSubField("value.index"); if (value.get()) value->put(static_cast(dbr)[0]); } else { // not supported std::cerr << "caChannel: array of enums not supported" << std::endl; } } // template template void copy_DBR_STS(const void * dbr, unsigned count, PVStructure::shared_pointer const & pvStructure) { const T* data = static_cast(dbr); PVStructure::shared_pointer alarm = pvStructure->getSubField("alarm"); if (alarm.get()) { PVIntPtr status = alarm->getSubField("status"); if (status.get()) status->put(dbrStatus2alarmStatus[data->status]); PVIntPtr severity = alarm->getSubField("severity"); if (severity.get()) severity->put(data->severity); PVStringPtr message = alarm->getSubField("message"); if (message.get()) message->put(dbrStatus2alarmMessage[data->status]); } copy_DBR(&data->value, count, pvStructure); } // template template void copy_DBR_TIME(const void * dbr, unsigned count, PVStructure::shared_pointer const & pvStructure) { const T* data = static_cast(dbr); PVStructure::shared_pointer ts = pvStructure->getSubField("timeStamp"); if (ts.get()) { epics::pvData::int64 spe = data->stamp.secPastEpoch; spe += 7305*86400; PVLongPtr secondsPastEpoch = ts->getSubField("secondsPastEpoch"); if (secondsPastEpoch.get()) secondsPastEpoch->put(spe); PVIntPtr nanoseconds = ts->getSubField("nanoseconds"); if (nanoseconds.get()) nanoseconds->put(data->stamp.nsec); } copy_DBR_STS(dbr, count, pvStructure); } template void copy_format(const void * /*dbr*/, PVStructure::shared_pointer const & pvDisplayStructure) { PVStringPtr format = pvDisplayStructure->getSubField("format"); if (format.get()) format->put("%d"); } #define COPY_FORMAT_FOR(T) \ template <> \ void copy_format(const void * dbr, PVStructure::shared_pointer const & pvDisplayStructure) \ { \ const T* data = static_cast(dbr); \ \ if (data->precision) \ { \ char fmt[16]; \ sprintf(fmt, "%%.%df", data->precision); \ PVStringPtr format = pvDisplayStructure->getSubField("format");\ if (format.get()) format->put(std::string(fmt));\ } \ else \ { \ PVStringPtr format = pvDisplayStructure->getSubField("format");\ if (format.get()) format->put("%f");\ } \ } COPY_FORMAT_FOR(dbr_gr_float) COPY_FORMAT_FOR(dbr_ctrl_float) COPY_FORMAT_FOR(dbr_gr_double) COPY_FORMAT_FOR(dbr_ctrl_double) #undef COPY_FORMAT_FOR // template template void copy_DBR_GR(const void * dbr, unsigned count, PVStructure::shared_pointer const & pvStructure) { const T* data = static_cast(dbr); PVStructurePtr alarm = pvStructure->getSubField("alarm"); if (alarm.get()) { PVIntPtr status = alarm->getSubField("status"); if (status.get()) status->put(dbrStatus2alarmStatus[data->status]); PVIntPtr severity = alarm->getSubField("severity"); if (severity.get()) severity->put(data->severity); PVStringPtr message = alarm->getSubField("message"); if (message.get()) message->put(dbrStatus2alarmMessage[data->status]); } PVStructurePtr disp = pvStructure->getSubField("display"); if (disp.get()) { PVStringPtr units = disp->getSubField("units"); if (units.get()) units->put(std::string(data->units)); PVDoublePtr limitHigh = disp->getSubField("limitHigh"); if (limitHigh.get()) limitHigh->put(data->upper_disp_limit); PVDoublePtr limitLow = disp->getSubField("limitLow"); if (limitLow.get()) limitLow->put(data->lower_disp_limit); copy_format(dbr, disp); } PVStructurePtr va = pvStructure->getSubField("valueAlarm"); if (va.get()) { std::tr1::shared_ptr highAlarmLimit = va->getSubField("highAlarmLimit"); if (highAlarmLimit.get()) highAlarmLimit->put(data->upper_alarm_limit); std::tr1::shared_ptr highWarningLimit = va->getSubField("highWarningLimit"); if (highWarningLimit.get()) highWarningLimit->put(data->upper_warning_limit); std::tr1::shared_ptr lowWarningLimit = va->getSubField("lowWarningLimit"); if (lowWarningLimit.get()) lowWarningLimit->put(data->lower_warning_limit); std::tr1::shared_ptr lowAlarmLimit = va->getSubField("lowAlarmLimit"); if (lowAlarmLimit.get()) lowAlarmLimit->put(data->lower_alarm_limit); } copy_DBR(&data->value, count, pvStructure); } // enum specialization template<> void copy_DBR_GR (const void * dbr, unsigned count, PVStructure::shared_pointer const & pvStructure) { const dbr_gr_enum* data = static_cast(dbr); copy_DBR_STS(data, count, pvStructure); } // template template void copy_DBR_CTRL(const void * dbr, unsigned count, PVStructure::shared_pointer const & pvStructure) { const T* data = static_cast(dbr); PVStructure::shared_pointer alarm = pvStructure->getSubField("alarm"); if (alarm.get()) { PVIntPtr status = alarm->getSubField("status"); if (status.get()) status->put(dbrStatus2alarmStatus[data->status]); PVIntPtr severity = alarm->getSubField("severity"); if (severity.get()) severity->put(data->severity); PVStringPtr message = alarm->getSubField("message"); if (message.get()) message->put(dbrStatus2alarmMessage[data->status]); } PVStructurePtr disp = pvStructure->getSubField("display"); if (disp.get()) { PVStringPtr units = disp->getSubField("units"); if (units.get()) units->put(std::string(data->units)); PVDoublePtr limitHigh = disp->getSubField("limitHigh"); if (limitHigh.get()) limitHigh->put(data->upper_disp_limit); PVDoublePtr limitLow = disp->getSubField("limitLow"); if (limitLow.get()) limitLow->put(data->lower_disp_limit); copy_format(dbr, disp); } PVStructurePtr va = pvStructure->getSubField("valueAlarm"); if (va.get()) { std::tr1::shared_ptr highAlarmLimit = va->getSubField("highAlarmLimit"); if (highAlarmLimit.get()) highAlarmLimit->put(data->upper_alarm_limit); std::tr1::shared_ptr highWarningLimit = va->getSubField("highWarningLimit"); if (highWarningLimit.get()) highWarningLimit->put(data->upper_warning_limit); std::tr1::shared_ptr lowWarningLimit = va->getSubField("lowWarningLimit"); if (lowWarningLimit.get()) lowWarningLimit->put(data->lower_warning_limit); std::tr1::shared_ptr lowAlarmLimit = va->getSubField("lowAlarmLimit"); if (lowAlarmLimit.get()) lowAlarmLimit->put(data->lower_alarm_limit); } PVStructurePtr ctrl = pvStructure->getSubField("control"); if (ctrl.get()) { PVDoublePtr limitHigh = ctrl->getSubField("limitHigh"); if (limitHigh.get()) limitHigh->put(data->upper_ctrl_limit); PVDoublePtr limitLow = ctrl->getSubField("limitLow"); if (limitLow.get()) limitLow->put(data->lower_ctrl_limit); } copy_DBR(&data->value, count, pvStructure); } // enum specialization template<> void copy_DBR_CTRL (const void * dbr, unsigned count, PVStructure::shared_pointer const & pvStructure) { const dbr_ctrl_enum* data = static_cast(dbr); copy_DBR_STS(data, count, pvStructure); } static copyDBRtoPVStructure copyFuncTable[] = { copy_DBR, // DBR_STRING copy_DBR, // DBR_INT, DBR_SHORT copy_DBR, // DBR_FLOAT copy_DBR, // DBR_ENUM copy_DBR, // DBR_CHAR copy_DBR, // DBR_LONG copy_DBR, // DBR_DOUBLE copy_DBR_STS, // DBR_STS_STRING copy_DBR_STS, // DBR_STS_INT, DBR_STS_SHORT copy_DBR_STS, // DBR_STS_FLOAT copy_DBR_STS, // DBR_STS_ENUM copy_DBR_STS, // DBR_STS_CHAR copy_DBR_STS, // DBR_STS_LONG copy_DBR_STS, // DBR_STS_DOUBLE copy_DBR_TIME, // DBR_TIME_STRING copy_DBR_TIME, // DBR_TIME_INT, DBR_TIME_SHORT copy_DBR_TIME, // DBR_TIME_FLOAT copy_DBR_TIME, // DBR_TIME_ENUM copy_DBR_TIME, // DBR_TIME_CHAR copy_DBR_TIME, // DBR_TIME_LONG copy_DBR_TIME, // DBR_TIME_DOUBLE copy_DBR_STS, // DBR_GR_STRING -> DBR_STS_STRING copy_DBR_GR, // DBR_GR_INT, DBR_GR_SHORT copy_DBR_GR, // DBR_GR_FLOAT copy_DBR_GR, // DBR_GR_ENUM copy_DBR_GR, // DBR_GR_CHAR copy_DBR_GR, // DBR_GR_LONG copy_DBR_GR, // DBR_GR_DOUBLE copy_DBR_STS, // DBR_CTRL_STRING -> DBR_STS_STRING copy_DBR_CTRL, // DBR_CTRL_INT, DBR_CTRL_SHORT copy_DBR_CTRL, // DBR_CTRL_FLOAT copy_DBR_CTRL, // DBR_CTRL_ENUM copy_DBR_CTRL, // DBR_CTRL_CHAR copy_DBR_CTRL, // DBR_CTRL_LONG copy_DBR_CTRL // DBR_CTRL_DOUBLE }; } // namespace void CAChannelGet::getDone(struct event_handler_args &args) { if (args.status == ECA_NORMAL) { copyDBRtoPVStructure copyFunc = copyFuncTable[getType]; if (copyFunc) copyFunc(args.dbr, args.count, pvStructure); else { // TODO remove std::cout << "no copy func implemented" << std::endl; } EXCEPTION_GUARD(channelGetRequester->getDone(Status::Ok, shared_from_this(), pvStructure, bitSet)); } else { Status errorStatus(Status::STATUSTYPE_ERROR, string(ca_message(args.status))); EXCEPTION_GUARD(channelGetRequester->getDone(errorStatus, shared_from_this(), PVStructure::shared_pointer(), BitSet::shared_pointer())); } } void CAChannelGet::get() { channel->threadAttach(); /* From R3.14.12 onwards ca_array_get_callback() replies will give a CA client application the current number of elements in an array field, provided they specified an element count of zero in their original request. The element count is passed in the callback argument structure. Prior to R3.14.12 requesting zero elements in a ca_array_get_callback() call was illegal and would fail immediately. */ int result = ca_array_get_callback(getType, 0, channel->getChannelID(), ca_get_handler, this); if (result == ECA_NORMAL) { ca_flush_io(); } else { Status errorStatus(Status::STATUSTYPE_ERROR, string(ca_message(result))); EXCEPTION_GUARD(channelGetRequester->getDone(errorStatus, shared_from_this(), PVStructure::shared_pointer(), BitSet::shared_pointer())); } if (lastRequestFlag) destroy(); } /* --------------- epics::pvData::ChannelRequest --------------- */ Channel::shared_pointer CAChannelGet::getChannel() { return channel; } void CAChannelGet::cancel() { // noop } void CAChannelGet::lastRequest() { // TODO sync !!! lastRequestFlag = true; } /* --------------- Destroyable --------------- */ void CAChannelGet::destroy() { // TODO } CAChannelPutPtr CAChannelPut::create( CAChannel::shared_pointer const & channel, ChannelPutRequester::shared_pointer const & channelPutRequester, epics::pvData::PVStructure::shared_pointer const & pvRequest) { return CAChannelPutPtr(new CAChannelPut(channel, channelPutRequester, pvRequest)); } CAChannelPut::~CAChannelPut() { } CAChannelPut::CAChannelPut(CAChannel::shared_pointer const & channel, ChannelPutRequester::shared_pointer const & channelPutRequester, epics::pvData::PVStructure::shared_pointer const & pvRequest) : channel(channel), channelPutRequester(channelPutRequester), pvRequest(pvRequest), lastRequestFlag(false), block(false) { } void CAChannelPut::activate() { if(pvStructure) throw std::runtime_error("CAChannelPut::activate() was called twice"); getType = getDBRType(pvRequest,channel->getNativeType()); pvStructure = createPVStructure(channel, getType, pvRequest); bitSet = BitSetPtr(new BitSet(pvStructure->getStructure()->getNumberFields())); // NOTE: we require value type, we can only put value field PVStringPtr pvString = pvRequest->getSubField("record._options.block"); if(pvString) { std::string val = pvString->get(); if(val.compare("true")==0) block = true; } bitSet->set(pvStructure->getSubFieldT("value")->getFieldOffset()); channel->addChannelPut(shared_from_this()); EXCEPTION_GUARD(channelPutRequester->channelPutConnect(Status::Ok, shared_from_this(), pvStructure->getStructure())); } void CAChannelPut::channelCreated(const Status& status,Channel::shared_pointer const & c) { chtype newType = getDBRType(pvRequest, channel->getNativeType()); if(newType!=getType) { pvStructure.reset(); activate(); } } void CAChannelPut::channelStateChange( Channel::shared_pointer const & channel, Channel::ConnectionState connectionState) { if(connectionState==Channel::DISCONNECTED || connectionState==Channel::DESTROYED) { EXCEPTION_GUARD(channelPutRequester->channelDisconnect(connectionState==Channel::DESTROYED);) } } void CAChannelPut::channelDisconnect(bool destroy) { EXCEPTION_GUARD(channelPutRequester->channelDisconnect(destroy);) } /* --------------- epics::pvAccess::ChannelPut --------------- */ namespace { static void ca_put_handler(struct event_handler_args args) { CAChannelPut *channelPut = static_cast(args.usr); channelPut->putDone(args); } static void ca_put_get_handler(struct event_handler_args args) { CAChannelPut *channelPut = static_cast(args.usr); channelPut->getDone(args); } typedef int (*doPut)(CAChannel::shared_pointer const & channel, void *usrArg, PVStructure::shared_pointer const & from); // template template int doPut_pvStructure(CAChannel::shared_pointer const & channel, void *usrArg, PVStructure::shared_pointer const & pvStructure) { bool isScalarValue = pvStructure->getStructure()->getField("value")->getType() == scalar; if (isScalarValue) { std::tr1::shared_ptr value = std::tr1::static_pointer_cast(pvStructure->getSubFieldT("value")); pT val = value->get(); int result = 0; if(usrArg!=NULL) { result = ca_array_put_callback(channel->getNativeType(), 1, channel->getChannelID(), &val, ca_put_handler, usrArg); } else { result = ca_array_put(channel->getNativeType(), 1, channel->getChannelID(), &val); } if (result == ECA_NORMAL) { ca_flush_io(); } return result; } else { std::tr1::shared_ptr value = pvStructure->getSubFieldT("value"); const pT* val = value->view().data(); int result = 0; if(usrArg!=NULL) { result = ca_array_put_callback(channel->getNativeType(), static_cast(value->getLength()), channel->getChannelID(), val, ca_put_handler, usrArg); } else { result = ca_array_put(channel->getNativeType(), static_cast(value->getLength()), channel->getChannelID(), val); } if (result == ECA_NORMAL) { ca_flush_io(); } return result; } } // string specialization template<> int doPut_pvStructure(CAChannel::shared_pointer const & channel, void *usrArg, PVStructure::shared_pointer const & pvStructure) { bool isScalarValue = pvStructure->getStructure()->getField("value")->getType() == scalar; if (isScalarValue) { std::tr1::shared_ptr value = std::tr1::static_pointer_cast(pvStructure->getSubFieldT("value")); string val = value->get(); int result = 0; if(usrArg!=NULL) { result = ca_array_put_callback( channel->getNativeType(), 1, channel->getChannelID(), val.c_str(), ca_put_handler, usrArg); } else { result = ca_array_put( channel->getNativeType(), 1, channel->getChannelID(), val.c_str()); } if (result == ECA_NORMAL) { ca_flush_io(); } return result; } else { std::tr1::shared_ptr value = pvStructure->getSubFieldT("value"); PVStringArray::const_svector stringArray(value->view()); size_t arraySize = stringArray.size(); size_t ca_stringBufferSize = arraySize * MAX_STRING_SIZE; char* ca_stringBuffer = new char[ca_stringBufferSize]; memset(ca_stringBuffer, 0, ca_stringBufferSize); char *p = ca_stringBuffer; for(size_t i = 0; i < arraySize; i++) { string value = stringArray[i]; size_t len = value.length(); if (len >= MAX_STRING_SIZE) len = MAX_STRING_SIZE - 1; memcpy(p, value.c_str(), len); p += MAX_STRING_SIZE; } int result = 0; if(usrArg!=NULL) { result = ca_array_put_callback( channel->getNativeType(), arraySize, channel->getChannelID(), ca_stringBuffer, ca_put_handler, usrArg); } else { result = ca_array_put( channel->getNativeType(), arraySize, channel->getChannelID(), ca_stringBuffer); } delete[] ca_stringBuffer; if (result == ECA_NORMAL) { ca_flush_io(); } return result; } } // enum specialization template<> int doPut_pvStructure(CAChannel::shared_pointer const & channel, void *usrArg, PVStructure::shared_pointer const & pvStructure) { bool isScalarValue = pvStructure->getStructure()->getField("value")->getType() == structure; if (isScalarValue) { std::tr1::shared_ptr value = std::tr1::static_pointer_cast(pvStructure->getSubFieldT("value.index")); dbr_enum_t val = value->get(); int result = 0; if(usrArg!=NULL) { result = ca_array_put_callback( channel->getNativeType(), 1, channel->getChannelID(), &val, ca_put_handler, usrArg); } else { result = ca_array_put( channel->getNativeType(), 1, channel->getChannelID(), &val); } if (result == ECA_NORMAL) { ca_flush_io(); } return result; } else { // no enum arrays in V3 return ECA_NOSUPPORT; } } static doPut doPutFuncTable[] = { doPut_pvStructure, // DBR_STRING doPut_pvStructure, // DBR_INT, DBR_SHORT doPut_pvStructure, // DBR_FLOAT doPut_pvStructure, // DBR_ENUM doPut_pvStructure, // DBR_CHAR #if defined(__vxworks) || defined(__rtems__) doPut_pvStructure, // DBR_LONG #else doPut_pvStructure, // DBR_LONG #endif doPut_pvStructure, // DBR_DOUBLE }; } // namespace void CAChannelPut::putDone(struct event_handler_args &args) { if (args.status == ECA_NORMAL) { EXCEPTION_GUARD(channelPutRequester->putDone(Status::Ok, shared_from_this())); } else { Status errorStatus(Status::STATUSTYPE_ERROR, string(ca_message(args.status))); EXCEPTION_GUARD(channelPutRequester->putDone(errorStatus, shared_from_this())); } } void CAChannelPut::put(PVStructure::shared_pointer const & pvPutStructure, BitSet::shared_pointer const & /*putBitSet*/) { channel->threadAttach(); doPut putFunc = doPutFuncTable[channel->getNativeType()]; if (putFunc) { // TODO now we always put all if(block) { int result = putFunc(channel, this, pvPutStructure); if (result != ECA_NORMAL) { Status errorStatus(Status::STATUSTYPE_ERROR, string(ca_message(result))); EXCEPTION_GUARD(channelPutRequester->putDone(errorStatus, shared_from_this())); } } else { int result = putFunc(channel,NULL, pvPutStructure); if (result == ECA_NORMAL) { EXCEPTION_GUARD(channelPutRequester->putDone(Status::Ok, shared_from_this())); } else { Status errorStatus(Status::STATUSTYPE_ERROR, string(ca_message(result))); EXCEPTION_GUARD(channelPutRequester->putDone(errorStatus, shared_from_this())); } } } else { // TODO remove std::cout << "no put func implemented" << std::endl; } // TODO here???!!! if (lastRequestFlag) destroy(); } void CAChannelPut::getDone(struct event_handler_args &args) { if (args.status == ECA_NORMAL) { copyDBRtoPVStructure copyFunc = copyFuncTable[getType]; if (copyFunc) copyFunc(args.dbr, args.count, pvStructure); else { // TODO remove std::cout << "no copy func implemented" << std::endl; } EXCEPTION_GUARD(channelPutRequester->getDone(Status::Ok, shared_from_this(), pvStructure, bitSet)); } else { Status errorStatus(Status::STATUSTYPE_ERROR, string(ca_message(args.status))); EXCEPTION_GUARD(channelPutRequester->getDone(errorStatus, shared_from_this(), PVStructure::shared_pointer(), BitSet::shared_pointer())); } // TODO here???!!! if (lastRequestFlag) destroy(); } void CAChannelPut::get() { channel->threadAttach(); int result = ca_array_get_callback(getType, channel->getElementCount(), channel->getChannelID(), ca_put_get_handler, this); if (result == ECA_NORMAL) { ca_flush_io(); } else { Status errorStatus(Status::STATUSTYPE_ERROR, string(ca_message(result))); EXCEPTION_GUARD(channelPutRequester->getDone(errorStatus, shared_from_this(), PVStructure::shared_pointer(), BitSet::shared_pointer())); } } /* --------------- epics::pvData::ChannelRequest --------------- */ Channel::shared_pointer CAChannelPut::getChannel() { return channel; } void CAChannelPut::cancel() { // noop } void CAChannelPut::lastRequest() { // TODO sync !!! lastRequestFlag = true; } /* --------------- Destroyable --------------- */ void CAChannelPut::destroy() { // TODO } /* --------------- Monitor --------------- */ static void ca_subscription_handler(struct event_handler_args args) { CAChannelMonitor *channelMonitor = static_cast(args.usr); channelMonitor->subscriptionEvent(args); } class CACMonitorQueue : public std::tr1::enable_shared_from_this { public: POINTER_DEFINITIONS(CACMonitorQueue); private: size_t queueSize; bool overrunInProgress; bool isStarted; Mutex mutex; std::queue monitorElementQueue; public: CACMonitorQueue( int32 queueSize) : queueSize(queueSize), overrunInProgress(false), isStarted(false) {} ~CACMonitorQueue() { } void start() { Lock guard(mutex); while(!monitorElementQueue.empty()) monitorElementQueue.pop(); overrunInProgress = false; isStarted = true; } void stop() { Lock guard(mutex); while(!monitorElementQueue.empty()) monitorElementQueue.pop(); overrunInProgress = false; isStarted = false; } // return true if added to queue bool event(const PVStructurePtr &pvStructure) { Lock guard(mutex); if(!isStarted) return false; if(monitorElementQueue.size()==queueSize) { overrunInProgress = true; return false; } else { PVStructure::shared_pointer pvs = getPVDataCreate()->createPVStructure(pvStructure->getStructure()); pvs->copy(*pvStructure); MonitorElementPtr monitorElement(new MonitorElement(pvs)); monitorElement->changedBitSet->set(0); if(overrunInProgress) { overrunInProgress = false; monitorElement->overrunBitSet->set(0); } monitorElementQueue.push(monitorElement); return true; } } MonitorElementPtr poll() { Lock guard(mutex); if(!isStarted) return MonitorElementPtr(); if(monitorElementQueue.empty()) return MonitorElementPtr(); MonitorElementPtr retval = monitorElementQueue.front(); return retval; } void release(MonitorElementPtr const & monitorElement) { Lock guard(mutex); if(monitorElementQueue.empty()) { throw std::runtime_error("client error calling release"); } monitorElementQueue.pop(); } }; CAChannelMonitorPtr CAChannelMonitor::create( CAChannel::shared_pointer const & channel, MonitorRequester::shared_pointer const & monitorRequester, epics::pvData::PVStructure::shared_pointer const & pvRequest) { return CAChannelMonitorPtr(new CAChannelMonitor(channel, monitorRequester, pvRequest)); } CAChannelMonitor::~CAChannelMonitor() { } CAChannelMonitor::CAChannelMonitor( CAChannel::shared_pointer const & channel, MonitorRequester::shared_pointer const & monitorRequester, PVStructurePtr const & pvRequest) : channel(channel), monitorRequester(monitorRequester), pvRequest(pvRequest) { } void CAChannelMonitor::activate() { if(pvStructure) throw std::runtime_error("CAChannelMonitor::activate() was called twice"); getType = getDBRType(pvRequest, channel->getNativeType()); pvStructure = createPVStructure(channel, getType, pvRequest); int32 queueSize = 2; PVStructurePtr pvOptions = pvRequest->getSubField("record._options"); if (pvOptions) { PVStringPtr pvString = pvOptions->getSubField("queueSize"); if (pvString) { int size; std::stringstream ss; ss << pvString->get(); ss >> size; if (size > 1) queueSize = size; } } monitorQueue = CACMonitorQueuePtr(new CACMonitorQueue(queueSize)); channel->addChannelMonitor(shared_from_this()); EXCEPTION_GUARD(monitorRequester->monitorConnect(Status::Ok, shared_from_this(), pvStructure->getStructure())); } void CAChannelMonitor::channelCreated(const Status& status,Channel::shared_pointer const & c) { chtype newType = getDBRType(pvRequest, channel->getNativeType()); if(newType!=getType) { pvStructure.reset(); activate(); } } void CAChannelMonitor::channelStateChange( Channel::shared_pointer const & channel, Channel::ConnectionState connectionState) { if(connectionState==Channel::DISCONNECTED || connectionState==Channel::DESTROYED) { EXCEPTION_GUARD(monitorRequester->channelDisconnect(connectionState==Channel::DESTROYED);) } } void CAChannelMonitor::channelDisconnect(bool destroy) { EXCEPTION_GUARD(monitorRequester->channelDisconnect(destroy);) } void CAChannelMonitor::subscriptionEvent(struct event_handler_args &args) { if (args.status == ECA_NORMAL) { copyDBRtoPVStructure copyFunc = copyFuncTable[getType]; if (copyFunc) { copyFunc(args.dbr, args.count, pvStructure); monitorQueue->event(pvStructure); // call monitorRequester even if queue is full monitorRequester->monitorEvent(shared_from_this()); } else { std::cout << "no copy func implemented" << std::endl; } } else { //Status errorStatus(Status::STATUSTYPE_ERROR, string(ca_message(args.status))); //EXCEPTION_GUARD(channelMonitorRequester->MonitorDone(errorStatus)); } } epics::pvData::Status CAChannelMonitor::start() { channel->threadAttach(); /* From R3.14.12 onwards when using the IOC server and the C++ client libraries monitor callbacks replies will give a CA client application the current number of elements in an array field, provided they specified an element count of zero in their original request. The element count is passed in the callback argument structure. Prior to R3.14.12 you could request a zero-length subscription and the zero would mean “use the value of chid->element_count() for this particular channel”, but the length of the data you got in your callbacks would never change (the server would zero-fill everything after the current length of the field). */ // TODO DBE_PROPERTY support int result = ca_create_subscription(getType, 0, channel->getChannelID(), DBE_VALUE, ca_subscription_handler, this, &eventID); if (result == ECA_NORMAL) { monitorQueue->start(); ca_flush_io(); return Status::Ok; } else { return Status(Status::STATUSTYPE_ERROR, string(ca_message(result))); } } epics::pvData::Status CAChannelMonitor::stop() { channel->threadAttach(); int result = ca_clear_subscription(eventID); if (result == ECA_NORMAL) { monitorQueue->stop(); return Status::Ok; } else { return Status(Status::STATUSTYPE_ERROR, string(ca_message(result))); } } MonitorElementPtr CAChannelMonitor::poll() { return monitorQueue->poll(); } void CAChannelMonitor::release(MonitorElementPtr const & monitorElement) { monitorQueue->release(monitorElement); } /* --------------- epics::pvData::ChannelRequest --------------- */ void CAChannelMonitor::cancel() { // noop } /* --------------- Destroyable --------------- */ void CAChannelMonitor::destroy() { channel->threadAttach(); ca_clear_subscription(eventID); } }}}