Message Passing Facility
MPF provides client server message passing. The messages are handled by message routers. Both local and remote message routers are provided.Industry Pack Support
Support is provided for Industry Pack modules.Epics Device Support for the MPF
Device support is provided that communicates with MPF.The support requires vxWorks version 5.3 or later. MPF has been tested with epic base version 3.13.beta12.
All code is written in C++. It is written to minimize dependencies between various components. For example the IP support can be used without MPF, MPF without the IP support, etc. The current implementation uses the standard C library but not the standard C++ library.
The most important goal is to provide a migration path for existing HIDEOS applications. An EPICS Hideos application uses the following hideos components:
EPICS Records
The DTYP and INP fields of records can specify a connection to HIDEOS device support. For some devices, MPF supports the HIDEOS conventions, which means existing EPICS record instances do not have to be modified when converting from HIDEOS to EPICS. In other cases, e.g. the ip330, changes are necessary.
Modified to interface to the new MPF.Hideos IP tasks
These are more generic. IP support is independent of MPF. MPF servers are provided to talk to the IP support.Message Types
Message types are more general than the Hideos messages. Network messages are passed in an architecture independent representation. Hideos only allowed in-line methods. MPF removes this restriction.HIDEOS_SYS
Everything currently residing under the HIDEOS_SYS sub directory is abandoned.MPF provides all the features needed to support existing Hideos IP tasks and the Hideos EPICS device support. Existing support must, however, be modified. In addition it provides the following features not supported by Hideos.
When MPF is used between processors, communication is via a tcp connection. Thus the two processors can reside anywhere as long as it is possible for them to communicate via tcp.
If you have access to the epics CVS repository.
Otherwise obtain a tar file from the APS WWW site and:cd cvs checkout -d mpf epics/unbundled/mpf
cd tar -xvf <file>
Next edit config/RELEASE:
In RELEASE set EPICS_BASE to reference your epics base. This MUST be release baseR3.13.0.beta12 or later.cd cd mpf/config vi RELEASE cd ..
In order to run the tests included with MPF, the following files must be edited in mpf/iocBoot
The inet address "164.54.8.197" must be replaced by the address of your test mv162.iocgpibclient/st.cmd iocgpibserver/st.cmd iocmpfclient/st.cmd iocmpfserver/startServers iocrstclient/st.cmd iocrstserver/st.cmd ioctcpclient/testClientWaitBuffer100 ioctcpclient/testClientWaitNoBuffer ioctcplocal/tcpTestWaitLocalBuffer100 ioctcplocal/tcpTestWaitLocalNoBuffer ioctcpserver/testServerWaitBuffer100 ioctcpserver/testServerWaitNoBuffer iocip330scanclient/st.cmd iocip330scanserver/st.cmd
Now issue the commands:
Now proceed to the next two sections.cd cd mpf gnumake
MV167
MV162boot device : ei processor number : 0 host name : <host> file name : <full path>/vxWorks inet on ethernet (e) : <IPmv167>:<subnet mask> inet on back plane (b): <IPgateway>:<subnet mask> host inet (h) : <IPhost> gateway inet (g) : user (u) : <user> ftp password (pw) (blank = use rsh): <password> flags (f) : 0x0 target name (tn) : ioc167 startup script (s) : <full path name>/st.cmd other (o) :
boot device : sm=0x80000600 processor number : 1 host name : <host> file name : <full path>/vxWorks inet on ethernet (e) : inet on back plane (b): <IPmv162>:<subnet mask> host inet (h) : <IPhost> gateway inet (g) : <IPgateway> user (u) : <user> ftp password (pw) (blank = use rsh): <password> flags (f) : 0x0 target name (tn) : ioc162 startup script (s) : <full path name>/st.cmd
< startEpics
The test consists of the following:
record(calc,"Char8Array:calc$(ind)") { field(CALC,"a=1000?0:a+1") field(FLNK,"Char8Array:solnk$(ind)") field(INPA,"Char8Array:calc$(ind)") } record(stringout,"Char8Array:solnk$(ind)") { field(DOL,"Char8Array:calc$(ind)") field(OMSL,"closed_loop") field(FLNK,"Char8Array:StringIn$(ind)") } record(stringin,"Char8Array:StringIn$(ind)") { field(DTYP,"MPF stringin") field(INP, "#C1 S0 @Char8Array,Char8Array:StringOut$(ind)") }The calc record counts from 0 to 1000 and links to a stringout record which reads the value of the calc record and forward links to a stringin record. The stringin record reads the value from the stringout record and sends it to serverChar8array. The reply from serverChar8array is put into the val field.
mpf/mpfServerApp/testSrc/serverInt32.cc | The echo server for Int32 messages. |
mpf/mpfServerApp/testSrc/ | The makefiles, etc show how to build server applications |
mpf/epicsDevApp/testSrc/DevMpfInt32Test.cc | The device support described above |
mpf/epicsDevApp/testSrc/ | The makefiles, etc. show how to build client applications |
mpf/epicsDevApp/Db/*.db | The databases for the example |
mpf/iocBoot/iocrstclient | All files. NOTE: cdCommands is generated by make |
mpf/iocBoot/iocrstserver | All files. NOTE: cdCommands is generated by make |
Edit the files CONFIG_APP and RELEASE.
In CONFIG_APP add the lines:
In RELEASE add the line:ifdef MPF USR_INCLUDES += -I$(MPF)/include MPF_BIN = $(MPF)/bin/$(T_A) USER_DBDFLAGS += -I $(MPF)/dbd endif
Then in Makefile.Vx statements likeMPF=<full path name>
can be added. In addition .dbd files can be taken from mpf. For example:LIBOBJS += $(MPF_BIN)/mpfLib
include "devMpf.dbd"
<top>/ config/ RELEASE utilApp/ utilSrc/ mpfApp/ kernelSrc/ messageSrc/ libSrc/ testSrc/ ipApp/ src/ mpfServerApp/ serverSrc/ testSrc/ clientInt32.cc serverInt32.cc epicsDevApp/ Db/ adl/ epicsDevSrc/ devStringMpf.cc iocBoot/ iocrstclient/ iocrstserver/ iocmpfclient/ iocmpfserver/ iocmpflocal/ ioctcpclient/ ioctcpserver/
config | This is almost exactly like <top>config described in the 3.13.1 version of makeBaseApp |
RELEASE | EPICS_BASE must be defined correctly. |
utilSrc | DLList, DataFreeList, FreeList, and WatchDog; Builds mpfUtilLib. |
kernelSrc | Contains the MPF code: Message, Routers, Tcp support; Builds mpfKernelLib |
messageSrc | Contains the code to build specific message types. If new messages type are added this is the place. If it proves necessary to support an endless number of message types a better way of building new message types must be developed. Builds mpfMessageLib. |
libSrc | Builds mpfLib which contains mpfUtilLib, mpfKernelLib, and mpfMessageLib. |
testSrc | Contains some low level test programs. Not of interest except for MPF core development. |
ipApp/src | The current ip support: Ip itself, Gpib, Serial Support. |
mpfServerApp/serverSrc | Contains gpibServer and serialServer. gpibServer is a generic server for gpib that is usable for remote gpib communication by any EPICS applications which use the gpib support provided with epics base. serialServer is generic MPF SerialPort support but as described elsewhere is not usable by all serial applications. |
mpfServerApp/testSrc | Contains serverInt32 and serverChar8Array which just echoes the messages sent to them. They are intended for testing and examples. clientInt32 and clientChar8Array are MPF clients. They are good examples of an MPF client and are also used for testing. |
epicsDevApp/Db and adl | Db contains the epics databases for the example in iocrstclient. adl contains the example medm screen. |
epicsDevApp/epicsDevSrc | This contains epics device support. Look at devStringMpf.cc for an example of how to write MPF device support. The code in epicsDevApp/testSrc/epicsDevApp/DevMpfInt32Test.cc is also a good example. |
iocrstclient | Epics (mv167) side of example described previously. |
iocrstserver | Server (mv162) side of example described previously. |
iocmpfclient/iocmpfserver | Tests for remote MPF communication |
iocmpflocal | Test for local MPF communication |
ioctcpclient/ioctcpserver | Test for TCP communication. |
whereINP("C1 S0 @serialServer,<command>")
Messages are sent through a router. Two routers are provided: localRouter and RMR (Remote Message Router). The localRouter passes messages between a client and server residing on the same processor. RMR sends messages between a client and server on different processors via a tcp connection.
typedef void (*clientCallback)(Message *message,void *clientPvt); class MessageClient { public: MessageClient(clientCallback,void *clientPvt); int bind(char *server, int location); int send(Message *message); void *getClientPvt(); private: ... }A client of the message passing system must:
class MessageServer { public: MessageServer(const char* name); void waitForMessage(); Message *receive(); Message *allocReplyMessage(Message *clientMessage,messageType type); int reply(Message *); void setQueueSize(int size); const char *getName() const; void report() const; private: ... }A message server must
Message instances are kept on a free list. Thus malloc is called only if the free list is empty. Free is never called. Each message type has it's own free list.
For servers it provides a report like the following:
iocrstserver> mrr clientRouterList serverRouterList 1 RMRServer stateConnected queueSize 100 inQueue 0 replyQueueFull 0 sendPerSec 240 receivePerSec 240 tcpSendPerSec 10 tcpReceivePerSec 10It provides a report for each server router. It shows the connection state. The meaning of the other fields are:
For clients it produces the following type of report
iocrstclient> mrr clientRouterList 1 RMRClient stateConnected queueSize 100 inQueue 0 sendQueueFull 0 Server Int32 has 4 clients. bindState connected Server Char8Array has 4 clients. bindState connected Server serial[0] has 2 clients. bindState connected Server serial[1] has 2 clients. bindState connected Server serial[2] has 2 clients. bindState connected Server serial[3] has 2 clients. bindState connected Server serial[4] has 2 clients. bindState connected Server serial[5] has 2 clients. bindState connected Server serial[6] has 2 clients. bindState connected Server serial[7] has 2 clients. bindState connected sendPerSec 240 receivePerSec 240 tcpSendPerSec 10 tcpReceivePerSec 10 serverRouterListIt produces a report for each client router. It shows if the router is connected. The queueSize, inQueue, and sendQueueFull provide statistics about the send queue. It also shows a list of each server attached to the router.
iocrstserver> msr "serial[7]" serial[7] queueSize 2 inQueue 0 queueRequests 132171 queueFullResponses 1 replyRequests 132171The meaning of the fields are:
serialPortSniff "UART[5]",1 value = 0 = 0x0 iocrstserver> T31 T34 R31 T35 R34 T00 R35 R00 T31 T34 14154.5.14 R31 T36 R34 T00 R36 R00 T34 T31 R34 T32 164.6.4142 R31 T00 R32 R00 T31 T34 R31 T37 R34 T00 1.2.14174. R37 R00 T31 T34 R31 T38 R34 T00 R38 R00 7.14184.8. T31 T34 R31 T39 R34 T00 R39 R00 T31 T35 14194.9.15 R31 T30 R35 T00 R30 R00 T31 T35 R31 T31 105.0.1511 R35 T00 R31 R00 T34 T31 R34 T33 R31 T00 5.1.41431. R33 R00 T31 T35 R31 T32 R35 T00 R32 R00 3.15125.2. T31 T35 R31 T33 R35 T00 R33 R00 T31 T35 15135.3.15 R31 T34 R35 T00 R34 R00 145.4. serialPortSniff doneThe output displays 10 characters per line. It first shows the hex value preceeded by T (transmit) or R (receive). It then shows the ascii values. Non-printable ascii values are shown as ".".
Each vxWorks system using the message passing system needs the following command in the startup file:
routerInitIf local communication is desired then the following command must appear:
localMessageRouterStart(location)The parameter is the location of the local server. Note that the client specifies a location when it binds to a server. Each cpu can start a local router.
If tcp communication is desired then for each remote system the client must have the following command:
tcpMessageRouterClientStart(location,port,"address",bufSize,queueSize)
tcpMessageRouterServerStart(location,port,"address",bufSize,queueSize)
<message class> *pmessage = new <message class>;
for example an Int32 message can be allocated as follows:
Int32Message *pmessage = new Int32Message;
Int32Message *pmessage =(Int32Message*)pMessageServer->
allocReplyMessage(preceive,messageTypeInt32);
where preceive must be a message received from the client. The reason is that the receive message contains information describing where the client is located.
delete pmessage;
Thus the server must delete any message received from a client and a client must delete reply messages from the server.
Client and server code normally only call new, delete, getType, and message specific methods. The other methods are called by MPF itself. A message is allocated via a call to new and released via a call to delete. When delete is called the message is placed on a free list.class Message { public: messageType getType(); virtual ~Message(); virtual void init() = 0; virtual int toBuffer(char **buffer); virtual int fromBuffer(const char **buffer); virtual int fromBufferSwitch(const char **buffer); virtual void print() const; static Message *allocate(messageType type); protected: Message(messageType type); .... }
The virtual methods must be implement by any class that derives from Message. The init method is called whenever a messages in allocated. The toBuffer and fromBuffer methods are called to put and take messages from a buffer that passes over a communication link.
MPF provides a header file "mpfType.h" which defines typedefs for int16, uint16, int32, uint32, float32, and float64. MPF provides static methods to transfer the following to/from network buffers:
Field | Recommended Meaning |
timeout | The timeout the server can use it it has to wait for a device. It should be in seconds. |
cmd | A command. Servers can supply a header file defining an enum for the commands. |
status | A status value. |
address | For anything that is an int32 and can be interpreted as an address. |
extra | An extra int32 word that client/server can use for anything they want as long as they agree on the meaning. |
The value field is just a pointer to a char8 array The standard C library routines such as ::strcpy and ::memcpy can be used on the value field.
Char8ArrayMessage provides the following methods for allocating and
freeing the string value.
method | usage |
allocValue | This is a static method which looks for the smallest freelist that can provide the needed space. If the requested size is larger than the size for the largest freelist, it calls new to allocate space. |
setSize | Sets the current size for the array. This size MUST be less than that allocated by allocValue. |
getSize | Gets the current array size. |
getMaxSize | This gets the size allocated by the call to allocValue |
freeValue | This puts storage obtained by allocValue back on the appropriate free list or calls delete if the storage was obtained via a call to new. This method is called automatically when a Char8Array message is deleted. |
Field | Recommended Meaning |
value | The Int32 value being passed. |
method | usage |
allocValue | This is a static method which looks for the smallest freelist that can provide the needed space. If the requested size is larger than the size for the largest freelist, it calls new to allocate space. |
setLength | Sets the current length for the array. This length MUST be less than that allocated by allocValue. |
getLength | Gets the current array length. |
getMaxLength | This gets the length allocated by the call to allocValue |
freeValue | This puts storage obtained by allocValue back on the appropriate free list or calls delete if the storage was obtained via a call to new. This method is called automatically when an Int32Array message is deleted. |
Field | Recommended Meaning |
value | The Float64 value being passed |
method | usage |
allocValue | This is a static method which looks for the smallest freelist that can provide the needed space. If the requested size is larger than the size for the largest freelist, it calls new to allocate space. |
setLength | Sets the current length for the array. This length MUST be less than that allocated by allocValue. |
getLength | Gets the current array length. |
getMaxLength | This gets the length allocated by the call to allocValue |
freeValue | This puts storage obtained by allocValue back on the appropriate free list or calls delete if the storage was obtained via a call to new. This method is called automatically when an Float64Array message is deleted. |
field | usage |
baud | Baud rate. 0 means don't change |
stopBits | Stop bits. Normally 1 or 2. 0 means don't change. |
bitsPerChar | Bits per character. Normally 5,6,7, or 8. 0 means don't change. |
parity | Single character. Normally 'E', 'O', or 'N'. 0 means don't change. |
flowControl | Single character. Normally 'H' or 'N'. 0 means don't change. |
// return codes for startIO and completeIO. #define MPF_OK 0 #define MPF_NoConvert 2
class DevMpf { public: DevMpf(dbCommon*,link*,bool iointValid); // Following must be implemented by device support modules virtual long startIO(dbCommon*)=0; // start async IO virtual long completeIO(dbCommon*,Message *)=0; // end async IO // the following can be implemented by device support modules virtual void connectIO(dbCommon*,Message *); // connection message virtual long convert(dbCommon*,int pass); // do linear conversion // send a message to MPF server with no reply expected int send(Message*); // send a message to MPF server and wait for reply via completeIO int sendReply(Message*); // This routine gets a pointer to the user portion of the parm field const char* getUserParm() const { return((const char*)userParm); } long getStatus() const { return(status);} // Following are DSET routines static long read_write(void*); // generic DSET read/write routine static long ioint(int cmd,dbCommon*,IOSCANPVT* iopvt); // DSET i/o intr static long linconv(void*,int); // calls convert private: ...
}
method | Usage | meaning |
DevMpf | Constructor | The constructor must be given the address of the record and link (INP or OUT). iointValid specifies if the record can be set to be I/O Intr scanned. The time-out value is for waiting for responses from the MPF server. |
startIO | Called by DevMpf | When a request is made to process a record this is called only when the following conditions are all true: The server is connected, PACT is false, and no reply message from the server is available. |
completeIO | Called by DevMpf | DevMpf calls this whenever a reply message is received from the server. |
connectIO | Called by DevMpf
Default supplied by DevMpf. |
DevMpf calls this when it receives a ConnectMessage. DevMpf provides a default implementation. For devices that generate unsolicited output the device support can implement this method. Device support, when it connects to a server, sends a message to the server stating that it wants to receive the unsolicited output. The server then sends a message to the client every time it receives a complete set of output from the device. After sending the message, DevMpf::connectIO is called to ensure correct behavior. |
convert | Called by DevMpf, Default supplied by DevMpf | This is the DSET convert routine for ai, ao type records. The default version does nothing. Device support derived from base can provide it's own version. |
send | Called by derived class | Send a message to the server without expecting a reply. |
sendReply | Called by derived class | Send a message to the server and complete the first phase of record processing. When the server sends a reply call completeIO. |
getUserParm | Called by derived class | Get the portion of the parm of the INP or OUT link that follows the server name. |
getStatus | Called by derived class | Get the status. A 0 value is success. Any other value is device dependent. |
read_write | Called by record support | This is the read or write DSET routine. Handled automatically by DevMpf |
ioint | Called by record support | This is DSET get_io_intr routine. Handled automatically by DevMpf |
linconv | Called by record support | This is DSET linr_conv routine. It calls convert. |
The INP or OUT field MUST have the format:
field(INP or OUT, "#C<location> S<signal> @<server>,<deviceSpecific>")
parameter | meaning |
location | Location of the server. Must be integer which determines which message router handles messages. |
signal | For optional use by device support derived from DevMpf |
server | Name of the server which receives messages. |
deviceSpecific | For optional use by device support derived from DevMpf. Either a blank or comma can separate the server name from the device specific information. |
The first is used for record types that support linear conversions, e.g. ai and ao, and the second for other record types.MAKE_LINCONV_DSET(<dset name>,<dev init>) MAKE_DSET(<dset name>,<dev init>)
FreeList provides the same functionality as the freeList facility provided with epics base, in fact it is just freeList redone in C++.
DLList is a double linked list class that does not require nodes to be embedded in objects placed in a list. This is different than the ellList facility provided by epics base.
WatchDog is a class that provides functionality similar to the vxWorks wdLib. The major exception is that the user supplied callback is called by a WatchDog supplied task rather than being called at interrupt level.
DataFreeList is a class for data free lists. It provides free lists of sizes ranging from 16 bytes to 4096 bytes. It is used by the array message types.
Reboot can be used to turn off interrupts when a soft reboot is being performed.
These classes are not described in this document. If you want to use
them in new code look at examples in existing code.
IndustryPack Support consists of the following components:
IndustryPackCarrier
The base class for an IndustryPack carrier. It allows access to IndustryPack carrier configuration and control independent of the specific carrier hardware board.IndustryPackSite
The base class for a specific site on a specific carrier, i.e. the place where an IP module resides.IndustryPackMVME162
Implements the IndustryPackCarrier and IndustryPackSite classes for the MVME162 hardware board.IndustryPackModule
Provides IndustryPack Module configuration independent of the specific IP carrier. This class is used by specific module support such as OctalUART and GpibGsTi9914.
class IndustryPackCarrier { public: IndustyPackCarrier(); int registerName(const char *carrierName); static IndustryPackCarrier *find(const char *carrierName); IndustryPackSite *findSite(const char *siteName); IndustryPackSite *addSite(IndustryPackSite *pSite); const char *getCarrierName(); virtual volatile void *allocMemMapIP(int size) = 0; private: ... };
class IndustryPackSite { public: IndustryPackSite(); const char *getSiteName(); virtual volatile void* allocMemMapIP(int size) = 0; virtual volatile IP_ID_PROM* getMemBaseID() = 0; virtual volatile void* getMemBaseIO() = 0; virtual volatile void* getMemBaseIP() = 0; virtual int intConfig(int num) = 0; virtual void intEnable(int num) = 0; virtual void intDisable(int num) = 0; protected: const char *siteName; IndustryPackCarrier *pIpCarrier; private: };
Method | Implementor | Usage | Description |
registerName | IndustryPackCarrier | Derived class. | Register the carrier name. |
find | IndustryPackCarrier | Public use. | Find and return IndustryPackCarrier. |
findSite | IndustryPackCarrier | IndustryPackModule | Find and return site. |
addSite | IndustryPackCarrier | Called by derived class. | Add a site. |
getCarrierName | IndustryPackCarrier | Public use. | Return carrier name. |
allocMemMapIP | Derived Class | IndustryPackModule | Allocate memory. |
getSiteName | IndustryPackSite | Derived class. | Get site name |
allocMemMapIP | Derived class. | IndustryPackModule | See IndustryPackModule |
getMemBaseID | Derived class. | IndustryPackModule | See IndustryPackModule |
getMemBaseIO | Derived class. | IndustryPackModule | See IndustryPackModule |
getMemBaseIP | Derived class. | IndustryPackModule | See IndustryPackModule |
intConfig | Derived class. | IndustryPackModule | See IndustryPackModule |
intEnable | Derived class. | IndustryPackModule | See IndustryPackModule |
intDisable | Derived class. | IndustryPackModule | See IndustryPackModule |
initIndustryPackMVME162("carrierName",memoryBase)
Parameter | Value |
carrierName | The IP carrier name to register |
memoryBase | The base address for mapping IP module memory to local memory |
class IndustryPackModule { public: static IndustryPackModule * createIndustryPackModule( const char *moduleName, const char *carrierName, const char *siteName); static IndustryPackModule *find(const char *moduleName); static IndustryPackModule *find( const char *carrierName, const char *siteName); volatile void *allocMemMapIP(int size); int intConfig(int num); int intEnable(int num); int intDisable(int num); volatile IP_ID_PROM *getMemBaseID(); volatile void *getMemBaseIO(); volatile void *getMemBaseIP(); int getMemSizeIP(); unsigned char getManufacturer(); unsigned char getModel(); unsigned char getRevision();
private: ... };All public methods are implemented by IndustryPackModule and used by module specific code.
Method | Description |
createIndustryPackModule | Creates an instance of IndustryPackModule. It prints an error message and returns 0 if carrierName or siteName does not exist or if moduleName already exists. |
find | Two find methods are provided. One finds modules by module name. The other finds modules by carrier and site name. |
allocMemMapIP | Allocate IP memory. |
intConfig | Interrupt Configure. The argumenmt must be 0 or 1. |
intEnable | Enable interrupt. The argumenmt must be 0 or 1. |
intDisable | Disable interrupt. The argumenmt must be 0 or 1. |
getMemBaseID | Get the address of the module ID space. |
getMemBaseIO | Get the address of the module IO space. |
getMemBaseIP | Get the address of the module IP space. |
getMemSizeIP | Get the size of allocated IP memory. |
getManufacturer | Get the manufacter ID. |
getModel | Get the model ID. |
getRevision | Get the revision |
SerialPort
An abstract base class for a serial port. It allows access to a serial port independent of the specific serial interface.OctalSerial
Implements SerialPort for the Green Springs Quad/Octal IP carrier.serialServer
A generic MPF server for SerialPort. It accepts Char8Array messages. It can accept input from the serial port if the input has a known single character terminator.devStringMpf
Provides EPICS device support to send a string to a MPF server and receive a reply stringSerialPort and OctalSerial can be used independent of MPF. SerialServer is the "glue" between MPF and SerialPort. DevStringMpf provides EPICS device support to send and receive string messages. It can be used with MPF servers other than serialServer. For example the MPF provides serverChar8Array, which just echoes whatever messages it is sent.
Because devices attached to serial ports have private protocols, serialServer and devStringMpf may not be usable. In such cases a new server and device support must be supplied. serialServer and devStringMpf provide a model of how to write special support. This section provides details about SerialPort so the the code for serialServer is easier to follow.
SerialPort is independent of MPF and Industry Pack. It can be used as the base class for any type of serial port, i.e. VME, IP, etc. The code for OctalSerial provides a model of what needs to be done to derive from SerialPort.
enum byteHandlerRC {byteHandlerOK,byteHandlerEndRead,byteHandlerError}; enum serialPortStatus {serialPortSuccess, serialPortTimeout, serialPortFailure};
typedef byteHandlerRC (*BYTE_HANDLER)(void* parm,unsigned char byte);
typedef void (*PEEK_HANDLER) (void* parm,bool isReceive, unsigned char byte); // If registered PEEK_HANDLER is called for every byte transmitted or received
class SerialPort { public: SerialPort(); public: SerialPort(); int registerName(const char *portName); static SerialPort *find(const char *portName); static SerialPort *bind(const char *portName,void* bhparm,BYTE_HANDLER bh); void release(); static bool setSniffHandler( const char *portName,void* phParm,PEEK_HANDLER ph); static void freeSniffHandler(const char *portName); serialPortStatus getStatus() { return(ioStatus);} virtual serialPortStatus write( unsigned char *data, int nbytes, int timeoutSeconds) = 0; virtual serialPortStatus read(int timeoutSeconds) =0; virtual serialPortStatus writeRead( unsigned char *data, int nbytes, int timeoutSeconds) =0; virtual bool config( int baud, int stopBits, int bitsPerChar, char parity, char flowControl) = 0; protected: void startIoTimeout(int seconds); void waitIo(); requestType request; serialPortStatus ioStatus; BYTE_HANDLER bh; void* bhParm; PEEK_HANDLER ph; void* phParm; SEM_ID ioComplete;
private:
...
};
Method | Implementor | Usage | Description |
find | SerialPort | Called by servers | Given the name of a port, find and return SerialPort. |
bind | SerialPort | Called by servers | Bind the caller to the specified port and if successful return the address of the SerialPort. Only one caller can be bound to a particular port. After a successful bind the caller may call write, read, writeRead, and release. |
setSniffHandler | SerialPort | Called by serialPortSniff or other diagnostics | SerialPort locates the serial port and sets the field ph. The Derived Class is expected to call the peek handler for every character trasmitted and received if ph has a value. |
freeSniffHandler | SerialPort | Called by serialPortSniff or other diagnostics | SerialPort locates the serial port and clears the field ph. |
release | SerialPort | Called by servers | Release the caller that is bound to the port. |
getStatus | SerialPort | Called by servers | Return the status of the last I/O request. |
write | Derived Class | Called by servers. | Send data to the serial port. |
read | Derived Class | Called by servers | Read data from the serial port. |
writeRead | Derived Class | Called by servers | Send data to the serial port while simultaneously reading. |
registerName | SerialPort | Called by derived class | Register the serial port. |
startIoTimeout | SerialPort | Called by derived class | Start a time out request. |
waitIo | SerialPort | Called by derived class | Wait for I/O to complete or timeout. |
The bind call must supply a byte handler which is called for every input
character received by the serial port. The byte handler returns one of
the following values.
byteHandlerOK | If any read request is outstanding, just continue. |
byteHandlerEndRead | If any read request is outstanding terminate it with success. |
byteHandlerError | If any read request is outstanding terminate it with failure. |
initOctalUART("moduleName","carrierName","carrierSite",nports,intVec)
parameter | value |
moduleName | The IP Module name to be registered. |
carrierName | The name of the IP Carrier. |
carrierSite | The mezzanine slot location on a carrier. Eg: IP_a, IP_b, IP_c, IP_d |
nports | The number of ports. Must be 4 or 8. |
intVec | First interrupt vector to be used. |
After the OctalUart is initialized each port can be initialized via
the command.
initOctalUARTPort("portName","moduleName",port,baud,"parity", stop_bits,bits_char,"flowType")
parameter | value |
portName | portName which is registered for port. |
moduleName | The IP module name for identification. |
port | port number. Must be between 0 and 7. |
baud | 1200, 2400, 4800, 9600, 19200, 38400 |
parity | O, E, N for (Odd, Even, None) |
stop_bits | 1,2 |
bits_char | 5,6,7,8 |
flowType | H, N means (? , ?) |
When it receives a SerialConfigMessage it calls SerialPort::config and returns an Int32Message. A status of 0 means success.
When it receives a Char8Array message it returns either an Int32
message (failure) or a Char8Array message (success). The header file serialServer.h
describes how it handles the request message. In addition to the value,
it uses the timeout, cmd, and extra fields of the request message. The
timeout is in seconds. The cmd field can have the values:
cmdWrite | Write with no response from serial port.
Failure returns Int32Message. Success returns Char8ArrayMessage of length 0. |
cmdRead | Read message from serial port.
Failure returns Int32Message. Success returns Char8ArrayMessage. |
cmdWriteRead | Write and also get response.
Failure returns Int32Message. Success returns Char8ArrayMessage. |
cmdSetEom | value contains end of message (up to 2 characters).
Returns Int32Message. status is 0 for success. |
The following command initializes a serial server:
initSerialServer("serverName","portName",bufSize,"eomstr")
parameter | value |
serverName | The MPF name for this server. |
portName | The name of the serial port to which the server is attached. |
bufSize | The maximum size for an input message from the serial port. |
eomstr | The initial input terminator. It must be one or two characters. |
record(stringin,"<pvname>") { field(DTYP,"MPF stringin") field(INP, "#C<location> S0 @<server>,<inputRecord>") }
pvname | Record name |
location | Location of message server. |
server | Name of the message server |
inputRecord | Name of record (and optionally field) from which a serial output string is obtained. |
When the record is processed the following happens:
Restriction: Only one of GpibHideosLocal or GpibHideosRemote can be used on a particular processor. Removing this restriction requires changes to the version of drvGpib supplied with base.
Device support sends Int32Messages with the following info:
address - channelThe server returns
value - gain
value - 32 bit unsigned integer from 0 to 0xffffThe following command initializes the server:
status - 0 means success. -1 means failure
initIp330Scan("modulename","carrier","site", "type","range",intVec,queueSize)
modulename | The IP Module name to be registered. This is also the server name |
carrier | The name of the IP Carrier |
site | The slot location on the carrier. |
type | The type: Must be (D,S) for (Differential, Single Ended) |
range | Range: Must be one of -5to5, -10to10, 0to5, 0to10 |
intVec | The interrupt vector location |
queueSize | Maximum number of messages to queue |
The device definition is:
Analog input records have the fields defined as follows:device(ai,VME_IO,devAiIp330Scan,"ip330Scan")
field(SCAN,"1 second") field(DTYP,"ip330Scan") field(INP,"#C{card} S{signal} @{servername},{gain}
card | The location of the server. |
signal | The input channel. For Differential 0-15. For single ended 0 - 31. |
servername | Must match the modulename specified with initIp330Scan. |
gain | Optional. If given must be 0,1,2,or 3. Default is 0. |
The following summerizes the configuration options. The DIP Switch Settings
column correspond to the value switches 1 - 10. A value of 101100001 corresponds
to switches 1,3,4,9 on and 2,5,6,7,8 off.
Range | DIP Switch | Gain | Full Value | Low Value |
-5to5 | 1011000010 | 0 | 5 | -5 |
-5to5 | 1011000010 | 1 | 2.5 | -2.5 |
-5to5 | 1011000010 | 2 | 1.25 | -1.25 |
-5to5 | 1011000010 | 3 | 0.625 | -.0625 |
-10to10 | 0100110010 | 0 | 10 | -10 |
-10to10 | 0100110010 | 1 | 5 | -5 |
-10to10 | 0100110010 | 2 | 2.5 | -2.5 |
-10to10 | 0100110010 | 3 | 1.25 | -1.25 |
0to5 | 1010100100 | 0 | 5 | 0 |
0to5 | 1010100100 | 1 | 2.5 | 0 |
0to5 | 1010100100 | 2 | 1.25 | 0 |
0to5 | 1010100100 | 3 | 0.625 | 0 |
0to10 | 1011001000 | 0 | 10 | 0 |
0to10 | 1011001000 | 1 | 5 | 0 |
0to10 | 1011001000 | 2 | 2.5 | 0 |
0to10 | 1011001000 | 3 | 1.25 | 0 |
Note for Hideos Users
This support is similar to the hideos ip330adc support. The main differences for database records are:
Class DevAiIp330Scan implements device support which communicates with the ip330Server described below. It derives from class DevMpf. DevMpf calls the methods DevAiIp330Scan, startIO, completeIO, and convert.class DevAiIp330Scan : public DevMpf { public: DevAiIp330Scan(dbCommon*,DBLINK*); long startIO(dbCommon* pr); long completeIO(dbCommon* pr,Message* m); long convert(dbCommon* pr,int pass); static long dev_init(void*); private: int channel; int gain; };
The macro MAKE_LINCONV_DSET creates Device Support Entry Table devAiIp330Scan. DevAiIp330Scan::dev_init will be called by aiRecord during the second phase of record initialization.MAKE_LINCONV_DSET(devAiIp330Scan,DevAiIp330Scan::dev_init)
Constructor DevAiIp330Scan is called by DevAiIp330Scan::dev_init. It first calls DevMpf(pr,l,false) and then does private initialization. DevMpf takes care of establishing communication with the server, and it also calls startIO and completeIO.DevAiIp330Scan::DevAiIp330Scan(dbCommon* pr,DBLINK* l) : DevMpf(pr,l,false) { vmeio* io = (vmeio*)&(l->value); gain = 0; channel=io->signal; aiRecord* prec=(aiRecord*)pr; const char* up = getUserParm(); if(up && strlen(up)>0) { // decode the parm field, format: "gain" if((sscanf(up,"%d",&gain)<1) || gain<0 || gain>3) { epicsPrintf("%s Illegal INP parm field\n", prec->name); prec->pact=TRUE; } } convert((dbCommon *)prec,1); return; }
startIO is called by DevMpf when dbProcess requests record processing. It just sends a message to the server and returns.long DevAiIp330Scan::startIO(dbCommon* ) { Int32Message *message = new Int32Message; message->value =gain; message->address =channel; return(sendReply(message)); }
completeIO is called when the server returns a message. It checks that it has received a valid message, sets rval equal to the new value received from the server, deletes the message, and returns.long DevAiIp330Scan::completeIO(dbCommon* pr,Message* m) { aiRecord* pai = (aiRecord*)pr; if((m->getType()) != messageTypeInt32) { epicsPrintf("%s ::completeIO illegal message type %d\n", pai->name,m->getType()); recGblSetSevr(pai,READ_ALARM,INVALID_ALARM); delete m; return(MPF_NoConvert); } Int32Message *pint32Message = (Int32Message *)m; if(pint32Message->status) { recGblSetSevr(pai,READ_ALARM,INVALID_ALARM); delete m; return(MPF_NoConvert); } pai->rval=pint32Message->value; pai->udf=0; delete m; return(MPF_OK); }
convert is called by the aiRecord when conversion fields are changed and also by the constructor DevAiIp330Scan. This implementation knows that ip330Scan always returns a value between 0 and 0xffff.long DevAiIp330Scan::convert(dbCommon* pr,int /*pass*/) { aiRecord* pai = (aiRecord*)pr; pai->eslo=(pai->eguf-pai->egul)/(double)0xffff; return 0; }
dev_init calls the constructor and returns a status obtained from DevMpf.long DevAiIp330Scan::dev_init(void* v) { aiRecord* pr = (aiRecord*)v; DevAiIp330Scan* pDevAiIp330Scan = new DevAiIp330Scan((dbCommon*)pr,&(pr->inp)); return pDevAiIp330Scan->getStatus(); }
This is a class definition that connects ip330Server to Ip330Scan.class Ip330ScanServer { public: Ip330Scan *pIp330Scan; MessageServer *pMessageServer; static void ip330Server(Ip330ScanServer *); };
The initialization routine is called from the server st.cmd file. It does the following:static char taskname[] = "ip330"; extern "C" int initIp330Scan( const char *moduleName, const char *carrierName, const char *siteName, const char *typeString,const char *rangeString, int intVec, int queueSize) { Ip330Scan *pIp330Scan = Ip330Scan::init(moduleName,carrierName,siteName, typeString,rangeString,intVec); if(!pIp330Scan) return(0); Ip330ScanServer *pIp330ScanServer = new Ip330ScanServer; pIp330ScanServer->pIp330Scan = pIp330Scan; pIp330ScanServer->pMessageServer = new MessageServer(moduleName,queueSize); int taskId = taskSpawn(taskname,100,VX_FP_TASK,2000, (FUNCPTR)Ip330ScanServer::ip330Server,(int)pIp330ScanServer, 0,0,0,0,0,0,0,0,0); if(taskId==ERROR) printf("%s ip330Server taskSpawn Failure\n", pIp330ScanServer->pMessageServer->getName()); return(0); }
ip330Server accepts MPF messages, calls Ip330Scan methods, and sends a reply message.void Ip330ScanServer::ip330Server(Ip330ScanServer *pIp330ScanServer) { while(true) { MessageServer *pMessageServer = pIp330ScanServer->pMessageServer; Ip330Scan *pIp330Scan = pIp330ScanServer->pIp330Scan; pMessageServer->waitForMessage(); Message *inmsg; while((inmsg = pMessageServer->receive())) { if(inmsg->getType()!=messageTypeInt32) { printf("%s ip330Server got illegal message type %d\n", pMessageServer->getName(), inmsg->getType()); delete inmsg; } else { Int32Message *pmessage = (Int32Message *)inmsg; pmessage->status = 0; int gain = pmessage->value; int channel = pmessage->address; if(pIp330Scan->setGain( gain,channel)) pmessage->status= -1; if(pmessage->status==0) { pmessage->value = (int32)pIp330Scan->getValue(channel); } pMessageServer->reply(pmessage); } } } }
This is a typical example of the class definition for an IP support module. It has the following public methods: init, getValue, and setGain. getValue and setGain are specific to the IP330 support. The init routine is typical of what is needed for many IP support modules. Note that the class constructor is private because a new instance should only be created via a call to init.class WatchDog; class MessageServer; class IndustryPackModule; class ip330ADCregs; class ip330ADCchans; enum signalType {differential, singleEnded}; class Ip330Scan { public: static Ip330Scan * init( const char *moduleName, const char *carrierName, const char *siteName, const char *type,const char *range,int intVec); int getValue(int channel); int setGain(int gain,int channel); private: Ip330Scan(IndustryPackModule *pIndustryPackModule, signalType type, int range, int intVec); ... };
Ip330Scan * Ip330Scan::init( const char *moduleName, const char *carrierName, const char *siteName, const char *typeString,const char *rangeString, int intVec) { IndustryPackModule *pIPM = IndustryPackModule::createIndustryPackModule( moduleName,carrierName,siteName); if(!pIPM) return(0); unsigned char manufacturer = pIPM->getManufacturer(); unsigned char model = pIPM->getModel(); if(manufacturer!=ACROMAG_ID) { printf("initIp330Scan manufacturer 0x%x not ACROMAG_ID\n", manufacturer); return(0); } if(model!=ACRO_IP330) { printf("initIp330Scan model 0x%x not a ACRO_IP330\n",model); return(0); } .... // device specific details Ip330Scan *pIp330Scan = new Ip330Scan(pIPM,type,range,intVec); return(pIp330Scan); }This code creates and binds to a new IndustryPackModule. It then checks that the IP module is what it expects. Finally it creates a new Ip330Scan object.
Ip330Scan:: Ip330Scan( IndustryPackModule *pIndustryPackModule, signalType type, int range, int intVec) : pIPM(pIndustryPackModule), type(type), range(range), accumulated(0) { ... // device specific details pIPM->intConfig(0); pIPM->intConfig(1); if(intConnect(INUM_TO_IVEC(intVec),(VOIDFUNCPTR)intFunc,(int)this)==ERROR){ printf("Ip330Scan intConnect Failure\n"); } pIPM->intDisable(0); pIPM->intClear(0); ... // device specific details }This shows how to connect to an interrupt location.
The local tests were run on an mv162. The remote tests were run on an mv167 client and mv162 server. I think they were both 25 MHz processors. msgPerSec and tcpPerSec are round trip, i.e. a send and receive. The message in all cases was just an Int32 value. The no buffer case means that a value was sent and the client waited for that response before sending the next message. The buffer case means that the specified number of values were sent before waiting for the responses.
Test | msgPerSec | tcpPerSec | client idle | server idle |
tcpLocalNoBuffer | 237 | 237 | 15% | - |
tcpLocalBuffer100 | 6050 | 60 | 5% | - |
tcpRemoteNoBuffer | 485 | 485 | 54% | 39% |
tcpRemoteBuffer100 | 10050 | 100 | 52% | 40% |
Test | msgPerSec | tcpPerSec | client idle | server idle |
mpfLocalNoBuffer | 3705 | - | 0% | - |
mpfLocalBuffer100 | 7966 | - | 0% | - |
mpfRemoteNoBuffer | 331 | 331 | 52% | 40% |
mpfRemoteBuffer100 | 1440 | 14 | 52% | 43% |
mpfRemoteBuffer1000 | 2100 | 16 | 31% | 18% |
Test | msgPerSec | tcpPerSec | client idle | server idle |
local | 240 | - | 51% | - |
remote | 240 | 10 | 64% | 73% |
Test | msgPerSec | interrupt | idle | total |
local | 159 | 76% | 0% | 89% |
remote | 156 | 65% | 10% | 91% |