Extending the System |
TRoomCamacController class is defined by kinoko/src/kernel/lib-common/room/RoomCamacAccess.hh.
Almost all methods are pure virtual functions and all of these must be implemented by child classes. Methods that are not virtual will be overridden if needed.
class TRoomCamacController: public TRoomController { public: TRoomCamacController(void); virtual ~TRoomCamacController(); virtual TRoomCamacController* Clone(void) = 0; public: virtual void Open(void) throw(THardwareException) = 0; virtual void Close(void); virtual int Transact(int StationNumber, int Function, int Address, int &Data, int &Q, int &X) throw(THardwareException) = 0; virtual int Initialize(void) throw(THardwareException) = 0; virtual int Clear(void) throw(THardwareException) = 0; virtual int SetInhibition(void) throw(THardwareException) = 0; virtual int ReleaseInhibition(void) throw(THardwareException) = 0; virtual int EnableInterruptNotification(void) throw(THardwareException) = 0; virtual int DisableInterruptNotification(void) throw(THardwareException) = 0; virtual bool IsSignalOnInterruptAvailable(void); virtual int ReadLam(void) throw(THardwareException) = 0; virtual int WaitLam(int TimeOut_sec) throw(THardwareException) = 0; };
The following list explains each method.
- TRoomCamacController* Clone(void)
- Creates an instance of itself and returns the pointer to it.
- void Open(void) throw(THardwareException)
- Open. Called only once before any other method is called. Normally used to open drivers.
- void Close(void)
- Close. Called only once at the end of all processes. Normally used to close drivers.
- int Transact(int StationNumber, int Function, int Address, int &Data, int &Q, int &X) throw(THardwareException)
- Executes CAMAC action once according to the argument passed. If it is a READ action, returns the data value in the argument "Data." Q and X responses are returned in the cooresponding arguments Q and X.
- int Initialize(void) throw(THardwareException)
- Issues Z (Initialize).
- int Clear(void) throw(THardwareException)
- Issues C (Clear).
- int SetInhibition(void) throw(THardwareException)
- Sets I (Inhibit).
- int ReleaseInhibition(void) throw(THardwareException)
- Releases I (Inhibit).
- int EnableInterruptNotification(void) throw(THardwareException)
- Enables interrupts to controller devices.
- int DisableInterruptNotification(void) throw(THardwareException)
- Disables interrupts to controller devices.
- bool IsSignalOnInterruptAvailable(void)
- Returns "true" if the device driver has a function that can be interrupted to issues signals.
- int ReadLam(void) throw(THardwareException)
- Returns LAM pattern as a bit sequence. If the driver does not have this function, exceptions can be thrown.
- int WaitLam(int TimeOut_sec) throw(THardwareException)
- Waits for LAM with TimeOut_sec indicated in the argument as an upper limit of waiting time. If there is a LAM signal, the value of the LAM signal pattern in bit array is returned. If timed out, returns a negative value.
Controller drivers are made with file names "controller-vender name_device name.hh/cc". Place this in the ROOM directory (kinoko/src/kernel/lib-common/room) and edit Makefile.in and Makefile so it can be compiled.
Add the following bolded line before all: in Makefile.in/Makefile (in the following example, the vender name is CamacInternational, and the device name is Camac2000).
# add the following two lines DEVOBJS += controller-CamacInternational_Camac2000.o HAS_DEVICE=yes all: $(ARC) $(DEVOBJS) $(BINS) @if [ $(HAS_DEVICE) = yes ]; then \ cd samples; $(MAKE); \ fi
To register the controller driver object we just made to the system, TRoomCamacControllerCreator defined in RoomDeviceFactory.hh is used. This is a little bit tricky, but create the static Creator object inside the .cc file, and pass the controller driver name and the controller driver instance as the contructor argument. By doing this, the controller driver instance is automatically created when the driver code is loaded and will be registered to the system.
/* controller-CamacInternational_Camac2000.cc */ #include "RoomDeviceFactory.hh" #include "controller-CamacInternational_Camac2000.hh" static TRoomCamacControllerCreator Creator( "CamacInternational_Camac2000", new TCamacController_CamacInternational_Camac2000() ); //...
Since the VME module has many parameters related to accessing such as base address and access mode, it is common to open a device driver for each module and set access modes and map memory. Therefore VME controller drivers will be opened repeatedly for each modules connected.
By system call "open()", the VME controller driver in Kinoko will create a VmeAccessProperty object and return it. Information such as device descriptor of the driver and access mode parameters are recorded in the VmeAccessProperty object so that VME access after "open()" will be done with VmeAccessProperty called. In general, the information inside VmeAccessProperty is used only by the VME controller driver.
The following shows the structure of VmeAccessProperty.
The meaning of each field is the follwoing.
class TRoomVmeAccessProperty { public: off_t VmeAddress; // VME base address of the module TRoomVmeTransferMode TransferMode; // transfer mode (address width, data word length) int DeviceDescriptor; // device descriptor returned by calling open() caddr_t MappedAddress; // address mapped by mmap() size_t MapSize; // map size passed to mmap() off_t MapOffset; // offset of the address passed to mmap() from the base address int DmaDeviceDescriptor; // device descriptor when open() is called for DMA transfer int InterruptNumber; // interrupt IRQ (or interrupt number) int InterruptVector; // interrupt vector int InterruptSignalId; // signal ID of the signal issued by interrupt void* DeviceSpecificData; // data space the controller driver can use freely };
The creating of a VME controller driver is done similarly to the CAMAC case by inheriting the abstract class TRoomVmeController and overriding necessary methods. The TRoomVmeController class is defined in RoomVmeAccess.hh.
- off_t VmeAddress
- Module's VME offset address. Normally set by jumpers of the module.
- TRoomVmeTransferMode TransferMode
- enum values that show the address width and data word length of the transfer mode. The following values are defined.
enum TRoomVmeTransferMode { VmeTransferMode_A16D16, VmeTransferMode_A16D32, VmeTransferMode_A24D16, VmeTransferMode_A24D32, VmeTransferMode_A32D16, VmeTransferMode_A32D32, VmeTransferMode_Unknown };
- int DeviceDescriptor
- device descriptor returned by system call open().
- caddr_t MappedAddress
- address of the process in the address space set by system call mmap().
- size_t MapSize
- map size passed to system call mmap().
- off_t MapOffset
- offset from the base address (VmeAddress) of the address in VME address space passed to system call mmap().
- int DmaDeviceDescriptor
- device descriptor of another device entry opened for DMA transfer.
- int InterruptNumber
- interrupt number when interrupts are used. Generally the IRQ number. For certain devices, it refers to different numbers such as entry number of an interrupt table.
- int InterruptVector
- interrupt vector.
- int InterruptSignalId
- signal ID issued if the interrupt issues signal IDs. A signal ID is the value defined in <signal.h>.
- void* DeviceSpecificData
- free space for the controller driver.
The following list explains each method. Unlike CAMAC, some common methods such as Open() / Close() / Read() / Write( ) / Seek() / Map() / Unmap() are already implemented and do not need to be overridden in child classes unless there is a special need to (other virtual functions need to be implemented in child classes).
class TRoomVmeController: public TRoomController { public: TRoomVmeController(void); virtual ~TRoomVmeController(); virtual TRoomVmeController* Clone(void) = 0; public: virtual TRoomVmeAccessProperty* Open(TRoomVmeTransferMode TransferMode) throw(THardwareException); virtual void Close(TRoomVmeAccessProperty* Property); virtual long Read(TRoomVmeAccessProperty* Property, void* Buffer, int Size) throw(THardwareException); virtual long Write(TRoomVmeAccessProperty* Property, const void* Buffer, int Size) throw(THardwareException); virtual off_t Seek(TRoomVmeAccessProperty* Property, off_t OffsetAddress) throw(THardwareException); virtual caddr_t Map(TRoomVmeAccessProperty* Property, off_t VmeAddress, size_t MapSize, off_t MapOffset) throw(THardwareException); virtual void Unmap(TRoomVmeAccessProperty* Property) throw(THardwareException); virtual void RegisterInterruptNotification(TRoomVmeAccessProperty* Property, int SignalId = 0) throw(THardwareException) = 0; virtual void UnregisterInterruptNotification(TRoomVmeAccessProperty* Property) throw(THardwareException) = 0; virtual void EnableInterruptNotification(TRoomVmeAccessProperty* Property) throw(THardwareException) = 0; virtual void DisableInterruptNotification(TRoomVmeAccessProperty* Property) throw(THardwareException) = 0; virtual bool WaitForInterruptNotification(TRoomVmeAccessProperty* Property, unsigned TimeOut_sec) throw(THardwareException) = 0; virtual long DmaRead(TRoomVmeAccessProperty* Property, void* Buffer, int Size) throw(THardwareException); virtual long DmaWrite(TRoomVmeAccessProperty* Property, const void* Buffer, int Size) throw(THardwareException); virtual off_t DmaSeek(TRoomVmeAccessProperty* Property, off_t OffsetAddress) throw(THardwareException); public: virtual bool IsSignalOnInterruptAvailable(void); protected: virtual const char* DeviceFileName(TRoomVmeTransferMode TransferMode) = 0; virtual const char* DmaDeviceFileName(TRoomVmeTransferMode TransferMode) = 0; virtual void DmaOpen(TRoomVmeAccessProperty* Property) throw(THardwareException); virtual void DmaClose(TRoomVmeAccessProperty* Property); };
Like the CAMAC controller driver, VME controller driver files need to be named such as "controller-vender name_device name.hh/cc". Place this in the ROOM directory (kinoko/src/kernel/lib-common/room) and edit Makefile.in and Makefile so it can be compiled.
- TRoomVmeController* Clone(void)
- Creates an instance of itself and returns the pointer to it.
- const char* DeviceFileName(TRoomVmeTransferMode TransferMode)
- returns the UNIX device driver entry name (file name of /dev) that corresponds to the transfer mode specified in the argument.
- const char* DmaDeviceFileName(TRoomVmeTransferMode TransferMode)
- returns the UNIX device driver entry name (file name of /dev) that corresponds to the transfer mode specified in the argument when transfering with DMA transfer. Even if the device driver has the same entry with PIO and DMA, it returns that entry name. If DMA is not supported, then PIO entry name is returned.
- TRoomVmeAccessProperty* Open(TRoomVmeTransferMode TransferMode) throw(THardwareException)
- opens the detice driver for PIO mode and creats an AccessProperty object and returns it. Normally there is no need to override.
- void Close(TRoomVmeAccessProperty* Property)
- releases resources and closes the device driver. Normally there is no need to override.
- long Read(TRoomVmeAccessProperty* Property, void* Buffer, int Size) throw(THardwareException)
- readout data from the device. Normally there is no need to override.
- long Write(TRoomVmeAccessProperty* Property, const void* Buffer, int Size) throw(THardwareException)
- write in data into the device. Normally there is no need to override.
- off_t Seek(TRoomVmeAccessProperty* Property, off_t OffsetAddress) throw(THardwareException)
- move the file pointer for Read()/Write()(lseek()). Normally there is no need to override.
- caddr_t Map(TRoomVmeAccessProperty* Property, off_t VmeAddress, size_t MapSize, off_t MapOffset) throw(THardwareException)
- maps (mmap()) the VME address space indicated in the argument to the process address space and returns the address in process address space. Normally there is no need to override.
- void Unmap(TRoomVmeAccessProperty* Property) throw(THardwareException)
- unmaps the area mapped by Map(). Normally there is no need to override.
- void RegisterInterruptNotification(TRoomVmeAccessProperty* Property, int SignalId = 0) throw(THardwareException)
- registers interrupts and prepares for receiving them. If the argument SignalId is not 0, signal is issued on interrupt.
- void UnregisterInterruptNotification(TRoomVmeAccessProperty* Property) throw(THardwareException)
- unregisters a registered interrupt and releace resource.
- void EnableInterruptNotification(TRoomVmeAccessProperty* Property) throw(THardwareException)
- enables interrupts.
- void DisableInterruptNotification(TRoomVmeAccessProperty* Property) throw(THardwareException)
- disables interrupts.
- bool WaitForInterruptNotification(TRoomVmeAccessProperty* Property, unsigned TimeOut_sec) throw(THardwareException)
- waits for an interrupt with the time indicated in argument TimeOut_sec as the maximum waiting time. If there is an interrupt, return ture. If it timed out, return false.
- void DmaOpen(TRoomVmeAccessProperty* Property) throw(THardwareException)
- opens device driver for DMA mode. Normally there is no need to overload.
- void DmaClose(TRoomVmeAccessProperty* Property)
- closes the device driver opened for DMA. Normally there is no need to overload.
- long DmaRead(TRoomVmeAccessProperty* Property, void* Buffer, int Size) throw(THardwareException)
- reads out data from the device in DMA mode. Normally there is no need to overload.
- long DmaWrite(TRoomVmeAccessProperty* Property, const void* Buffer, int Size) throw(THardwareException)
- writes data in to the device in DMA mode. Normally there is no need to overload.
- off_t DmaSeek(TRoomVmeAccessProperty* Property, off_t OffsetAddress) throw(THardwareException)
- moves file pointers for DmaRead()/DmaWrite() (lseek()). Normally there is no need to overload.
- bool IsSignalOnInterruptAvailable(void)
- returns true if the device driver has a function that can issue a signal on interrupt.
# add the following two lines DEVOBJS += controller-VmeInternational_Vme2000.o HAS_DEVICE=yes all: $(ARC) $(DEVOBJS) $(BINS) @if [ $(HAS_DEVICE) = yes ]; then \ cd samples; $(MAKE); \ fi
Again, use TRoomVmeControllerCreator to register the controller driver object to the system, just like the CAMAC case.
/* controller-VmeInternational_Vme2000.cc */ #include "RoomDeviceFactory.hh" #include "controller-VmeInternational_Vme2000.hh" static TRoomVmeControllerCreator Creator( "VmeInternational_Vme2000", new TVmeController_VmeInternational_Vme2000() ); //...
Classes for VME and CAMAC modules, TRoomVmeModule and TRoomCamacModule, are generated from the TRoomModule class. These posess pointers from VME and CAMAC controller drivers, and through these classes basic controls to access VME and CAMAC are provided. Module drivers that correspond to a specific module implement interfaces using these basic controls.
The class TRoomModule is defined in src/kernel/lib-common/room/RoomModule.hh. The following is an excerpt from the definition.![]()
The meaning of each method is explained below. Be aware that only the ones necessary need to be overridden in the module driver. If a control that is not overridden is called, a default behavier is executed if there is one and if not, THardwareException is thrown.
class TRoomModule: public TRoomServiceRequester { protected: TRoomModule(const std::string& ModuleType, const std::string& ModelName); public: virtual ~TRoomModule(); // control related // virtual bool Probe(void) throw(THardwareException); virtual int Initialize(int InitialState = 0) throw(THardwareException); virtual int Finalize(int FinalState = 0) throw(THardwareException); virtual int Enable(int Address = -1) throw(THardwareException); virtual int Disable(int Address = -1) throw(THardwareException); virtual int IsEnabled(int Address = -1) throw(THardwareException); virtual int IsBusy(int Address = -1) throw(THardwareException); virtual int Clear(int Address = -1) throw(THardwareException); // data input/output related // /* signal input/output */ virtual int Read(int Address, int &Data) throw(THardwareException); virtual int Write(int Address, int Data) throw(THardwareException); /* sequential input/output */ virtual int SequentialRead(int Address, int Data[], int MaxSize) throw(THardwareException); virtual int SequentialWrite(int Address, int Data[], int Size) throw(THardwareException); virtual int NextNumberOfDataElements(int Address = -1) throw(THardwareException); /* block input/output */ virtual int BlockRead(int Address, void *Data, int MaxSize) throw(THardwareException); virtual int BlockWrite(int Address, const void *Data, int Size) throw(THardwareException); virtual int NextDataBlockSize(int Address = -1) throw(THardwareException); // interrupt processing related // virtual int EnableInterrupt(int SignalId = 0) throw(THardwareException); virtual int DisableInterrupt(void) throw(THardwareException); virtual int ClearInterrupt(void) throw(THardwareException); // introspection related // virtual int NumberOfChannels(void) throw(THardwareException); virtual int AddressBitLength(void); virtual int DataBitLength(void); // others // virtual int ReadRegister(int Address, int& Data) throw(THardwareException); virtual int WriteRegister(int Address, int Data) throw(THardwareException); virtual bool HasData(int Address = -1) throw(THardwareException); virtual bool WaitData(unsigned TimeOut_sec = 1) throw(THardwareException); };
- = = = = = [Control Related] = = = = =
- bool Probe(void) throw(THardwareException)
- returns true if the module is connected and working properly. Usually checks Identification register. If the module does not have this feature, true is returned unconditionally (unless it is overridden).
- int Initialize(int InitialState = 0) throw(THardwareException)
- initializes the module. Nothing occurs if it is not overridden. happen.
- int Finalize(int FinalState = 0) throw(THardwareException)
- prepares the module for finalizing. Nothing occurs if it is not overridden.
- int Enable(int Address = -1) throw(THardwareException)
- enables the indicated channel (if a negative number is indicated, all channels are enabled). For starting FADC or starting scalar counting.
- int Disable(int Address = -1) throw(THardwareException)
- disables the indicated channel (if a negative number is indicated, all channels are disabled). For stopping scalar counting and such.
- int IsEnabled(int Address = -1) throw(THardwareException)
- returns 1 if the indicated channel is enabled (or if all channels are enabled for negative numbers), and 0 if not.
- int IsBusy(int Address = -1) throw(THardwareException)
- returns 1 if the indicated channel is busy (or if all channels are busy for negative numbers), and 0 otherwise.
- int Clear(int Address = -1) throw(THardwareException)
- clears the indicated channel (or all channels for negative numbers).
- = = = = = [Data Input/Output Related] = = = = =
- [Signal Input/Output]
- int Read(int Address, int &Data) throw(THardwareException)
- reads out one word of data from the indicated address (channel). Returns 1 if successfully read out, and 0 if the data does not exist. In FADC and multihit TDC, the next dataword in that specific channel is returned to the argument everytime this method is called, and returns 0 when all data words are read. In case of error, THardwareException is thrown.
- int Write(int Address, int Data) throw(THardwareException)
- writes one word of data in the indicated address (channel). Returns 1 if finished successfully and throws THardwareException when an error occurs.
- [Sequential Input/Output]
- int SequentialRead(int Address, int Data[], int MaxSize) throw(THardwareException)
- reads out data from the specified address (channel) while there is data with the number of data limited with MaxSize and records to Data[]. Returns the number of dataword read out. If this method is not overridden, an implementation that loops Read() is used.
- int SequentialWrite(int Address, int Data[], int Size) throw(THardwareException)
- data word array passed to Data and Size is written in to the indicated address (channel) and the number of datawords is returned. If this method is not overridden, an implementation that loops Write() is used.
- int NextNumberOfDataElements(int Address = -1) throw(THardwareException)
- returns the number of datawords (or its' maximum) that will be readout by the next SequencialRead(). For FADC's number of words and multihit TDC's maximum hits. This value obtained is used for securing buffer for the next SequentialRead() and argument MaxSize. Be aware that the actual number of words readout will be returned when SequencialRead() is called.
- [Block Input/Output]
- int BlockRead(int Address, void *Data, int MaxSize) throw(THardwareException)
- reads out datablocks from the indicated address (channel) with MaxSize as its' maximum, records them in data area indicated in Data, and returns the size of data readout. The interpretation of the argument Address depends on the module. Sometimes it will mean channel and other times it might mean the offset from the base address. In many cases, argument Address is simply ignored.
- int BlockWrite(int Address, const void *Data, int Size) throw(THardwareException)
- writes the datablock indicated by Data and Size to the specified address (channel) and returns the size it wrote in. Depending on the specs of the module, the argument Address relies on the module itself (in many cases, simply ignored).
- int NextDataBlockSize(int Address = -1) throw(THardwareException)
- returns the datablock size (or its' upper limit) of the next datablock that will be readout after calling BlockRead(). This value is used for securing buffer for the next calling of BlockRead() and to specify MaxSize. Be aware that the actual readout size will be returned when BlockRead() is called.
- = = = = = [Interrupt Processing Related] = = = = =
- int EnableInterrupt(int SignalId = 0) throw(THardwareException)
- enables interrupts. If the SignalId is not 0, a signal is issued upon interrupt.
- int DisableInterrupt(void) throw(THardwareException)
- disables interrupts.
- int ClearInterrupt(void) throw(THardwareException)
- clears interrupts.
- = = = = = [Introspection Related] = = = = =
- int NumberOfChannels(void) throw(THardwareException)
- returns the number of channels a module has.
- int AddressBitLength(void)
- returns the number of bits required to specify any address (channel) used in Read()/SequencialRead(). If there are 16 channels, this method will return 4. This value is used to determine data format.
- int DataBitLength(void)
- returns the number of bits of the dataword returned by calling Read()/SequencialRead(). This value is used to determine dataformat.
- = = = = = [Others] = = = = =
- int ReadRegister(int Address, int& Data) throw(THardwareException)
- reads value from the address indicated in argument Address and stores to argument Data.
- int WriteRegister(int Address, int Data) throw(THardwareException)
- writes the value in the argument Data to the address indicated in argument Address.
- bool HasData(int Address = -1) throw(THardwareException)
- returns true if the indicated address (channel) has data that can be readout immediately.
- bool WaitData(unsigned TimeOut_sec = 1) throw(THardwareException)
- waits for data with time passed in the argument as maximum waiting time. If data arrives, returns ture. If not, returns false.
Some of the TRoomCamacModule class definitions are shown below.
A constructor and the Clone() method are two implementations that is necessary in module drivers. The TRoomCamacModule constructor takes the module type and model number as its string arguments. The Clone() method creates one instance of itself and returns it.
class TRoomCamacModule: public TRoomModule { public: TRoomCamacModule(const std::string& ModuleType, const std::string& ModelName); virtual ~TRoomCamacModule(); virtual TRoomCamacModule* Clone(void) = 0; public: // methods that provide access to CAMAC devices // int Transact(int Function, int Address, int &Data, int &Q, int &X); int Transact(int Function, int Address, int &Data) throw(THardwareException); int Transact(int Function, int Address) throw(THardwareException); int EnableLam(void) throw(THardwareException); int DisableLam(void) throw(THardwareException); int ClearLam(void) throw(THardwareException); bool WaitLam(int TimeOut_sec) throw(THardwareException); bool IsRequestingLam(void); public: // standard implementations for module driver interfaces // virtual int Read(int Address, int& Data) throw(THardwareException); virtual int Write(int Address, int Data) throw(THardwareException); virtual int ReadRegister(int Address, int& Data) throw(THardwareException); virtual int WriteRegister(int Address, int Data) throw(THardwareException); virtual int Clear(int Address = -1) throw(THardwareException); virtual bool HasData(int Address = -1) throw(THardwareException); virtual bool WaitData(unsigned TimeOut_sec = 1) throw(THardwareException); virtual int EnableInterrupt(int SignalId = 0) throw(THardwareException); virtual int DisableInterrupt(void) throw(THardwareException); virtual int ClearInterrupt(void) throw(THardwareException); virtual int AddressBitLength(void); virtual int DataBitLength(void); protected: // common constants used throughout CAMAC // enum TCamacFunctionTable { fnRead = 0, fnTestLam = 8, fnClear = 9, fnClearLam = 10, fnWrite = 16, fnDisable = 24, fnEnable = 26, _NumberOfCamacFunctions }; enum TCamacAddressTable { adAny = 0, _NumberOfCamacAddresses }; };
The following is an example of Rinei(REPIC)'s 16ch 12bit cs ADC (RPC-022). As a general rule, classes are named T{module type}_{vendor name}_{model number}, and files are named module-{vendor name}_{model number}.hh/cc.
// module-Rinei_RPC022.hh // #include "RoomCamacAccess.hh" class TCamacQADC_Rinei_RPC022: public TRoomCamacModule { public: TCamacQADC_Rinei_RPC022(void); virtual ~TCamacQADC_Rinei_RPC022(); virtual TRoomCamacModule* Clone(void); // implementations of other interfaces // // ... };
The following methods are methods that TRoomCamacModule provides for each module driver to access CAMAC devices. Module drivers use these methods to implement interfaces such as Read().
// module-Rinei_RPC022.cc // #include "RoomCamacAccess.hh" #include "module-Rinei_RPC022.hh" TCamacQADC_Rinei_RPC022::TCamacQADC_Rinei_RPC022(void) : TRoomCamacModule("CamacQADC", "RPC022") { } TCamacQADC_Rinei_RPC022::~TCamacQADC_Rinei_RPC022() { } TRoomCamacModule* TCamacQADC_Rinei_RPC022::Clone(void) { return new TCamacQADC_Rinei_RPC022(); } // implementations of other interfaces // // ...
Interfaces with common module behaviers have TRoomCamacAccess implemented. The following methods are already implemented methods and their default behavier. If these default behaviers do not suit what the user wants, they can be overridden in each module driver.
- int Transact(int Function, int Address, int &Data, int &Q, int &X)
- executes a CAMAC cycle with the function and address passed as arguments. If it is a write-in cycle the value passed as argument Data is used as data value, and if it s a readout cycle the data value is set to the value in argument Data. The Q and X responses are returned to the argument Q and X respectively. Return value is the Q response.
- int Transact(int Function, int Address, int &Data) throw(THardwareException)
- executes a CAMAC cycle with the function and address passed as arguments. If it is a write-in cycle the value passed as argument Data is used as data value, and if it s a readout cycle the data value is set to the value in argument Data. Q response is returned as the return value. If there is no X response, exception THardwareException is thrown.
- int Transact(int Function, int Address) throw(THardwareException)
- executes a CAMAC cycle without the involvement of data with function and address passed as arguments. Q response is returned as the return value. If there is no X response, exception THardwareException is thrown. This is the simplified version of the above Transact() method.
- int EnableLam(void) throw(THardwareException)
- sends F26 and enables LAM.
- int DisableLam(void) throw(THardwareException)
- sends F24 and disables LAM.
- int ClearLam(void) throw(THardwareException)
- sends F10 and clears LAM.
- bool WaitLam(int TimeOut_sec) throw(THardwareException)
- waits for a LAM signal with the time passed as argument a maximum waiting time. If there is a LAM signal, returns true. If timed out or terminated with other reasons, returns false.
- bool IsRequestingLam(void)
- sends F8 and sees if there is a LAM signal.
These default implementations work for almost all CAMAC modules. Therefore, there is no need (in the case of CAMAC) to implement these interfaces individually.
- int Read(int Address, int& Data) throw(THardwareException);
- reads data with F0 and returns Q response.
- int Write(int Address, int Data) throw(THardwareException);
- writes data with F16 and returns Q response.
- int ReadRegister(int Address, int& Data) throw(THardwareException);
- same as Read().
- int WriteRegister(int Address, int Data) throw(THardwareException);
- same as Write().
- int Clear(int Address = -1) throw(THardwareException);
- clears data with F9. If the Address argument is negative, it is set to 0 (in many modules, F9 ignores addresses).
- bool HasData(int Address = -1) throw(THardwareException);
- calls IsRequestingLam().
- bool WaitData(unsigned TimeOut_sec = 1) throw(THardwareException);
- calls WaitLam().
- int EnableInterrupt(int SignalId = 0) throw(THardwareException);
- calls EnableLam().
- int DisableInterrupt(void) throw(THardwareException);
- calls DisableLam().
- int ClearInterrupt(void) throw(THardwareException);
- calls ClearLam().
- int AddressBitLength(void);
- returns 8. This value is the address width of the CAMAC cycle.
- int DataBitLength(void);
- returns 24. This value is the data word length of the CAMAC cycle.
To register the module driver to the system, TRoomCamacModuleCreater defined in RoomDeviceFactory.hh is used. This is a little bit tricky, but create the static Creator object inside the .cc file, and pass the module driver name and the module driver instance as the contructor argument. By doing this, the module driver instance is automatically created when the driver code is loaded and will be registered to the system.
/* module-Rinei_RPC022.cc */ #include "RoomCamacAccess.hh" #include "RoomDeviceFactory.hh" #include "module-Rinei_RPC022.hh" static TRoomCamacModuleCreator Creator( "Rinei_RPC022", new TCamacModule_Rinei_RPC022() ); //...
Place the created module driver's .hh/.cc file in kinoko/src/kernel/lib-common/room and recompile Kinoko. Now this can be used through Kinoko. There is no need to edit Makefile.
Some of the TRoomVmeModule class definitions are shown below.
A constructor and the Clone() method are two implementations that is necessary in module drivers. The TRoomVmeModule constructor takes the module type and model number as its string arguments, and also a couple of parameters regarding transfer mode and memory map as the rest of the arguments. The Clone() method creates one instance of itself and returns it.
class TRoomVmeModule: public TRoomModule { public: TRoomVmeModule( const std::string& ModuleType, const std::string& ModelName, TRoomVmeTransferMode TransferMode, size_t MapSize = 0, off_t MapOffset = 0 ); virtual ~TRoomVmeModule(); virtual TRoomVmeModule* Clone(void) = 0; public: // methods that provide access to VME devices // inline volatile Word* WordPointer(off_t OffsetAddress = 0); inline volatile Word& WordAt(off_t OffsetAddress); inline volatile DoubleWord* DoubleWordPointer(off_t OffsetAddress = 0); inline volatile DoubleWord& DoubleWordAt(off_t OffsetAddress); long PioRead(off_t Address, void* Buffer, int Size) throw(THardwareException); long PioWrite(off_t Address, const void* Buffer, int Size) throw(THardwareException); long DmaRead(off_t Address, void* Buffer, int Size) throw(THardwareException); long DmaWrite(off_t Address, const void* Buffer, int Size) throw(THardwareException); public: // standard implementations for module driver interfaces // virtual int BlockRead(int Address, void* Data, int MaxSize) throw(THardwareException) virtual int BlockWrite(int Address, const void* Data, int Size) throw(THardwareException) virtual int ReadRegister(int Address, int& Data) throw(THardwareException) virtual int WriteRegister(int Address, int Data) throw(THardwareException) virtual int EnableInterrupt(int SignalId = 0) throw(THardwareException) virtual int DisableInterrupt(void) throw(THardwareException) virtual int ClearInterrupt(void) throw(THardwareException) virtual bool WaitData(unsigned TimeOut_sec) throw(THardwareException) };
The following is an example of Renei (REPIC)'s Flash ADC (RPV-160). This module's transfer mode is address 32bit data 16bit and the group of registers that is to be accessed is located in range 0x31000 (size 0x1000) from the offset 0x30000.
// module-Rinei_RPV160.hh // #include "RoomVmeAccess.hh" class TVmeFADC_Rinei_RPV160: public TRoomVmeModule { public: TVmeFADC_Rinei_RPV160(void); virtual ~TVmeFADC_Rinei_RPV160(); virtual TRoomVmeModule* Clone(void); // implementations of other interfaces // // ... };
The following methods are methods that TRoomCamacModule provides for each module driver to access VME devices. Module drivers use these methods to implement interfaces such as Read().
// module-Rinei_RPV160.cc // #include "RoomVmeAccess.hh" #include "module-Rinei_RPV160.hh" static const TVmeTransferMode TransferMode = VmeTransferMode_A32D16; static const off_t MapOffset = 0x30000; static const size_t MapSize = 0x1000; TVmeFADC_Rinei_RPV160::TVmeFADC_Rinei_RPV160(void) : TRoomVmeModule("VmeFADC", "Rinei_RPV160", TransferMode, MapSize, MapOffset) { } TVmeFADC_Rinei_RPV160::~TVmeFADC_Rinei_RPV160() { } TRoomVmeModule* TVmeFADC_Rinei_RPV160::Clone(void) { return new TVmeFADC_Rinei_RPV160(); } // implementations of other interfaces // // ...
Interfaces with common module behaviers have TRoomVmeAccess implemented. The following methods are already implemented methods and their default behavier. If these default behaviers do not suit what the user wants, they can be overridden in each module driver.
- volatile Word* WordPointer(off_t OffsetAddress = 0)
- returns a pointer to the word (16bit) that is located at the offset passed as the argument from the beginning of the memory map area.
- volatile Word& WordAt(off_t OffsetAddress)
- returns a reference to the word (16bit) that is located at the offset passed as the argument from the beginning of the memory map area.
- volatile DoubleWord* DoubleWordPointer(off_t OffsetAddress = 0)
- returns a pointer to the double word (32bit) that is located at the offset passed as the argument from the beginning of the memory map area.
- volatile DoubleWord& DoubleWordAt(off_t OffsetAddress)
- returns a reference to the double word (32bit) that is located at the offset passed as the argument from the beginning of the memory map area.
- long PioRead(off_t Address, void* Buffer, int Size) throw(THardwareException)
- reads data of the indicated Size from the indicated Address (offset from base address) both passed as arguments by PIO (Programmed I/O), stores it in the area passed as the Buffer argument, and returns the actual readout size.
- long PioWrite(off_t Address, const void* Buffer, int Size) throw(THardwareException)
- writes the data indicated by arguments Buffer and Size using PIO (Programmed I/O) and returns the actual size written in.
- long DmaRead(off_t Address, void* Buffer, int Size) throw(THardwareException)
- reads data of the indicated Size from the indicated Address (offset from base address) both passed as arguments using DMA (Direct Memory Access), stores it in the area passed as the Buffer argument, and returns the actual readout size.
- long DmaWrite(off_t Address, const void* Buffer, int Size) throw(THardwareException)
- writes the data indicated by arguments Buffer and Size using DMA (Direct Memory Access) and returns the actual size written in.
Unlike CAMAC, these methods are not particularly useful with its default behaviers. Be especially aware that there are no Read()/Write() that allow I/O one word at a time. The interrupt related methods do not manipulate the module at all. Also, the Address arguments in BlockRead()/BlockWrite() discribe offsets from the base address as their default, but in most cases this does not hold much meaning for proper behavier. Further more, ReadRegister()/WriteRegister() use PioRead()/PioWrite() but if memory mapping is being used, accessing the mapped region with a pointer is far more efficient.
- int BlockRead(int Address, void* Data, int MaxSize) throw(THardwareException)
- calls DmaRead(Address, Data, MaxSize).
- int BlockWrite(int Address, const void* Data, int Size) throw(THardwareException)
- calls DmaWrite(Address, Data, Size).
- int ReadRegister(int Address, int& Data) throw(THardwareException)
- calls PioRead(Address, Data, {word size set by transfer mode}).
- int WriteRegister(int Address, int Data) throw(THardwareException)
- calls PioWrite(Address, Data, {word size set by transfer mode}).
- int EnableInterrupt(int SignalId = 0) throw(THardwareException)
- sets the settings for VME controller interrupts and enables them. Normally an interrupt related register of the module needs to be set so a user needs to override this method and add other processes.
- int DisableInterrupt(void) throw(THardwareException)
- sets interrupts to the VME controller disabled. Normally an interrupt related register of the module needs to be set so a user needs to override this method and add other processes.
- int ClearInterrupt(void) throw(THardwareException)
- does not do anything.Normally an interrupt related register of the module needs to be set so a user needs to override this method and add other processes.
- bool WaitData(unsigned TimeOut_sec) throw(THardwareException)
- calls EnableInterrupt() and waits for interrupts using the controller's WaitForInterrupt().
To register the module driver to the system, TRoomVmeModuleCreater defined in RoomDeviceFactory.hh is used. This is a little bit tricky, but create the static Creator object inside the .cc file, and pass the module driver name and the module driver instance as the contructor argument. By doing this, the module driver instance is automatically created when the driver code is loaded and will be registered to the system.
/* module-Rinei_RPV160.cc */ #include "RoomVmeAccess.hh" #include "RoomDeviceFactory.hh" #include "module-Rinei_RPV160.hh" static TRoomVmeModuleCreator Creator( "Rinei_RPV160", new TCamacModule_Rinei_RPV160() ); //...
Place the created module driver's .hh/.cc file in kinoko/src/kernel/lib-common/room and recompile Kinoko. Now this can be used through Kinoko. There is no need to edit Makefile.
In MiscControl, the command name is given as a string, and information such as parameters and data are passed back and forth as integer . The following are methods used in MiscControl.
An example of MiscControl is shown below. Here a CAMAC output register KC3471 (Kaizu-seisakusyo) is used with commands outputLevel and outputPulse.
- int MiscControlIdOf(const std::string& CommandName) throw(THardwareException)
- returns a unique ID (non-negative integer) corresponding to the command name passed in the argument. If the command name is invalid, a negative value is returned.
- int MiscControl(int ControlId, int* ArgumentList = 0, int NumberOfArguments = 0) throw (THardwareException)
- executes the control expressed by the ID passed as an argument and returns 1 if successfuly executed and 0 if not. Parameters are passed through "ArgumentList." If values need to be returned, directly insert them in the elements of the ArgumentList. If not, then do NOT make changes to the ArgumentList values.
class TCamacOutputRegister_Kaizu_KC3471: public TRoomCamacModule { public: virtual int MiscControlIdOf(const std::string& CommandName) throw (THardwareException); virtual int MiscControl(int ControlId, int* ArgumentList = 0, int NumberOfArguments = 0) throw (THardwareException); public: // define Control ID enum TControlId { ControlId_OutputLevel, ControlId_OutputPulse, _NumberOfControls }; };
// convert from command name to Control ID // int TCamacOutputRegister_Kaizu_KC3471::MiscControlIdOf(const std::string& CommandName) throw (THardwareException) { int ControlId = -1; if (CommandName == "outputLevel") { ControlId = ControlId_OutputLevel; } else if (CommandName == "outputPulse") { ControlId = ControlId_OutputPulse; } else { throw THardwareException( _ModelName, "unknown command: " + CommandName ); } return ControlId; } // execute command // int TCamacOutputRegister_Kaizu_KC3471::MiscControl(int ControlId, int* ArgumentList, int NumberOfArguments) throw (THardwareException) { int Result = 0; if (ControlId == ControlId_OutputLevel) { if (NumberOfArguments < 1) { throw THardwareException( "TCamacOutputRegister_Kaizu_KC3471::OutputLevel(int Data)", "too few argument[s]" ); } // intermediary variables are used in order not to change values in ArgumentList int Data = ArgumentList[0]; Transact(fnOutputLevel, adOutput, Data); Result = 1; } else if (ControlId == ControlId_OutputPulse) { if (NumberOfArguments < 1) { throw THardwareException( "TCamacOutputRegister_Kaizu_KC3471::OutputPulse(int Data)", "too few argument[s]" ); } int Data = ArgumentList[0]; Transact(fnOutputPulse, adOutput, Data); Result = 1; } return Result; }
The service requester interface is abstract class TRoomServiceRequester and defined in RoomServiceRequester.hh. This class is also the parent class of the standard module interface describing abstract class TRoomModule.
The explanation of each method is the following.
class TRoomServiceRequester { public: TRoomServiceRequester(void); virtual ~TRoomServiceRequester(); // Basic Interface // virtual bool WaitForServiceRequest(int TimeOut_sec) throw(THardwareException); virtual int RequestingServiceNumber(void) throw(THardwareException); // Polling Interface // virtual void EnableServiceRequest(void) throw(THardwareException); virtual void DisableServiceRequest(void) throw(THardwareException); virtual void ClearServiceRequest(void) throw(THardwareException); virtual bool IsRequestingService(void) throw(THardwareException); // Signal on Service Request // virtual bool IsSignalOnServiceRequestAvailable(void); virtual void EnableSignalOnServiceRequest(int SignalId) throw(THardwareException); virtual void DisableSignalOnServiceRequest(void) throw(THardwareException); };
- bool WaitForServiceRequest(int TimeOut_sec) throw(THardwareException)
- waits for service request with maximum wait time passed as an argument. If there is a service request, returns true. If not, or if for some reason it is interrupted, returns false.
- int RequestingServiceNumber(void) throw(THardwareException)
- returns the parameter value if the service request has a parameter.
- void EnableServiceRequest(void) throw(THardwareException)
- enables service request.
- void DisableServiceRequest(void) throw(THardwareException)
- disables service request.
- void ClearServiceRequest(void) throw(THardwareException)
- clears service request.
- bool IsRequestingService(void) throw(THardwareException)
- returns true if service is requested now and false if not.
- bool IsSignalOnServiceRequestAvailable(void)
- returns true if there is a function that issues signals on service request and false if not.
- void EnableSignalOnServiceRequest(int SignalId) throw(THardwareException)
- sets it so that a signal is issued on service request.
- void DisableSignalOnServiceRequest(void) throw(THardwareException)
- disables the setting that issues a signal on service request.
Some of these service requesters are implemented with common behaviers in TRoomCamacModule and TRoomVmeModule.
Service Requesters Implemented in TRoomCamacModule
Most CAMAC modules will suffice with these default implementations.
- bool WaitForServiceRequest(int TimeOut_sec) throw(THardwareException)
- calls WaitData(TimeOut_sec). WaitData() calls WaitLam().
- int RequestingServiceNumber(void) throw(THardwareException)
- returns 0.
- void EnableServiceRequest(void) throw(THardwareException)
- calls EnableLam().
- void DisableServiceRequest(void) throw(THardwareException)
- calls DisableLam().
- void ClearServiceRequest(void) throw(THardwareException)
- calls ClearLam().
- bool IsRequestingService(void) throw(THardwareException)
- calls HasData().
- bool IsSignalOnServiceRequestAvailable(void)
- calls control driver's IsSignalOnInterruptAvailable().
- void EnableSignalOnServiceRequest(int SignalId) throw(THardwareException)
- does not do anyting.
- void DisableSignalOnServiceRequest(void) throw(THardwareException)
- does not do anything.
Service Requesters Implemented In TRoomVmeModule
Once again, unlike the CAMAC case, the standard implementations described here are not so complete. EnableServiceRequest() / DisableServiceRequest() / ClearServiceRequest() and many more methods need to be implemented compatible to the individual module.
- bool WaitForServiceRequest(int TimeOut_sec) throw(THardwareException)
- calls WaitData(TimeOut_sec). WaitData() calls EnableInterrupt() and waits for an interrupt.
- int RequestingServiceNumber(void) throw(THardwareException)
- returns 0.
- void EnableServiceRequest(void) throw(THardwareException)
- does not do anything. Most likely needs to be overridden and implemented.
- void DisableServiceRequest(void) throw(THardwareException)
- does not do anything. Most likely needs to be overridden and implemented.
- void ClearServiceRequest(void) throw(THardwareException)
- throws exception THardwareException. Needs to be overridden and implemented.
- bool IsRequestingService(void) throw(THardwareException)
- cals HasData(). Since HasData() is not implemented in TRoomVmeModule, at least HasData() has to be overridden and implemented.
- bool IsSignalOnServiceRequestAvailable(void)
- calls controller driver's IsSignalOnInterruptAvailable().
- void EnableSignalOnServiceRequest(int SignalId) throw(THardwareException)
- calls EnableInterrupt(SignalId).
- void DisableSignalOnServiceRequest(void) throw(THardwareException)
- calls DisableInterrupt().
Here as examples of simple custom components, we will create the following.
- alarm clock
- a component that issues an alarm event everytime a certain interval of time passes.
- event counter
- a component that counts the number of events in the datastream and issues an alarm event when the total reaches a certain number.
- dataflow meter
- a component that measures the amount of data flow and attaches that amount as a new data, then passes it down the stream.
The complete source code of this component can be found at kinoko/local/tutorials/Component (KinokoAlarmClock.hh/cc etc).component KinokoAlarmClock { emits ring(); accepts setInterval(int interval_sec); accepts start(); accepts stop(); accepts quit(); }
This component simply acquires the current time, calculates the time interval that passed, and issues an alarm event when the interval exceeds a set value. To implement this, a component event slot that sets a specific time and controls the start/stop of an action needs to be implemented as well as a component event source that issues an alarm event and the calculations needed to calculate the time interval.
The steps that need to be taken to implement this simple component are the following.
1) Defining Custom Component Class
Create file KinokoAlarmClockCom.hh and define class TKinokoAlarmClockCom within it.
Methods on lines 17-19 are declared in the component frame class TKcomComponent and has to be overridden in each component implementation class. Methos on lines 21-24 are supplements used internally. All events are distinguished by unique event IDs. In order to add an original event, an event ID must be given. This is done on lines 26-32 by the enum constant. Member variables on lines 34-35 are used internally.
13: class TKinokoAlarmClockCom: public TKinokoSystemComponent { 14: public: 15: TKinokoAlarmClockCom(void); 16: virtual ~TKinokoAlarmClockCom(); 17: virtual void BuildDescriptor(TKcomComponentDescriptor& Descriptor); 18: virtual int ProcessEvent(int EventId, TKcomEvent& Event); 19: virtual int DoTransaction(void) throw(TKcomException); 20: protected: 21: int ProcessSetIntervalEvent(TKcomEvent& Event); 22: int ProcessStartEvent(TKcomEvent& Event); 23: int ProcessStopEvent(TKcomEvent& Event); 24: void EmitRingEvent(void); 25: protected: 26: enum TEventId { 27: EventId_SetInterval = TKinokoSystemComponent::_NumberOfEvents, 28: EventId_Start, 29: EventId_Stop, 30: EventId_Ring, 31: _NumberOfEvents 32: }; 33: protected: 34: bool _IsRunning; 35: long _StartTime, _Interval; 36: };
Now we will create file KinokoAlarmClockCom.cc and implement methods in class TKinokoAlarmClockCom. First methods are the constructor and destructor. The component frame class takes component type names as its argument. This is usually the same name as the component class name. The initialization of member variable is done in the constructor as well.
12: TKinokoAlarmClockCom::TKinokoAlarmClockCom(void) 13: : TKinokoSystemComponent("KinokoAlarmClock") 14: { 15: _IsRunning = false; 16: _Interval = 0; 17: _StartTime = 0; 18: } 19: 20: TKinokoAlarmClockCom::~TKinokoAlarmClockCom() 21: { 22: }
2) Building The Component Descriptor
The component descriptor scripts the component's external interface (component definition). Structure a descriptor object by overriding method BuildDescriptor(). If event slots and properties are written in the descriptor, they will be reflected in the component interface declaration (try comparing it with the interface declaration above).
First call the same method in the parent class and take in the interfaces inherited from the parent component. Then create an original event descriptor object and register it in the component descriptor. The event descriptor only holds signiture information of the event, so when registering in the component descriptor, pass the corresponding event ID as well. The descriptor registering method's argument is passed by copying so passing the object to the event object for a moment is not a problem.
24: void TKinokoAlarmClockCom::BuildDescriptor(TKcomComponentDescriptor& Descriptor) 25: { 26: TKinokoSystemComponent::BuildDescriptor(Descriptor); 27: 28: TKcomEventDeclaration SetIntervalEvent("setInterval"); 29: SetIntervalEvent.AddArgument(TKcomPropertyDeclaration( 30: "interval_sec", TKcomPropertyDeclaration::Type_Int 31: )); 32: Descriptor.RegisterEventSlot(EventId_SetInterval, SetIntervalEvent); 33: 34: TKcomEventDeclaration StartEvent("start"); 35: Descriptor.RegisterEventSlot(EventId_Start, StartEvent); 36: 37: TKcomEventDeclaration StopEvent("stop"); 38: Descriptor.RegisterEventSlot(EventId_Stop, StopEvent); 39: 40: TKcomEventDeclaration RingEvent("ring"); 41: Descriptor.RegisterEventSource(EventId_Ring, RingEvent); 42: }
3) Implementing Event Processing Functions
When the component receives an event, it will call method ProcessEvent(). The arguments passed are event ID of the event slot and the event object which holds event arguments. Here we will assign it to an appropriate processing function simply by the event ID. If the event is processed successfully, return 1. If not, return 0.
Each event processing functions are implemented as the following.
44: int TKinokoAlarmClockCom::ProcessEvent(int EventId, TKcomEvent& Event) 45: { 46: int Result = 0; 47: 48: switch (EventId) { 49: case EventId_SetInterval: 50: Result = ProcessSetIntervalEvent(Event); 51: break; 52: 53: case EventId_Start: 54: Result = ProcessStartEvent(Event); 55: break; 56: 57: case EventId_Stop: 58: Result = ProcessStopEvent(Event); 59: break; 60: 61: default: 62: Result = TKinokoSystemComponent::ProcessEvent(EventId, Event); 63: } 64: 65: return Result; 66: }
In ProcessSetIntervalEvent() the first argument is read and set as member variable _Interval. As shown in this example, the TKcomEvent class returns a list of event arguments by ArgumentList() method. The type ArgumentList() returns is a vector of strings (vector<string>&). When event start is received, current time is obtained and active flag (_IsRunning) is set.
81: int TKinokoAlarmClockCom::ProcessSetIntervalEvent(TKcomEvent& Event) 82: { 83: int Result = 0; 84: if (istrstream(Event.ArgumentList()[0].c_str()) >> _Interval) { 85: Result = 1; 86: } 87: 88: return Result; 89: } 90: 91: int TKinokoAlarmClockCom::ProcessStartEvent(TKcomEvent& Event) 92: { 93: _StartTime = TMushDateTime().AsLong(); 94: _IsRunning = true; 95: 96: return 1; 97: } 98: 99: int TKinokoAlarmClockCom::ProcessStopEvent(TKcomEvent& Event) 100: { 101: _IsRunning = false; 102: 103: return 1; 104: }
4) Implementing Component Tasks
The component calls method DoTransaction() repeatedly while it is running. Tasks executed normally by the component are scripted here (component tasks). In this example, while in active mode, current time is obtained, elapsed time is calculated, and alarm events are issued if this elapsed time exceeds the pre-set time interval.
68: int TKinokoAlarmClockCom::DoTransaction(void) throw(TKcomException) 69: { 70: if (_IsRunning && (_Interval > 0)) { 71: int CurrentTime = TMushDateTime().AsLong(); 72: if ((CurrentTime - _StartTime) > _Interval) { 73: _StartTime = CurrentTime; 74: EmitRingEvent(); 75: } 76: } 77: 78: return TKinokoSystemComponent::DoTransaction(); 79: }
If there is nothing to do in the component task or if the component task can be completed in a very short period of time and has no need of being called repeatedly, call the same method in the parent class. This method in the component framework releases the CPU for a certain amount of time and sleeps the process (order in ms). If this is not done, CPU usage percentage is increased largely for unneeded processes (If there aren't any similar processes, it will take up close to 100%).
Method EmitRingEvent() that issues alarm events are implemented as the following.
This event does not take event arguments so it throws the event object created as it is (only EventId is used). If it is an event that takes arguments, then an obtain the argument list with the event object's ArgumentList() method and set the values there.
106: void TKinokoAlarmClockCom::EmitRingEvent(void) 107: { 108: TKcomEvent Event; 109: EmitEventOneWay(EventId_Ring, Event); 110: }
The issuing of an event is done by EmitEvent() method or EmitEventOneWay() method. The EmitEvent() method transmits the event and stops run untill it is processed. EmitEventOneWay() starts processing right away after throwing the event without waiting for a response. If there is no need for synchronization between components, use EmitEventOneWay().
5) Generating Component Processes
Finally, an instance of the custom component class is created, registered in component process object, and connected to the function main(). Create file KinokoAlarmClock-kcom.cc and write in the following. Here the file name has to be "component type name-kcom.cc".
Here an instance to the component class generated is created, the instnace of the compnent process class is wrapped, and compnent process object is allowd to start executing. This format is the same with all component implementation.
6: #include <iostream.h> 7: #include "KcomProcess.hh" 8: #include "KinokoAlarmClockCom.hh" 9: 10: 11: int main(int argc, char** argv) 12: { 13: TMushArgumentList ArgumentList(argc, argv); 14: 15: TKcomComponent* Component = new TKinokoAlarmClockCom(); 16: TKcomProcess* ComProcess = new TKcomProcess(Component); 17: 18: try { 19: ComProcess->Start(ArgumentList); 20: } 21: catch (TKcomException &e) { 22: cerr << "ERROR: " << argv[0] << ": " << e << endl; 23: } 24: 25: delete ComProcess; 26: delete Component; 27: 28: return 0; 29: }
The complete source code of this component can be found at kinoko/local/tutorials/Component (KinokoEventCounter.hh/cc etc).component KinokoEventCounter { // parts handed over from KinokoStreamSinkComponent omitted emits alarm(); accepts setNumberOfEvents(long number_of_events); }
This component is a component that issues an event using the number of events that comes down the datastream rather than using time measurements like in Example 1). It is almost the same as the alarm clock in Example 1 except for the fact that it accesses the datastream inside the compnent task. Here compnents, methods, and objects that were not involved in the first example are explained explicitly.
Components that access the datastream inherit and implement one of the following classes depending on the connection format to the stream.
- TKinokoStreamSourceComponent
- component that becomes the source of data to the datastream
- TKinokoStreamPipeComponent
- compnent that receives data from the stream, processes it if required, and passes it down the datastream again
- TKinokoStreamSinkComponent
- component that simple receives data in the datastream
In these classes, the following methods are declared as virtual functions in order to override them in the generated class and to implement them.
Further more, there are the following objects that the framework class provies so they can be used in the generated class.
- void Construct(void)
- called when the component shifts from StreamReady state to SystemReady state. Construction and sending out internal objects and data descriptors are done here.
- void Destruct(void)
- called when the component shifts from SystemReady state to StreamReady state. Destruction and cleaning up internal objects are done here. Sometimes Construct() is called again after this method is called so a user must make sure there are no contradictions.
- int ProcessData(void)
- called repeatedly from the data processing loop. If necessary data is received from the stream (other than Source), processed, and sent out (other than Sink).
- void OnStart(void)
- called when the component shifts from SystemReady state to Running state. If there is no need to then this method does not have to be implemented.
- void OnStop(void)
- called when the component shifts from Running state to SystemReady state. If there is no need to then this method does not have to be implemented.
- TKinokoInputStream* _InputDataStream;
- datastream object on the side of receiving data
- TKinokoOutputStream* _OutputDataStream;
- datastream object on the side of sending out data
- TKinokoStreamCommandProcessor* _StreamCommandProcessor;
- object that processes the command received from the datastream and tells the component
- TKinokoLogger* _Logger;
- object imported from logger component. Used to record logs.
Now we will actually use these to create an event counter component. The event counter simple reads the data from the stream and does not write anything out so we inherit the stream sink component and implement it.
The constructor and destructor on lines 17-18 and methods on lines 24-26 are explained here. For other parts, please refer to the explanations in Example 1) alarm clock.
15: class TKinokoEventCounterCom: public TKinokoStreamSinkComponent { 16: public: 17: TKinokoEventCounterCom(void); 18: virtual ~TKinokoEventCounterCom(); 19: virtual void BuildDescriptor(TKcomComponentDescriptor& Descriptor); 20: virtual int ProcessEvent(int EventId, TKcomEvent& Event); 21: protected: 22: virtual int ProcessSetNumberOfEventsEvent(TKcomEvent& Event); 23: virtual void EmitAlarmEvent(void); 24: virtual void Construct(void) throw(TKinokoException); 25: virtual void Destruct(void) throw(TKinokoException); 26: virtual int ProcessData(void)throw(TKinokoException); 27: protected: 28: enum TEventId { 29: EventId_SetNumberOfEvents = TKinokoStreamSinkComponent::_NumberOfEvents, 30: EventId_Alarm, 31: _NumberOfEvents 32: }; 33: protected: 34: int _EventCount; 35: int _MaxNumberOfEvents; 36: TKinokoDataStreamScanner* _StreamScanner; 37: };
First the constructor and the destructor.
The parent class constructor takes arguments as component type names. StreamScanner is an object that pulls out information such as packet type from the data package (void pointer) received from the datastream.
10: TKinokoEventCounterCom::TKinokoEventCounterCom(void) 11: : TKinokoStreamSinkComponent("KinokoEventCounter") 12: { 13: _MaxNumberOfEvents = 0; 14: _StreamScanner = new TKinokoDataStreamScanner(); 15: } 16: 17: TKinokoEventCounterCom::~TKinokoEventCounterCom() 18: { 19: delete _StreamScanner; 20: }
Construct() method normally constructs internal objects and initializes but here it simply resets the event counter. Destruct() does not need to do anything.
79: void TKinokoEventCounterCom::Construct(void) throw(TKinokoException) 80: { 81: _EventCount = 0; 82: } 83: 84: void TKinokoEventCounterCom::Destruct(void) throw(TKinokoException) 85: { 86: }
ProcessData() method reads data from the datastream and processes. First it reads the data from the stream and corrects the byte orders (if necessary). _InputDataStream's NextEntry() method waits for the data to arrive in the stream and returns the data size, but returns 0 if the data waiting is terminated for some reason or if the datastream reaches the end.
88: int TKinokoEventCounterCom::ProcessData(void)throw(TKinokoException) 89: { 90: static const int Trailer_Event = TKinokoDataStreamScanner::Trailer_Event; 91: 92: void* Data; 93: int DataSize = _InputDataStream->NextEntry(Data); 94: if (DataSize <= 0) { 95: return 0; 96: } 97: _StreamScanner->CorrectByteOrder(Data, DataSize); 98:
Next we set how to process special packets (data descriptor or commands) when they are received. In this example, we do not need a detailed explanation (meaning, structure, etc) of the data we receive so descripter packets are simply discarded. For command packets, we hand it over to the _StreamCommandProcessor object the parent class provides for processing.
99: if (_StreamScanner->IsDataDescriptorPacket(Data)) { 100: ; 101: } 102: else if (_StreamScanner->IsCommandPacket(Data)) { 103: int CommandValue = _StreamScanner->CommandValueOf(Data); 104: _StreamCommandProcessor->ProcessCommand(CommandValue); 105: }
If an event trailer packet is received, the event counter is incremented and checked if it has reached the event number set as the maximum. If it has reached that number, it is recorded in the log and an alarm event is issued.
106: else if ( 107: _StreamScanner->IsTrailerPacket(Data) && 108: (_StreamScanner->TrailerValueOf(Data) == Trailer_Event) 109: ){ 110: _EventCount++; 111: 112: if (_EventCount % _MaxNumberOfEvents == 0) { 113: _Logger->WriteNotice( 114: ComponentName(), "event counts reached the maximum value" 115: ); 116: EmitAlarmEvent(); 117: } 118: } 119:
Finally, data packets held in the stream are released.
120: _InputDataStream->Flush(Data); 121: 122: return 1; 123: }
The component is now complete. As in example 1, this component can be used in the script after wrapping it with the component process object.
The complete source code of this component can be found at kinoko/local/tutorials/Component (KinokoDataFlowMeter.hh/cc etc).datasource "flow_meter"<1> { section "data_flow"<1>: tagged { field "event_rate": int-32bit; field "data_flow": int-32bit; } }
This component simply reads data from the datastream, performs simple calculations to it, and writes it out in the datastream and does not issue or receive any original component events. Component like this that does not handle component events directly can be created easily by using the already implemented component class KinokoDataProcessorCom and registering its own DataProcessor object to it.
The component created here is a pipe type component so the base analysis framework class used is the TKinokoDataProcessor class. This framework class and its parent classes TKinokoDataSender and TKinokoDataReceiver have the following methods declared as virtual functions to be overriden in the generated class and their content implemented.
Be careful that OnReceivePacket() is called even if OnReceiveDataPacket() or other equivalent methods are implemented.
- void BuildDataSource(TKinokoDataSource* DataSource)
- called on system construction. DataSource objects that correspond to data that is outputted is constructed here. This method has to be implemented.
- void OnConstruct(void)
- called on system construction. Necessary initializations such as constructing internal objects are done here. Implementation not necessary if there is no need.
- void OnDestruct(void)
- called on system destruction. Necessary termination processes such as destructing internal objects are done here. Implementation not necessary if there is no need.
- void OnReceivePacket(void* DataPacket, long PacketSize)
- called when a packet is received from the stream with the packet as its argument. Called for all packets regardless of the packet type received. Does not have to be implemented if there is no need.
- void OnReceiveDataPacket(void* DataPacket, long PacketSize)
- called when a data packet is received from the stream with the data packet as its argument. Implementation not necessary there is no need to.
- void OnReceiveEventTrailerPacket(void* DataPacket, long PacketSize)
- called when an event trailer packet is received from the stream with the event trailer packet as its argument. Implementation not necessary there is no need to.
Also the following methods that are used in the generated class are provided by these framework classes.
- void SendDataDestructorPacket(void)
- generates a data descriptor packet and sends it out to the output stream.
- void SendRunBeginPacket(void)
- generates a RunBegin packet and sends it out to the output stream.
- void SendRunEndPacket(void)
- generates a RunEnd packet and sends it out to the output stream.
- void SendEventTrailerPacket(void)
- generates an EventTrailer packet and sends it out to the output stream.
- void SendPacket(void* Packet, long PacketSize)
- sends the packet passed as the argument to the output stream.
A couple more objects are provided that can be used in the generated class.
Using these methods and objects, we will now implement the DataFlowMeter class. The following is the class definition of the DataFlowMeter class (write in file KinokoDataFlowMeter.hh).
- TKinokoInputStream* _InputStream
- a datastream object that is on the side of receiving data.
- TKinokoDataStreamScanner* _InputStreamScanner
- an object that extracts information out from the data packet received.
- TKinokoDataDescriptor* _InputDataDescriptor
- a data descriptor object of data that comes from the input datastream.
- TKinokoOutputStream* _OutputStream
- a datastream object that is on the side of sending out data.
- TKinokoDataStreamFormatter* _OutputStreamFormatter
- an objects that writes in information to the packet being sent out.
- TKinokoDataDescriptor* _InputDataDescriptor
- a data descriptor object of data that will be sent out to the output datastream.
- TKinokoDataSource* _OutputDataSource
- a datasource defining object of data that will be sent out to the output datastream.
The SendReportPacket() method on line 25 is a method that is used internally to send dataflow data generated in this component to the stream. _DataSection on line 27 is an object that has the structure of the dataflow data section to be sent out scripted, and _Formatter on line 28 is an utility object for writing in data into the data stream packet.
14: class TKinokoDataFlowMeter: public TKinokoDataProcessor { 15: public: 16: TKinokoDataFlowMeter(void); 17: virtual ~TKinokoDataFlowMeter(); 18: protected: 19: virtual void BuildDataSource(TKinokoDataSource* DataSource); 20: virtual void OnConstruct(void) throw(TKinokoException); 21: virtual void OnReceivePacket(void* Packet, long PacketSize) throw(TKinokoException); 22: virtual void OnReceiveDataPacket(void* Packet, long PacketSize) throw(TKinokoException); 23: virtual void OnReceiveEventTrailerPacket(void* Packet, long PacketSize) throw(TKinokoException); 24: protected: 25: virtual void SendReportPacket(int EventRate, int DataFlow) throw(TKinokoException); 26: protected: 27: TKinokoTaggedDataSection* _DataSection; 28: TKinokoTaggedDataSectionFormatter* _Formatter; 29: long _EventCount, _DataAmount; 30: int _EventRateIndex, _DataFlowIndex; 31: int _LastReportTime, _ReportInterval; 32: };
Create file KinokoDataFlowMeter.cc and implement the following methods inside.
The constructor and destructor. There is nothing that needs to be done here in this example.
13: TKinokoDataFlowMeter::TKinokoDataFlowMeter(void) 14: { 15: } 16: 17: TKinokoDataFlowMeter::~TKinokoDataFlowMeter() 18: { 19: }
BuildDataSource()
Because the section type of the data generated here is Tagged, object TKinokoTaggedDataSection is created. The data source object's pointer and section name has to be passed to the constructor as in the examle.
21: void TKinokoDataFlowMeter::BuildDataSource(TKinokoDataSource* DataSource) 22: { 23: _DataSection = new TKinokoTaggedDataSection(DataSource, "data_flow"); 24: 25: int DataWidth = 32; 26: _EventRateIndex = _DataSection->AddField("event_rate", DataWidth); 27: _DataFlowIndex = _DataSection->AddField("data_flow", DataWidth); 28: 29: DataSource->AddDataSection(_DataSection); 30: _Formatter = _DataSection->Formatter(); 31: }
Next, necessary fields are added to the generated section. TKinokoTaggedDataSection's AddField() method takes field name and data width as arguments, adds fields to the data section, and returns the index of the field. This index necessary in order to access the field later so it is saved in the member variable.
After building the data section object, tegister it to the data source object and the building of data sources is done. Later when recording the data values to the data packet of the section, that section's formatter oject is necessary, so obtain the formatter object from the section and save it as a member variable.
Next we will deal with methods OnConstruct() and OnDestruct() that are called when an internal object is constructed or destructed. In this example there are no internal objects that need to be constructed, so inside OnConstruct(), clearing the counter and obtaining the current time are the only actions recorded. Also, there is nothing that needs to be processed OnDestruct() so we will not override this method.
33: void TKinokoDataFlowMeter::OnConstruct(void) throw(TKinokoException) 34: { 35: _EventCount = 0; 36: _DataAmount = 0; 37: 38: _LastReportTime = TMushDateTime().AsLong(); 39: _ReportInterval = 10; 40: }
Now we finally come to processes upon receiving data packets. Since OnReceiveDataPacket() is called everytime a data packet is received and OnReceiveEventTrailerPacket() is called everytime a data trailer packet is received, we will override these two methods and implement them.
Since this component only records the data sizes and event counts, the only process that needs to happen upon receiving data packets is recording the size. When event trailers are received, the number of events recorded is incremented.
In OnReceiveEventTrailer(), time elapsed after the last dataflow data was passed on to the stream is calculated, and if this time interval exceeds the time interval setting, event trailers are calculated, a data packet is passed to the stream and the event counter is cleared.
47: void TKinokoDataFlowMeter::OnReceiveDataPacket(void* Packet, long PacketSize) throw(TKinokoException) 48: { 49: _DataAmount += PacketSize; 50: } 51: 52: void TKinokoDataFlowMeter::OnReceiveEventTrailerPacket(void* Packet, long PacketSize) throw(TKinokoException) 53: { 54: _EventCount++; 55: 56: long PastTime = TMushDateTime().AsLong() - _LastReportTime; 57: if (PastTime > _ReportInterval) { 58: int EventRate = _EventCount / PastTime; 59: int DataFlow = _DataAmount / PastTime; 60: SendReportPacket(EventRate, DataFlow); 61: 62: _EventCount -= EventRate * PastTime; 63: _DataAmount -= DataFlow * PastTime; 64: _LastReportTime += PastTime; 65: } 66: }
Sending out data packets is done through method SendReportPacket(). The implementation of this method is shown below.
First memory size necessary for the packet is calculated (lines 70-71). DataSize is the size of the storage part that stores actual data NOT including the packet header. The Formatter's PacketSizeFor() method takes this data size as its argument and calculates the packet size. In the case of this example, Tagged Section has a fixed data size, but take notice that all other section types have different data size every time.
68: void TKinokoDataFlowMeter::SendReportPacket(int EventRate, int DataFlow) throw(TKinokoException) 69: { 70: int DataSize = _Formatter->DataSize(); 71: int PacketSize = _Formatter->PacketSizeFor(DataSize); 72: 73: void* Buffer; 74: do { 75: _OutputStream->NextEntry(Buffer, PacketSize); 76: } while (Buffer == 0); 77: 78: _Formatter->WriteHeaderTo(Buffer, DataSize); 79: _Formatter->WriteTo(Buffer, _EventRateIndex, EventRate); 80: _Formatter->WriteTo(Buffer, _DataFlowIndex, DataFlow); 81: 82: _OutputStream->Flush(Buffer, PacketSize); 83: 84: SendEventTrailerPacket(); 85: }
After calculating packet size, memory size enough for this packet is secured in the stream (lines 73-76). _OutputStream returns 0 when writing is temporary not possible (when the output is buffer and there is no space). In this example, it is set so that when this does happen, the program simply asks for memory again.
Then the formatter object is used to write in values to each field in the packet (lines 78-80). After that, method Flush() of the _OutputStream is used to send out packets to the stream and release buffer memory (line 82. method Flush() does both of these).
After sending every data packet out, call SendEventTrailer(). Do not forget to send the event trailer packet after that, because at the low end of the stream the end of an event is judged with the trailer packet.
The section type Tagged is the only exception where the data size and packet size are the same every single time, so a fixed memory can be secured and reused like the following.
//// write the following in OnConstruct() //// if (_Buffer == 0) { int DataSize = _Formatter->DataSize(); int PacketSize = _Formatter->PacketSizeFor(DataSize); _Buffer = new char[PacketSize]; } - - - - - - - - - - - - - -separate here- - - - - - - - - - - - - - //// the following corresponds to the equivalent of lines 70-83 in SendReportPacket() //// _Formatter->WriteHeaderTo(Buffer, DataSize); _Formatter->WriteTo(Buffer, _EventRateIndex, EventRate); _Formatter->WriteTo(Buffer, _DataFlowIndex, DataFlow); SendPacket(Buffer, PacketSize); - - - - - - - - - - - - - -separate here- - - - - - - - - - - - - - //// write the following in OnDestruct() //// delete[] _Buffer; _Buffer = 0;
With these codes, a component (or its core part)that receiveds data from the input data stream, calculates dataflow, and sends it to the output stream is completed. However, this component absorbes all the data that it received so it is actually not a very realistic component (only the dataflow packet gets passed to the lower ends of the stream). Most likely components in the lower end of the stream would want all data.
So now we will upgrade this component so that all the data will continue down the stream and also the generated dataflow information will too. Actually, this is not very hard. Override the method OnReceivePacket() that always gets called after receiving a packet so that packets get sent again to the stream.
42: void TKinokoDataFlowMeter::OnReceivePacket(void* Packet, long PacketSize) throw(TKinokoException) 43: { 44: SendPacket(Packet, PacketSize); 45: }
Finally, an instance of the class generated by function main() is created and wrapped by component object for the analysiss framework, and then that is wrapped by a component process object.
10: #include "KinokoDataProcessorCom.hh" 11: #include "KinokoDataFlowMeter.hh" 12: 13: int main(int argc, char** argv) 14: { 15: TMushArgumentList ArgumentList(argc, argv); 16: 17: TKinokoDataProcessor* MyDataProcessor = new TKinokoDataFlowMeter(); 18: TKcomComponent* Component = new TKinokoDataProcessorCom(MyDataProcessor); 19: TKcomProcess* ComProcess = new TKcomProcess(Component); 20: 21: try { 22: ComProcess->Start(ArgumentList); 23: } 24: catch (TKcomException &e) { 25: cerr << "ERROR: " << argv[0] << ": " << e << endl; 26: } 27: 28: delete ComProcess; 29: delete Component; 30: delete MyDataProcessor; 31: 32: return 0; 33: }