Area Detector Drivers Towards A Pattern Jon Thompson.
-
Upload
gabriella-barton -
Category
Documents
-
view
219 -
download
0
Transcript of Area Detector Drivers Towards A Pattern Jon Thompson.
Area Detector DriversTowards A Pattern
Jon Thompson
Contents
1. Original Practice2. Exception Handling
1. Mutexes2. NDArrays
3. ASYN Parameters4. Stream I/O5. Decomposition6. State Machines7. Vendor Library Interfacing
Typical Requirements
• Vendor SDK library (often a Windows DLL).• Ability to switch to a simulation library for
offline testing that is as complete as possible.• Multithreaded calls to the SDK often not
supported or just not mentioned.• Logging of SDK calls for debugging/blame
purposes.
Original Pattern
• It’s straightforward and simple to understand!
ADDriver
DllApi
Pco
Things I Would Like to Improve
• Fault propagation and reporting.• One class does everything and gets too large.• Proliferation of flags that control operation.• Keeping track of the Mutex Lock.• Be more OO and C++.• Conformance to the area detector defined
API.
Exceptions• To be able to throw exceptions, functions need to be
made tolerant.• C++ guarantees the execution of destructors as the stack
is unwound during an exception throw.• It should not be necessary to catch exceptions so that
clean up can be performed before re-throwing them. Note lack of ‘finally’ clause in C++.
• Use objects to represent access to resources that need to be cleaned up and take advantage of the scoping rules.
• We need smart pointer variants for mutex locks, NDArray pointers, etc.
The Port Driver Mutex
AnADDriver::someFunctionThatUsesTheLock(){ TakeLock takeLock(this); // Code that does stuff with the lock on aFunctionThatNeedsTheLock(takeLock);}
AnADDriver::aFunctionThatNeedsTheLock(TakeLock& takeLock){ // Do stuff with the lock { FreeLock freeLock(takeLock); // Do stuff with the lock freed } // Do more stuff with the lock taken}
• Example use:
asynPortDriverTakeLockFreeLock
TakeLock classclass TakeLock {friend class FreeLock;public: TakeLock(asynPortDriver* driver, bool alreadyTaken=false); TakeLock(FreeLock& freeLock); ~TakeLock();private: TakeLock(); TakeLock(const TakeLock& other); TakeLock& operator=(const TakeLock& other); asynPortDriver* driver; bool initiallyTaken;};
• Functions that require the lock to be taken should have a TakeLock object in their signature, even if they don’t use it internally.
• Copy constructor and assignment operator declared private. This forces them to be passed around by reference.
• Destructor automatically calls callParamCallbacks?
FreeLock class
• Functions that must be called without the lock taken could have a FreeLock object in the signature.
• Copy constructor and assignment operator declared private. This forces them to be passed around by reference.
class FreeLock {friend class TakeLock;public: FreeLock(TakeLock& takeLock); ~FreeLock();private: FreeLock(); FreeLock(const FreeLock& other); FreeLock& operator=(const FreeLock& other); asynPortDriver* driver;};
NDArray Pointers
• Another smart pointer type class must be used to hold NDArray pointers.
• Automatically calls release in the destructor.• Every time the array is copied, reserve is called.
class NdArrayRef {public:
NdArrayRef();NdArrayRef(NDArray* array);NdArrayRef(const NdArrayRef& other);virtual ~NdArrayRef();NdArrayRef& operator=(const NdArrayRef& other);operator NDArray*() const;
private:NDArray* array;
};
Asyn Parameters• We currently write this kind of code:
{ TakeLock takeLock(this); param2 = param2 + param1;}
{ lock(); int p1, p2; getIntegerParam(handle1, &p1); getIntegerParam(handle2, &p2); setIntegerParam(handle2, p2+p1); callParamCallbacks(); unlock();}
• We could write this (assuming paramx are objects):
• Or maybe this (which makes the locking requirement explicit):{ TakeLock takeLock(this); param2.set(takeLock, param2.get(takeLock) + param1.get(takeLock));}
Asyn Parameters• A change notification mechanism is required. We could use
templated functors to call a member function:
class MyDriver: public ADDriver{protected: IntegerParam param1; void onP1Change();};
MyDriver::MyDriver() : IntegerParam param1(this, “PARAM1”, new Notify<MyDriver>(this, &MyDriver::onP1Change)){}
void MyDriver::onP1Change(){ // Do what we need to do when P1 changes}
Asyn Parameters• Need an abstract class so that the system can manipulate the functors
without knowing the type of the target.
class AbstractNotify{public: AbstractNotify() {} virtual void operator()() = 0;};
template<class Target>class Notify: public AbstractNotify{public: Notify(Target* target, void (Target::*fn)()) : target(target), fn(fn) {} virtual void operator()() {(target->*fn)();}private: Target* target; void (Target::*fn)();};
Use stream I/O?• Wrap asynTrace inside an iostream object.• Allows use of << operators to output trace information.• This does mean that the strings are generated BEFORE the decision
whether to output or not is made.• Uses a mutex to prevent traces from different threads getting mixed up.• No comment is made on the desirability of the << operator syntax.
{ TraceStream tracer(getAsynUser(), 0x0100); tracer << “Hello world: ”; for(int i=0; i<5; i++) { tracer << i << “ “; } tracer << std::endl;}
ostream
TraceBuf
streambuf
TraceStream
-mutex: epicsMutexasynUser
Main Class Too Big• For many cameras, the main ADDriver derived class gets very
large.• Indicative that the decomposition of the problem could be
better.• First attempt not too successful as the three resulting classes
were too closely coupled.ADDriver ADDriver
ControllerGrabber ADComponent
ADDriver
ADDriverExPixium
Pco2Component
Pco2Pco
* *
State Machines• Do you end up with a collection of booleans controlling the operation of your
software?• Instead, use a state variable and draw the state transition diagram.• Let’s define a class to represent state variables.
• The version above implements a thread in which events can be processed.• The doTransition virtual function is where the state machine implementation sits.
ADDriver
User
+doTransition()
epicsThreadRunable
Pco
+doTransition()
«diagrams»
StateDiagram
StateMachine
+post(event)
epicsMessageQueue
requestQueue
stateMachine
State Machines• Attempting to write code
that obviously represents a state diagram
int MyDriver::doTransition(StateMachine* sm, int state, int event){ switch(state) { case STATE1: if(event == EVENT1) { doStuff(); state = STATE2; } break; case STATE2: if(event == EVENT1) { if(withCond() == 0) { state = STATE1; } else { state = STATE2; } } break; // etc... } return state;}
STATE2STATE1EVENT1()
/ result = withCond()
EVENT1()/ doStuff()
[`result == 1`]
[`result == 0`]
State Machines• State transitions are all written in one place.• It is a little clumsy though.• What would we like to write?class Pco{public: Pco(); enum state_t {STATE1, STATE2}; enum event_t {EVENT1}; int doStuff(); int withCond(); StateMachine sm;};
Pco::Pco(){ // curState, event, action function, next States sm.transition(STATE1, EVENT1, new Act<Sm>(this, &Sm::doStuff), STATE2); sm.transition(STATE2, EVENT1, new Act<Sm>(this, &Sm::withCond), STATE1, STATE2); // etc...}
STATE2STATE1EVENT1()
/ result = withCond()
EVENT1()/ doStuff()
[`result == 1`]
[`result == 0`]
Vendor Library Interface
• Abstract base class to control access to the device.• Place to implement logging and convert return codes into exceptions.• Derived classes for vendor library and simulation.• Can isolate any OS specific things inside the vendor library derived class.• Startup script creates the appropriate object.• The intention is not to abstract the API.• It can be a lot of typing though…
ADDriver
DllApi
Pco
Conclusions
• An extended interface library to the existing area detector is emerging.
• Along with a usage pattern.• Some things may be appropriate for integration
into a future area detector.• Some things are just my own personal hobby
horses.• C++11 could improve the notation in some places.• Any questions?
File Operations Mixer ClassAll file handling related parts of asynNDArrayDriver are moved out into a separate class. The constructor of this class must take an asynNDArrayDriver pointer so that it can create the asyn parameters.Any class that wants to make use of file operations includes the file operations class in its base class list.The slightly awkward bit is that this class must call the file operations class writeOctet function from its own override of writeOctet (parameter objects with a notify system would eliminate this).That’s it. All features of the file operations class are now available to the user class in just the same way as they are now.
fileOperations
-NDFilePath
-NDFilePathExists
-NDFileName
-NDFileNumber
-NDFileTemplate
-NDAutoIncrement
-NDFullFileName
-NDFileFormat
-NDAutoSave
-NDWriteFile
-NDReadFile
-NDFileWriteMode
-NDFileWriteStatus
-NDFileWriteMessage
-NDFileNumCapture
-NDFileNumCaptured
-NDFileDeleteDriverFile
+checkPath()
+createFileName(maxChars, fullFileName)
+createFileName(maxChars, filePath, fileName)
+writeOctet()
ADDriver NDPluginDriver
asynNDArrayDriver
+writeOctet()
NDPluginFile
+writeOctet()
pilatusDetector
+writeOctet()