#include #include #include #include // redirect stdout/stderr #include #include "pvalink.h" namespace { using namespace pvalink; #define TRY pvaLink *self = static_cast(plink->value.json.jlink); assert(self->alive); try #define CATCH() catch(std::exception& e) { \ errlogPrintf("pvaLink %s fails %s: %s\n", CURRENT_FUNCTION, plink->precord->name, e.what()); \ } #define CHECK_VALID() if(!self->valid()) { DEBUG(self, <channelName<<" !valid"); return -1;} dbfType getLinkType(DBLINK *plink) { dbCommon *prec = plink->precord; pdbRecordIterator iter(prec); for(long status = dbFirstField(&iter.ent, 0); !status; status = dbNextField(&iter.ent, 0)) { if(iter.ent.pfield==plink) return iter.ent.pflddes->field_type; } throw std::logic_error("DBLINK* corrupt"); } void pvaOpenLink(DBLINK *plink) { try { pvaLink* self((pvaLink*)plink->value.json.jlink); self->type = getLinkType(plink); // workaround for Base not propagating info(base:lsetDebug to us { pdbRecordIterator rec(plink->precord); if(epicsStrCaseCmp(rec.info("base:lsetDebug", "NO"), "YES")==0) { self->debug = 1; } } DEBUG(self, <precord->name<<" OPEN "<channelName); // still single threaded at this point. // also, no pvaLinkChannel::lock yet self->plink = plink; if(self->channelName.empty()) return; // nothing to do... pvd::PVStructure::const_shared_pointer pvRequest(self->makeRequest()); pvaGlobal_t::channels_key_t key; { std::ostringstream strm; strm<<*pvRequest; // print the request as a convient key for our channel cache key = std::make_pair(self->channelName, strm.str()); } std::tr1::shared_ptr chan; bool doOpen = false; { Guard G(pvaGlobal->lock); pvaGlobal_t::channels_t::iterator it(pvaGlobal->channels.find(key)); if(it!=pvaGlobal->channels.end()) { // re-use existing channel chan = it->second.lock(); } if(!chan) { // open new channel chan.reset(new pvaLinkChannel(key, pvRequest)); chan->AP->lc = chan; pvaGlobal->channels.insert(std::make_pair(key, chan)); doOpen = true; } doOpen &= pvaGlobal->running; // if not running, then open from initHook } if(doOpen) { chan->open(); // start subscription } if(!self->local || chan->providerName=="QSRV"){ Guard G(chan->lock); chan->links.insert(self); chan->links_changed = true; self->lchan.swap(chan); // we are now attached self->lchan->debug |= !!self->debug; } else { // TODO: only print duing iocInit()? fprintf(stderr, "%s Error: local:true link to '%s' can't be fulfilled\n", plink->precord->name, self->channelName.c_str()); plink->lset = NULL; } return; }CATCH() // on error, prevent any further calls to our lset functions plink->lset = NULL; } void pvaRemoveLink(struct dbLocker *locker, DBLINK *plink) { try { p2p::auto_ptr self((pvaLink*)plink->value.json.jlink); DEBUG(self, <precord->name<<" "<channelName); assert(self->alive); }CATCH() } int pvaIsConnected(const DBLINK *plink) { TRY { Guard G(self->lchan->lock); bool ret = self->valid(); DEBUG(self, <precord->name<<" "<channelName<<" "<lchan->lock); CHECK_VALID(); // if fieldName is empty, use top struct value // if fieldName not empty // if sub-field is struct, use sub-struct .value // if sub-field not struct, treat as value pvd::PVField::const_shared_pointer value(self->getSubField("value")); pvd::ScalarType ftype = pvd::pvInt; // default for un-mapable types. if(!value) { // no-op } else if(value->getField()->getType()==pvd::scalar) ftype = static_cast(value->getField().get())->getScalarType(); else if(value->getField()->getType()==pvd::scalarArray) ftype = static_cast(value->getField().get())->getElementType(); int ret; switch(ftype) { #define CASE(BASETYPE, PVATYPE, DBFTYPE, PVACODE) case pvd::pv##PVACODE: ret = DBF_##DBFTYPE; #define CASE_REAL_INT64 #include "pv/typemap.h" #undef CASE_REAL_INT64 #undef CASE case pvd::pvString: ret = DBF_STRING; // TODO: long string? } DEBUG(self, <precord->name<<" "<channelName<<" "<lchan->lock); CHECK_VALID(); long ret = 0; if(self->fld_value && self->fld_value->getField()->getType()==pvd::scalarArray) ret = static_cast(self->fld_value.get())->getLength(); DEBUG(self, <precord->name<<" "<channelName<<" "<lchan->lock); if(!self->valid()) { // disconnected if(self->ms != pvaLink::NMS) { recGblSetSevr(plink->precord, LINK_ALARM, self->snap_severity); } // TODO: better capture of disconnect time epicsTimeGetCurrent(&self->snap_time); if(self->time) { plink->precord->time = self->snap_time; } DEBUG(self, <channelName<<" !valid"); return -1; } if(self->fld_value) { long status = copyPVD2DBF(self->fld_value, pbuffer, dbrType, pnRequest); if(status) { DEBUG(self, <precord->name<<" "<channelName<<" "<fld_seconds) { self->snap_time.secPastEpoch = self->fld_seconds->getAs() - POSIX_TIME_AT_EPICS_EPOCH; if(self->fld_nanoseconds) { self->snap_time.nsec = self->fld_nanoseconds->getAs(); } else { self->snap_time.nsec = 0u; } } else { self->snap_time.secPastEpoch = 0u; self->snap_time.nsec = 0u; } if(self->fld_severity) { self->snap_severity = self->fld_severity->getAs(); } else { self->snap_severity = NO_ALARM; } if((self->snap_severity!=NO_ALARM && self->ms == pvaLink::MS) || (self->snap_severity==INVALID_ALARM && self->ms == pvaLink::MSI)) { recGblSetSevr(plink->precord, LINK_ALARM, self->snap_severity); } if(self->time) { plink->precord->time = self->snap_time; } DEBUG(self, <precord->name<<" "<channelName<<" OK"); return 0; }CATCH() return -1; } long pvaGetControlLimits(const DBLINK *plink, double *lo, double *hi) { TRY { Guard G(self->lchan->lock); CHECK_VALID(); if(self->fld_control) { pvd::PVScalar::const_shared_pointer value; if(lo) { value = std::tr1::static_pointer_cast(self->fld_control->getSubField("limitLow")); *lo = value ? value->getAs() : 0.0; } if(hi) { value = std::tr1::static_pointer_cast(self->fld_control->getSubField("limitHigh")); *hi = value ? value->getAs() : 0.0; } } else { *lo = *hi = 0.0; } DEBUG(self, <precord->name<<" "<channelName<<" "<<(lo ? *lo : 0)<<" "<<(hi ? *hi : 0)); return 0; }CATCH() return -1; } long pvaGetGraphicLimits(const DBLINK *plink, double *lo, double *hi) { TRY { Guard G(self->lchan->lock); CHECK_VALID(); if(self->fld_display) { pvd::PVScalar::const_shared_pointer value; if(lo) { value = std::tr1::static_pointer_cast(self->fld_display->getSubField("limitLow")); *lo = value ? value->getAs() : 0.0; } if(hi) { value = std::tr1::static_pointer_cast(self->fld_display->getSubField("limitHigh")); *hi = value ? value->getAs() : 0.0; } } else { *lo = *hi = 0.0; } DEBUG(self, <precord->name<<" "<channelName<<" "<<(lo ? *lo : 0)<<" "<<(hi ? *hi : 0)); return 0; }CATCH() return -1; } long pvaGetAlarmLimits(const DBLINK *plink, double *lolo, double *lo, double *hi, double *hihi) { TRY { //Guard G(self->lchan->lock); //CHECK_VALID(); *lolo = *lo = *hi = *hihi = 0.0; DEBUG(self, <precord->name<<" "<channelName<<" "<<(lolo ? *lolo : 0)<<" "<<(lo ? *lo : 0)<<" "<<(hi ? *hi : 0)<<" "<<(hihi ? *hihi : 0)); return 0; }CATCH() return -1; } long pvaGetPrecision(const DBLINK *plink, short *precision) { TRY { //Guard G(self->lchan->lock); //CHECK_VALID(); // No sane way to recover precision from display.format string. *precision = 0; DEBUG(self, <precord->name<<" "<channelName<<" "<lchan->lock); CHECK_VALID(); if(unitsSize==0) return 0; if(units && self->fld_display) { pvd::PVString::const_shared_pointer value(std::tr1::static_pointer_cast(self->fld_display->getSubField("units"))); if(value) { const std::string& egu = value->get(); strncpy(units, egu.c_str(), unitsSize); } } else if(units) { units[0] = '\0'; } units[unitsSize-1] = '\0'; DEBUG(self, <precord->name<<" "<channelName<<" "<lchan->lock); CHECK_VALID(); if(severity) { *severity = self->snap_severity; } if(status) { *status = self->snap_severity ? LINK_ALARM : NO_ALARM; } DEBUG(self, <precord->name<<" "<channelName<<" "<<(severity ? *severity : 0)<<" "<<(status ? *status : 0)); return 0; }CATCH() return -1; } long pvaGetTimeStamp(const DBLINK *plink, epicsTimeStamp *pstamp) { TRY { Guard G(self->lchan->lock); CHECK_VALID(); if(pstamp) { *pstamp = self->snap_time; } DEBUG(self, <precord->name<<" "<channelName<<" "<<(pstamp ? pstamp->secPastEpoch : 0)<<":"<<(pstamp ? pstamp->nsec: 0)); return 0; }CATCH() return -1; } // note that we handle DBF_ENUM differently than in pvif.cpp pvd::ScalarType DBR2PVD(short dbr) { switch(dbr) { #define CASE(BASETYPE, PVATYPE, DBFTYPE, PVACODE) case DBR_##DBFTYPE: return pvd::pv##PVACODE; #define CASE_SKIP_BOOL #define CASE_REAL_INT64 #include "pv/typemap.h" #undef CASE_SKIP_BOOL #undef CASE_REAL_INT64 #undef CASE case DBF_ENUM: return pvd::pvUShort; case DBF_STRING: return pvd::pvString; } throw std::invalid_argument("Unsupported DBR code"); } long pvaPutValueX(DBLINK *plink, short dbrType, const void *pbuffer, long nRequest, bool wait) { TRY { (void)self; Guard G(self->lchan->lock); if(nRequest < 0) return -1; if(!self->retry && !self->valid()) { DEBUG(self, <channelName<<" !valid"); return -1; } pvd::ScalarType stype = DBR2PVD(dbrType); pvd::shared_vector buf; if(dbrType == DBF_STRING) { const char *sbuffer = (const char*)pbuffer; pvd::shared_vector sval(nRequest); for(long n=0; nput_scratch = pvd::static_shared_vector_cast(pvd::freeze(sval)); } else { pvd::shared_vector val(pvd::ScalarTypeFunc::allocArray(stype, size_t(nRequest))); assert(size_t(dbValueSize(dbrType)*nRequest) == val.size()); memcpy(val.data(), pbuffer, val.size()); self->put_scratch = pvd::freeze(val); } self->used_scratch = true; #ifdef USE_MULTILOCK if(wait) self->lchan->after_put.insert(plink->precord); #endif if(!self->defer) self->lchan->put(); DEBUG(self, <precord->name<<" "<channelName<<" "<lchan->op_put.valid()); return 0; }CATCH() return -1; } long pvaPutValue(DBLINK *plink, short dbrType, const void *pbuffer, long nRequest) { return pvaPutValueX(plink, dbrType, pbuffer, nRequest, false); } long pvaPutValueAsync(DBLINK *plink, short dbrType, const void *pbuffer, long nRequest) { return pvaPutValueX(plink, dbrType, pbuffer, nRequest, true); } void pvaScanForward(DBLINK *plink) { TRY { Guard G(self->lchan->lock); if(!self->retry && !self->valid()) { return; } // FWD_LINK is never deferred, and always results in a Put self->lchan->put(true); DEBUG(self, <precord->name<<" "<channelName<<" "<lchan->op_put.valid()); }CATCH() } #undef TRY #undef CATCH } //namespace namespace pvalink { lset pva_lset = { 0, 1, // non-const, volatile &pvaOpenLink, &pvaRemoveLink, NULL, NULL, NULL, &pvaIsConnected, &pvaGetDBFtype, &pvaGetElements, &pvaGetValue, &pvaGetControlLimits, &pvaGetGraphicLimits, &pvaGetAlarmLimits, &pvaGetPrecision, &pvaGetUnits, &pvaGetAlarm, &pvaGetTimeStamp, &pvaPutValue, &pvaPutValueAsync, &pvaScanForward //&pvaReportLink, }; } //namespace pvalink