/* * Copyright 2011 EFDA | European Fusion Development Agreement * * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by the European Commission - subsequent versions of the EUPL (the "Licence"); * You may not use this work except in compliance with the Licence. * You may obtain a copy of the Licence at: * * http://ec.europa.eu/idabc/eupl * * Unless required by applicable law or agreed to in writing, software distributed under the Licence is distributed on an "AS IS" basis, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the Licence for the specific language governing permissions and limitations under the Licence. * * $Id$ * **/ #include "EPICSHandler.h" #include "Atomic.h" #include "CDBExtended.h" // EPICS version #include "nciu.h" #include "caProto.h" #include "epicsVersion.h" #include "epicsRelease.h" // EPICS FileDescriptor Manager #include #include // EPICS pCAS GDD stuff #include "gddAppFuncTable.h" #include "smartGDDPointer.h" #include "gddApps.h" #include "aitTypes.h" #include "FString.h" #include "BasicTypes.h" // BasicTypeDescriptor #include "Sleep.h" #define DEFAULT_EPICS_DBG 0 #define DEFAULT_EPICS_HOPR 10.0 #define DEFAULT_EPICS_LOPR -10.0 #define DEFAULT_EPICS_LEN 1 #define DEFAULT_EPICS_PREC 4 //#define DEFAULT_EPICS_SCAN 1.0 #define DEFAULT_EPICS_SCAN 0 #define DEFAULT_EPICS_EGU "" #define DEFAULT_EPICS_SYNC "excasIoSync" #define DEFAULT_EPICS_ASYNC "excasIoAsync" #define DEFAULT_EPICS_SCANON true #define DEFAULT_EPICS_ASYNCSCAN true #define DEFAULT_EPICS_ASYNCDELAY 0.1 #define DEFAULT_EPICS_MAXSIMULTASYNCIO 1000u #define DEFAULT_EPICS_HYST 0.0 #define DEFAULT_EPICS_ADEL 0.0 #define DEFAULT_EPICS_MDEL 0.0 #define DEFAULT_EPICS_CPUMASK 0xFFFF #define DEFAULT_EVENT_CPUMASK 0xFFFF #define DEFAULT_BUFFER_SIZE 0xFFFF #define DEFAULT_BUFFER_ALIGN 4 const char * EPICSHandler::tf_strings[] = { "false", "true" }; const int32 EPICSHandler::tf_values[] = { false, true }; /* typedef enum { aitEnumInvalid=0, aitEnumInt8, aitEnumUint8, aitEnumInt16, aitEnumUint16, aitEnumEnum16, aitEnumInt32, aitEnumUint32, aitEnumFloat32, aitEnumFloat64, aitEnumFixedString, aitEnumString, aitEnumContainer } aitEnum; */ // TODO //see base-3-14-11/src/gdd/aitTypes.c:41 aitName //see base-3-14-11/src/gdd/aitTypes.h:131 ... //aitSize aitName aitPrintf aitScanf const char * EPICSHandler::aitEnum_strings[] = { "aitEnumInvalid", "aitEnumInt8", "aitEnumUint8", "aitEnumInt16", "aitEnumUint16", "aitEnumEnum16", "aitEnumInt32", "aitEnumUint32", "aitEnumFloat32", "aitEnumFloat64", "aitEnumFixedString", "aitEnumString", "aitEnumContainer", 0 }; // the following must match epics/base/include/menuAlarmSevr.h #include "menuAlarmSevr.h" const char * EPICSHandler::menuAlarmSevr_strings[] = { "NO_ALARM", "MINOR", "MAJOR", "INVALID", 0 }; // the following must match epics/base/include/menuAlarmStat.h #include "menuAlarmStat.h" const char * EPICSHandler::menuAlarmStat_strings[] = { "NO_ALARM", "READ", "WRITE", "HIHI", "HIGH", "LOLO", "LOW", "STATE", "COS", "COMM", "TIMEOUT", "HWLIMIT", "CALC", "SCAN", "LINK", "SOFT", "BAD_SUB", "UDF", "DISABLE", "SIMM", "READ_ACCESS", "WRITE_ACCESS", 0 }; // the following must match epics/base/include/menuScan.h #include "menuScan.h" const char * EPICSHandler::menuScan_strings[] = { "Passive", "Event", "I/O Intr", "10 second", "5 second", "2 second", "1 second", ".5 second", ".2 second", ".1 second", 0 }; const float EPICSHandler::menuScan_values[] = { -1.0, -1.0, -1.0, 10.0, 5.0, 2.0, 1.0, 0.5, 0.2, 0.1, 0 }; // the following must match epics/base/include/menuYesNo.h #include "menuYesNo.h" const char * EPICSHandler::menuYesNo_strings[] = { "NO", "YES", 0 }; /* enum excasIoType { excasIoSync, excasIoAsync }; */ const char * EPICSHandler::excasIoType_strings[] = { DEFAULT_EPICS_SYNC, DEFAULT_EPICS_ASYNC }; const int EPICSHandler::menu_values[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23 }; bool EPICSHandler::ObjectLoadSetup( ConfigurationDataBase &info,StreamInterface *err) { // we use an Extended CDB in order to read double, strings and etc.. CDBExtended cdb(info); int32 _i32; if ( !GCNamedObject::ObjectLoadSetup(info,err) ) { AssertErrorCondition(InitialisationError, "EPICSHandler::ObjectLoadSetup: ?!?!: GCNamedObject::ObjectLoadSetup Failed"); return False; } AssertErrorCondition(Information, "EPICSHandler::ObjectLoadSetup: %s: Loading Process Variables", Name()); // read the common prefix of all Process Variables (is not mandatory if ( !cdb.ReadFString(pvPrefix, "PREFIX", "") ) { AssertErrorCondition(Information, "EPICSHandler::ObjectLoadSetup: %s: PREFIX not specified. Assuming no prefix.", Name()); } if ( !cdb.ReadOptions(_i32, "scanOn", tf_strings, tf_values, DEFAULT_EPICS_SCANON) ) { AssertErrorCondition(Information, "EPICSHandler::ObjectLoadSetup: %s: scanOn not specified. Assuming %d.", Name(), DEFAULT_EPICS_SCANON); } scanOn = _i32; if ( !cdb.ReadOptions(_i32, "asyncScan", tf_strings, tf_values, DEFAULT_EPICS_ASYNCSCAN) ) { AssertErrorCondition(Information, "EPICSHandler::ObjectLoadSetup: %s: asyncScan not specified. Assuming %d.", Name(), DEFAULT_EPICS_ASYNCSCAN); } asyncScan = _i32; if ( !cdb.ReadDouble(asyncDelay, "asyncDelay", DEFAULT_EPICS_ASYNCDELAY) ) { AssertErrorCondition(Information, "EPICSHandler::ObjectLoadSetup: %s: asyncDelay not specified. Assuming %f.", Name(), DEFAULT_EPICS_ASYNCDELAY); } if ( !cdb.ReadInt32( _i32, "maxSimultAsyncIO", (int32)DEFAULT_EPICS_MAXSIMULTASYNCIO) ) { AssertErrorCondition(Information, "EPICSHandler::ObjectLoadSetup: %s: maxSimultAsyncIO not specified. Assuming %d.", Name(), DEFAULT_EPICS_MAXSIMULTASYNCIO); } maxSimultAsyncIO = _i32; if ( !cdb.ReadInt32( _i32, "debugLevel", (int32)DEFAULT_EPICS_DBG) ) { AssertErrorCondition(Information, "EPICSHandler::ObjectLoadSetup: %s: debugLevel not specified. Assuming %d.", Name(), DEFAULT_EPICS_DBG); } debugLevel = _i32; //PublishSubscriber Interface(TODO) configuration options if ( !cdb.ReadInt32(cpuMask,"RunOnCPU", DEFAULT_EPICS_CPUMASK) ) { AssertErrorCondition(Information, "EPICShandler ::ObjectLoadSetup: %s: RunOnCPU not specified. Assuming %d.", Name(), cpuMask); } if ( !cdb.ReadInt32(cpuMask_event,"RunOnCPU_event", DEFAULT_EVENT_CPUMASK) ) { AssertErrorCondition(Information, "EPICShandler ::ObjectLoadSetup: %s: RunOnCPU_event not specified. Assuming %d.", Name(), cpuMask_event); } //buffers number, buffer must be allocated before event's thread allocation if ( !cdb.ReadInt32(buffer_size,"BufferSize", DEFAULT_BUFFER_SIZE) ) { AssertErrorCondition(Information, "EPICShandler ::ObjectLoadSetup: %s: BufferSize not specified. Assuming %08x.", Name(), buffer_size); } if ( !cdb.ReadInt32(buffer_align,"BufferAlign", DEFAULT_BUFFER_ALIGN) ) { AssertErrorCondition(Information, "EPICShandler ::ObjectLoadSetup: %s: BufferAlign not specified. Assuming %08x.", Name(), buffer_align); } //create the array of Process Variable descriptors (pvInfo) if ( !cdb->Move("ProcessVariable") ) { AssertErrorCondition(InitialisationError, "EPICSHandler::ObjectLoadSetup: %s: No Process Variable specified. Fatal error!", Name()); return False; } numberOfPVs = cdb->NumberOfChildren(); if ( numberOfPVs == 0 ) { AssertErrorCondition(InitialisationError, "EPICSHandler::ObjectLoadSetup: %s: Number of signals is zero. Fatal error!", Name()); return False; } pvList = new pvInfo* [numberOfPVs]; if ( pvList == NULL ) { AssertErrorCondition(InitialisationError, "EPICSHandler::ObjectLoadSetup: %s: Failed to allocate space for %d ProcessVariables. Fatal error!", Name(), numberOfPVs); return False; } //create the pCAS here and then attach the PV's pCAS = new exServer( scanOn != 0, asyncScan== 0, asyncDelay, maxSimultAsyncIO ); if ( !pCAS ) { AssertErrorCondition(InitialisationError, "EPICSHandler::ObjectLoadSetup: %s: Failed to allocate space for pCAS. Fatal error!", Name() ); return False; } pCAS->setDebugLevel(debugLevel); //read process variables properties loop //TODO every PV must have it's own custom ObjectLoadSetup //TODO at least do that for the DBR/DBF types int i=0; for(i=0; iMoveToChildren(i); FString pvName; if ( !cdb.ReadFString(pvName, "NAME") ) { AssertErrorCondition(InitialisationError, "EPICSHandler::ObjectLoadSetup: %s: NAME not specified. Fatal error!", Name()); return False; } // ((char *)(pvName.Buffer()))[pvName.Size()] = 0; FString pvType; aitEnum aitType; if ( !cdb.ReadFString(pvType, "TYPE") ) { AssertErrorCondition(InitialisationError, "EPICSHandler::ObjectLoadSetup: %s: TYPE not specified. Fatal error!", Name()); return False; } // convert to ait type aitType = ConvertToaitEnum( pvType.Buffer() ); FString pvSync; excasIoType excasSync; if ( !cdb.ReadFString(pvSync, "SYNC", DEFAULT_EPICS_SYNC) ) { AssertErrorCondition(InitialisationError, "EPICSHandler::ObjectLoadSetup: %s: TYPE not specified. Assuming %s", Name(), DEFAULT_EPICS_SYNC); } // convert to excasIoType excasSync = ConvertToexcasIoType( pvSync.Buffer() ); unsigned pvElements; if ( !cdb.ReadInt32(_i32, "LEN", (int32)DEFAULT_EPICS_LEN) ) { AssertErrorCondition(Information, "EPICSHandler::ObjectLoadSetup: %s: LEN not specidfied. Assuming %d", Name(), pvElements); } pvElements = _i32; /* previous implementation double pvScanPeriod; if ( !cdb.ReadDouble(pvScanPeriod, "SCAN", DEFAULT_EPICS_SCAN)) { AssertErrorCondition(Information, "EPICSHandler::ObjectLoadSetup: %s: SCAN not specified. Assuming %f", Name(), DEFAULT_EPICS_SCAN); } */ if( !cdb.ReadOptions(_i32, "SCAN", menuScan_strings, menu_values, DEFAULT_EPICS_SCAN) ) { AssertErrorCondition(Information, "EPICSHandler::ObjectLoadSetup: %s: SCAN not specified. Assuming \"%s\"", Name(), menuScan_strings[DEFAULT_EPICS_SCAN]); } float pvScanPeriod = menuScan_values[_i32]; FString pvUnits; if ( !cdb.ReadFString(pvUnits, "EGU", DEFAULT_EPICS_EGU) ) { AssertErrorCondition(InitialisationError, "EPICSHandler::ObjectLoadSetup: %s: EGU not specified. Assuming \"%s\"", Name(), DEFAULT_EPICS_EGU); } // ((char *)(pvUnits.Buffer()))[pvUnits.Size()] = 0; // graphical limits "High/Low OPerational Range" double pvHopr; if(!cdb.ReadDouble(pvHopr, "HOPR", DEFAULT_EPICS_HOPR)) { AssertErrorCondition(Information, "EPICSHandler::ObjectLoadSetup: %s: HOPR not specified. Assuming %f", Name(), DEFAULT_EPICS_HOPR); } double pvLopr; if ( !cdb.ReadDouble(pvLopr, "LOPR", DEFAULT_EPICS_LOPR)) { AssertErrorCondition(Information, "EPICSHandler::ObjectLoadSetup: %s: LOPR not specified. Assuming %f", Name(), DEFAULT_EPICS_LOPR); } // alarm limits // the policy is to set alarms to OPerational Ranges if not specified double pvHihi; //Hihi Alarm Limit if(!cdb.ReadDouble(pvHihi, "HIHI", pvHopr)) { AssertErrorCondition(Information, "EPICSHandler::ObjectLoadSetup: %s: HIHI not specified. Assuming %f", Name(), pvHopr); } double pvHigh; //High Alarm Limit if(!cdb.ReadDouble(pvHigh, "HIGH", pvHopr)) { AssertErrorCondition(Information, "EPICSHandler::ObjectLoadSetup: %s: HIGH not specified. Assuming %f", Name(), pvHopr); } double pvLow; //Low Alarm Limit if ( !cdb.ReadDouble(pvLow, "LOW", pvLopr)) { AssertErrorCondition(Information, "EPICSHandler::ObjectLoadSetup: %s: LOW not specified. Assuming %f", Name(), pvLopr); } double pvLolo; //Lolo Alarm Limit if ( !cdb.ReadDouble(pvLolo, "LOLO", pvLopr)) { AssertErrorCondition(Information, "EPICSHandler::ObjectLoadSetup: %s: LOLO not specified. Assuming %f", Name(), pvLopr); } // menu alarm severity int32 pvHhsv; //Hihi Alarm Severity if( !cdb.ReadOptions(pvHhsv, "HHSV", menuAlarmSevr_strings, menu_values, 0) ) { AssertErrorCondition(Information, "EPICSHandler::ObjectLoadSetup: %s: HHSV not specified. Assuming %s", Name(), menuAlarmSevr_strings[pvHhsv] ); } int32 pvHsv; //High Alarm Severity if( !cdb.ReadOptions(pvHsv, "HSV", menuAlarmSevr_strings, menu_values, 0) ) { AssertErrorCondition(Information, "EPICSHandler::ObjectLoadSetup: %s: HSV not specified. Assuming %s", Name(), menuAlarmSevr_strings[pvHsv] ); } int32 pvLsv; //Low Alarm Severity if( !cdb.ReadOptions(pvLsv, "LSV", menuAlarmSevr_strings, menu_values, 0) ) { AssertErrorCondition(Information, "EPICSHandler::ObjectLoadSetup: %s: LSV not specified. Assuming %s", Name(), menuAlarmSevr_strings[pvLsv] ); } int32 pvLlsv; //Lolo Alarm Severity if( !cdb.ReadOptions(pvLlsv, "LLSV", menuAlarmSevr_strings, menu_values, 0) ) { AssertErrorCondition(Information, "EPICSHandler::ObjectLoadSetup: %s: LLSV not specified. Assuming %s", Name(), menuAlarmSevr_strings[pvLlsv] ); } // hysteresis/deadband values double pvHyst; // Hysteresis Alarm deadband if ( !cdb.ReadDouble(pvHyst, "HYST", DEFAULT_EPICS_HYST)) { AssertErrorCondition(Information, "EPICSHandler::ObjectLoadSetup: %s: HYST not specified. Assuming %f", Name(), pvHyst); } double pvAdel; // Archive deadband if ( !cdb.ReadDouble(pvAdel, "ADEL", DEFAULT_EPICS_ADEL)) { AssertErrorCondition(Information, "EPICSHandler::ObjectLoadSetup: %s: ADEL not specified. Assuming %f", Name(), pvAdel); } double pvMdel; // Monitor deadband if ( !cdb.ReadDouble(pvMdel, "MDEL", DEFAULT_EPICS_MDEL)) { AssertErrorCondition(Information, "EPICSHandler::ObjectLoadSetup: %s: MDEL not specified. Assuming %f", Name(), pvMdel); } // read precision (realted only to ai/ao records) int32 pvPrec; if ( !cdb.ReadInt32(pvPrec, "PREC", DEFAULT_EPICS_PREC) ) { AssertErrorCondition(Information, "EPICSHandler::ObjectLoadSetup: %s: PREC not specidfied. Assuming %d", Name(), pvPrec); } //--------------------------------------------------------------------- end of EPICS stuff // we have to allocate the memory for the pv's name (TODO modify pvInfo or dealloc) //FString * pvNameObj = new FString(pvName); //TODO deallocate this space -> DONE //FString * pvUnitsObj = new FString(pvUnits); //TODO deallocate this space -> DONE // create the pv's descriptor pvInfo pvInfo *pPVI = new pvInfo(pvScanPeriod, // scan period pvName.Buffer(), // name of the pv (will be copied) pvUnits.Buffer(), // units of the pv (will be copied) (aitFloat32) pvHopr, (aitFloat32) pvLopr, // HOPR and LOPR, gr=graphical pvHihi, pvHigh, pvLow, pvLolo, aitType, excasSync, pvElements); if ( !pPVI ) { AssertErrorCondition(InitialisationError, "EPICSHandler::ObjectLoadSetup: %s: cannot create pvInfo structure for %s. Fatal error!", Name(), pvName.Buffer() ); return False; } // create the real process variable exPV * pPV = pPVI->createPV(*pCAS, true, scanOn, asyncDelay); if (!pPV) { AssertErrorCondition(InitialisationError, "EPICSHandler::ObjectLoadSetup: %s: cannot create exPV structure for %s. Fatal error!", Name(), pPVI->getName() ); return False; } // Install canonical (root) name char pvAlias[256]; const char * const pNameFmtStr = "%.100s%.20s"; sprintf(pvAlias, pNameFmtStr, pvPrefix.Buffer(), pPVI->getName()); pCAS->installAliasName(*pPVI, pvAlias); // add the process variable to the list by copy and delete the current object pvList[i] = pPVI; // copy the process variable to the list (by pointer) //--------------------------------------------------------------------- end of EPICS stuff cdb->MoveToFather(); } // PVs loop cdb->MoveToFather(); // Publish Subscribe Interface(TODO) stuff if ( subList ) delete subList; subList = new subscriber [numberOfPVs]; if ( !subList ) { AssertErrorCondition(InitialisationError, "EPICSHandler::ObjectLoadSetup: %s: cannot create subList. Fatal error!", Name() ); return False; } // TODO is this the correct way to initialize the array?! memset ( subList, 0, (numberOfPVs * sizeof(subscriber)) ); // preparing the buffering infrastructure - this part will be moved in the abstract class // in the Process Variable loop we can calculate the minimum size required to work unsigned _size = buffer_size % buffer_align; if ( _size ) { buffer_size += buffer_align - _size; AssertErrorCondition(Information, "EPICSHandler::ObjectLoadSetup: %s: circular buffer resized to %x (aligned)", Name(), buffer_size); } // check if buffer was previously allocated if ( buffer_ptr ) delete [] buffer_ptr; buffer_ptr = new char [buffer_size]; if ( !buffer_ptr ) { AssertErrorCondition(InitialisationError, "EPICSHandler::ObjectLoadSetup: %s: cannot allocate space for the circular buffer. Fatal error!", Name() ); return False; } // set head and tail and free bytes buffer_head = (cbHeader * ) buffer_ptr; buffer_tail = (cbHeader * ) buffer_ptr; buffer_free = buffer_size; // ***** circular buffer rules ***** // head \E8 sempre in una posizione libera (quando e full?) // tail \E8 sempre nel primo elemento da mangiare tranne quando il ciruclar buffer e vuoto // check if the threads are already running: kill them and then reload them // TODO // in a single MARTe instance must exists only one instance of the "callback" thread // but you must register one instance of "callback_event" for every instance of this class // create the asynchronous EPICS thread --> from EPICS to MARTe callback_finalize++; threadID = Threads::BeginThread( (ThreadFunctionType) callback, pCAS, THREADS_DEFAULT_STACKSIZE, "EPICSHandler", XH_NotHandled, cpuMask); if ( !threadID ) { AssertErrorCondition(InitialisationError, "EPICSHandler::ObjectLoadSetup: %s: cannot create EpicsThreadCallback. Fatal error!", Name() ); return False; } /* AssertErrorCondition(Information, "EPICSHandler::ObjectLoadSetup: %s: Service started with tid 0x%08x.", Name(), threadID); */ // create the asynchronous MARTe thread --> from MARTe to EPICS callback_event_finalize++; threadID_event = Threads::BeginThread( (ThreadFunctionType) callback_event, this, THREADS_DEFAULT_STACKSIZE, "EPICSHandler_events", XH_NotHandled, cpuMask_event); if ( !threadID_event ) { AssertErrorCondition(InitialisationError, "EPICSHandler::ObjectLoadSetup: %s: cannot create eventBufferCallback. Fatal error!", Name() ); return False; } /* AssertErrorCondition(Information, "EPICSHandler::ObjectLoadSetup: %s: Service started with tid 0x%08x.", Name(), threadID_event); */ setup_complete = true; AssertErrorCondition(Information, "EPICSHandler::ObjectLoadSetup: %s: Loaded %d Process Variables.", Name(), numberOfPVs); return True; } //----------------------------------------------------------------------------- end ObjectLoadSetup // TODO use CDBExtended::ReadOptions instead aitEnum EPICSHandler::ConvertToaitEnum(const char * s) { int i; for (i=0; i<(sizeof(aitEnum_strings)/sizeof(char*)); i++ ) { if ( strcmp(aitEnum_strings[i], s)==0 ) //if ( strcasecmp(aitEnum_string[i], s)==0 ) return (aitEnum)i; } return aitEnumInvalid; //on error returns 0 (aitEnumInvalid) } //----------------------------------------------------------------------------- end ConvertToaitEnum // TODO use CDBExtended::ReadOptions instead excasIoType EPICSHandler::ConvertToexcasIoType(const char * s) { int i; for (i=0; i<(sizeof(excasIoType_strings)/sizeof(char*)); i++ ) { if ( strcmp(excasIoType_strings[i], s)==0 ) //if ( strcasecmp(aitEnum_string[i], s)==0 ) return (excasIoType)i; } return excasIoSync; //on error returns 0 (excasIoSync) } //----------------------------------------------------------------------------- end ConvertToexcasIoType int EPICSHandler::callback_finalize = 0; void EPICSHandler::callback (void * args) { //osiTime delay(1000u, 0u); // TODO ? while( callback_finalize ) fileDescriptorManager.process(0.1); // double seconds (timeout) } //----------------------------------------------------------------------------- end EPICSCallback #define SYNCHRONIZING int EPICSHandler::callback_event_finalize= 0; void EPICSHandler::callback_event (void * args) { unsigned id; unsigned timestamp; int _ret; EPICSHandler * THIS = static_cast(args); epicsTimeStamp current = epicsTime::getCurrent(); while( callback_event_finalize ) { // if there are no subscribers wait a bit and continue loop if ( !(THIS->subListSize) ) { SleepMsec(100); continue; } // collect data from the producer _ret = THIS->get ( id, THIS->subBuffer, THIS->subSize, timestamp); if ( _ret == 0 ) // no data, no problem continue; if ( _ret == -1 ) { // error CStaticAssertErrorCondition(FatalError, "EPICSHandler::callback_event: thread: get returns %d", _ret ); // TODO read buffer_err to write which kind of error happens break; } // check id if ( !(id < THIS->numberOfPVs) ) { // error again CStaticAssertErrorCondition(Warning, "EPICSHandler::callback_event: thread: id %d not in PVs range (0..%d)", id, THIS->numberOfPVs); continue; } // conversion is done by EPICS side (i.e. GDD) smartGDDPointer pDD; switch ( (THIS->subList[id].type).Type() ) { case BTDTInteger: //TODO switch on flag switch ( (THIS->subList[id].type).BitSize() ) { /* not supported by the DDB * case 8: if ( (THIS->subList[id].count) == 1 ) // scalar pDD = new gddScalar (gddAppType_value, aitEnumInt8); else // --------------------------- vector pDD = new gddAtomic (gddAppType_value, aitEnumInt8, 1u, THIS->subList[id].count); break; case 16: if ( (THIS->subList[id].count) == 1 ) // scalar pDD = new gddScalar (gddAppType_value, aitEnumInt16); else // --------------------------- vector pDD = new gddAtomic (gddAppType_value, aitEnumInt16, 1u, THIS->subList[id].count); break; */ case 32: switch ( (THIS->subList[id].type).Flags() ) { case BTDSTUnsigned: if ( (THIS->subList[id].count) == 1 ) // scalar pDD = new gddScalar (gddAppType_value, aitEnumUint32); else // --------------------------- vector pDD = new gddAtomic (gddAppType_value, aitEnumUint32, 1u, THIS->subList[id].count); break; case BTDSTNone: if ( (THIS->subList[id].count) == 1 ) // scalar pDD = new gddScalar (gddAppType_value, aitEnumInt32); else // --------------------------- vector pDD = new gddAtomic (gddAppType_value, aitEnumInt32, 1u, THIS->subList[id].count); break; default: BString bsbuf; CStaticAssertErrorCondition(Warning, "EPICSHandler::callback_event: thread: BTDTInteger, %s BasicTypeDescriptor not supported for id %d (%s) (32bit)", (THIS->subList[id].type).ConvertToString(bsbuf), id, (THIS->subList[id].count > 1) ? "array" : "scalar" ); } break; default: // in this case it is EPICS that does not support Int64 (MARTe supports it already) BString bsbuf; CStaticAssertErrorCondition(Warning, "EPICSHandler::callback_event: thread: BTDTInteger, %s BasicTypeDescriptor not supported for id %d (%s)", (THIS->subList[id].type).ConvertToString(bsbuf), id, (THIS->subList[id].count > 1) ? "array" : "scalar" ); } break; case BTDTFloat: switch ( (THIS->subList[id].type).BitSize() ) { case 32: if ( (THIS->subList[id].count) == 1 ) // scalar pDD = new gddScalar (gddAppType_value, aitEnumFloat32); else // --------------------------- vector pDD = new gddAtomic (gddAppType_value, aitEnumFloat32, 1u, THIS->subList[id].count); break; case 64: if ( (THIS->subList[id].count) == 1 ) // scalar pDD = new gddScalar (gddAppType_value, aitEnumFloat64); else // --------------------------- vector pDD = new gddAtomic (gddAppType_value, aitEnumFloat64, 1u, THIS->subList[id].count); break; default: BString bsbuf; CStaticAssertErrorCondition(Warning, "EPICSHandler::callback_event: thread: BTDTFloat, %s BasicTypeDescriptor not supported for id %d (%s)", (THIS->subList[id].type).ConvertToString(bsbuf), id, (THIS->subList[id].count > 1) ? "array" : "scalar" ); } break; default: // error not supported by the driver BString bsbuf; CStaticAssertErrorCondition(Warning, "EPICSHandler::callback_event: thread: %s BasicTypeDescriptor not supported for id %d (%s)", (THIS->subList[id].type).ConvertToString(bsbuf), id, (THIS->subList[id].count > 1) ? "array" : "scalar" ); continue; } //------------------------------------------------------------------- end switch ( (THIS->subList[id].type).Type() ) if ( !pDD.valid() ) { CStaticAssertErrorCondition(Warning, "EPICSHandler::callback_event: thread: pDD not valid cannot continue (id is %d, ->count is %d)", id, THIS->subList[id].count); continue; } gddStatus gdds= pDD->unreference (); if ( gdds ) {// assert ( ! gddStatus ); CStaticAssertErrorCondition(Warning, "EPICSHandler::callback_event: thread: assert (!gddStatus) cannot continue" ); continue; } // copy data scalar or array switch ( (THIS->subList[id].type).Type() ) { case BTDTInteger: switch ( (THIS->subList[id].type).BitSize() ) { /* THE FOLLOWINGS ARE NOT SUPPORTED BY the DDB :-( * case 8: if ( (THIS->subList[id].count) == 1 ) // scalar pDD->put( (aitInt8) * (aitInt8 *)(THIS->subBuffer) ); else { //-------------------------- vector aitInt8 * pI8 = new aitInt8 [THIS->subList[id].count]; if ( !pI8 ) goto assert_allocation_error; // create destructor int8Destructor * pBTDFd = new int8Destructor; if ( !pBTDFd ) { delete [] pI8; goto assert_destructor_error; } // set the data pDD->putRef (pI8, pBTDFd); // copy array data memcpy(pI8, THIS->subBuffer, THIS->subList[id].size); } break; case 16: if ( (THIS->subList[id].count) == 1 ) // scalar pDD->put( (aitInt16) * (aitInt16 *)(THIS->subBuffer) ); else { //-------------------------- vector aitInt16 * pI16 = new aitInt16 [THIS->subList[id].count]; if ( !pI16 ) goto assert_allocation_error; // create destructor int16Destructor * pBTDFd = new int16Destructor; if ( !pBTDFd ) { delete [] pI16; goto assert_destructor_error; } // set the data pDD->putRef (pI16, pBTDFd); // copy array data memcpy(pI16, THIS->subBuffer, THIS->subList[id].size); } break; */ case 32: switch ( (THIS->subList[id].type).Flags() ) { case BTDSTUnsigned: if ( (THIS->subList[id].count) == 1 ) // scalar pDD->put( (aitUint32) * (aitUint32 *)(THIS->subBuffer) ); else { //-------------------------- vector aitUint32 * pI32 = new aitUint32 [THIS->subList[id].count]; if ( !pI32 ) goto assert_allocation_error; // create destructor int32Destructor * pBTDFd = new int32Destructor; if ( !pBTDFd ) { delete [] pI32; goto assert_destructor_error; } // set the data pDD->putRef (pI32, pBTDFd); // copy array data memcpy(pI32, THIS->subBuffer, THIS->subList[id].size); } break; case BTDSTNone: if ( (THIS->subList[id].count) == 1 ) // scalar pDD->put( (aitInt32) * (aitInt32 *)(THIS->subBuffer) ); else { //-------------------------- vector aitInt32 * pI32 = new aitInt32 [THIS->subList[id].count]; if ( !pI32 ) goto assert_allocation_error; // create destructor int32Destructor * pBTDFd = new int32Destructor; if ( !pBTDFd ) { delete [] pI32; goto assert_destructor_error; } // set the data pDD->putRef (pI32, pBTDFd); // copy array data memcpy(pI32, THIS->subBuffer, THIS->subList[id].size); } break; //default: } break; //default: } break; case BTDTFloat: switch ( (THIS->subList[id].type).BitSize() ) { case 32: if ( (THIS->subList[id].count) == 1 ) // scalar pDD->put( (aitFloat32) * (aitFloat32 *)(THIS->subBuffer) ); else { //-------------------------- vector aitFloat32 * pF32 = new aitFloat32 [THIS->subList[id].count]; if ( !pF32 ) goto assert_allocation_error; // create destructor float32Destructor * pBTDFd = new float32Destructor; if ( !pBTDFd ) { delete [] pF32; goto assert_destructor_error; } // set the data pDD->putRef (pF32, pBTDFd); // copy array data memcpy(pF32, THIS->subBuffer, THIS->subList[id].size); } break; case 64: if ( (THIS->subList[id].count) == 1 ) // scalar pDD->put( (aitFloat64) * (aitFloat64 *)(THIS->subBuffer) ); else { //-------------------------- vector aitFloat64 * pF64 = new aitFloat64 [THIS->subList[id].count]; if ( !pF64 ) goto assert_allocation_error; // create destructor float64Destructor * pBTDFd = new float64Destructor; if ( !pBTDFd ) { delete [] pF64; goto assert_destructor_error; } // set the data pDD->putRef (pF64, pBTDFd); // copy array data memcpy(pF64, THIS->subBuffer, THIS->subList[id].size); } break; } break; default: CStaticAssertErrorCondition(FatalError, "EPICSHandler::callback_event: static method: %d This point cannot be reached", id ); continue; } goto update; assert_allocation_error: { BString bsbuf; CStaticAssertErrorCondition(Warning, "EPICSHandler::callback_event: static method: assert array allocation error %s for id %d", (THIS->subList[id].type).ConvertToString(bsbuf), id ); continue; } assert_destructor_error: { BString bsbuf; CStaticAssertErrorCondition(Warning, "EPICSHandler::callback_event: static method: assert array destructor error %s for id %d", (THIS->subList[id].type).ConvertToString(bsbuf), id ); continue; } update: //convert to EPICS timestamp (DDB time is usec) long nsec = current.nsec + ((timestamp % 1000000) *1000); long sec = nsec / 1000000000; if (current.nsec < nsec) sec++; sec += current.secPastEpoch; //add timestamp aitTimeStamp gddts ( sec , nsec); pDD->setTimeStamp ( & gddts ); // update value! //status = (pvList[id]->getPV())->update ( *pDD ); //if ( !(THIS->pvList[id]->getScanPeriod() > 00) ) (THIS->pvList[id]->getPV())->update ( *pDD, (THIS->pvList[id]->getScanPeriod() > 00) ? false : true, true ); } //----------------------------------------------------------------------- end while ( callback_event_finalize ) }//---------------------------------------------------------------------------- end MARTeCallback bool EPICSHandler::subscribe ( const char * nameIn, BasicTypeDescriptor typeIn, int countIn, unsigned &idOut) { if ( !setup_complete ) return False; // qui e gia stato aggiunto sia sulla lista che sull'hash table.. // ottimo per cerarlo by name e tornare l'id della lista.. (come ottenere l'id?) char pvAlias[256]; const char * const pNameFmtStr = "%.100s%.20s"; pvExistReturn _exist = (this->pCAS)->pvExistTest(nameIn); if ( _exist.getStatus() == pverDoesNotExistHere ) { AssertErrorCondition(FatalError, "EPICSHandler::subscribe: %s: Process Variable %s does NOT exist in the lookup table", Name(), nameIn ); return False; } idOut = -1; for (int i=0; igetName() ); if ( strcmp(nameIn, pvAlias) == 0) {//string match idOut = i; break; } } if (idOut == -1) // variable not found -> return false return False; // Process Variable already subscribed? -> error if ( subList[idOut].count != 0 ) { AssertErrorCondition(ParametersError, "EPICSHandler::subscribe: %s: Process Variable %s already subscribed", Name(), nameIn); return False; } // if typeIn has no type (i.e. BTDTNone) has no sense to subscribe on it or also to write it if ( typeIn == BTDTNone ) { AssertErrorCondition(ParametersError, "EPICSHandler::subscribe: %s: Process Variable %s type BTDTNone not valid!", Name(), nameIn); return False; } // TODO the PV creation must be done here or in another place? avrebbe senso crearla // solo se qualcuno ci si iscrive.. // altrimenti la registri cmoe eusbscirber e ne registri il tipo e quindi il numeo di byte da copiare //ricordiamoci che countin e il numero di elementi dell'array. // se facciamo che la PV viene creata solo se sottoscritta allora non serve avere una variabile // per sapere se e sottoscritta (una sottoscrizione per variabile) se \E8 creata allora ok // check if the buffer has enough space to hold the data container (+ header) int _size = (typeIn.ByteSize() * countIn); if ( buffer_size < (sizeof(cbHeader) + _size) ) { AssertErrorCondition(ParametersError, "EPICSHandler::subscribe: %s: Process Variable %s circular buffer is too small!", Name(), nameIn); return False; } // check if the process variable has at least the number of elements of the DDB array // otherwise signal an error (EPICS code checks for that, so we maintain the same) if ( countIn > pvList[idOut]->getElementCount() ) { AssertErrorCondition(ParametersError, "EPICSHandler::subscribe: %s: Process Variable %s holds %d elements, less then %d required", Name(), nameIn, pvList[idOut]->getElementCount(), countIn); return False; } subList[idOut].size = _size; subList[idOut].type = typeIn; subList[idOut].count = countIn; subList[idOut].statisticGet = 0; subList[idOut].statisticPut = 0; subListSize++; // if the buffer was allocated but is too small delete and allocate if (subSize < _size) { if ( subBuffer ) // consider when subBuffer is 0 (never allocated) delete subBuffer; subSize =_size; subBuffer = (char *) malloc (subSize); } return True; } //----------------------------------------------------------------------------- end subscribe bool EPICSHandler::unsubscribe (unsigned idIn) { if ( !(idIn < numberOfPVs) ) return False; // index out of bounds // TODO vedi sopra se decidiamo di aggiungerla al server solo quando facciamo subscribe allora // qui in unsubscribe lo dobbiamo togliere dal server (che sarebbe la migliore idea) // per il momento il thread \E8 always running! if ( subList[idIn].count <= 0 ) { AssertErrorCondition(ParametersError, "EPICSHandler::subscribe: %s: Process Variable id %d is not subscribed!", idIn, Name() ); return False; // no one is subscribed } subList[idIn].type = BTDTNone; subList[idIn].count = 0; subListSize--; return True; } //----------------------------------------------------------------------------- end unsubscribe // REALTIME - put -> head int EPICSHandler::put (unsigned idIn, void * bufferIn, unsigned timestamp) { if ( !(idIn < numberOfPVs) ) return -1; // index out of bounds if ( !bufferIn ) return -1; // buffer is not a valid pointer int _size = subList[idIn].size + sizeof(cbHeader); if ( buffer_free < _size ) return 0; // check if there is room for data // if there is no room the user can retry many times // copy data to circular buffer handling cross bounding cbHeader _tmpHeader = { CB_HEADER_MAGIC, idIn, timestamp}; long offset = (char *) buffer_head - buffer_ptr; int len = (buffer_size - offset); // write the header and data in the circular buffer if ( len < sizeof(cbHeader) ) { memcpy ((char *)buffer_head, (char *)&_tmpHeader, len ); // fill last bytes of the buffer buffer_head = (cbHeader *) buffer_ptr; // rewind the buffer memcpy ((char *)buffer_head, (((char *)&_tmpHeader) + len), sizeof(cbHeader) - len); buffer_head = (cbHeader *) (buffer_ptr + (sizeof(cbHeader) - len)); // now I can copy the data , for sure not sliced memcpy((char*)buffer_head, bufferIn, subList[idIn].size); buffer_head = (cbHeader *)((char *)buffer_head + subList[idIn].size); // always subList .size } else { memcpy ((char *)buffer_head, (char *)&_tmpHeader, sizeof(cbHeader) ); // fill last bytes of the buffer buffer_head = (cbHeader *) (((char *)buffer_head) + sizeof(cbHeader)); if ( (len - sizeof(cbHeader)) == 0) { buffer_head = (cbHeader *)buffer_ptr; // rewind the buffer len = subList[idIn].size; } else len -= sizeof(cbHeader); //check if buffer must be sliced if ( len < subList[idIn].size ) { memcpy ((char *)buffer_head, bufferIn, len ); // fill last bytes of the buffer buffer_head = (cbHeader *)buffer_ptr; // rewind the buffer memcpy ((char *)buffer_head, ((char *)bufferIn + len), (subList[idIn].size - len) ); buffer_head = (cbHeader *)(buffer_ptr + (subList[idIn].size - len) ); } else { memcpy((char*)buffer_head, bufferIn, subList[idIn].size); buffer_head = (cbHeader *)((char *) buffer_head + subList[idIn].size); } } //buffer_free -= _size; Atomic::Sub(&buffer_free, _size); subList[idIn].statisticPut++; //synchronizza EPICS !! semaphore del get! (next version) #ifdef SYNCHRONIZING sem.Post(); #endif return subList[idIn].size; } //----------------------------------------------------------------------------- end put /* * you can use get with sizeIn 0 to query about the status of the first buffer * in the sense you want know the id to provide the correct buffer size * * temporaneamente facciamo che: * idOut viene scritto dalla routine "get" bufferIn dev'essere valido * sizeIn != 0 * l'idea e che esiste un'ulteriore routine che ti dice la size e l'id per il momento * e quindi passi il giusto buffer (con la giusta size) altrimenti * allochi un max buffer in subscribe.. or something similar.. cmq relato agli iscritti.. * * return the amount of bytes written - TODO make the same in method put * * if size is less then the buffer just fill the buffer and move to the next buffer */ // REALTIME - get <- get int EPICSHandler::get (unsigned &idOut, void * bufferIn, unsigned sizeIn, unsigned ×tamp) { if ( !bufferIn ) // buffer cannot be zero return -1; if ( !sizeIn ) // size cannot be zero return -1; // if there is no data in the buffer return 0 if ( buffer_free == buffer_size ) #ifdef SYNCHRONIZING sem.Wait(); //ok, but we can also check for timeout here.. if ( buffer_free == buffer_size ) return 0; #else { idOut = -1; return 0; } #endif cbHeader _tmpHeader = {0, 0, 0}; long offset = (char *) buffer_tail - buffer_ptr; int len = (buffer_size - offset); int dataLen = 0; // fragmented header or payload around the circular buffer if (len < sizeof(cbHeader)) { memcpy ( (char *)&_tmpHeader, (char *)buffer_tail, len ); // fill last bytes of the buffer buffer_tail = (cbHeader *)buffer_ptr; // rewind the buffer memcpy ( (((char *)&_tmpHeader) + len), (char *)buffer_tail , (sizeof(cbHeader) - len) ); buffer_tail = (cbHeader *)((char*)buffer_tail + (sizeof(cbHeader) - len) ); //check data validity.. i.e. check MAGIC and id validity if ( (_tmpHeader.magic != CB_HEADER_MAGIC) ) return -1; // corrupted buffer do nothing (nor realign or any thing else) if ( !(_tmpHeader.id < numberOfPVs) ) return -1; // corrupted id number cannot do nothing because cannot infere size idOut = _tmpHeader.id; timestamp = _tmpHeader.timestamp; //check how much data can be copied dataLen = (sizeIn < subList[idOut].size) ? sizeIn : subList[idOut].size; // now I can copy the data , for sure not sliced memcpy((char*)bufferIn, buffer_tail, dataLen); buffer_tail = (cbHeader *) ((char *)buffer_tail + subList[idOut].size); } else { // here at least the header is all together memcpy ((char *)&_tmpHeader, (char *)buffer_tail, sizeof(cbHeader) ); // fill last bytes of the buffer // check data validity.. MAGIC and id validity if ( (_tmpHeader.magic != CB_HEADER_MAGIC) ) return -1; // corrupted buffer do nothing (nor realign or any thing else) if ( !(_tmpHeader.id < numberOfPVs) ) return -1; // corrupted id number cannot do nothing because cannot infere size buffer_tail = (cbHeader *) ((char *)buffer_tail + sizeof(cbHeader)); idOut = _tmpHeader.id; timestamp = _tmpHeader.timestamp; //check how much data can be copied dataLen = (sizeIn < subList[idOut].size) ? sizeIn : subList[idOut].size; len -= sizeof(cbHeader); if ( len == 0) { // data is at the beginning of the data buffer memcpy(bufferIn, (char*)buffer_ptr, dataLen); buffer_tail = (cbHeader *)(buffer_ptr + subList[idOut].size); } else if ( len >= subList[idOut].size ) { // the data is contiguous memcpy(bufferIn, (char*)buffer_tail, dataLen); buffer_tail = (cbHeader *)((char *)buffer_tail + subList[idOut].size); } else { // segmented data memcpy (bufferIn, (char *)buffer_tail, (dataLen > len) ? len : dataLen ); // fill last bytes of the buffer buffer_tail = (cbHeader *)buffer_ptr; // rewind the buffer memcpy (((char*)bufferIn + len), (char *)buffer_tail, (dataLen > len) ? (dataLen - len) : 0); buffer_tail = (cbHeader *)(buffer_ptr + (subList[idOut].size -len)); } } //buffer_free += (sizeof(cbHeader) + subList[_tmpHeader.id].size); // one instruction beacause we hope to be atomic int _size = (sizeof(cbHeader) + subList[_tmpHeader.id].size); // one instruction beacause we hope to be atomic /* int32 * atomic_access = &buffer_free; int32 c, d; c = *atomic_access; d = Atomic::Exchange(atomic_access, (c+_size)); if (c != d) { // something goes wrong d = Atomic::Exchange(atomic_access, (d+_size)); c = (c+_size); if (c != d) { // something goes wrong again AssertErrorCondition(FatalError, "EPICSHandler::get: Circular Buffer Fatal error TWICE ERROR"); } else AssertErrorCondition(FatalError, "EPICSHandler::get: Circular Buffer Fatal error ONE ERROR"); } */ Atomic::Add(&buffer_free, _size); subList[idOut].statisticGet++; // comment non e proprio necessario, i.e. buffer_head/buffer_tail dovrebbero venir modificate in // nello stesso istante di buffer_free in modo tale che questo check funzioni /* char* a = (char*) buffer_head; int b = buffer_free; if ( ((((char*)buffer_tail -buffer_ptr) + (buffer_size -b)) % buffer_size) != (a -buffer_ptr) ) AssertErrorCondition(FatalError, "EPICSHandler::get: Circular Buffer Fatal error _ptr %p, _head %p, _tail %p, size %d, free%d", buffer_ptr, a, buffer_tail, buffer_size, b); */ // TODO invalidate cbHeader buffer //free the space and mark magic as not valid 0x0000 return dataLen; } //----------------------------------------------------------------------------- end get // TODO why not writing a buffer walker? :-) const char* EPICSHandler::css = "table.bltable {" "margin: 1em 1em 1em 2em;" "background: whitesmoke;" "border-collapse: collapse;" "}" "table.bltable th, table.bltable td {" "border: 1px silver solid;" "padding: 0.2em;" "}" "table.bltable th {" "background: gainsboro;" "text-align: left;" "}" "table.bltable caption {" "margin-left: inherit;" "margin-right: inherit;" "}"; // ---------------------------------------------------------------------------- #define TABLE_NEWROW hStream.Printf("\n") #define TABLE_ENDROW hStream.Printf("\n") bool EPICSHandler::ProcessHttpMessage( HttpStream &hStream ) { hStream.SSPrintf("OutputHttpOtions.Content-Type","text/html"); hStream.keepAlive = False; hStream.Printf("%s", Name()); hStream.Printf( "\n" ); //the following version numeration comes from //epics/base-3-14-11/src/cas/generic/caServerI.cc //EPICS/base-3-14-11/configure/CONFIG_BASE_VERSION //epics/base-3-14-11/src/misc/epicsRelease.c hStream.Printf("

EPICSLib Version

\n"); hStream.Printf("Channel Access V%s\n", CA_VERSION_STRING ( CA_MINOR_PROTOCOL_REVISION ) ); hStream.Printf("
\n"); // hStream.Printf("revision @(#) %s\n", EPICS_VERSION_STRING ); hStream.Printf("%s\n", epicsReleaseVersion ); hStream.Printf("
\n"); hStream.Printf("

EPICSLib Parameters

\n"); hStream.Printf("\n"); TABLE_NEWROW; hStream.Printf("\n", pvPrefix.Buffer() ); TABLE_ENDROW; TABLE_NEWROW; hStream.Printf("\n", (scanOn==true) ? "true" : "false"); TABLE_ENDROW; TABLE_NEWROW; hStream.Printf("\n", (asyncScan==true) ? "true" : "false"); TABLE_ENDROW; TABLE_NEWROW; hStream.Printf("\n", asyncDelay); TABLE_ENDROW; TABLE_NEWROW; hStream.Printf("\n", maxSimultAsyncIO); TABLE_ENDROW; TABLE_NEWROW; hStream.Printf("\n", numberOfPVs); TABLE_ENDROW; hStream.Printf("
PV prefix %s scan on %s async scan on %s async delay %lf max simultaneous async %d PV number %d
\n"); hStream.Printf("
\n"); // per process variable description hStream.Printf("

Detailed Process Variables description

\n"); hStream.Printf("\n"); // table header TABLE_NEWROW; hStream.Printf(" " " " "\n"); hStream.Printf("" " " ""); TABLE_ENDROW; for (int pv=0; pvgetName()); // output the HTTP data TABLE_NEWROW; hStream.Printf(" " // pvAlias, pvName, pvUnits, pvType, pvSync " " //hopr, lopr, hihi, high, low, lolo "\n", pvAlias, pvList[pv]->getName(), pvList[pv]->getUnits(), aitEnum_strings[ (unsigned) pvList[pv]->getType() ], excasIoType_strings[ (unsigned) pvList[pv]->getIOType() ], pvList[pv]->getHopr(), pvList[pv]->getLopr(), pvList[pv]->getHihi(), pvList[pv]->getHigh(), pvList[pv]->getLow(), pvList[pv]->getLolo(), pvList[pv]->getElementCount() ); int index = 0; do { if ( menuScan_values[index] == pvList[pv]->getScanPeriod() ) break; index++; } while (menuScan_values[index] != 0); if (menuScan_values[index] == 0) index = 0; hStream.Printf ("\n", menuScan_strings[index]); /* TABLE_ENDROW; // additional information regarding the same PV TABLE_NEWROW;*/ hStream.Printf("" " " // - - hhsv hsv lsv llsv "\n", // acks ackt pvList[pv]->prec, menuAlarmSevr_strings[ pvList[pv]->hhsv ], menuAlarmSevr_strings[ pvList[pv]->hsv ], menuAlarmSevr_strings[ pvList[pv]->lsv ], menuAlarmSevr_strings[ pvList[pv]->llsv ], menuAlarmSevr_strings[ pvList[pv]->acks ], menuYesNo_strings [pvList[pv]->ackt ] ); TABLE_ENDROW; } hStream.Printf("
PV NAME EGU TYPE SYNCHOPR LOPR HIHI HIGH LOW LOLOLEN SCANPRECHHSV HSV LSV LLSVACKS ACKT%s %s %s %s %s%lf %lf %lf %lf %lf %lf%d%s%d %s %s %s %s%s %s
\n"); hStream.Printf("
\n"); // per process variable description hStream.Printf("

Subscriber table (num of subscriber %d)

\n", subListSize); hStream.Printf("\n"); // table header TABLE_NEWROW; hStream.Printf(" " ""); TABLE_ENDROW; for (int si=0; si %d ", si, subList[si].count, subList[si].size, (subList[si].size == 0) ? "not assigned" : (subList[si].type).ConvertToString(bsbuf), subList[si].statisticGet, subList[si].statisticPut); TABLE_ENDROW; } hStream.Printf("
index elements element size DDB typestatistic Get statistic Put%d %d %s %d %d
\n"); hStream.Printf("
\n"); hStream.Printf(""); hStream.WriteReplyHeader(True); return True; } //----------------------------------------------------------------------------- end ProcessHttpMessage OBJECTLOADREGISTER(EPICSHandler,"$Id: EPICSHandler.cpp,v 1.1 2011/06/17 14:19:12 abarb Exp $")