commit 90a6fd4af9fd6cb04b46959d4889e3544d620b59 Author: zimoch Date: Tue Jul 31 09:19:37 2007 +0000 version 2.2 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..bce1710 --- /dev/null +++ b/Makefile @@ -0,0 +1,44 @@ +include /ioc/tools/driver.makefile +#EXCLUDE_VERSIONS = 3.13.2 +EXCLUDE_VERSIONS = 3.13 +PRIJECT=stream + +DOCUDIR = doc +SOURCES += $(wildcard src/*.c) +SOURCES += $(wildcard src/*.cc) + +DBDS = stream.dbd + +BUSSES += AsynDriver +FORMATS += Enum +FORMATS += BCD +FORMATS += Raw +FORMATS += Binary +FORMATS += Checksum +RECORDTYPES += aai aao +RECORDTYPES += ao ai +RECORDTYPES += bo bi +RECORDTYPES += mbbo mbbi +RECORDTYPES += mbboDirect mbbiDirect +RECORDTYPES += longout longin +RECORDTYPES += stringout stringin +RECORDTYPES += waveform +RECORDTYPES += calcout + +StreamCore.o: streamReferences + +streamReferences: + @for i in $(BUSSES); \ + do echo "extern void* ref_$${i}Interface;"; \ + echo "void* p$$i = ref_$${i}Interface;"; \ + done > $@ + @for i in $(FORMATS); \ + do echo "extern void* ref_$${i}Converter;"; \ + echo "void* p$$i = ref_$${i}Converter;"; \ + done >> $@ + +stream.dbd: + @for r in $(RECORDTYPES); \ + do echo "device($$r,INST_IO,dev$${r}Stream,\"stream\")"; \ + done > $@ + @echo "driver(stream)" >> $@ diff --git a/doc/EPICS.gif b/doc/EPICS.gif new file mode 100644 index 0000000..1d4a4b7 Binary files /dev/null and b/doc/EPICS.gif differ diff --git a/doc/PSI.gif b/doc/PSI.gif new file mode 100644 index 0000000..ccbcda8 Binary files /dev/null and b/doc/PSI.gif differ diff --git a/doc/SLS.gif b/doc/SLS.gif new file mode 100644 index 0000000..08a8734 Binary files /dev/null and b/doc/SLS.gif differ diff --git a/doc/aai.html b/doc/aai.html new file mode 100644 index 0000000..f50e3d9 --- /dev/null +++ b/doc/aai.html @@ -0,0 +1,128 @@ + + + +StreamDevice: aai Records + + + + + + +

StreamDevice: aai Records

+

+Note: aai record support is disabled per default. +Enable it in src/CONFIG_STREAM. +

+ +

Normal Operation

+

+With aai records, the format converter is applied to +each element. Between the elements, a separator is printed +or expected as specified by the Separator +variable in the protocol. +When parsing input, a space as the first character of the +Separator matches any number of any whitespace +characters. +

+

+During input, a maximum of NELM elements is +read and NORD is updated accordingly. +Parsing of elements stops when the separator does not match, +conversion fails, or the end of the input is reached. +A minimum of one element must be available. +

+

+During output, the first NORD elements are +written. +

+

+The format data type must be convertible to or from the type +specified in the FTVL field. +The variable x[i] stands for one element of +the written or read value. +

+
+
DOUBLE format (e.g. %f):
+
+ Output:x[i]=double(VAL[i])
+ FTVL can be "DOUBLE", "FLOAT", + "LONG", "ULONG", "SHORT", + "USHORT", "CHAR", "UCHAR", + or "ENUM" (which is treated as "USHORT").
+ Input: VAL[i]=FTVL(x[i])
+ FTVL must be "FLOAT" or "DOUBLE" +
+
LONG or ENUM format (e.g. %i or %{):
+
+ Output: x[i]=long(VAL[i])
+ FTVL can be + "LONG", "ULONG", "SHORT", + "USHORT", "CHAR", "UCHAR", + or "ENUM" (which is treated as "USHORT").
+ Signed values are sign-extended to long, unsigned values are + zero-extended to long before converting them.
+ Input: VAL[i]=FTVL(x[i])
+ FTVL can be "DOUBLE", "FLOAT", + "LONG", "ULONG", "SHORT", + "USHORT", "CHAR", "UCHAR", + or "ENUM" (which is treated as "USHORT").
+ The value is truncated to the least significant bytes if + FTVL has a smaller data size than long. +
+
STRING format (e.g. %s):
+
+
+
If FTVL=="STRING":
+
+ Output: x[i]=VAL[i]
+ Input: VAL[i]=x[i]
+ Note that this is an array of strings, not an array of characters. +
+
If FTVL=="CHAR" or FTVL="UCHAR":
+
+ In this case, the complete aai is treated as a large + single string of size NORD. + No separators are printed or expected.
+ Output: x=range(VAL,0,NORD)
+ The first NORD characters are printed, + which might be less than NELM.
+ Input: VAL=x, NORD=length(x)
+ A maximum of NELM-1 characters can be read. + NORD is updated to the index of the first of the + trailing zeros. + Usually, this is the same as the string length. +
+
+ Other values of FTVL are not allowed for this format. +
+
+ +

Initialization

+

+ During initialization, the @init handler is executed, if + present. All format converters work like in normal operation. +

+ +
+

+aao +ai +ao +bi +bo +mbbi +mbbo +mbbiDirect +mbboDirect +longin +longout +stringin +stringout +calcout +scalcout +

+

Dirk Zimoch, 2006

+ + + diff --git a/doc/aao.html b/doc/aao.html new file mode 100644 index 0000000..24c4ba4 --- /dev/null +++ b/doc/aao.html @@ -0,0 +1,128 @@ + + + +StreamDevice: aao Records + + + + + + +

StreamDevice: aao Records

+

+Note: aao record support is disabled per default. +Enable it in src/CONFIG_STREAM. +

+ +

Normal Operation

+

+With aao records, the format converter is applied to +each element. Between the elements, a separator is printed +or expected as specified by the Separator +variable in the protocol. +When parsing input, a space as the first character of the +Separator matches any number of any whitespace +characters. +

+

+During output, the first NORD elements are +written. +

+

+During input, a maximum of NELM elements is +read and NORD is updated accordingly. +Parsing of elements stops when the separator does not match, +conversion fails, or the end of the input is reached. +A minimum of one element must be available. +

+

+The format data type must be convertible to or from the type +specified in the FTVL field. +The variable x[i] stands for one element of +the written or read value. +

+
+
DOUBLE format (e.g. %f):
+
+ Output:x[i]=double(VAL[i])
+ FTVL can be "DOUBLE", "FLOAT", + "LONG", "ULONG", "SHORT", + "USHORT", "CHAR", "UCHAR", + or "ENUM" (which is treated as "USHORT").
+ Input: VAL[i]=FTVL(x[i])
+ FTVL must be "FLOAT" or "DOUBLE" +
+
LONG or ENUM format (e.g. %i or %{):
+
+ Output: x[i]=long(VAL[i])
+ FTVL can be + "LONG", "ULONG", "SHORT", + "USHORT", "CHAR", "UCHAR", + or "ENUM" (which is treated as "USHORT").
+ Signed values are sign-extended to long, unsigned values are + zero-extended to long before converting them.
+ Input: VAL[i]=FTVL(x[i])
+ FTVL can be "DOUBLE", "FLOAT", + "LONG", "ULONG", "SHORT", + "USHORT", "CHAR", "UCHAR", + or "ENUM" (which is treated as "USHORT").
+ The value is truncated to the least significant bytes if + FTVL has a smaller data size than long. +
+
STRING format (e.g. %s):
+
+
+
If FTVL=="STRING":
+
+ Output: x[i]=VAL[i]
+ Input: VAL[i]=x[i]
+ Note that this is an array of strings, not an array of characters. +
+
If FTVL=="CHAR" or FTVL="UCHAR":
+
+ In this case, the complete aao is treated as a large + single string of size NORD. + No separators are printed or expected.
+ Output: x=range(VAL,0,NORD)
+ The first NORD characters are printed, + which might be less than NELM.
+ Input: VAL=x, NORD=length(x)
+ A maximum of NELM-1 characters can be read. + NORD is updated to the index of the first of the + trailing zeros. + Usually, this is the same as the string length. +
+
+ Other values of FTVL are not allowed for this format. +
+
+ +

Initialization

+

+ During initialization, the @init handler is executed, if + present. All format converters work like in normal operation. +

+ +
+

+aai +ai +ao +bi +bo +mbbi +mbbo +mbbiDirect +mbboDirect +longin +longout +stringin +stringout +calcout +scalcout +

+

Dirk Zimoch, 2006

+ + + diff --git a/doc/ai.html b/doc/ai.html new file mode 100644 index 0000000..51be662 --- /dev/null +++ b/doc/ai.html @@ -0,0 +1,81 @@ + + + +StreamDevice: ai Records + + + + + + +

StreamDevice: ai Records

+ +

Normal Operation

+

+Depending on the format type, different record fields are used +for output and input. The variable x stands for the +written or read value. +

+
+
DOUBLE format (e.g. %f):
+
+ Output: x=(VAL-AOFF)/ASLO
+ Input: VAL=(x*ASLO+AOFF)*(1.0-SMOO)+VAL*SMOO
+ In both cases, if ASLO==0.0, it is treated as 1.0. + Default values are ASLO=1.0, AOFF=0.0, + SMOO=0.0.
+ If input is successful, UDF is cleared. +
+
LONG format (e.g. %i):
+
+ Output: x=RVAL
+ Input: RVAL=x
+ Note that the record calculates + VAL=(((RVAL+ROFF)*ASLO+AOFF)*ESLO+EOFF)*(1.0-SMOO)+VAL*SMOO + if LINR=="LINEAR". + ESLO and EOFF might be set in the record + definition. StreamDevice does not set it. For example, + EOFF=-10 and ESLO=0.000305180437934 + (=20.0/0xFFFF) maps 0x0000 to -10.0, 0x7FFF to 0.0 and 0xFFFF to 10.0. +
+
ENUM format (e.g. %{):
+
+ Not allowed. +
+
STRING format (e.g. %s):
+
+ Not allowed. +
+
+ +

Initialization

+

+ During initialization, the @init handler is executed, if present. + In contrast to normal operation, in DOUBLE input SMOO is ignored + (treated as 0.0). +

+ +
+

+aai +aao +ao +bi +bo +mbbi +mbbo +mbbiDirect +mbboDirect +longin +longout +stringin +stringout +waveform +calcout +scalcout +

+

Dirk Zimoch, 2005

+ + + diff --git a/doc/ao.html b/doc/ao.html new file mode 100644 index 0000000..75c88fb --- /dev/null +++ b/doc/ao.html @@ -0,0 +1,82 @@ + + + +StreamDevice: ao Records + + + + + + +

StreamDevice: ao Records

+ +

Normal Operation

+

+Depending on the format type, different record fields are used +for output and input. The variable x stands for the +written or read value. +

+
+
DOUBLE format (e.g. %f):
+
+ Output: x=(OVAL-AOFF)/ASLO
+ Input: VAL=x*ASLO+AOFF
+ In both cases, if ASLO==0.0, it is treated as 1.0. + Default values are ASLO=1.0, AOFF=0.0.
+ Note that OVAL is not necessarily equal to VAL + if OROC!=0.0. +
+
LONG format (e.g. %i):
+
+ Output: x=RVAL
+ Input: RBV=x
+ Note that the record calculates + RVAL=(((OVAL-EOFF)/ESLO)-AOFF)/ASLO if + LINR=="LINEAR". ESLO and EOFF + might be set in the record definition. StreamDevice does not set it. + For example, EOFF=-10 and ESLO=0.000305180437934 + (=20.0/0xFFFF) maps -10.0 to 0x0000, 0.0 to 0x7FFF and 10.0 to 0xFFFF. +
+
ENUM format (e.g. %{):
+
+ Not allowed. +
+
STRING format (e.g. %s):
+
+ Not allowed. +
+
+ +

Initialization

+

+ During initialization, the @init handler is executed, if + present. In contrast to normal operation, output in DOUBLE format uses + VAL instead of OVAL. Note that the record + initializes VAL from DOL if that is a constant. + LONG input is put to RVAL as well as to RBV and + converted by the record. +

+
+

+aai +aao +ai +bi +bo +mbbi +mbbo +mbbiDirect +mbboDirect +longin +longout +stringin +stringout +waveform +calcout +scalcout +

+

Dirk Zimoch, 2005

+ + + diff --git a/doc/bg.gif b/doc/bg.gif new file mode 100644 index 0000000..e86e76c Binary files /dev/null and b/doc/bg.gif differ diff --git a/doc/bi.html b/doc/bi.html new file mode 100644 index 0000000..581afef --- /dev/null +++ b/doc/bi.html @@ -0,0 +1,77 @@ + + + +StreamDevice: bi Records + + + + + + +

StreamDevice: bi Records

+ +

Normal Operation

+

+Depending on the format type, different record fields are used +for output and input. The variable x stands for the +written or read value. +

+
+
DOUBLE format (e.g. %f):
+
+ Not allowed. +
+
LONG format (e.g. %i):
+
+ Output: x=RVAL
+ Input: RVAL=x&MASK
+ MASK can be set be set in the record definition. Stream + Device does not set it. If MASK==0, it is ignored + (i.e. RVAL=x). The record sets + VAL=(RVAL!=0), i.e. 1 if RVAL!=0 + and 0 if RVAL==0. +
+
ENUM format (e.g. %{):
+
+ Output: x=VAL
+ Input: VAL=(x!=0)
+
+
STRING format (e.g. %s):
+
+ Output: Depending on VAL, ZNAM or + ONAM is written, i.e. x=VAL?ONAM:ZNAM.
+ Input: If input is equal to ZNAM or ONAM, + VAL is set accordingly. Other input strings are not accepted. +
+
+ +

Initialization

+

+ During initialization, the @init handler is executed, if + present. All format converters work like in normal operation. +

+ +
+

+aai +aao +ai +ao +bo +mbbi +mbbo +mbbiDirect +mbboDirect +longin +longout +stringin +stringout +waveform +calcout +scalcout +

+

Dirk Zimoch, 2005

+ + + diff --git a/doc/bo.html b/doc/bo.html new file mode 100644 index 0000000..638621b --- /dev/null +++ b/doc/bo.html @@ -0,0 +1,76 @@ + + + +StreamDevice: bo Records + + + + + + +

StreamDevice: bo Records

+ +

Normal Operation

+

+Depending on the format type, different record fields are used +for output and input. The variable x stands for the +written or read value. +

+
+
DOUBLE format (e.g. %f):
+
+ Not allowed. +
+
LONG format (e.g. %i):
+
+ Output: x=RVAL
+ Input: RBV=x&MASK
+ MASK can be set be set in the record definition. Stream + Device does not set it. If MASK==0, it is ignored + (i.e. RBV=x). +
+
ENUM format (e.g. %{):
+
+ Output: x=VAL
+ Input: VAL=(x!=0)
+
+
STRING format (e.g. %s):
+
+ Output: Depending on VAL, ZNAM or + ONAM is written, i.e. x=VAL?ONAM:ZNAM.
+ Input: If input is equal to ZNAM or ONAM, + VAL is set accordingly. Other input strings are not accepted. +
+
+ +

Initialization

+

+ During initialization, the @init handler is executed, if + present. In contrast to normal operation, LONG input is put to + RVAL as well as to RBV and converted by the record. +

+ +
+

+aai +aao +ai +ao +bi +mbbi +mbbo +mbbiDirect +mbboDirect +longin +longout +stringin +stringout +waveform +calcout +scalcout +

+

Dirk Zimoch, 2005

+ + + diff --git a/doc/businterface.html b/doc/businterface.html new file mode 100644 index 0000000..1bc61e7 --- /dev/null +++ b/doc/businterface.html @@ -0,0 +1,634 @@ + + + +StreamDevice: Bus API + + + + + + +

Bus API

+ + +

Bus Interface Class

+

+StreamDevice already comes with an interface to + asynDriver. +You should first try to implement your bus driver compatible to +asynDriver. +Then it can be used by StreamDevice automatically. +Only if that does not work, write your own bus interface. +

+

+A bus interface is a C++ class that inherits from +StreamBusInterface. +Its purpose is to provide an interface to StreamDevice for a +low-level I/O bus driver. +StreamDevice acts as a client of the interface, calling interface +methods and receiving replies via callbacks. +Since the internal details of StreamDevice are not of +interest to a bus interface, I will reference it simply as +client in this chapter. +The interface class must be registered via a call to + +RegisterStreamBusInterface() +in the global context of the C++ file (not in a header file). +

+

+Interface methods called by the client must not block for arbitrary +long times. +That means the interface is allowed to take mutex semaphores to protect +its internal data structures but it must not take event semaphores to +wait for external I/O or similar. +

+

+It is assumed that the interface creates a separate thread to handle +blocking I/O and to call the callback methods in the context of that +thread when I/O has completed or timed out. +The callback methods don't block but may in turn call interface methods. +Much of the actual work will be done in the context of those callbacks, +i.e. in the interface thread, thus be generous with stack. +

+ +

Example bus interface class declaration

+
+#include <StreamBusInterface.h>
+
+class MyInterface : StreamBusInterface
+{
+    // ... (internally used attributes and methods)
+
+    MyInterface(Client* client);
+    ~MyInterface();
+
+    // StreamBusInterface virtual methods
+    bool lockRequest(unsigned long lockTimeout_ms);
+    bool unlock();
+    bool writeRequest(const void* output, size_t size,
+        unsigned long writeTimeout_ms);
+    bool readRequest(unsigned long replyTimeout_ms,
+        unsigned long readTimeout_ms,
+        long expectedLength, bool async);
+    bool supportsAsyncRead();
+    bool supportsEvent();
+    bool acceptEvent(unsigned long mask,
+        unsigned long replytimeout_ms);
+    bool connectRequest(unsigned long connecttimeout_ms);
+    bool disconnect();
+    void finish();
+
+public:
+    // creator method
+    static StreamBusInterface* getBusInterface(
+        Client* client, const char* busname,
+        int addr, const char* param);
+};
+
+RegisterStreamBusInterface(MyInterface);
+
+// ... (implementation)
+
+ +

Methods to implement

+

+The interface class must implement a public static creator method: +

+
+static StreamBusInterface* + getBusInterface(Client* client, + const char* busname, int addr, + const char* param); +
+

+And it must implement the following pure virtual methods: +

+
+bool lockRequest(unsigned long lockTimeout_ms); +
+
+bool unlock(); +
+

+It may implement additional virtual methods if the bus supports it: +

+
+bool writeRequest(const void* output, + size_t size, unsigned long writeTimeout_ms); +
+
+bool readRequest(unsigned long replyTimeout_ms, + unsigned long readTimeout_ms, + long expectedLength, bool async); +
+
+bool supportsAsyncRead(); +
+
+bool supportsEvent(); +
+
+bool acceptEvent(unsigned long mask, + unsigned long replytimeout_ms); +
+
+bool connectRequest(unsigned long connecttimeout_ms); +
+
+bool disconnect(); +
+
+void finish(); +
+

+It also may override the following virtual method: +

+
+void release(); +
+ +

Callback methods provided

+

+The base class StreamBusInterface implements a set of protected +callback methods which must be called in response to the above request +methods (most probably from another thread): +

+
+void lockCallback(StreamIoStatus status); +
+
+void writeCallback(StreamIoStatus status); +
+
+long readCallback(StreamIoStatus status, + const void* input = NULL, + long size = 0); +
+
+void eventCallback(StreamIoStatus status); +
+
+void connectCallback(StreamIoStatus status); +
+ +

Other provided methods, attibutes, and types

+ +
+StreamBusInterface(Client* client); +
+
+long priority(); +
+
+const char* clientName(); +
+
+const char* getOutTerminator(size_t& length); +
+
+const char* getInTerminator(size_t& length); +
+
+enum StreamIoStatus {StreamIoSuccess, StreamIoTimeout, StreamIoNoReply, StreamIoEnd, StreamIoFault}; +
+ + +

Theory of Operation

+ + +

Registration

+
+RegisterStreamBusInterface(interfaceClass); +
+

+During initialization, the macro RegisterStreamBusInterface() +registers the bus interface. +It must be called exactly once for each bus interface class in global +file context. +

+ + +

Creation and deletion

+
+static StreamBusInterface* getBusInterface(Client* client, + const char* busname, int addr, + const char* param); +
+
+StreamBusInterface(Client* client); +
+
+void release(); +
+
+const char* clientName(); +
+ +

+During startup, each client instance searches for its bus interface +by name. +It does so by calling the static getBusInterface() method +of every registered interface class. +This method should check by busname if its interface class +is responsible for that bus. +If yes, it should check if the address addr is valid and +associate a device with busname/addr. +Some busses do not have addresses and allow only one device +(e.g. RS232). +Interfaces to such busses can ignore addr. +The bus interface may then try to connect to the device, but it should +allow it to be disconnected or switched off at that time. +If the bus interface requires additional parameters, parse the +param string. +Your constructor should pass client to the base class +constructor StreamBusInterface(Client* client). +

+

+On success, getBusInterface should then return a pointer +to a bus interface instance. +Note that many client instances may want to connect to the same device. +Each needs its own bus interface instance. +The bus interface can get a string containing the name of the +client instance from clientName(). +This name is for use in error and log messages. +

+

+On failure, or if this interface class is not responsible for that bus, +getBusInterface should return NULL. +The client will then try other bus interface classes. +

+

+When the client does not need the interface any more, it calls +release(). +The default implementation of release() assumes that +getBusInterface() has allocated a new bus interface +and just calls delete. +You should change release() if that assumption is not +correct. +

+ + +

Connecting and disconnecting

+
+bool connectRequest(unsigned long connecttimeout_ms); +
+
+bool disconnect(); +
+
+void connectCallback(IoStatus status); +
+

+Connection should be handled automatically. +If the device is disconnected, each attempt to access the +device should try to (re-)connect. +Normally, the interface should not try to disconnect unless +the device does so. +

+

+However, sometimes the client wants to connect or +disconnect explicitely. +To connect, the client calls connectRequest(). +This function should return true immediately +or false if the request cannot be accepted. +The interface should call connectCallback(StreamIoSuccess) +once the bus could be connected. +If the bus cannot be connected within connecttimeout_ms +milliseconds, the bus interface should call +connectCallback(StreamIoTimeout). +

+

+If a device cannot be connected, for example because there is +something wrong with the I/O hardware, +connectCallback(StreamIoFault) may be called. +

+

+To disconnect, the client calls disconnect(); +This function should return true immediately or +false if disconnecting is impossible. +There is no callback for disconnect(). +

+ + +

Bus locking

+
+bool lockRequest(unsigned long lockTimeout_ms); +
+
+void lockCallback(IoStatus status); +
+
+bool unlock(); +
+
+long priority(); +
+
+void finish(); +
+

+Before doing output, the client calls lockRequest() to get +exclusive access to the device. +This function should return true immediately +or false if the request cannot be accepted. +If the device is already locked, the bus interface should add itself to +a queue, sorted by priority(). +As soon as the device is available, the bus interface should call +lockCallback(StreamIoSuccess). +If the bus cannot be locked within lockTimeout_ms +milliseconds, the bus interface should call +lockCallback(StreamIoTimeout). +

+

+If a device cannot be locked, for example because there is +something wrong with the I/O hardware, +lockCallback(StreamIoFault) may be called. +

+

+Normally, it is not necessary to lock the complete bus but only one +device (i.e. one address). +Other clients should still be able to talk to other devices on the same bus. +

+

+The client may perform several read and write operations when it has +locked the device. +When the protocol ends and the device is locked, the client calls +unlock(). +If other bus interfaces are in the lock queue, the next one should +call lockCallback(StreamIoSuccess) now. +

+

+The client calls finish() when the protocol ends. +This allows the bus interface to clean up. +The bus interface should also cancel any outstanding requests of +this client. +

+ + +

Writing output

+
+bool writeRequest(const void* output, + size_t size, unsigned long writeTimeout_ms); +
+
+void writeCallback(IoStatus status); +
+
+const char* getOutTerminator(size_t& length); +
+

+To start output, the client calls writeRequest(). +You can safely assume that the device has already been locked at this +time. +That means, no other client will call writeRequest() +for this device and no other output is currently active for this device +until it has been unlocked. +

+

+The function should arrange transmission of size bytes of +output but return true immediately +or false if the request cannot be accepted. +It must not block until output has completed. +After all output has been successfully transmitted, but not earlier, the +interface should call writeCallback(StreamIoSuccess). +

+

+If output blocks for writeTimeout_ms milliseconds, +the interface should abort the transmision and call +writeCallback(StreamIoTimeout). +

+

+If output is impossible, for example because there is +something wrong with the I/O hardware, +writeCallback(StreamIoFault) may be called. +

+

+The interface must transmit excactly the size bytes +from output. +It must not change anything and it should not assume that +any bytes have a special meaning. +In particular, a null byte does not terminate output. +

+

+A call to getOutTerminator tells the interface which +terminator has already been added to the output. +If NULL was returned, the client is not aware of a +terminator (no outTerminator was defined in the protocol). +In this case, the interface may add a terminator which it knows from +other sources. +An interface is not required to support NULL results +and may not add any terminator in this case. +

+

+The buffer referenced by output stays valid until +writeCallback() is called. +

+

+The client may request more I/O or call unlock() after +writeCallback() has been called. +

+ + +

Reading input

+
+bool readRequest(unsigned long replyTimeout_ms, + unsigned long readTimeout_ms, + long expectedLength, bool async); +
+
+long readCallback(IoStatus status, + const void* input = NULL, + long size = 0); +
+
+const char* getInTerminator(size_t& length); +
+
+bool supportsAsyncRead(); +
+

+The client calls readRequest() to tell the bus interface +that it expects input. +Depending on the bus, this function might have to set the bus hardware +into receive mode. +If expectedLength>0, the the bus interface should stop +input after this number of bytes have been received. +In opposite to writing, the device may be in a non-locked status when +readRequest() is called. +

+

+This function must not block until input is available. +Instead, it should arrange for +readCallback(StreamIoSuccess, buffer, size) to be called +when input has been received and return true +immediately or false if the request cannot be accepted. +

+

+Here, buffer is a pointer to +size input bytes. +The bus interface is responsible for the buffer. +The client copies its contents. It does not modify or free it. +

+

+It is not necessary to wait until all data has been received. +The bus interface can call n=readCallback() after +any amount of input has been received. +If the client needs more input, readCallback() +returns a non-zero value. +A positive n means, the client needs another +n bytes of input. +A negative n means, the client needs an unspecified +amount of additional input. +

+

+With some bus interfaces, readRequest() might not have to +do anything because the bus is always receiving. +It might also be that the bus has no local buffer associated to store +input before it is fetched with some read() call. +In this case, a race condition between device and client can occure. +To avoid loss of data, readCallback(StreamIoSuccess, buffer, size) +may be called in this case even before readRequest(). +If the client is expecting input in the next future, it will store it. +Otherwise the input is dropped. +

+

+The replyTimeout_ms parameter defines how many milliseconds +to wait for the first byte of a reply before the device is considered +offline. +If no input has been received after replyTimeout_ms +milliseconds, the bus interface should call +readCallback(StreamIoNoReply). +

+

+The readTimeout_ms parameter is the maximum time to wait +for further input. +If input stops for longer than readTimeout_ms milliseconds +the bus interface should call +readCallback(StreamIoTimeout,buffer,size). +The client decides if this timeout is an error or a legal termination. +Thus, pass all input received so far. +

+

+A call to getInTerminator(length) tells the interface which +terminator is expected for input and length is set to the +number of bytes of the terminator. The result is a hint to the bus +interface to recognize the end of an input. +Once the terminator string is found, the bus interface should stop +receiving input and call +readCallback(StreamIoSuccess, buffer, size). +It is not necessary to remove the terminator string from the received input. +An empty terminator string (length==0) means: +Don't look for terminators. +

+

+If NULL was returned, the client is not aware of a +terminator (no inTerminator was defined in the protocol). +In this case, the interface may look for a terminator which it knows from +other sources, reduce size by the terminator length and call +readCallback(StreamIoEnd, buffer, size). +A bus interface is not required to support NULL results and +may treat them as empty terminator (see above). +

+

+Some busses (e.g. GPIB) support special "end of message" signals. +If such a signal is received, the bus interface should call +readCallback(StreamIoEnd, buffer, size). +Use it to indicate a special "end of message" signal which is not +visible in the normal byte data stream. +If getInTerminator() has not returned NULL +it it not necessary to remove a terminator which may come in +addition to the "end of message" signal. +

+

+If input is impossible, for example because there is +something wrong with the I/O hardware, +readCallback(StreamIoFault) may be called. +

+

+If the async flag is true, the client +wants to read input asyncronously without any timeout. +That means, the bus interface should call readCallback() +even if the input was requested by another client. +

+

+If a client wishes to receive asynchonous input, it first calls +supportsAsyncRead(). +The default implementation of this method always returns +false. +A bus interface may overwrite this method to return true +and eventually prepare for asynchonous input. +The client is then allowed to call readRequest() with +the async==true. +This means that the client is interested in any input. +It should receive a readCallback() of all input which came +in response to a synchonous (async==false) request from +another client (which of course should receive the input, too). +The interface should also receive asynchonous input when no +synchonous client is active at the moment. +Many asynchonous readRequest() calls from different clients +may be active at the same time. +All of them should receive the same input. +

+

+For asynchonous requests, replyTimeout_ms has a different +meaning: If the bus interface has to poll the bus for input, it may take +replyTimeout_ms as a hint for the poll period. +If many asynchonous requests are active at the same time, it should poll +with the shortest period of all clients. +An asynchonous request does not time out. +It stays active until the next input arrives. +The client may reissue the asynchronous readRequest() +from within the readCallback() if it wants to continue +receiving asynchonous input. +

+

+If the client calls finish() at any time, the bus +interface should cancel all outstanding requests, including +asynchonous read requests. +

+ +

Handling events

+
+bool supportsEvent(); +
+
+bool acceptEvent(unsigned long mask, unsigned long replytimeout_ms); +
+
+void eventCallback(StreamIoStatus status); +
+

+An event is a sort of input from a device which is not part of +the normal byte stream. +One example is the SRQ line of GPIB. +Not all bus types have events. +To support events, the bus interface must overwrite +supportsEvent() to return true. +The default implementation always returns false. +

+

+If true is returned, the client is allowed to call +acceptEvent(), where mask defines the +(bus dependent) type of event or events to wait for. +If mask is illegal, acceptEvent() should +return false. +The call to acceptEvent() must not block. +It should arrange to call eventCallback(StreamIoSuccess) +when the event matching mask arrives within +replytimeout_ms milliseconds. +If no such event arrives within this time, the bus interface +should call eventCallback(StreamIoTimeout). +

+

+To avoid race conditions, the bus interface should buffer events and +also report a matching event which occured before the actual call +to acceptEvent() but after any previous call of any +other request method like writeRequest(). +

+
+

Dirk Zimoch, 2007

+ + + diff --git a/doc/calcout.html b/doc/calcout.html new file mode 100644 index 0000000..5376631 --- /dev/null +++ b/doc/calcout.html @@ -0,0 +1,83 @@ + + + +StreamDevice: calcout Records + + + + + + +

StreamDevice: calcout Records

+ +

+Note: Device support for calcout records is only available for +EPICS base R3.14.5 or higher. +

+ +

Normal Operation

+

+Different record fields are used for output and input. The variable +x stands for the written or read value. +

+
+
DOUBLE format (e.g. %f):
+
+ Output: x=OVAL
+ Input: VAL=x
+ Note that the record calculates OVAL from CALC + or OCAL depending on DOPT. +
+
LONG format (e.g. %i):
+
+ Output: x=int(OVAL)
+ Input: VAL=x
+
+
ENUM format (e.g. %{):
+
+ Output: x=int(OVAL)
+ Input: VAL=x
+
+
STRING format (e.g. %s):
+
+ Not allowed. +
+
+

+ For calcout records, it is probably more useful to access fields + A to L directly (e.g. "%(A)f"). + However, even if OVAL is not used, it is calculated by the + record. Thus, CALC must always contain a valid expression + (e.g. "0"). +

+ +

Initialization

+

+ During initialization, the @init handler is executed, if + present. All format converters work like in normal operation. +

+ +
+

+aai +aao +ai +ao +bi +bo +mbbi +mbbo +mbbiDirect +mbboDirect +longin +longout +stringin +stringout +waveform +scalcout +

+

Dirk Zimoch, 2005

+ + + diff --git a/doc/epics3_13.html b/doc/epics3_13.html new file mode 100644 index 0000000..674da8b --- /dev/null +++ b/doc/epics3_13.html @@ -0,0 +1,224 @@ + + + +StreamDevice: Using EPICS 3.13 + + + + + + +

StreamDevice: Using EPICS 3.13

+ + +

1. Prerequisites

+

+StreamDevice version 2.2 and higher can run on EPICS 3.13. +However, this requires some preparation, because EPICS 3.13 is missing +some libraries and header files. +Also asynDriver needs to be modified to compile with EPICS 3.13. +Due to the limitations of EPICS 3.13, you can build streamDevice only for +vxWorks systems. +

+

+Of course, you need an installation of EPICS 3.13. +I guess you already have that, otherwhise you would want to +install StreamDevice on EPICS 3.14. +I have tested StreamDevice with EPICS versions 3.13.7 up to 3.13.10 +with vxWorks 5.3.1 and 5.5 on a ppc604 processor. +

+

+Download my +compatibility package, +asynDriver +version 4-3 or higher, +and my +configure patches. +

+ + +

2. Build the Compatibility Package

+

+Unpack compat-1-0.tgz in the <top> directory of +your application build area. +(Please refer to the +EPICS IOC Software Configuration Management document.) +

+

+Change to the compat directory and run make. +This installs many EPICS 3.14-style header files and a small library +(compatLib). +

+ + +

3. Build the asynDriver Library

+

+Unpack the asynDriver package and change to its top directory. +

+

+Unpack configure.tgz here. +This will modify files in the configure directory. +Change to the configure directory and edit CONFIG_APP. +Set COMPAT=... to the <top> +directory where you have installed the compatibility package before. +(This patch might also allow you to compile other 3.14-style drivers for 3.13. +It has absolutely no effect if you use EPICS 3.14.) +

+

+Edit RELEASE and comment out IPAC=... +(unless you have the ipac package and somehow made it +compatible to EPICS 3.13). +Set EPICS_BASE to your EPICS 3.13 installation. +

+

+Run make in the configure directory. +

+

+Change to ../asyn/devGpib and edit +devGpib.h and devSupportGpib.c. +Change all occurrences of static gDset to +gDset. +

+

+Go one directory up (to asyn) and run make twice! +(The first run will just create Makefile.Vx.) +Ignore all compiler warnings. +

+

+Do not try to build the test applications. It will not work. +

+ + +

4. Build the StreamDevice Library

+

+Go to the <top> directory of your application build area. +

+

+Edit config/RELEASE and add the variable ASYN. +Set it to the location of the asynDriver installation. +Also set the COMPAT variable to the location of the +compatibility package. +Run make in the config directory. +

+Unpack the StreamDevice package in your <top> +directory. +Change to the newly created StreamDevice directory +and run make. +

+ + +

5. Build an Application

+

+To use StreamDevice, your application must be built with the +asyn, stream, and compat libraries and must load +asyn.dbd and stream.dbd. +Also, as the stream library contains C++ code, the application +must be munched. +Therefore, include $(TOP)/config/RULES.munch. +(Put your application in the same <top> as the +StreamDevice installation.) +

+

+Include the following lines in your Makefile.Vx: +

+
+LDLIBS += $(COMPAT_BIN)/compatLib
+LDLIBS += $(ASYN_BIN)/asynLib
+LDLIBS += $(INSTALL_BIN)/streamLib
+
+include $(TOP)/config/RULES.munch
+
+

+Include the following lines in your xxxAppInclude.dbd file to use +stream and asyn (you also need a base.dbd): +

+
+include "base.dbd"
+include "stream.dbd"
+include "asyn.dbd"
+
+

+You can find an example application in the streamApp +subdirectory. +

+ + +

6. The Startup Script

+

+StreamDevice is based on protocol files. +To tell StreamDevice where to search for protocol files, +set the environment variable STREAM_PROTOCOL_PATH to a +list of directories to search. +Directories are separated by :. +The default value is STREAM_PROTOCOL_PATH=., +i.e. the current directory. +

+

+Also configure the buses (in asynDriver terms: ports) you want +to use with StreamDevice. +You can give the buses any name you want, like COM1 or +socket, but I recommend to use names related to the +connected device. +

+

Example:

+

+A power supply with serial communication (9600 baud, 8N1) is connected to +/dev/ttyS1. +The name of the power supply is PS1. +Protocol files are either in the current working directory or in the +../protocols directory. +

+

+Then the startup script must contain lines like this: +

+
+ld < iocCore
+ld < streamApp.munch
+dbLoadDatabase ("streamApp.dbd")
+
+putenv ("STREAM_PROTOCOL_PATH=.:../protocols")
+
+drvAsynSerialPortConfigure ("PS1","/dev/ttyS1")
+asynSetOption ("PS1", 0, "baud", "9600")
+asynSetOption ("PS1", 0, "bits", "8")
+asynSetOption ("PS1", 0, "parity", "none")
+asynSetOption ("PS1", 0, "stop", "1")
+
+ +

+An alternative approach is to skip step 5 (do not build an application) +and load all components explicitely in the startup script. +The STREAM_PROTOCOL_PATH variable can also be a vxWorks shell +variable. +

+
+ld < iocCore
+ld < compatLib
+ld < asynLib
+ld < streamLib.munch
+dbLoadDatabase ("asyn.dbd")
+dbLoadDatabase ("stream.dbd")
+
+STREAM_PROTOCOL_PATH=".:../protocols"
+
+drvAsynSerialPortConfigure ("PS1","/dev/ttyS1")
+asynSetOption ("PS1", 0, "baud", "9600")
+asynSetOption ("PS1", 0, "bits", "8")
+asynSetOption ("PS1", 0, "parity", "none")
+asynSetOption ("PS1", 0, "stop", "1")
+
+ +

7. Continue as with EPICS 3.14.

+ +
+

Dirk Zimoch, 2006

+ + + diff --git a/doc/ex.png b/doc/ex.png new file mode 100644 index 0000000..7f10d33 Binary files /dev/null and b/doc/ex.png differ diff --git a/doc/exr.png b/doc/exr.png new file mode 100644 index 0000000..ea9630b Binary files /dev/null and b/doc/exr.png differ diff --git a/doc/formatconverter.html b/doc/formatconverter.html new file mode 100644 index 0000000..54e9cc1 --- /dev/null +++ b/doc/formatconverter.html @@ -0,0 +1,20 @@ + + + +StreamDevice: Format Converter API + + + + + + +

StreamDevice: Format Converter API

+ +

Sorry, this documentation is still missing.

+ +
+

Dirk Zimoch, 2006

+ + + diff --git a/doc/formats.html b/doc/formats.html new file mode 100644 index 0000000..5cb5d04 --- /dev/null +++ b/doc/formats.html @@ -0,0 +1,410 @@ + + + +StreamDevice: Format Converters + + + + + + +

StreamDevice: Format Converters

+ + +

1. Format Syntax

+

+StreamDevice format converters work very similar to the format +converters of the C functions printf() and scanf(). +But StreamDevice provides more different converters and you can +also write your own converters. +Formats are specified in quoted strings +as arguments of out or in +commands. +

+

+A format converter consists of +

+ + +

+The * flag skips data in input formats. +Input is consumed and parsed, a mismatch is an error, but the read +data is dropped. +This is useful if input contains more than one value. +Example: in "%*f%f"; reads the second floating point +number. +

+

+The # flag may alter the format, depending on the +converter (see below). +

+

+The ' ' (space) and + flags print a space +or a + sign before positive numbers, where negative +numbers would have a -. +

+

+The 0 flag says that numbers should be left padded with +0 if width is larger than required. +

+

+The - flag specifies that output is left justified if +width is larger than required. +

+ +

Examples:

+ + + + + + + + + + + + + + + + + + + + + +
in "%f";float
out "%(HOPR)7.4f";the HOPR field as 7 char float with precision 4
out "%#010x";0-padded 10 char alternate hex (with leading 0x)
in "%[_a-zA-Z0-9]";string of chars out of a charset
in "%*i";skipped integer number
+ + +

2. Data Types and Record Fields

+

Default fields

+

+Every conversion character corresponds to one of the data types DOUBLE, +LONG, ENUM, or STRING. +In opposite to to printf() and scanf(), it is not +required to specify a variable for the conversion. +The variable is typically the VAL or RVAL field +of the record, selected automatically depending on the data type. +Not all data types make sense for all record types. +Refer to the description of supported record +types for details. +

+

+StreamDevice makes no difference between float +and double nor between short, int +and long values. +Thus, data type modifiers like l or h do not +exist in StreamDevice formats. +

+ +

Accessing record fields directly

+

+To use other fields of the record or even fields of other records on the +same IOC for the conversion, write the field name in parentheses directly +after the %. +For example out "%(EGU)s"; outputs the EGU +field formatted as a string. +Use in "%(otherrecord.VAL)f"; to write the floating +point input value into the VAL field of +otherrecord. +(You can't skip .VAL here.) +This is very useful when one line of input contains many values that should +be distributed to many records. +If otherrecord is passive and the field has the PP +attribute (see +Record Reference Manual), the record will be processed. +It is your responsibility that the data type of the record field is +compatible to the the data type of the converter. +Note that using this syntax is by far not as efficient as using the +default field. +

+ +

Pseudo-converters

+

+Some formats are not actually converters. +They format data which is not stored in a record field, such as a +checksum. +No data type corresponds to those pseudo-converters and the +%(FIELD) syntax cannot be used. +

+ + +

3. Standard DOUBLE Converters (%f, %e, +%E, %g, %G)

+

+In output, %f prints fixed point, %e prints +exponential notation and %g prints either fixed point or +exponential depending on the magnitude of the value. +%E and %G use E instead of +e to separate the exponent. +With the # flag, output always contains a period character. +

+

+In input, all these formats are equivalent. +Leading whitespaces are skipped. +

+ + +

4. Standard LONG Converters (%d, %i, +%u, %o, %x, %X)

+

+In output, %d and %i print signed decimal, +%u unsigned decimal, %o unsigned octal, and +%x or %X unsigned hexadecimal. +%X uses upper case letters. +With the # flag, octal values are prefixed with 0 +and hexadecimal values with 0x or 0X. +

+

+In input, %d matches signed decimal, %u matches +unsigned decimal, %o unsigned octal. +%x and %X both match upper or lower case unsigned +hexadecimal. +Octal and hexadecimal values can optionally be prefixed. +%i matches any integer in decimal, or prefixed octal or +hexadecimal notation. +Leading whitespaces are skipped. +

+ + +

5. Standard STRING Converters (%s, %c)

+

+In output, %s prints a string. +If precision is specified, this is the maximum string length. +%c is a LONG format in output, printing one character! +

+

+In input, %s matches a sequence of non-whitespace characters +and %c a sequence of not-null characters. +The maximum string length is given by width. +The default width is infinite for %s and +1 for %c. +Leading whitespaces are skipped with %s but not with +%c. +The empty string matches. +

+ + +

6. Standard Charset STRING Converter (%[charset])

+

+This is an input-only format. +It matches a sequence of characters from charset. +If charset starts with ^, the format matches +all characters not in charset. +Leading whitespaces are not skipped. +

+

+Example: %[_a-z] matches a string consisting +entirely of _ (underscore) or letters from a +to z. +

+ + +

7. ENUM Converter (%{string0|string1|...})

+

+This format maps an unsigned integer value on a set of strings. +The value 0 corresponds to string0 and so on. +The strings are separated by |. +If one of the strings contains | or }, +a \ must be used to escape the character. +

+

+Example: %{OFF|STANDBY|ON} mapps the string OFF +to the value 0, STANDBY to 1 and ON to 2. +

+

+In output, depending on the value, one of the strings is printed. +

+

+In input, if any of the strings matches the value is set accordingly. +

+ + +

8. Binary LONG Converter (%b, %Bzo)

+

+This format prints or scans an unsigned integer represented as a binary +string (one character per bit). +The %b format uses the characters 0 and +1. +With the %B format, you can choose two other characters +to represent zero and one. +

+

+Examples: %B.! or %B\x00\xff. +%B01 is equivalent to %b. +

+

+If in output width is larger than the number of significant bits, +then the flag 0 means that the value should be padded with +with the chosen zero character instead of spaces. +If precision is set, it means the number of significant bits. +Otherwise, the highest 1 bit defines the number of significant bits. +There is no alternate format when # is used. +

+

+In input, leading spaces are skipped. +A maximum of width characters is read. +Conversion stops with the first character that is not the zero or the +one character. +

+ + +

9. Raw LONG Converter (%r)

+

+The raw converter does not really "convert". +A signed integer value is written or read in the internal +(usually two's complement) representation of the computer. +The normal byte order is big endian, i.e. most significant byte +first. +With the # flag, the byte order is changed to little +endian, i.e. least significant byte first. +

+

+In output, the width least significant bytes of the value +are written. +If width is larger than the size of a long, +the value is sign extended. +

+

+In input, width bytes are read and put into the value. +If width is longer than the size of a long, only +the least significant bytes are used. +

+ + +

10. Packed BCD (Binary Coded Decimal) LONG Converter (%D)

+

+Packed BCD is a format where each byte contains two binary coded +decimal digits (0 ... 9). +Thus a BCD byte is in the range from 0x00 to 0x99. +The normal byte order is big endian, i.e. most significant byte +first. +With the # flag, the byte order is changed to little +endian, i.e. least significant byte first. +The + flag defines that the value is signed, using the +upper half of the most significant byte for the sign. +Otherwise the value is unsigned. +

+

+In output, precision decimal digits are printed in at least +width output bytes. +Signed negative values have 0xF in their most significant half +byte followed by the absolute value. +

+

+In input, width bytes are read. +If the value is signed, a one in the most significant bit is interpreted as +a negative sign. +Input stops with the first byte (after the sign) that does not represent a +BCD value, i.e. where either the upper or the lower half byte is larger +than 9. +

+ + +

11. Checksum Pseudo-Converter (%<checksum>)

+

+This is not a normal "converter", because no user data is converted. +Instead, a checksum is calculated from the input or output. +The width field is the byte number from which to start +calculating the checksum. +Default is 0, i.e. the first byte of the input or output of the current +command. +The last byte is prec bytes before the checksum (default 0). +For example in "abcdefg%<xor>" the checksum is calculated +from abcdefg, +but in "abcdefg%2.1<xor>" only from cdef. +

+

+Normally, multi-byte checksums are in big endian byteorder, +i.e. most significant byte first. +With the # flag, the byte order is changed to little +endian, i.e. least significant byte first. +

+

+The 0 flag changes the checksum representation from +binary to hexadecimal ASCII (2 bytes per checksum byte). + +

+

+In output, the checksum is appended. +

+

+In input, the next byte or bytes must match the checksum. +

+ +

Implemented checksum functions

+
+
%<SUM> or %<SUM8>
+
One byte. The sum of all characters modulo 28.
+
%<SUM16>
+
Two bytes. The sum of all characters modulo 216.
+
%<SUM32>
+
Four bytes. The sum of all characters modulo 232.
+
%<NEGSUM> or %<NSUM> or %<-SUM>
+
One byte. The negative of the sum of all characters modulo 28.
+
%<NOTSUM> or %<~SUM>
+
One byte. The bitwise inverse of the sum of all characters modulo 28.
+
%<XOR>
+
One byte. All characters xor'ed.
+
%<CRC8>
+
One byte. An often used 8 bit CRC checksum + (poly=0x07, init=0x00, xorout=0x00).
+
%<CCITT8>
+
One byte. The CCITT standard 8 bit CRC checksum + (poly=0x31, init=0x00, xorout=0x00).
+
%<CRC16>
+
Two bytes. An often used 16 bit CRC checksum + (poly=0x8005, init=0x0000, xorout=0x0000).
+
%<CRC16R>
+
Two bytes. An often used reflected 16 bit CRC checksum + (poly=0x8005, init=0x0000, xorout=0x0000).
+
%<CCITT16>
+
Two bytes. The usual (but wrong?) + implementation of the CCITT standard 16 bit CRC checksum + (poly=0x1021, init=0xFFFF, xorout=0x0000).
+
%<CCITT16A>
+
Two bytes. The unusual (but correct?) + implementation of the CCITT standard 16 bit CRC checksum with augment. + (poly=0x1021, init=0x1D0F, xorout=0x0000).
+
%<CRC32>
+
Four bytes. The standard 32 bit CRC checksum. + (poly=0x04C11DB7, init=0xFFFFFFFF, xorout=0xFFFFFFFF).
+
%<CRC32R>
+
Four bytes. The standard reflected 32 bit CRC checksum. + (poly=0x04C11DB7, init=0xFFFFFFFF, xorout=0xFFFFFFFF).
+
%<JAMCRC>
+
Four bytes. Another reflected 32 bit CRC checksum. + (poly=0x04C11DB7, init=0xFFFFFFFF, xorout=0x00000000).
+
%<ADLER32>
+
Four bytes. The Adler32 checksum according to RFC 1950.
+
%<HEXSUM8>
+
One byte. The sum of all hex digits. (Other characters are ignored.)
+
+ +
+

Next: Record Processing

+

Dirk Zimoch, 2007

+ + + diff --git a/doc/head.html b/doc/head.html new file mode 100644 index 0000000..98913e5 --- /dev/null +++ b/doc/head.html @@ -0,0 +1,32 @@ + + + +Headbar + + + + + + + + + + + + + + + + + +
PSI
EPICS + StreamDevice
SLS
+ + diff --git a/doc/index.html b/doc/index.html new file mode 100644 index 0000000..4c6c671 --- /dev/null +++ b/doc/index.html @@ -0,0 +1,20 @@ + + + +StreamDevice Documentation + + + + + + + + + + + + <a href="stream.html">Click here for no-frames version.</a> + + + diff --git a/doc/longin.html b/doc/longin.html new file mode 100644 index 0000000..74dc3a8 --- /dev/null +++ b/doc/longin.html @@ -0,0 +1,68 @@ + + + +StreamDevice: longin Records + + + + + + +

StreamDevice: longin Records

+ +

Normal Operation

+

+The variable x stands for the +written or read value. +

+
+
DOUBLE format (e.g. %f):
+
+ Not allowed. +
+
LONG format (e.g. %i):
+
+ Output: x=VAL
+ Input: VAL=x +
+
ENUM format (e.g. %{):
+
+ Output: x=VAL
+ Input: VAL=x +
+
STRING format (e.g. %s):
+
+ Not allowed. +
+
+ +

Initialization

+

+ During initialization, the @init handler is executed, if + present. All format converters work like in normal operation. +

+ +
+

+aai +aao +ai +ao +bi +bo +mbbi +mbbo +mbbiDirect +mbboDirect +longout +stringin +stringout +waveform +calcout +scalcout +

+

Dirk Zimoch, 2005

+ + + diff --git a/doc/longout.html b/doc/longout.html new file mode 100644 index 0000000..af8820d --- /dev/null +++ b/doc/longout.html @@ -0,0 +1,68 @@ + + + +StreamDevice: longout Records + + + + + + +

StreamDevice: longout Records

+ +

Normal Operation

+

+The variable x stands for the +written or read value. +

+
+
DOUBLE format (e.g. %f):
+
+ Not allowed. +
+
LONG format (e.g. %i):
+
+ Output: x=VAL
+ Input: VAL=x +
+
ENUM format (e.g. %{):
+
+ Output: x=VAL
+ Input: VAL=x +
+
STRING format (e.g. %s):
+
+ Not allowed. +
+
+ +

Initialization

+

+ During initialization, the @init handler is executed, if + present. All format converters work like in normal operation. +

+ +
+

+aai +aao +ai +ao +bi +bo +mbbi +mbbo +mbbiDirect +mbboDirect +longin +stringin +stringout +waveform +calcout +scalcout +

+

Dirk Zimoch, 2005

+ + + diff --git a/doc/mbbi.html b/doc/mbbi.html new file mode 100644 index 0000000..3e5caba --- /dev/null +++ b/doc/mbbi.html @@ -0,0 +1,94 @@ + + + +StreamDevice: mbbi Records + + + + + + +

StreamDevice: mbbi Records

+ +

Normal Operation

+

+Depending on the format type, different record fields are used +for output and input. The variable x stands for the +written or read value. +

+
+
DOUBLE format (e.g. %f):
+
+ Not allowed. +
+
LONG format (e.g. %i):
+
+
+
If any of ZRVL ... FFVL is set + (is not 0):
+
+ Output: x=RVAL&MASK
+ Input: RVAL=x&MASK
+ Note that the record shifts RVAL right by + SHFT bits, compares the result with all of + ZRVL ... FFVL, and sets VAL + to the index of the first match. + MASK is initialized to NOBT 1-bits shifted + left by SHFT. If MASK==0 (because + NOBT was not set) it is ignored, i.e. + x=RVAL and RVAL=x. +
+
If none of ZRVL ... FFVL is set + (all are 0):
+
+ Output: x=VAL
+ Input: VAL=x
+
+
+
+
ENUM format (e.g. %{):
+
+ Output: x=VAL
+ Input: VAL=x
+
+
STRING format (e.g. %s):
+
+ Output: Depending on VAL, one of ZRST + or FFST is written. VAL must be in the range + 0 ... 15.
+ Input: If input is equal one of ZRST ... + FFST, VAL is set accordingly. + Other input strings are not accepted. +
+
+ +

Initialization

+

+ During initialization, the @init handler is executed, if + present. All format converters work like in normal operation. +

+ +
+

+aai +aao +ai +ao +bi +bo +mbbo +mbbiDirect +mbboDirect +longin +longout +stringin +stringout +waveform +calcout +scalcout +

+

Dirk Zimoch, 2005

+ + + diff --git a/doc/mbbiDirect.html b/doc/mbbiDirect.html new file mode 100644 index 0000000..e7bb376 --- /dev/null +++ b/doc/mbbiDirect.html @@ -0,0 +1,80 @@ + + + +StreamDevice: mbbiDirect Records + + + + + + +

StreamDevice: mbbiDirect Records

+ +

Normal Operation

+

+Depending on the format type, different record fields are used +for output and input. The variable x stands for the +written or read value. +

+
+
DOUBLE format (e.g. %f):
+
+ Not allowed. +
+
LONG format (e.g. %i):
+
+
+
If MASK==0 (because NOBT is not set):
+
+ Output: x=VAL
+ Input: VAL=x
+
+
If MASK!=0:
+
+ Output: x=RVAL&MASK
+ Input: RVAL=x&MASK
+
+
+ MASK is initialized to NOBT 1-bits shifted + left by SHFT. +
+
ENUM format (e.g. %{):
+
+ Not allowed. +
+
STRING format (e.g. %s):
+
+ Not allowed. +
+
+ +

Initialization

+

+ During initialization, the @init handler is executed, if + present. All format converters work like in normal operation. +

+ +
+

+aai +aao +ai +ao +bi +bo +mbbi +mbbo +mbboDirect +longin +longout +stringin +stringout +waveform +calcout +calcout +

+

Dirk Zimoch, 2005

+ + + diff --git a/doc/mbbo.html b/doc/mbbo.html new file mode 100644 index 0000000..5c4d961 --- /dev/null +++ b/doc/mbbo.html @@ -0,0 +1,95 @@ + + + +StreamDevice: mbbo Records + + + + + + +

StreamDevice: mbbo Records

+ +

Normal Operation

+

+Depending on the format type, different record fields are used +for output and input. The variable x stands for the +written or read value. +

+
+
DOUBLE format (e.g. %f):
+
+ Not allowed. +
+
LONG format (e.g. %i):
+
+
+
If any of ZRVL ... FFVL is set + (is not 0):
+
+ Output: x=RVAL&MASK
+ Note that the record calculates RVAL by choosing one of + ZRVL ... FFVL depending on VAL + and by shifting it left by SHFT bits.
+ Input: RBV=x&MASK
+ MASK is initialized to NOBT 1-bits shifted + left by SHFT. If MASK==0 (because + NOBT was not set) it is ignored, i.e. + x=RVAL and RBV=x. +
+
If none of ZRVL ... FFVL is set + (all are 0):
+
+ Output: x=VAL
+ Input: VAL=x
+
+
+
+
ENUM format (e.g. %{):
+
+ Output: x=VAL
+ Input: VAL=x
+
+
STRING format (e.g. %s):
+
+ Output: Depending on VAL, one of ZRST + ... FFST is written. VAL must be in the + range 0 ... 15.
+ Input: If input is equal one of ZRST ... + FFST, VAL is set accordingly. + Other input strings are not accepted. +
+
+ +

Initialization

+

+ During initialization, the @init handler is executed, if + present. In contrast to normal operation, LONG input is put to + RVAL as well as to RBV and converted by the + record. +

+ +
+

+aai +aao +ai +ao +bi +bo +mbbi +mbbiDirect +mbboDirect +longin +longout +stringin +stringout +waveform +calcout +scalcout +

+

Dirk Zimoch, 2005

+ + + diff --git a/doc/mbboDirect.html b/doc/mbboDirect.html new file mode 100644 index 0000000..e7f6d17 --- /dev/null +++ b/doc/mbboDirect.html @@ -0,0 +1,82 @@ + + + +StreamDevice: mbboDirect Records + + + + + + +

StreamDevice: mbboDirect Records

+ +

Normal Operation

+

+Depending on the format type, different record fields are used +for output and input. The variable x stands for the +written or read value. +

+
+
DOUBLE format (e.g. %f):
+
+ Not allowed. +
+
LONG format (e.g. %i):
+
+
+
If MASK==0 (because NOBT is not set):
+
+ Output: x=VAL
+ Input: VAL=x
+
+
If MASK!=0:
+
+ Output: x=RVAL&MASK
+ Input: RBV=x&MASK
+
+
+ MASK is initialized to NOBT 1-bits shifted + left by SHFT. +
+
ENUM format (e.g. %{):
+
+ Not allowed. +
+
STRING format (e.g. %s):
+
+ Not allowed. +
+
+ +

Initialization

+

+ During initialization, the @init handler is executed, if + present. In contrast to normal operation, input is put to + RVAL as well as to RBV and converted by the + record if MASK!=0. +

+ +
+

+aai +aao +ai +ao +bi +bo +mbbi +mbbo +mbbiDirect +longin +longout +stringin +stringout +waveform +calcout +scalcout +

+

Dirk Zimoch, 2005

+ + + diff --git a/doc/nav.html b/doc/nav.html new file mode 100644 index 0000000..cc2ed0b --- /dev/null +++ b/doc/nav.html @@ -0,0 +1,218 @@ + + + +Navbar + + + + + + + +

+

+
+

User's Guide

+
+ Intro +
+ + + + + + + +

Programmer's Guide

+ + + + +
+

+ +

+ + + diff --git a/doc/osinterface.html b/doc/osinterface.html new file mode 100644 index 0000000..89c9fff --- /dev/null +++ b/doc/osinterface.html @@ -0,0 +1,20 @@ + + + +StreamDevice: Operating System API + + + + + + +

StreamDevice: Operating System API

+ +

Sorry, this documentation is still missing.

+ +
+

Dirk Zimoch, 2006

+ + + diff --git a/doc/printer.gif b/doc/printer.gif new file mode 100644 index 0000000..c3ef2ea Binary files /dev/null and b/doc/printer.gif differ diff --git a/doc/processing.html b/doc/processing.html new file mode 100644 index 0000000..6424375 --- /dev/null +++ b/doc/processing.html @@ -0,0 +1,244 @@ + + + +StreamDevice: Record Processing + + + + + + +

StreamDevice: Record Processing

+ + +

1. Normal Processing

+

+StreamDevice is an asynchronous device support +(see IOC Application Developer's Guide chapter 12: +Device Support). +Whenever the record is processed, the protocol +is scheduled to start and the record is left active (PACT=1). +The protocol itself runs in another thread. +That means that any waiting in the protocol does not delay any other +part of the IOC. +

+

+After the protocol has finished, the record is processed again, leaving +PACT=0 this time, triggering monitors and processing +the forward link FLNK. +Note that input links with PP flag pointing to a StreamDevice +record will read the old value first and start the protocol +afterward. +This is a problem all asynchronous EPICS device supports have. +

+

+The first out command in the protocol locks the device +for exclusive access. +That means that no other record can communicate with that device. +This ensures that replies given by the device reach the record +which has sent the request. +On a bus with many devices on different addresses, this +normally locks only one device. +The device is unlocked when the protocol terminates. +Another record trying to lock the same device has to wait and +might get a LockTimeout. +

+

+

If any error happens, the protocol is aborted. The record will have +its SEVR field set to INVALID and its +STAT field to something describing the error: +

+
+
TIMEOUT
+
+ The device could not be locked (LockTimeout) or the + device did not reply (ReplyTimeout). +
+
WRITE
+
+ Output could not be written to the device (WriteTimeout). +
+
READ
+
+ Input from the device started but stopped unexpectedly + (ReadTimeout). +
+
COMM
+
+ The device driver reported some other communication error (e.g. + unplugged cable). +
+
CALC
+
+ Input did not match the argument string of the in command + or it contained values the record did not accept. +
+
UDF
+
+ Some fatal error happened or the record has not been initialized + correctly (e.g. because the protocol is erroneous). +
+
+ +

+If the protocol is aborted, an +exception handler might be executed +if defined. +Even if the exception handler can complete with no further error, the +protocol will not resume and SEVR and STAT +will be set according to the original error. +

+ + +

2. Initialization

+

+Often, it is required to initialize records from the hardware after +booting the IOC, especially output records. +For this purpose, initialization is formally handled as an +exception. +The @init handler is called as part of the +initRecord() function during iocInit +before any scan task starts. +

+

+In contrast to normal processing, the protocol +is handled synchronously. +That means that initRecord() does not return before the +@init handler has finished. +Thus, the records initialize one after the other. +The scan tasks are not started and iocInit does not +return before all @init handlers have finished. +If the handler fails, the record remains uninitialized: +UDF=1, SEVR=INVALID, +STAT=UDF. +

+

+The @init handler has nothing to do with the +PINI field. +The handler does not process the record nor does it trigger +forward links or other PP links. +It runs before PINI is handled. +If the record has PINI=YES, the PINI +processing is a normal processing after the +@init handlers of all records have completed. +

+

+Depending on the record type, format converters might work +slightly different from normal processing. +Refer to the description of +supported record types for details. +

+

+If the @inithandler has read a value and has completed +without error, the record starts in a defined state. +That means UDF=0, SEVR=NO_ALARM, +STAT=NO_ALARM and the VAL field contains +the value read from the device. +

+

+If no @init handler is installed, VAL and +RVAL fields remain untouched. +That means they contain the value defined in the record definition, +read from a constant INP or DOL field, +or restored from a bump-less reboot system +(e.g. autosave from the synApps package). +

+ + +

3. I/O Intr

+

+StreamDevice supports I/O event scanning. +This is a mode where record processing is triggered by the device +whenever the device sends input. +

+

+In terms of protocol execution this means: +When the SCAN field is set to I/O Intr +(during iocInit or later), +the protocol starts without processing the record. +With the first in command, the protocol is suspended. +If the device has been locked (i.e there was an out +command earlier in the protocol), it is unlocked now. +That means that other records can communicate to the device while +this record is waiting for input. +This in command ignores replyTimeout, +it waits forever. +

+

+The protocol now receives any input from the device. + +It also gets a copy of all input directed to other records. +Non-matching input does not generate a mismatch +exception. +It just restarts the in command until matching input +is received. +

+

+After receiving matching input, the protocol continues normally. +All other in commands are handled normally. +When the protocol has completed, the record is processed. +It then triggers monitors, forward links, etc. +After the record has been processed, the protocol restarts. +

+

+This mode is useful in two cases: +First for devices that send data automatically without being asked. +Second to distribute multiple values in one message to different +records. +In this case, one record would send a request to the device and pick +only one value out of the reply. +The other values are read by records in I/O Intr mode. +

+ +

Example:

+ +

+Device dev1 has a "region of interest" (ROI) defined by +a start value and an end value. When asked "ROI?", +it replies something like "ROI 17.3 58.7", +i.e. a string containing both values. +

+

+We need two ai records to store the two values. Whenever record +ROI:start is processed, it requests ROI from the device. +Record ROI:end updates automatically. +

+
+record (ai "ROI:start") {
+    field (DTYP, "stream")
+    field (INP,  "@myDev.proto getROIstart dev1")
+}
+record (ai "ROI:end") {
+    field (DTYP, "stream")
+    field (INP,  "@myDev.proto getROIend dev1")
+    field (SCAN, "I/O Intr")
+}
+
+

+Only one of the two protocols sends a request, but both read +their part of the same reply message. +

+
+getROIstart {
+    out "ROI?";
+    in  "ROI %f %*f";
+}
+getROIend {
+    in  "ROI %*f %f";
+}
+
+

+Note that the other value is also parsed by each protocol, but skipped +because of the %* format. +Even though the getROIend protocol may receive input +from other requests, it silently ignores every message that does not start +with "ROI", followed by two floating point numbers. +

+
+

Next: Supported Record Types

+

Dirk Zimoch, 2005

+ + + diff --git a/doc/protocol.html b/doc/protocol.html new file mode 100644 index 0000000..56db86e --- /dev/null +++ b/doc/protocol.html @@ -0,0 +1,552 @@ + + + +StreamDevice: Protocol Files + + + + + + +

StreamDevice: Protocol Files

+ +

1. General Information

+

+A protocol file describes the communication with one device type. +It contains protocols for each function +of the device type and variables which affect +how the commands in a protocol work. +It does not contain information about the individual device or the used +communication bus. +

+

+Each device type should have its own protocol file. +I suggest to choose a file name that contains the name of the device type. +Don't use spaces in the file name and keep it short. +The file will be referenced by its name in the INP +or OUT link of the records which use it. +The protocol file must be stored in one of the directories listed +in the environment variable STREAM_PROTOCOL_PATH +(see chapter Setup). +

+

+The protocol file is a plain text file. +Everything not enclosed in quotes +(single ' or double ") is not case sensitive. +This includes the names of commands, +protocols and variables. +There may be any amount of whitespaces (space, tab, newline, ...) or +comments between names, quoted strings and special +characters, such as ={};. +A comment is everything starting from an unquoted # +until the end of the line. +

+ +

Example Protocol File:

+
+# This is an example protocol file
+
+Terminator = CR LF;
+
+# Frequency is a float
+# use ai and ao records
+
+getFrequency {
+    out "FREQ?"; in "%f";
+}
+
+setFrequency {
+    out "FREQ %f";
+    @init { getFrequency; }
+}
+
+# Switch is an enum, either OFF or ON
+# use bi and bo records
+
+getSwitch {
+    out "SW?"; in "SW %{OFF|ON}";
+}
+
+setSwitch {
+    out "SW %{OFF|ON}";
+    @init { getSwitch; }
+}
+
+# Connect a stringout record to this to get
+# a generic command interface.
+# After processing finishes, the record contains the reply.
+
+debug {
+    ExtraInput = Ignore;
+    out "%s"; in "%39c"
+}
+
+ + +

2. Protocols

+

+For each function of the device type, define one protocol. +A protocol consists of a name followed by a body in braces {}. +The name must be unique within the protocol file. +It is used to reference the protocol in the +INP or OUT link of the record, +thus keep it short. +It should describe the function of the protocol. +It must not contain spaces or any of the characters +,;={}()$'"\#. +

+

+The protocol body contains a sequence of commands and +optionally variable assignments separated by +;. +

+

Referencing other protocols

+

+To save some typing, a previously defined protocol can be called inside +another protocol like a command without parameters. +The protocol name is replaced by the commands in the referenced protocol. +However, this does not include any +variable assignments or +exception handlers from the referenced protocol. +See the @init handlers in the above example. +

+

Limitations

+

+The StreamDevice protocol is not a programming language. +It has neither loops nor conditionals +(in this version of StreamDevice). +However, if an error occurs, e.g. a timeout or a mismatch in input +parsing, an exception handler can be called to +clean up. +

+ + + +

3. Commands

+

+Seven different commands can be used in a protocol: +out, in, wait, event, +exec, disconnect, and connect. +Most protocols will consist only of a single out command to +write some value, +or an out command followed by an in command to +read a value. +But there can be any number of commands in a protocol. +

+
+
out string;
+
+ Write output to the device. + The argument string may contain + format converters which are replaced by the + formatted value of the record before sending. +
+
in string;
+
+ Read and parse input from the device. + The argument string may contain + format converters which specify how to + interpret data to be put into the record. + Input must match the argument string. + Any input from the device should be consumed with an + in command. + If a device, for example, acknowledges a setting, use an + in command to check the acknowledge, even though + it contains no user data. +
+
wait milliseconds;
+
+ Just wait for some milliseconds. + Depending on the resolution of the timer system, the actual delay + can be slightly longer than specified. +
+
event(eventcode) milliseconds;
+
+ Wait for event eventcode with some timeout. + What an event actually means depends on the used + bus. + Some buses do not support events at all, some provide many different + events. + If the bus supports only one event, (eventcode) + is dispensable. +
+
exec string;
+
+ The argument string is passed to the IOC shell + as a command to execute. +
+
disconnect;
+
+ Disconnect from the hardware. + This is probably not supported by all busses. + Any in or out command will automatically + reconnect. + Only records reading in + "I/O Intr" mode + will not cause a reconnect. +
+
connect milliseconds;
+
+ Explicitely connect to the hardware with milliseconds + timeout. + Since connection is handled automatically, this command is normally not + needed. + It may be useful after a disconnect. +
+
+ + +

4. Strings

+

+In a StreamDevice protocol file, strings can be written +as quoted literals (single quotes or double quotes), as +a sequence of bytes values, or as a combination of both. +

+

+Examples for quoted literals are:
+"That's a string."
+'Say "Hello"' +

+

+There is no difference between double quoted and single quoted +literals, it just makes it easier to use quotes of the other type +in a string. +To break long strings into multiple lines of the protocol file, +close the quotes before the line break and reopen them in the next line. +Don't use a line break inside quotes. +

+

+As arguments of out or in +commands, string literals can contain +format converters. +A format converter starts with % and works similar +to formats in the C functions printf() and scanf(). +

+

+StreamDevice uses the backslash character \ to +define some escape sequences in quoted string literals:
+\", \', \%, and \\ +mean literal ", ', %, and +\.
+\a means alarm bell (ASCII code 7).
+\b means backspace (ASCII code 8).
+\t means tab (ASCII code 9).
+\n means new line (ASCII code 10).
+\r means carriage return (ASCII code 13).
+\e means escape (ASCII code 27).
+\x followed by up to two hexadecimal digits means a byte with +that hex value.
+\0 followed by up to three octal digits means a byte with +that octal value.
+\1 to \9 followed by up to two more decimal +digits means a byte with that decimal value.
+\? in the argument string of an in +command matches any input byte
+\$ followed by the name of a +protocol varible is replaced by the contents of that +variable. +

+

+For non-printable characters, it is often easier to write sequences of +byte values instead of escaped quoted string literals. +A byte is written as an unquoted decimal, hexadecimal, or octal +number in the range of -128 to 255 (-0x80 to 0xff, -0200 to 0377). +StreamDevice also defines some symbolic names for frequently +used byte codes as aliases for the numeric byte value:
+EOT means end of transmission (ASCII code 4).
+ACK means acknowledge (ASCII code 6).
+BEL means bell (ASCII code 7).
+BS means backspace (ASCII code 8).
+HT or TAB mean horizontal tabulator +(ASCII code 9).
+LF or NL mean line feed / +new line (ASCII code 10).
+CR means carriage return (ASCII code 13).
+ESC means escape (ASCII code 27).
+DEL means delete (ASCII code 127).
+SKIP in the argument string of an in +command matches any input byte. +

+

+A single string can be built from several quoted literals and byte values +by writing them separated by whitespaces or comma. +

+ +

Example:

+

+The following lines represent the same string:
+"Hello world\r\n"
+'Hello',0x20,"world",CR,LF
+72 101 108 108 111 32 119 111 114 108 100 13 10 +

+ + +

5. Protocol Variables

+

+StreamDevice uses three types of variables in a protocol file. +System variables influence the behavior +of in and out commands. +Protocol arguments work like function +arguments and can be specified in the INP or +OUT link of the record. +User variables can be defined and used +in the protocol as abbreviations for often used values. +

+

+System and user variables can be set in the global context of the +protocol file or locally inside protocols. +When set globally, a variable keeps its value until overwritten. +When set locally, a variable is valid inside the protocol only. +To set a variable use the syntax:
+variable = value; +

+

+Set variables can be referenced outside of +quoted strings by +$variable or ${variable} +and inside quoted strings by +\$variable or \${variable}. +The reference will be replaced by the value of the variable at +this point. +

+ + +

System variables

+

+This is a list of system variables, their default settings and +what they influence. +

+
+
LockTimeout = 5000;
+
+ Integer. Affects first out command in a protocol.
+ If other records currently use the device, how many milliseconds + to wait for exclusive access to the device before giving up? +
+
WriteTimeout = 100;
+
+ Integer. Affects out commands.
+ If we have access to the device but output cannot be written + immediately, how many milliseconds to wait before giving up? +
+
ReplyTimeout = 1000;
+
+ Integer. Affects in commands.
+ Different devices need different times to calculate + a reply and start sending it. + How many milliseconds to wait for the first byte of the input + from the device? + Since several other records may be waiting to access the device + during this time, LockTimeout should be larger than + ReplyTimeout. +
+
ReadTimeout = 100;
+
+ Integer. Affects in commands.
+ The device may send input in pieces (e.g. bytes). + When it stops sending, how many milliseconds to wait for more + input bytes before giving up? + If InTerminator = "", a read timeout is not an error + but a valid input termination. +
+
PollPeriod = $ReplyTimeout;
+
+ Integer. Affects first in command in + I/O Intr mode (see chapter + Record Processing).
+ In that mode, some buses require periodic polling to get asynchronous + input if no other record executes an in command at + the moment. + How many milliseconds to wait after last poll or last received + input before polling again? + If not set the same value as for ReplyTimeout is + used. +
+
Terminator
+
+ String. Affects out and in commands.
+ Most devices send and expect terminators after each message, + e.g. CR LF. + The value of the Terminator variable is automatically + appended to any output. + It is also used to find the end of input. + It is removed before the input is passed to the in + command. + If no Terminator or InTerminator is defined, + the underlying driver may use its own terminator settings. + For example, asynDriver defines its own terminator settings. +
+
OutTerminator = $Terminator;
+
+ String. Affects out commands.
+ If a device has different terminators for input and output, + use this for the output terminator. +
+
InTerminator = $Terminator;
+
+ String. Affects in commands.
+ If a device has different terminators for input and output, + use this for the input terminator. + If no Terminator or InTerminator is defined, + the underlying driver may use its own terminator settings. + If InTerminator = "", a read timeout is not an error + but a valid input termination. +
+
MaxInput = 0;
+
+ Integer. Affects in commands.
+ Some devices don't send terminators but always send a fixed message + size. How many bytes to read before terminating input even without + input terminator or read timeout? + The value 0 means "infinite". +
+
Separator = "";
+
+ String. Affects out and in commands.
+ When formatting or parsing array values in a format converter + (see formats and + waveform record), what string + to write or to expect between values? + If the first character of the Separator is a + space, it matches any number of any whitespace characters in + an in command. +
+
ExtraInput = Error;
+
+ Error or Ignore. + Affects in commands.
+ Normally, when input parsing has completed, any bytes left in the + input are treated as parse error. + If extra input bytes should be ignored, set + ExtraInput = Ignore; +
+
+ + +

Protocol arguments

+

+Sometimes, protocols differ only very little. +In that case it can be convenient to write only one protocol +and use protocol arguments for the difference. +For example a motor controller for the 3 axes X, Y, Z requires +three protocols to set a position. +

+
+moveX { out "X GOTO %d"; }
+moveY { out "Y GOTO %d"; }
+moveZ { out "Z GOTO %d"; }
+
+

+It also needs three versions of any other protocol. +That means basically writing everything three times. +To make this easier, protocol arguments can be used: +

+
+move { out "\$1 GOTO %d"; }
+
+

+Now, the protocol can be references in the OUT link +of three different records as move(X), +move(Y) and move(Z). +Up to 9 parameters, referenced as $1 ... $9 +can be specified in parentheses, separated by comma. +The variable $0 is replaced by the name of the protocol. +

+ + +

User variables

+

+User defined variables are just a means to save some typing. +Once set, a user variable can be referenced later in the protocol. +

+
+f = "FREQ";     # sets f to "FREQ" (including the quotes)
+f1 = $f " %f";  # sets f1 to "FREQ %f"
+
+getFrequency {
+    out $f "?"; # same as: out "FREQ?"; 
+    in $f1;     # same as: in "FREQ %f"; 
+}
+
+setFrequency {
+    out $f1;    # same as: out "FREQ %f"; 
+}
+
+ +

6. Exception Handlers

+

+When an error happens, an exception handler may be called. +Exception handlers are a kind of sub-protocols in a protocol. +They consist of the same set of commands and are intended to +reset the device or to finish the protocol cleanly in case of +communication problems. +Like variables, exception handlers can be defined globally or +locally. +Globally defined handlers are used for all following protocols +unless overwritten by a local handler. +There is a fixed set of exception handler names starting with +@. +

+
+
@mismatch
+
+ Called when input does not match in an in command.
+ It means that the device has sent something else than what the + protocol expected. + If the handler starts with an in command, then this + command reparses the old input from the unsuccessful in. + Error messages from the unsuccessful in are suppressed. + Nevertheless, the record will end up in INVALID/CALC + state (see chapter Record Processing). +
+
@writetimeout
+
+ Called when a write timeout occurred in an out command.
+ It means that output cannot be written to the device. + Note that out commands in the handler are + also likely to fail in this case. +
+
@replytimeout
+
+ Called when a reply timeout occurred in an in command.
+ It means that the device does not send any data. + Note that in commands in the handler are + also likely to fail in this case. +
+
@readtimeout
+
+ Called when a read timeout occurred in an in command.
+ It means that the device stopped sending data unexpectedly after + sending at least one byte. +
+
@init
+
+ Not really an exception but formally specified in the same syntax. + This handler is called from iocInit during record + initialization. + It can be used to initialize an output record with a value read from + the device. + Also see chapter Record Processing. +
+
+

Example:

+
+setPosition {
+    out "POS %f";
+    @init { out "POS?"; in "POS %f"; }
+}    
+
+

+After executing the exception handler, the protocol terminates. +If any exception occurs within an exception handler, no other handler +is called but the protocol terminates immediately. +An exception handler uses all system variable +settings from the protocol in which the exception occurred. +

+
+

Next: Format Converters

+

Dirk Zimoch, 2006

+ + + diff --git a/doc/recordinterface.html b/doc/recordinterface.html new file mode 100644 index 0000000..9cbc871 --- /dev/null +++ b/doc/recordinterface.html @@ -0,0 +1,20 @@ + + + +StreamDevice: Record API + + + + + + +

StreamDevice: Record API

+ +

Sorry, this documentation is still missing.

+ +
+

Dirk Zimoch, 2006

+ + + diff --git a/doc/recordtypes.html b/doc/recordtypes.html new file mode 100644 index 0000000..b1acb28 --- /dev/null +++ b/doc/recordtypes.html @@ -0,0 +1,54 @@ + + + +StreamDevice: Record Types + + + + + + +

StreamDevice: Record Types

+ +

Supported Record Types

+

+StreamDevice comes with support for all standard record types +in EPICS base which can have device support. +

+

+There is a separate page for each supported record type:
+aai +aao +ai +ao +bi +bo +mbbi +mbbo +mbbiDirect +mbboDirect +longin +longout +stringin +stringout +waveform +calcout +scalcout +

+

+Each page describes which record fields are used in input and output +for different format data types +during normal record processing +and initialization. +

+

+It is also possible to +write support for other recordtypes. +

+ +
+

Dirk Zimoch, 2005

+ + + diff --git a/doc/scalcout.html b/doc/scalcout.html new file mode 100644 index 0000000..892ea25 --- /dev/null +++ b/doc/scalcout.html @@ -0,0 +1,101 @@ + + + +StreamDevice: scalcout Records + + + + + + +

StreamDevice: scalcout Records

+ +

+Note: The scalcout record is part of the calc module of +the synApps package. +Device support for scalcout records is only available for calc +module release 2-4 or higher. +You also need the synApps modules genSub and sscan to +build calc. +

+

+Up to release 2-6 (synApps release 5.1), the scalcout record needs a fix. +In sCalcout.c at the end of init_record add +before the final return(0): +

+
+        if(pscalcoutDSET->init_record ) {
+	    return (*pscalcoutDSET->init_record)(pcalc);
+        }
+
+ +

Normal Operation

+

+Different record fields are used for output and input. The variable +x stands for the written or read value. +

+
+
DOUBLE format (e.g. %f):
+
+ Output: x=OVAL
+ Input: VAL=x
+ Note that the record calculates OVAL from CALC + or OCAL depending on DOPT. +
+
LONG format (e.g. %i):
+
+ Output: x=int(OVAL)
+ Input: VAL=x
+
+
ENUM format (e.g. %{):
+
+ Output: x=int(OVAL)
+ Input: VAL=x
+
+
STRING format (e.g. %s):
+
+ Output: x=OSV
+ Input: SVAL=x
+
+
+

+ For scalcout records, it is probably more useful to access fields + A to L and AA to LL + directly (e.g. "%(A)f" or "%(BB)s"). + However, even if OVAL is not used, it is calculated by the + record. Thus, CALC must always contain a valid expression + (e.g. "0"). +

+ +

Initialization

+

+ During initialization, the @init handler is executed, if + present. All format converters work like in normal operation. +

+ +
+

+aai +aao +ai +ao +bi +bo +mbbi +mbbo +mbbiDirect +mbboDirect +longin +longout +stringin +stringout +waveform +calcout +

+

Dirk Zimoch, 2005

+ + + diff --git a/doc/setup.html b/doc/setup.html new file mode 100644 index 0000000..9a6c33c --- /dev/null +++ b/doc/setup.html @@ -0,0 +1,290 @@ + + + +StreamDevice: Setup + + + + + + +

StreamDevice: Setup

+ + +

1. Prerequisites

+

+StreamDevice requires either +EPICS base R3.14.6 or higher or +EPICS base R3.13.7 or higher. +How to use StreamDevice on EPICS R3.13 is described on a +separate page. +Because StreamDevice comes with an interface to +asynDriver version R4-3 or higher as the +underlying driver layer, +you should have asynDriver installed first. +

+

+StreamDevice has support for the +scalcout record from the +calc module of synApps. +Up to calc release R2-6 (synApps release R5_1), +the scalcout record needs a fix. +(See separate scalcout page.) +

+

+Up to release R3.14.8.2, a fix in EPICS base is required to build +StreamDevice on Windows (not cygwin). +In src/iocsh/iocsh.h, add the following line +and rebuild base. +

+
+epicsShareFunc int epicsShareAPI iocshCmd(const char *command);
+
+ +

+Make sure that the asyn library and the calc module of +synApps can be found, e.g. by +adding ASYN +and (if installed) CALC or SYNAPPS +to your <top>/configure/RELEASE file: +

+ASYN=/home/epics/asyn/4-5
+CALC=/home/epics/synApps/calc/2-7
+
+

+For details on <top> directories and RELEASE files, +please refer to the +IOC Application Developer's Guide chapter 4: +EPICS Build Facility. +

+ + +

2. Build the StreamDevice Library

+

+Unpack the +StreamDevice package in a <top> directory +of your application build area. +(You might probably have done this already.) +Go to the newly created StreamDevice directory +and run make (or gmake). +This will create and install the stream library and the +stream.dbd file. +

+ +

3. Build an Application

+

+To use StreamDevice, your application must be built with the +asyn and stream libraries and must load +asyn.dbd and stream.dbd. +

+

+Include the following lines in your application Makefile: +

+
+PROD_LIBS += stream
+PROD_LIBS += asyn
+
+

+Include the following lines in your xxxAppInclude.dbd file to use +stream and asyn with serial lines and IP sockets: +

+
+include "base.dbd"
+include "stream.dbd"
+include "asyn.dbd"
+registrar(drvAsynIPPortRegisterCommands)
+registrar(drvAsynSerialPortRegisterCommands)
+
+

+You can find an example application in the streamApp +subdirectory. +

+ + +

4. The Startup Script

+

+StreamDevice is based on protocol files. +To tell StreamDevice where to search for protocol files, +set the environment variable STREAM_PROTOCOL_PATH to a +list of directories to search. +On Unix and vxWorks systems, directories are separated by :, +on Windows systems by ;. +The default value is STREAM_PROTOCOL_PATH=., +i.e. the current directory. +

+

+Also configure the buses (in asynDriver terms: ports) you want +to use with StreamDevice. +You can give the buses any name you want, like COM1 or +socket, but I recommend to use names related to the +connected device. +

+

Example:

+

+A power supply with serial communication (9600 baud, 8N1) is connected to +/dev/ttyS1. +The name of the power supply is PS1. +Protocol files are either in the current working directory or in the +../protocols directory. +

+

+Then the startup script must contain lines like this: +

+
+epicsEnvSet ("STREAM_PROTOCOL_PATH", ".:../protocols")
+
+drvAsynSerialPortConfigure ("PS1","/dev/ttyS1")
+asynSetOption ("PS1", 0, "baud", "9600")
+asynSetOption ("PS1", 0, "bits", "8")
+asynSetOption ("PS1", 0, "parity", "none")
+asynSetOption ("PS1", 0, "stop", "1")
+asynSetOption ("PS1", 0, "clocal", "Y")
+asynSetOption ("PS1", 0, "crtscts", "N")
+
+ +

If the power supply was connected via telnet-style TCP/IP +at address 192.168.164.10 on port 23, +the startupscript would contain: +

+
+epicsEnvSet ("STREAM_PROTOCOL_PATH", ".:../protocols")
+
+drvAsynIPPortConfigure ("PS1", "192.168.164.10:23")
+
+ +

+With a VXI11 (GPIB via TCP/IP) connection, e.g. a +HP E2050A on IP address 192.168.164.10, it would look like this: +

+
+epicsEnvSet ("STREAM_PROTOCOL_PATH", ".:../protocols")
+
+vxi11Configure ("PS1","192.168.164.10",1,1000,"hpib")
+
+ + + +

5. The Protocol File

+

+For each different type of hardware, create a protocol file +which defines protocols for all needed functions of the device. +The file name is arbitrary, but I recommend that it contains +the device type. +It must not contain spaces and should be short. +During iocInit, streamDevice loads and parses +the required protocol files. +If the files contain errors, they are printed on the IOC shell. +Put the protocol file in one of the directories listed in +STREAM_PROTOCOL_PATH. +

+

Example:

+

+PS1 is an ExamplePS power supply. +It communicates via ASCII strings which are terminated by +<carriage return> <line feed> (ASCII codes 13, 10). +The output current can be set by sending a string like +"CURRENT 5.13". +When asked with the string "CURRENT?", the device returns +the last set value in a string like "CURRENT 5.13 A". +

+

+Normally, an analog output record should write its value to the device. +But during startup, the record should be initialized from the the device. +The protocol file ExamplePS.proto defines the protocol +setCurrent. +

+
+Terminator = CR LF;
+
+setCurrent {
+    out "CURRENT %.2f";
+    @init { 
+        out "CURRENT?";
+        in "CURRENT %f A";
+    }
+}
+
+ + +

Reloading the Protocol File

+

+During development, the protocol files might change frequently. +To prevent restarting the IOC all the time, it is possible to reload +the protocol file of one or all records with the shell function +streamReload("record"). +If "record" is not given, all records using +StreamDevice reload their protocols. +Furthermore, the streamReloadSub function can be used +with a subroutine record to reload all protocols. +

+

+Reloading the protocol file aborts currently running protocols. +This might set SEVR=INVALID and STAT=UDF. +If a record can't reload its protocol file (e.g. because of a syntax +error), it stays INVALID/UDF until a valid +protocol is loaded. +

+ +

+See the next chapter for protocol files in depth. +

+ + +

6. Configure the Records

+

+To make a record use StreamDevice, set its DTYP field to +"stream". +The INP or OUT link has the form +"@file protocol bus [address [parameters]]". +

+

+Here, file is the name of the protocol file and +protocol is the name of a protocol defined in this file. +If the protocol requires arguments, +specify them enclosed in parentheses: +protocol(arg1,arg2,...). +

+

+The communication channel is specified with bus and +addr. +If the bus does not have addresses, addr is dispensable. +Optional parameters are passed to the bus driver. +

+ +

Example:

+

+Create an output record to set the current of PS1. +Use protocol setCurrent from file ExamplePS.proto. +The bus is called PS1 like the device. +

+ +
+record (ao, "PS1:I-set")
+{
+    field (DESC, "Set current of PS1")
+    field (DTYP, "stream")
+    field (OUT,  "@ExamplePS.proto setCurrent PS1")
+    field (EGU,  "A")
+    field (PREC, "2")
+    field (DRVL, "0")
+    field (DRVH, "60")
+    field (LOPR, "0")
+    field (HOPR, "60")
+}
+
+ +
+

Next: Protocol Files

+

Dirk Zimoch, 2007

+ + + diff --git a/doc/sls_icon.ico b/doc/sls_icon.ico new file mode 100644 index 0000000..9a556f5 Binary files /dev/null and b/doc/sls_icon.ico differ diff --git a/doc/space.gif b/doc/space.gif new file mode 100644 index 0000000..e1e3800 Binary files /dev/null and b/doc/space.gif differ diff --git a/doc/stream.css b/doc/stream.css new file mode 100644 index 0000000..8f12e74 --- /dev/null +++ b/doc/stream.css @@ -0,0 +1,79 @@ +a:link {color: #0000D0; text-decoration:none;} +a:visited {color: #0000D0; text-decoration:none;} +a:hover {color: #FF0000; text-decoration:none;} + +body { + margin-right:20px; + font-family:sans-serif; + font-size:14px; + background-color:#ffffff; +} + +pre { + background-color:#f4f4f4; + padding:1ex; + border:1px solid #000000; + white-space:pre; + margin:2ex; +} + +dt { + margin-top:0.5ex; +} + +h1 { + font-size:225%; + margin-top:0; + font-style:italic; + font-family:serif; +} + +h2 { + font-size:150%; + margin-bottom:0.5ex; +} + +h3 { + font-size:100%; + margin-bottom:0.25ex; +} + +p { + margin-top:0.75ex; + margin-bottom:0.75ex; +} + +small { + font-size:75%; +} + +code { + color: #008000; +} + +.indent { + text-indent:-4ex; + margin-left:4ex; + margin-top:0.5ex; + text-align:left; +} + +/* + a[target=ex] { + background-image:url(ex.png); + background-repeat:no-repeat; + background-position:right center; + padding-right: 12px; + } + + a[target=ex]:hover { + background-image:url(exr.png); + } +*/ + +@media print { + a:link {color: black; text-decoration:none;} + a:visited {color: black; text-decoration:none;} + a:hover {color: black; text-decoration:none;} + code {color: black; } +} diff --git a/doc/stream.html b/doc/stream.html new file mode 100644 index 0000000..56b8e53 --- /dev/null +++ b/doc/stream.html @@ -0,0 +1,92 @@ + + + +StreamDevice + + + + + + +

EPICS StreamDevice Documentation

+ +

What is StreamDevice?

+

+StreamDevice is a generic +EPICS +device support for devices with a "byte stream" based +communication interface. +That means devices that can be controlled by sending and +receiving strings (in the broadest sense, including non-printable +characters and even null-bytes). +Examples for this type of communication interface are +serial line (RS-232, RS-485, ...), +IEEE-488 (also known as GPIB or HP-IB), and TCP/IP. +StreamDevice comes with an interface to +asynDriver +but can be extended to +support other bus drivers. +

+

+If the device can be controlled with strings like +"RF:FREQ 499.655 MHZ", StreamDevice can be used. +How the strings exactly look like is defined in protocols. +Formatting and interpretation of values is done with +format converters similar to those +known from the C functions printf() and scanf(). +To support other formats, it is possible to +write your own converters. +

+

+Each record with StreamDevice support runs one protocol +to read or write its value. +All protocols are defined in +protocol files in plain ASCII text. +No compiling is necessary to change a protocol or to support new devices. +Protocols can be as simple as just one output string or can +consist of many strings sent to and read from the device. +However, a protocol is linear. +That means it runs from start to end each time the record is +processed. +It does not provide loops or branches. +

+

+StreamDevice supports all +standard records of EPICS base +which can have device support. +It is also possible to +write support for new record types. +

+ +

What is StreamDevice not?

+

+It is not a programming language for a high-level application. +It is, for example, not possible to write a complete scanning program +in a protocol. +Use other tools for that and use StreamDevice only for the +primitive commands. +

+

+It is not a block oriented device support. +It is not possible to send or receive huge blocks of data that contain +many process variables distributed over many records. +

+ +

Recommended Readings

+

+IOC Application Developer's Guide (PDF) +

+

+EPICS Record Reference Manual +

+ +
+

Next: Setup

+

Dirk Zimoch, 2006

+ + + diff --git a/doc/stream.js b/doc/stream.js new file mode 100644 index 0000000..da8aaf0 --- /dev/null +++ b/doc/stream.js @@ -0,0 +1,5 @@ +if (parent.head) { + parent.head.document.getElementById('subtitle').innerHTML=document.title; + parent.document.title=document.title; + document.getElementsByTagName('h1')[0].style.display="none"; +} diff --git a/doc/stringin.html b/doc/stringin.html new file mode 100644 index 0000000..42b48a7 --- /dev/null +++ b/doc/stringin.html @@ -0,0 +1,67 @@ + + + +StreamDevice: stringin Records + + + + + + +

StreamDevice: stringin Records

+ +

Normal Operation

+

+The variable x stands for the +written or read value. +

+
+
DOUBLE format (e.g. %f):
+
+ Not allowed. +
+
LONG format (e.g. %i):
+
+ Not allowed. +
+
ENUM format (e.g. %{):
+
+ Not allowed. +
+
STRING format (e.g. %s):
+
+ Output: x=VAL
+ Input: VAL=x +
+
+ +

Initialization

+

+ During initialization, the @init handler is executed, if + present. All format converters work like in normal operation. +

+ +
+

+aai +aao +ai +ao +bi +bo +mbbi +mbbo +mbbiDirect +mbboDirect +longin +longout +stringout +waveform +calcout +scalcout +

+

Dirk Zimoch, 2005

+ + + diff --git a/doc/stringout.html b/doc/stringout.html new file mode 100644 index 0000000..a57bc54 --- /dev/null +++ b/doc/stringout.html @@ -0,0 +1,67 @@ + + + +StreamDevice: stringout Records + + + + + + +

StreamDevice: stringout Records

+ +

Normal Operation

+

+The variable x stands for the +written or read value. +

+
+
DOUBLE format (e.g. %f):
+
+ Not allowed. +
+
LONG format (e.g. %i):
+
+ Not allowed. +
+
ENUM format (e.g. %{):
+
+ Not allowed. +
+
STRING format (e.g. %s):
+
+ Output: x=VAL
+ Input: VAL=x +
+
+ +

Initialization

+

+ During initialization, the @init handler is executed, if + present. All format converters work like in normal operation. +

+ +
+

+aai +aao +ai +ao +bi +bo +mbbi +mbbo +mbbiDirect +mbboDirect +longin +longout +stringin +waveform +calcout +scalcout +

+

Dirk Zimoch, 2005

+ + + diff --git a/doc/tipsandtricks.html b/doc/tipsandtricks.html new file mode 100644 index 0000000..092d5f1 --- /dev/null +++ b/doc/tipsandtricks.html @@ -0,0 +1,297 @@ + + + +StreamDevice: Tips and Tricks + + + + + + +

StreamDevice: Tips and Tricks

+ + +

I have many almost identical protocols

+

+You can give arguments to a protocol. +In the INP or OUT link, write: +

+

+ +field (OUT, "@protocolfile protocol(arg1,arg2,arg3) bus") + +

+

+In the protocol, reference arguments as $1 $2 $3 or inside strings +as "\$1 \$2 \$3". +

+

+ +moveaxis {out "move\$1 %.6f";}
+field (OUT, "@motor.proto moveaxis(X) motor1") +
+

+

+ +readpressure {out 0x02 0x00 $1; in 0x82 0x00 $1 "%2r";}
+field (INP, "@vacuumgauge.proto readpressure(0x84) gauge3") +
+

+ + +

I have a device that sends unsolicited data

+

+Use I/O Intr processing. +The record receives any input and processes only when the input matches. +

+

+ +read {in "new value = %f";} + +

+

+ +record (ai, "$(RECORD)") {
+  field (DTYP, "stream")
+  field (INP, "@(DEVICETYPE).proto read $(BUS)")
+  field (SCAN, "I/O Intr")
+} +
+

+ + +

I have a device that sends multi-line messages

+
+Here is the value:
+3.1415
+
+

+Use as many in commands as you get input lines. +

+

+ +read_value {in "Here is the value:"; in "%f";} + +

+ + +

I need to write more than one value in one message

+

+There is more than one solution to this problem. +Different approaches have different requirements. +

+

A) All values have the same type and are separated by the same string

+

+Use array records (e.g. waveform, +aao). +

+

+ +array_out {separator=", "; out "an array: (%.2f)";} + +

+

+The format %.2f is repeated for each element of the array. +All elements are separated by ", ".
+Output will look like this: +

+an array: (3.14, 17.30, -12.34)
+
+

+

B) We have up to 12 numeric values

+

+Use a calcout record and +field references in the format. +

+

+ +write_ABC {out "A=%(A).2f B=%(B).6f C=%(C).0f";} + +

+

+You must specify a valid expression in the CALC field +even if you don't use it. +

+

+ +record (calcout, "$(RECORD)") {
+  field (INPA, "$(A_RECORD)")
+  field (INPB, "$(B_RECORD)")
+  field (INPC, "$(C_RECORD)")
+  field (CALC, "0")
+  field (DTYP, "stream")
+  field (OUT, "@(DEVICETYPE).proto write_ABC $(BUS)")
+} +
+

+

C) Values are in other records on the same IOC

+

+Use record references in the format. +

+

+ +acquire {out 'ACQUIRE "%(\$1:directory.VAL)s/%s",%(\$1:time.VAL).3f;';} + +

+

+You must specify the full record.FIELD name, +even for VAL fields. +To avoid record names in protocol files use +protocol arguments. +In the link, specify the record name or just the basename of the +other records (device name) in parentheses. +

+

+ +record (stringout, "$(DEVICE):getimage") {
+  field (DTYP, "stream")
+  field (OUT, "@(DEVICETYPE).proto acquire($(DEVICE)) $(BUS)")
+} +
+

+ + +

I need to read more than one value from one message

+

+Again, there is more than one solution to this problem. +

+

A) All values have the same type and are separated by the same string

+

+Use array records (e.g. waveform, +aai).
+

+

+ +array_in {separator=","; in "array = (%f)";} + +

+

+The format %f is repeated for each element of the array. +A "," is expected beween element.
+Input may look like this: +

+
+array = (3.14, 17.30, -12.34)
+
+ +

B) The message and the values in it can be filtered easily

+

+Use I/O Intr processing +and value skipping (%*)
+

+

+ +read_A {out "GET A,B"; in "A=%f, B=%*f";}
+read_B {in "A=%*f, B=%f";} +
+

+

+ +record (ai, "$(DEVICE):A") {
+  field (DTYP, "stream")
+  field (INP, "@(DEVICETYPE).proto read_A $(BUS)")
+  field (SCAN, "1 second")
+}
+record (ai, "$(DEVICE):B") {
+  field (DTYP, "stream")
+  field (INP, "@(DEVICETYPE).proto read_B $(BUS)")
+  field (SCAN, "I/O Intr")
+} +
+

+

+Record A actively requests values every second. +The reply contains values A and B. +Record A filters only value A from the input and ignores value B +by using the * flag. +Nevertheless, a complete syntax check is performed: B must be a +valid floating point number. +Record B is I/O Intr and gets (a copy of) any input, including +input that was directed to record A. +If it finds a matching string it ignores value A, reads value B and +then processes. +Any non-matching input is ignored by record B. +

+

C) Values should be stored in other records on the same IOC

+

+Use record references in the format. +To avoid record names in protocol files, use +protocol arguments. +

+

+ +read_AB {out "GET A,B"; in "A=%f, B=%(\$1.VAL)f";} + +

+

+ +record (ai, "$(DEVICE):A") {
+  field (DTYP, "stream")
+  field (INP, "@(DEVICETYPE).proto read_AB($(DEVICE):B) $(BUS)")
+  field (SCAN, "1 second")
+}
+record (ai, "$(DEVICE):B") {
+} +
+

+

+Whenever record A reads input, it stores the first value in its own VAL field +as usual and the second in the VAL field of record B. +Because the VAL field of record B has the PP attribute, this automatically +processes record B. +

+ + +

I have a device that sends mixed data types: numbers and strings

+

+Use a @mismatch +exception handler and +record references in the format. +To avoid record names in protocol files, use +protocol arguments. +

+

Example

+

+When asked "CURRENT?", the device send something like +"CURRENT 3.24 A" or a message like +"device switched off". +

+

+ +read_current {out "CURRENT?"; in "CURRENT %f A"; @mismatch {in "%(\1.VAL)39c";}} + +

+

+ +record (ai, "$(DEVICE):readcurrent") {
+  field (DTYP, "stream")
+  field (INP, "@(DEVICETYPE).proto read_current($(DEVICE):message) $(BUS)")
+}
+record (stringin, "$(DEVICE):message") {
+} +
+

+

+After processing the readcurrent record, you can see from +SEVR/STAT if the read was successful or not. +With some more records, you can clean the message record if SEVR is not INVALID. +

+ +record (calcout, "$(DEVICE):clean_1") {
+  field (INPA, "$(DEVICE):readcurrent.SEVR CP")
+  field (CALC, "A!=2")
+  field (OOPT, "When Non-zero")
+  field (OUT, "$(DEVICE):clean_2.PROC")
+}
+record (stringout, "$(DEVICE):clean_2") {
+  field (VAL, "OK")
+  field (OUT, "$(DEVICE):message PP")
+}
+ +
+
+

Dirk Zimoch, 2007

+ + + diff --git a/doc/waveform.html b/doc/waveform.html new file mode 100644 index 0000000..8614972 --- /dev/null +++ b/doc/waveform.html @@ -0,0 +1,126 @@ + + + +StreamDevice: waveform Records + + + + + + +

StreamDevice: waveform Records

+ +

Normal Operation

+

+With waveform records, the format converter is applied to +each element. Between the elements, a separator is printed +or expected as specified by the Separator +variable in the +protocol. +When parsing input, a space as the first character of the +Separator matches any number of any whitespace +characters. +

+

+During input, a maximum of NELM elements is +read and NORD is updated accordingly. +Parsing of elements stops when the separator does not match, +conversion fails, or the end of the input is reached. +A minimum of one element must be available. +

+

+During output, the first NORD elements are +written. +

+

+The format data type must be convertible to or from the type +specified in the FTVL field. +The variable x[i] stands for one element of +the written or read value. +

+
+
DOUBLE format (e.g. %f):
+
+ Output:x[i]=double(VAL[i])
+ FTVL can be "DOUBLE", "FLOAT", + "LONG", "ULONG", "SHORT", + "USHORT", "CHAR", "UCHAR", + or "ENUM" (which is treated as "USHORT").
+ Input: VAL[i]=FTVL(x[i])
+ FTVL must be "FLOAT" or "DOUBLE" +
+
LONG or ENUM format (e.g. %i or %{):
+
+ Output: x[i]=long(VAL[i])
+ FTVL can be + "LONG", "ULONG", "SHORT", + "USHORT", "CHAR", "UCHAR", + or "ENUM" (which is treated as "USHORT").
+ Signed values are sign-extended to long, unsigned values are + zero-extended to long before converting them.
+ Input: VAL[i]=FTVL(x[i])
+ FTVL can be "DOUBLE", "FLOAT", + "LONG", "ULONG", "SHORT", + "USHORT", "CHAR", "UCHAR", + or "ENUM" (which is treated as "USHORT").
+ The value is truncated to the least significant bytes if + FTVL has a smaller data size than long. +
+
STRING format (e.g. %s):
+
+
+
If FTVL=="STRING":
+
+ Output: x[i]=VAL[i]
+ Input: VAL[i]=x[i]
+ Note that this is an array of strings, not an array of characters. +
+
If FTVL=="CHAR" or FTVL="UCHAR":
+
+ In this case, the complete waveform is treated as a large + single string of size NORD. + No separators are printed or expected.
+ Output: x=range(VAL,0,NORD)
+ The first NORD characters are printed, + which might be less than NELM.
+ Input: VAL=x, NORD=length(x)
+ A maximum of NELM-1 characters can be read. + NORD is updated to the index of the first of the + trailing zeros. + Usually, this is the same as the string length. +
+
+ Other values of FTVL are not allowed for this format. +
+
+ +

Initialization

+

+ During initialization, the @init handler is executed, if + present. All format converters work like in normal operation. +

+ +
+

+aai +aao +ai +ao +bi +bo +mbbi +mbbo +mbbiDirect +mbboDirect +longin +longout +stringin +stringout +calcout +scalcout +

+

Dirk Zimoch, 2005

+ + + diff --git a/src/AsynDriverInterface.cc b/src/AsynDriverInterface.cc new file mode 100644 index 0000000..11b4e28 --- /dev/null +++ b/src/AsynDriverInterface.cc @@ -0,0 +1,1324 @@ +/*************************************************************** +* StreamDevice Support * +* * +* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) * +* * +* This is the interface to asyn driver for StreamDevice. * +* Please refer to the HTML files in ../doc/ for a detailed * +* documentation. * +* * +* If you do any changes in this file, you are not allowed to * +* redistribute it any more. If there is a bug or a missing * +* feature, send me an email and/or your patch. If I accept * +* your changes, they will go to the next release. * +* * +* DISCLAIMER: If this software breaks something or harms * +* someone, it's your problem. * +* * +***************************************************************/ + +#include "devStream.h" +#include "StreamBusInterface.h" +#include "StreamError.h" +#include "StreamBuffer.h" + +#ifdef EPICS_3_14 +#include +#include +#include +#else +#include +#include +#include +extern "C" { +#include +} +#endif + +#include +#include +#include +#include +#include + +/* How things are implemented: + +synchonous io: + +lockRequest() + pasynManager->blockProcessCallback(), + pasynCommon->connect() only if + lockTimeout is unlimited (0) and port is not yet connected + pasynManager->queueRequest() + when request is handled + lockCallback(StreamIoSuccess) + if request fails + lockCallback(StreamIoTimeout) + +writeRequest() + pasynManager->queueRequest() + when request is handled + pasynOctet->flush() + pasynOctet->writeRaw() + if writeRaw() times out + writeCallback(StreamIoTimeout) + if writeRaw fails otherwise + writeCallback(StreamIoFault) + if writeRaw succeeds and all bytes have been written + writeCallback(StreamIoSuccess) + if not all bytes can be written + pasynManager->queueRequest() to write next part + if request fails + writeCallback(StreamIoTimeout) + +readRequest() + pasynManager->queueRequest() + when request is handled + optionally: pasynOctet->setInputEos() + pasynOctet->read() + if time out at the first byte + readCallback(StreamIoNoReply) + if time out at next bytes + readCallback(StreamIoTimeout,buffer,received) + if other fault + readCallback(StreamIoFault,buffer,received) + if success and reason is ASYN_EOM_END or ASYN_EOM_EOS + readCallback(StreamIoEnd,buffer,received) + if success and no end detected + readCallback(StreamIoSuccess,buffer,received) + if readCallback() has returned true (wants more input) + loop back to the pasynOctet->read() call + if request fails + readCallback(StreamIoFault) + +unlock() + call pasynManager->unblockProcessCallback() + +asynchonous input support ("I/O Intr"): + +pasynOctet->registerInterruptUser(...,intrCallbackOctet,...) is called +initially. This calls intrCallbackOctet() every time input is received, +but only if someone else is doing a read. Thus, if nobody reads +something, arrange for periodical read polls. + +*/ + +extern "C" { +static void handleRequest(asynUser*); +static void handleTimeout(asynUser*); +static void intrCallbackOctet(void* pvt, asynUser *pasynUser, + char *data, size_t numchars, int eomReason); +static void intrCallbackInt32(void* pvt, asynUser *pasynUser, + epicsInt32 data); +static void intrCallbackUInt32(void* pvt, asynUser *pasynUser, + epicsUInt32 data); +} + +enum IoAction { + None, Lock, Write, Read, AsyncRead, AsyncReadMore, + ReceiveEvent, Connect, Disconnect +}; + +static const char* ioActionStr[] = { + "None", "Lock", "Write", "Read", + "AsyncRead", "AsyncReadMore", + "ReceiveEvent", "Connect", "Disconnect" +}; + +static const char* asynStatusStr[] = { + "asynSuccess", "asynTimeout", "asynOverflow", "asynError" +}; + +static const char* eomReasonStr[] = { + "NONE", "CNT", "EOS", "CNT+EOS", "END", "CNT+END", "EOS+END", "CNT+EOS+END" +}; + +class AsynDriverInterface : StreamBusInterface +#ifdef EPICS_3_14 + , epicsTimerNotify +#endif +{ + asynUser* pasynUser; + asynCommon* pasynCommon; + void* pvtCommon; + asynOctet* pasynOctet; + void* pvtOctet; + void* intrPvtOctet; + asynInt32* pasynInt32; + void* pvtInt32; + void* intrPvtInt32; + asynUInt32Digital* pasynUInt32; + void* pvtUInt32; + void* intrPvtUInt32; + asynGpib* pasynGpib; + void* pvtGpib; + IoAction ioAction; + double lockTimeout; + double writeTimeout; + double readTimeout; + double replyTimeout; + long expectedLength; + unsigned long eventMask; + unsigned long receivedEvent; + StreamBuffer inputBuffer; + const char* outputBuffer; + size_t outputSize; + int peeksize; +#ifdef EPICS_3_14 + epicsTimerQueueActive* timerQueue; + epicsTimer* timer; +#else + WDOG_ID timer; + CALLBACK timeoutCallback; +#endif + + AsynDriverInterface(Client* client); + ~AsynDriverInterface(); + + // StreamBusInterface methods + bool lockRequest(unsigned long lockTimeout_ms); + bool unlock(); + bool writeRequest(const void* output, size_t size, + unsigned long writeTimeout_ms); + bool readRequest(unsigned long replyTimeout_ms, + unsigned long readTimeout_ms, long expectedLength, bool async); + bool acceptEvent(unsigned long mask, unsigned long replytimeout_ms); + bool supportsEvent(); + bool supportsAsyncRead(); + bool connectRequest(unsigned long connecttimeout_ms); + bool disconnect(); + void finish(); + +#ifdef EPICS_3_14 + // epicsTimerNotify methods + epicsTimerNotify::expireStatus expire(const epicsTime &); +#else + static void expire(CALLBACK *pcallback); +#endif + + // local methods + void timerExpired(); + bool connectToBus(const char* busname, int addr); + void lockHandler(); + void writeHandler(); + void readHandler(); + void connectHandler(); + void disconnectHandler(); + bool connectToAsynPort(); + void asynReadHandler(const char *data, int numchars, int eomReason); + asynQueuePriority priority() { + return static_cast + (StreamBusInterface::priority()); + } + void startTimer(double timeout) { +#ifdef EPICS_3_14 + timer->start(*this, timeout); +#else + callbackSetPriority(priority(), &timeoutCallback); + wdStart(timer, (int)((timeout+1)*sysClkRateGet())-1, + reinterpret_cast(callbackRequest), + reinterpret_cast(&timeoutCallback)); +#endif + } + void cancelTimer() { +#ifdef EPICS_3_14 + timer->cancel(); +#else + wdCancel(timer); +#endif + } + + // asynUser callback functions + friend void handleRequest(asynUser*); + friend void handleTimeout(asynUser*); + friend void intrCallbackOctet(void* pvt, asynUser *pasynUser, + char *data, size_t numchars, int eomReason); + friend void intrCallbackInt32(void* pvt, asynUser *pasynUser, + epicsInt32 data); + friend void intrCallbackUInt32(void* pvt, asynUser *pasynUser, + epicsUInt32 data); +public: + // static creator method + static StreamBusInterface* getBusInterface(Client* client, + const char* busname, int addr, const char* param); +}; + +RegisterStreamBusInterface(AsynDriverInterface); + +AsynDriverInterface:: +AsynDriverInterface(Client* client) : StreamBusInterface(client) +{ + pasynCommon = NULL; + pasynOctet = NULL; + intrPvtOctet = NULL; + pasynInt32 = NULL; + intrPvtInt32 = NULL; + pasynUInt32 = NULL; + intrPvtUInt32 = NULL; + pasynGpib = NULL; + eventMask = 0; + receivedEvent = 0; + peeksize = 1; + pasynUser = pasynManager->createAsynUser(handleRequest, + handleTimeout); + assert(pasynUser); + pasynUser->userPvt = this; +#ifdef EPICS_3_14 + timerQueue = &epicsTimerQueueActive::allocate(true); + assert(timerQueue); + timer = &timerQueue->createTimer(); + assert(timer); +#else + timer = wdCreate(); + callbackSetCallback(expire, &timeoutCallback); + callbackSetUser(this, &timeoutCallback); +#endif +} + +AsynDriverInterface:: +~AsynDriverInterface() +{ + cancelTimer(); + + if (intrPvtInt32) + { + // Int32 event interface is connected + pasynInt32->cancelInterruptUser(pvtInt32, + pasynUser, intrPvtInt32); + } + if (intrPvtUInt32) + { + // UInt32 event interface is connected + pasynUInt32->cancelInterruptUser(pvtUInt32, + pasynUser, intrPvtUInt32); + } + if (pasynOctet) + { + // octet stream interface is connected + int wasQueued; + if (intrPvtOctet) + { + pasynOctet->cancelInterruptUser(pvtOctet, + pasynUser, intrPvtOctet); + } + pasynManager->cancelRequest(pasynUser, &wasQueued); + // does not return until running handler has finished + } + // Now, no handler is running any more and none will start. + +#ifdef EPICS_3_14 + timer->destroy(); + timerQueue->release(); +#else + wdDelete(timer); +#endif + pasynManager->disconnect(pasynUser); + pasynManager->freeAsynUser(pasynUser); + pasynUser = NULL; +} + +// interface function getBusInterface(): +// do we have this bus/addr ? +StreamBusInterface* AsynDriverInterface:: +getBusInterface(Client* client, + const char* busname, int addr, const char*) +{ + AsynDriverInterface* interface = new AsynDriverInterface(client); + if (interface->connectToBus(busname, addr)) + { + debug ("AsynDriverInterface::getBusInterface(%s, %d): " + "new Interface allocated\n", + busname, addr); + return interface; + } + delete interface; + return NULL; +} + +// interface function supportsEvent(): +// can we handle the StreamDevice command 'event'? +bool AsynDriverInterface:: +supportsEvent() +{ + return (pasynInt32 != NULL) || (pasynUInt32 != NULL); +} + +bool AsynDriverInterface:: +supportsAsyncRead() +{ + if (intrPvtOctet) return true; + + // hook "I/O Intr" support + if (pasynOctet->registerInterruptUser(pvtOctet, pasynUser, + intrCallbackOctet, this, &intrPvtOctet) != asynSuccess) + { + error("%s: bus does not support asynchronous input: %s\n", + clientName(), pasynUser->errorMessage); + return false; + } + return true; +} + +bool AsynDriverInterface:: +connectToBus(const char* busname, int addr) +{ + if (pasynManager->connectDevice(pasynUser, busname, addr) != + asynSuccess) + { + // asynDriver does not know this busname/address + return false; + } + + asynInterface* pasynInterface; + + // find the asynCommon interface + pasynInterface = pasynManager->findInterface(pasynUser, + asynCommonType, true); + if(!pasynInterface) + { + error("%s: bus %s does not support asynCommon interface\n", + clientName(), busname); + return false; + } + pasynCommon = static_cast(pasynInterface->pinterface); + pvtCommon = pasynInterface->drvPvt; + + // find the asynOctet interface + pasynInterface = pasynManager->findInterface(pasynUser, + asynOctetType, true); + if(!pasynInterface) + { + error("%s: bus %s does not support asynOctet interface\n", + clientName(), busname); + return false; + } + pasynOctet = static_cast(pasynInterface->pinterface); + pvtOctet = pasynInterface->drvPvt; + + // is it a GPIB interface ? + pasynInterface = pasynManager->findInterface(pasynUser, + asynGpibType, true); + if(pasynInterface) + { + pasynGpib = static_cast(pasynInterface->pinterface); + pvtGpib = pasynInterface->drvPvt; + // asynGpib returns overflow error if we try to peek + // (read only one byte first). + peeksize = inputBuffer.capacity(); + } + + // look for interfaces for events + pasynInterface = pasynManager->findInterface(pasynUser, + asynInt32Type, true); + if(pasynInterface) + { + pasynInt32 = static_cast(pasynInterface->pinterface); + pvtInt32 = pasynInterface->drvPvt; + pasynUser->reason = ASYN_REASON_SIGNAL; // required for GPIB + if (pasynInt32->registerInterruptUser(pvtInt32, pasynUser, + intrCallbackInt32, this, &intrPvtInt32) == asynSuccess) + { + return true; + } + error("%s: bus %s does not allow to register for " + "Int32 interrupts: %s\n", + clientName(), busname, pasynUser->errorMessage); + pasynInt32 = NULL; + intrPvtInt32 = NULL; + } + + // no asynInt32 available, thus try asynUInt32 + pasynInterface = pasynManager->findInterface(pasynUser, + asynUInt32DigitalType, true); + if(pasynInterface) + { + pasynUInt32 = + static_cast(pasynInterface->pinterface); + pvtUInt32 = pasynInterface->drvPvt; + pasynUser->reason = ASYN_REASON_SIGNAL; + if (pasynUInt32->registerInterruptUser(pvtUInt32, + pasynUser, intrCallbackUInt32, this, 0xFFFFFFFF, + &intrPvtUInt32) == asynSuccess) + { + return true; + } + error("%s: bus %s does not allow to register for " + "UInt32 interrupts: %s\n", + clientName(), busname, pasynUser->errorMessage); + pasynUInt32 = NULL; + intrPvtUInt32 = NULL; + } + + // no event interface available, never mind + return true; +} + +// interface function: we want exclusive access to the device +// lockTimeout_ms=0 means "block here" (used in @init) +bool AsynDriverInterface:: +lockRequest(unsigned long lockTimeout_ms) +{ + debug("AsynDriverInterface::lockRequest(%s, %ld msec)\n", + clientName(), lockTimeout_ms); + asynStatus status; + lockTimeout = lockTimeout_ms ? lockTimeout_ms*0.001 : -1.0; + if (!lockTimeout_ms) + { + if (!connectToAsynPort()) return false; + } + + ioAction = Lock; + status = pasynManager->queueRequest(pasynUser, priority(), + lockTimeout); + if (status != asynSuccess) + { + error("%s lockRequest: pasynManager->queueRequest() failed: %s\n", + clientName(), pasynUser->errorMessage); + return false; + } + // continues with: + // handleRequest() -> lockHandler() -> lockCallback() + // or handleTimeout() -> lockCallback(StreamIoTimeout) + return true; +} + +bool AsynDriverInterface:: +connectToAsynPort() +{ + asynStatus status; + int connected; + + debug("AsynDriverInterface::connectToAsynPort(%s)\n", + clientName()); + status = pasynManager->isConnected(pasynUser, &connected); + if (status != asynSuccess) + { + error("%s: pasynManager->isConnected() failed: %s\n", + clientName(), pasynUser->errorMessage); + return false; + } + if (!connected) + { + status = pasynCommon->connect(pvtCommon, pasynUser); + debug("AsynDriverInterface::connectToAsynPort(%s): " + "status=%s\n", + clientName(), asynStatusStr[status]); + if (status != asynSuccess) + { + error("%s: pasynCommon->connect() failed: %s\n", + clientName(), pasynUser->errorMessage); + return false; + } + } + return true; +} + +// now, we can have exclusive access (called by asynManager) +void AsynDriverInterface:: +lockHandler() +{ + debug("AsynDriverInterface::lockHandler(%s)\n", + clientName()); + pasynManager->blockProcessCallback(pasynUser, false); + lockCallback(StreamIoSuccess); +} + +// interface function: we don't need exclusive access any more +bool AsynDriverInterface:: +unlock() +{ + debug("AsynDriverInterface::unlock(%s)\n", + clientName()); + pasynManager->unblockProcessCallback(pasynUser, false); + return true; +} + +// interface function: we want to write something +bool AsynDriverInterface:: +writeRequest(const void* output, size_t size, + unsigned long writeTimeout_ms) +{ +#ifndef NO_TEMPORARY + debug("AsynDriverInterface::writeRequest(%s, \"%s\", %ld msec)\n", + clientName(), StreamBuffer(output, size).expand()(), + writeTimeout_ms); +#endif + + asynStatus status; + outputBuffer = (char*)output; + outputSize = size; + writeTimeout = writeTimeout_ms*0.001; + ioAction = Write; + status = pasynManager->queueRequest(pasynUser, priority(), + writeTimeout); + if (status != asynSuccess) + { + error("%s writeRequest: pasynManager->queueRequest() failed: %s\n", + clientName(), pasynUser->errorMessage); + return false; + } + // continues with: + // handleRequest() -> writeHandler() -> lockCallback() + // or handleTimeout() -> writeCallback(StreamIoTimeout) + return true; +} + +// now, we can write (called by asynManager) +void AsynDriverInterface:: +writeHandler() +{ + debug("AsynDriverInterface::writeHandler(%s)\n", + clientName()); + asynStatus status; + size_t written = 0; + pasynUser->timeout = writeTimeout; + + // discard any early input or early events + status = pasynOctet->flush(pvtOctet, pasynUser); + receivedEvent = 0; + + if (status != asynSuccess) + { + error("%s: pasynOctet->flush() failed: %s\n", + clientName(), pasynUser->errorMessage); + writeCallback(StreamIoFault); + return; + } + + // has stream already added a terminator or should + // asyn do so? + + size_t streameoslen; + const char* streameos = getOutTerminator(streameoslen); + if (streameos) // stream has added eos + { + status = pasynOctet->writeRaw(pvtOctet, pasynUser, + outputBuffer, outputSize, &written); + } + else // asyn should add eos + { + status = pasynOctet->write(pvtOctet, pasynUser, + outputBuffer, outputSize, &written); + } + switch (status) + { + case asynSuccess: + outputBuffer += written; + outputSize -= written; + if (outputSize > 0) + { + status = pasynManager->queueRequest(pasynUser, + priority(), lockTimeout); + if (status != asynSuccess) + { + error("%s writeHandler: " + "pasynManager->queueRequest() failed: %s\n", + clientName(), pasynUser->errorMessage); + writeCallback(StreamIoFault); + } + // continues with: + // handleRequest() -> writeHandler() -> writeCallback() + // or handleTimeout() -> writeCallback(StreamIoTimeout) + return; + } + writeCallback(StreamIoSuccess); + return; + case asynTimeout: + writeCallback(StreamIoTimeout); + return; + case asynOverflow: + error("%s: asynOverflow in write: %s\n", + clientName(), pasynUser->errorMessage); + writeCallback(StreamIoFault); + return; + case asynError: + error("%s: asynError in write: %s\n", + clientName(), pasynUser->errorMessage); + writeCallback(StreamIoFault); + return; + } +} + +// interface function: we want to read something +bool AsynDriverInterface:: +readRequest(unsigned long replyTimeout_ms, unsigned long readTimeout_ms, + long _expectedLength, bool async) +{ + debug("AsynDriverInterface::readRequest(%s, %ld msec reply, " + "%ld msec read, expect %ld bytes, asyn=%s)\n", + clientName(), replyTimeout_ms, readTimeout_ms, + _expectedLength, async?"yes":"no"); + + asynStatus status; + readTimeout = readTimeout_ms*0.001; + replyTimeout = replyTimeout_ms*0.001; + double queueTimeout; + expectedLength = _expectedLength; + + if (async) + { + ioAction = AsyncRead; + queueTimeout = -1.0; + // First poll for input (no timeout), + // later poll periodically if no other input arrives + // from intrCallbackOctet() + } + else { + ioAction = Read; + queueTimeout = replyTimeout; + } + status = pasynManager->queueRequest(pasynUser, + priority(), queueTimeout); + if (status != asynSuccess && !async) + { + error("%s readRequest: pasynManager->queueRequest() failed: %s\n", + clientName(), pasynUser->errorMessage); + return false; + } + // continues with: + // handleRequest() -> readHandler() -> readCallback() + // or handleTimeout() -> readCallback(StreamIoTimeout) + return true; +} + +// now, we can read (called by asynManager) +void AsynDriverInterface:: +readHandler() +{ + size_t streameoslen, deveoslen; + const char* streameos; + const char* deveos; + int oldeoslen = -1; + char oldeos[16]; + + // Setup eos if required. + streameos = getInTerminator(streameoslen); + deveos = streameos; + deveoslen = streameoslen; + if (streameos) do // streameos == NULL means: don't change eos + { + asynStatus status; + status = pasynOctet->getInputEos(pvtOctet, + pasynUser, oldeos, sizeof(oldeos)-1, &oldeoslen); + if (status != asynSuccess) + { + oldeoslen = -1; + // No EOS support? + } + // device (e.g. GPIB) might not accept full eos length + if (pasynOctet->setInputEos(pvtOctet, pasynUser, + deveos, deveoslen) == asynSuccess) + { +#ifndef NO_TEMPORARY + if (ioAction != AsyncRead) + { + debug("AsynDriverInterface::readHandler(%s) " + "input EOS set to %s\n", + clientName(), + StreamBuffer(deveos, deveoslen).expand()()); + } +#endif + break; + } + deveos++; deveoslen--; + if (!deveoslen) + { + error("%s: warning: pasynOctet->setInputEos() failed: %s\n", + clientName(), pasynUser->errorMessage); + } + } while (deveoslen); + + int bytesToRead = peeksize; + long buffersize; + + if (expectedLength > 0) + { + buffersize = expectedLength; + if (peeksize > 1) + { + /* we can't peek, try to read whole message */ + bytesToRead = expectedLength; + } + } + else + { + buffersize = inputBuffer.capacity(); + } + char* buffer = inputBuffer.clear().reserve(buffersize); + + if (ioAction == AsyncRead) + { + // In AsyncRead mode just poll + // and read as much as possible + pasynUser->timeout = readTimeout; + bytesToRead = buffersize; + } + else + { + pasynUser->timeout = replyTimeout; + } + bool waitForReply = true; + int received; + int eomReason; + asynStatus status; + long readMore; + + while (1) + { + readMore = 0; + received = 0; + eomReason = 0; + + status = pasynOctet->read(pvtOctet, pasynUser, + buffer, bytesToRead, (size_t*)&received, &eomReason); + if (ioAction != AsyncRead || status != asynTimeout) + { + debug("AsynDriverInterface::readHandler(%s): " + "read(..., bytesToRead=%d, ...) [timeout=%f seconds] = %s\n", + clientName(), bytesToRead, pasynUser->timeout, + asynStatusStr[status]); + } + // pasynOctet->read() has already cut off terminator. + + switch (status) + { + case asynSuccess: + if (ioAction == AsyncRead) + { +#ifndef NO_TEMPORARY + debug("AsynDriverInterface::readHandler(%s): " + "AsyncRead poll: received %d of %d bytes \"%s\" " + "eomReason=%s [data ignored]\n", + clientName(), received, bytesToRead, + StreamBuffer(buffer, received).expand()(), + eomReasonStr[eomReason&0x7]); +#endif + // ignore what we got from here. + // input was already handeled by asynReadHandler() + // read until no more input is available + readMore = -1; + break; + } +#ifndef NO_TEMPORARY + debug("AsynDriverInterface::readHandler(%s): " + "received %d of %d bytes \"%s\" " + "eomReason=%s\n", + clientName(), received, bytesToRead, + StreamBuffer(buffer, received).expand()(), + eomReasonStr[eomReason&0x7]); +#endif + // asynOctet->read() cuts off terminator, but: + // If stream has set a terminator which is longer + // than what the device (e.g. GPIB) can handle, + // only the last part is cut. If that part matches + // but the whole terminator does not, it is falsely cut. + // So what to do? + // Restore complete terminator and leave it to StreamCore to + // find out if this was really the end of the input. + // Warning: received can be < 0 if message was read in parts + // and a multi-byte terminator was partially read with last + // call. + + if (deveoslen < streameoslen && (eomReason & ASYN_EOM_EOS)) + { + size_t i; + for (i = 0; i < deveoslen; i++, received++) + { + if (received >= 0) buffer[received] = deveos[i]; + // It is safe to add to buffer here, because + // the terminator was already there before + // asynOctet->read() had cut it. + // Just take care of received < 0 + } + eomReason &= ~ASYN_EOM_EOS; + } + + readMore = readCallback( + eomReason & (ASYN_EOM_END|ASYN_EOM_EOS) ? + StreamIoEnd : StreamIoSuccess, + buffer, received); + break; + case asynTimeout: + if (received == 0 && waitForReply) + { + // reply timeout + if (ioAction == AsyncRead) + { + // start next poll after timer expires + if (replyTimeout != 0.0) startTimer(replyTimeout); + // continues with: + // timerExpired() -> queueRequest() -> handleRequest() -> readHandler() + // or intrCallbackOctet() -> asynReadHandler() + break; + } + debug("AsynDriverInterface::readHandler(%s): " + "no reply\n", + clientName()); + readMore = readCallback(StreamIoNoReply); + break; + } + // read timeout +#ifndef NO_TEMPORARY + debug("AsynDriverInterface::readHandler(%s): " + "ioAction=%s, timeout after %d of %d bytes \"%s\"\n", + clientName(), ioActionStr[ioAction], + received, bytesToRead, + StreamBuffer(buffer, received).expand()()); +#endif + if (ioAction == AsyncRead || ioAction == AsyncReadMore) + { + // we already got the data from asynReadHandler() + readCallback(StreamIoTimeout, NULL, 0); + break; + } + readMore = readCallback(StreamIoTimeout, buffer, received); + break; + case asynOverflow: + if (bytesToRead == 1) + { + // device does not support peeking + // try to read whole message next time + inputBuffer.clear().reserve(100); + } else { + // buffer was still too small + // try larger buffer next time + inputBuffer.clear().reserve(inputBuffer.capacity()*2); + } + peeksize = inputBuffer.capacity(); + // deliver whatever we could save + error("%s: asynOverflow in read: %s\n", + clientName(), pasynUser->errorMessage); + readCallback(StreamIoFault, buffer, received); + break; + case asynError: + error("%s: asynError in read: %s\n", + clientName(), pasynUser->errorMessage); + readCallback(StreamIoFault, buffer, received); + break; + } + if (!readMore) break; + if (readMore > 0) + { + bytesToRead = readMore; + } + else + { + bytesToRead = inputBuffer.capacity(); + } + debug("AsynDriverInterface::readHandler(%s) " + "readMore=%ld bytesToRead=%d\n", + clientName(), readMore, bytesToRead); + pasynUser->timeout = readTimeout; + waitForReply = false; + } + + // restore original EOS + if (oldeoslen >= 0) + { + pasynOctet->setInputEos(pvtOctet, pasynUser, + oldeos, oldeoslen); + } +} + +void intrCallbackOctet(void* /*pvt*/, asynUser *pasynUser, + char *data, size_t numchars, int eomReason) +{ + AsynDriverInterface* interface = + static_cast(pasynUser->userPvt); + +// Problems here: +// 1. We get this message too when we are the poller. +// Thus we have to ignore what we got from polling. +// 2. We get this message multiple times when original reader +// reads in chunks. +// 3. eomReason=ASYN_EOM_CNT when message was too long for +// internal buffer of asynDriver. + + if (interface->ioAction == AsyncRead || + interface->ioAction == AsyncReadMore) + { + interface->asynReadHandler(data, numchars, eomReason); + } + else + { +#ifndef NO_TEMPORARY + debug("AsynDriverInterface::intrCallbackOctet(%s, buffer=\"%s\", " + "received=%d eomReason=%s) ioAction=%s\n", + interface->clientName(), StreamBuffer(data, numchars).expand()(), + numchars, eomReasonStr[eomReason&0x7], ioActionStr[interface->ioAction]); +#endif + } +} + +// get asynchronous input +void AsynDriverInterface:: +asynReadHandler(const char *buffer, int received, int eomReason) +{ + // Due to multithreading, timerExpired() might come at any time. + // It queues the next poll request which is now useless because + // we got asynchronous input. + // Unfortunately, we must be very careful not to block in this + // function. That means, we must not call cancelRequest() from here! + // We must also not call cancelRequest() in any other method that + // might have been called in port thread context, like finish(). + // Luckily, that request cannot be active right now, because we are + // inside the read handler of some other asynUser in the port thread. + // Thus, it is sufficient to mark the request as obsolete by + // setting ioAction=None. See handleRequest(). + +#ifndef NO_TEMPORARY + debug("AsynDriverInterface::asynReadHandler(%s, buffer=\"%s\", " + "received=%d eomReason=%s) ioAction=%s\n", + clientName(), StreamBuffer(buffer, received).expand()(), + received, eomReasonStr[eomReason&0x7], ioActionStr[ioAction]); +#endif + + ioAction = None; + long readMore = 1; + if (received) + { + // At the moment, it seems that asynDriver does not cut off + // terminators for interrupt users and never sets eomReason. + // This may change in future releases of asynDriver. + + asynStatus status; + const char* streameos; + size_t streameoslen; + streameos = getInTerminator(streameoslen); + char deveos[16]; // I guess that is sufficient + int deveoslen; + + if (eomReason & ASYN_EOM_EOS) + { + // Terminator was cut off. + // If terminator is handled by stream, then we must + // restore the terminator, because the "real" terminator + // might be longer than what the octet driver supports. + + if (!streameos) // stream has not defined eos + { + // Just handle eos as normal end condition + // and don't try to add it. + eomReason |= ASYN_EOM_END; + } + else + { + // Try to add terminator + status = pasynOctet->getInputEos(pvtOctet, + pasynUser, deveos, sizeof(deveos)-1, &deveoslen); + if (status == asynSuccess) + { + // We can't just append terminator to buffer, because + // we don't own that piece of memory. + // First process received data with cut-off terminator + readCallback( + StreamIoSuccess, + buffer, received); + // then add terminator + buffer = deveos; + received = deveoslen; + } + } + } + else if (!streameos) + { + // If terminator was not cut off and terminator was not + // set by stream, cut it off now. + status = pasynOctet->getInputEos(pvtOctet, + pasynUser, deveos, sizeof(deveos)-1, &deveoslen); + if (status == asynSuccess && received >= deveoslen) + { + int i; + for (i = 1; i <= deveoslen; i++) + { + if (buffer[received - i] != deveos[deveoslen - i]) break; + } + if (i > deveoslen) // terminator found + { + received -= deveoslen; + eomReason |= ASYN_EOM_END; + } + } + } + readMore = readCallback( + eomReason & ASYN_EOM_END ? + StreamIoEnd : StreamIoSuccess, + buffer, received); + } + if (readMore) + { + // wait for more input + ioAction = AsyncReadMore; + startTimer(readTimeout); + } + debug("AsynDriverInterface::asynReadHandler(%s) readMore=%li, ioAction=%s \n", + clientName(), readMore, ioActionStr[ioAction]); +} + +// interface function: we want to receive an event +bool AsynDriverInterface:: +acceptEvent(unsigned long mask, unsigned long replytimeout_ms) +{ + if (receivedEvent & mask) + { + // handle early events + receivedEvent = 0; + eventCallback(StreamIoSuccess); + return true; + } + eventMask = mask; + ioAction = ReceiveEvent; + startTimer(replytimeout_ms*0.001); + return true; +} + +void intrCallbackInt32(void* /*pvt*/, asynUser *pasynUser, epicsInt32 data) +{ + AsynDriverInterface* interface = + static_cast(pasynUser->userPvt); + debug("AsynDriverInterface::intrCallbackInt32 (%s, %ld)\n", + interface->clientName(), (long int) data); + if (interface->eventMask) + { + if (data & interface->eventMask) + { + interface->eventMask = 0; + interface->eventCallback(StreamIoSuccess); + } + return; + } + // store early events + interface->receivedEvent = data; +} + +void intrCallbackUInt32(void* /*pvt*/, asynUser *pasynUser, + epicsUInt32 data) +{ + AsynDriverInterface* interface = + static_cast(pasynUser->userPvt); + debug("AsynDriverInterface::intrCallbackUInt32 (%s, %ld)\n", + interface->clientName(), (long int) data); + if (interface->eventMask) + { + if (data & interface->eventMask) + { + interface->eventMask = 0; + interface->eventCallback(StreamIoSuccess); + } + return; + } + // store early events + interface->receivedEvent = data; +} + +void AsynDriverInterface:: +timerExpired() +{ + int autoconnect, connected; + switch (ioAction) + { + case ReceiveEvent: + // timeout while waiting for event + ioAction = None; + eventCallback(StreamIoTimeout); + return; + case AsyncReadMore: + // timeout after reading some async data + // Deliver what we have. + readCallback(StreamIoTimeout); + // readCallback() may have started a new poll + return; + case AsyncRead: + // No async input for some time, thus let's poll. + // Due to multithreading, asynReadHandler() might be active + // at the moment if another asynUser got input right now. + // queueRequest might fail if another request was just queued + pasynManager->isAutoConnect(pasynUser, &autoconnect); + pasynManager->isConnected(pasynUser, &connected); + if (autoconnect && !connected) + { + // has explicitely been disconnected + // a poll would autoConnect which is not what we want + // just retry later + startTimer(replyTimeout); + } + else + { + // queue for read poll (no timeout) + pasynManager->queueRequest(pasynUser, + asynQueuePriorityLow, -1.0); + // continues with: + // handleRequest() -> readHandler() -> readCallback() + } + return; + default: + error("INTERNAL ERROR (%s): timerExpired() unexpected ioAction %s\n", + clientName(), ioActionStr[ioAction]); + return; + } +} + +#ifdef EPICS_3_14 +epicsTimerNotify::expireStatus AsynDriverInterface:: +expire(const epicsTime &) +{ + timerExpired(); + return noRestart; +} +#else +void AsynDriverInterface:: +expire(CALLBACK *pcallback) +{ + AsynDriverInterface* interface = + static_cast(pcallback->user); + interface->timerExpired(); +} +#endif + +bool AsynDriverInterface:: +connectRequest(unsigned long connecttimeout_ms) +{ + double queueTimeout = connecttimeout_ms*0.001; + asynStatus status; + ioAction = Connect; + status = pasynManager->queueRequest(pasynUser, + asynQueuePriorityConnect, queueTimeout); + if (status != asynSuccess) + { + error("%s connectRequest: pasynManager->queueRequest() failed: %s\n", + clientName(), pasynUser->errorMessage); + return false; + } + // continues with: + // handleRequest() -> connectHandler() -> connectCallback() + // or handleTimeout() -> connectCallback(StreamIoTimeout) + return true; +} + +void AsynDriverInterface:: +connectHandler() +{ + asynStatus status; + status = pasynCommon->connect(pvtCommon, pasynUser); + if (status != asynSuccess) + { + error("%s connectRequest: pasynCommon->connect() failed: %s\n", + clientName(), pasynUser->errorMessage); + connectCallback(StreamIoFault); + return; + } + connectCallback(StreamIoSuccess); +} + +bool AsynDriverInterface:: +disconnect() +{ + asynStatus status; + ioAction = Disconnect; + status = pasynManager->queueRequest(pasynUser, + asynQueuePriorityConnect, 0.0); + if (status != asynSuccess) + { + error("%s disconnect: pasynManager->queueRequest() failed: %s\n", + clientName(), pasynUser->errorMessage); + return false; + } + // continues with: + // handleRequest() -> disconnectHandler() + // or handleTimeout() + // (does not expect callback) + return true; +} + +void AsynDriverInterface:: +disconnectHandler() +{ + asynStatus status; + status = pasynCommon->disconnect(pvtCommon, pasynUser); + if (status != asynSuccess) + { + error("%s connectRequest: pasynCommon->disconnect() failed: %s\n", + clientName(), pasynUser->errorMessage); + return; + } +} + +void AsynDriverInterface:: +finish() +{ + debug("AsynDriverInterface::finish(%s) start\n", + clientName()); + cancelTimer(); + ioAction = None; + debug("AsynDriverInterface::finish(%s) done\n", + clientName()); +} + +// asynUser callbacks to pasynManager->queueRequest() + +void handleRequest(asynUser* pasynUser) +{ + AsynDriverInterface* interface = + static_cast(pasynUser->userPvt); + switch (interface->ioAction) + { + case None: + // ignore obsolete poll request + // see asynReadHandler() + break; + case Lock: + interface->lockHandler(); + break; + case Write: + interface->writeHandler(); + break; + case AsyncRead: // polled async input + case AsyncReadMore: + case Read: // sync input + interface->readHandler(); + break; + case Connect: + interface->connectHandler(); + break; + case Disconnect: + interface->disconnectHandler(); + break; + default: + error("INTERNAL ERROR (%s): " + "handleRequest() unexpected ioAction %s\n", + interface->clientName(), ioActionStr[interface->ioAction]); + } +} + +void handleTimeout(asynUser* pasynUser) +{ + AsynDriverInterface* interface = + static_cast(pasynUser->userPvt); + switch (interface->ioAction) + { + case Lock: + interface->lockCallback(StreamIoTimeout); + break; + case Write: + interface->writeCallback(StreamIoTimeout); + break; + case Read: + interface->readCallback(StreamIoFault, NULL, 0); + break; + case AsyncReadMore: + interface->readCallback(StreamIoTimeout, NULL, 0); + break; + case Connect: + interface->connectCallback(StreamIoTimeout); + break; + case Disconnect: + // not interested in callback + break; + // No AsyncRead here because we don't use timeout when polling + default: + error("INTERNAL ERROR (%s): handleTimeout() " + "unexpected ioAction %s\n", + interface->clientName(), ioActionStr[interface->ioAction]); + } +} + diff --git a/src/BCDConverter.cc b/src/BCDConverter.cc new file mode 100644 index 0000000..1c51b8b --- /dev/null +++ b/src/BCDConverter.cc @@ -0,0 +1,155 @@ +/*************************************************************** +* StreamDevice Support * +* * +* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) * +* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) * +* * +* This is the BCD format converter of StreamDevice. * +* Please refer to the HTML files in ../doc/ for a detailed * +* documentation. * +* * +* If you do any changes in this file, you are not allowed to * +* redistribute it any more. If there is a bug or a missing * +* feature, send me an email and/or your patch. If I accept * +* your changes, they will go to the next release. * +* * +* DISCLAIMER: If this software breaks something or harms * +* someone, it's your problem. * +* * +***************************************************************/ + +#include "StreamFormatConverter.h" +#include "StreamError.h" + +// packed BCD (0x00 - 0x99) + +class BCDConverter : public StreamFormatConverter +{ + int parse (const StreamFormat&, StreamBuffer&, const char*&, bool); + bool printLong(const StreamFormat&, StreamBuffer&, long); + int scanLong(const StreamFormat&, const char*, long&); +}; + +int BCDConverter:: +parse(const StreamFormat&, StreamBuffer&, const char*&, bool) +{ + return long_format; +} + +bool BCDConverter:: +printLong(const StreamFormat& fmt, StreamBuffer& output, long value) +{ + unsigned char bcd[6]={0,0,0,0,0,0}; // sufficient for 2^32 + int i; + int prec = fmt.prec; // number of nibbles + if (prec == -1) + { + prec = 2 * sizeof (value); + } + int width = (prec + (fmt.flags & sign_flag ? 2 : 1)) / 2; + if (fmt.width > width) width = fmt.width; + if (fmt.flags & sign_flag && value < 0) + { + // negative BCD value, I hope "F" as "-" is OK + bcd[5] = 0xF0; + value = -value; + } + if (prec > 10) prec = 10; + for (i = 0; i < prec; i++) + { + bcd[i/2] |= (value % 10) << (4 * (i & 1)); + value /= 10; + } + if (fmt.flags & alt_flag) + { + // least significant byte first (little endian) + for (i = 0; i < (prec + 1) / 2; i++) + { + output.append(bcd[i]); + } + for (; i < width; i++) + { + output.append('\0'); + } + output[-1] |= bcd[5]; + } + else + { + // most significant byte first (big endian) + int firstbyte = output.length(); + for (i = 0; i < width - (prec + 1) / 2; i++) + { + output.append('\0'); + } + for (i = (prec - 1) / 2; i >= 0; i--) + { + output.append(bcd[i]); + } + output[firstbyte] |= bcd[5]; + } + return true; +} + +int BCDConverter:: +scanLong(const StreamFormat& fmt, const char* input, long& value) +{ + int length = 0; + int val = 0; + unsigned char bcd1, bcd10; + int width = fmt.width; + if (width == 0) width = 1; + if (fmt.flags & alt_flag) + { + // little endian + int shift = 1; + while (width--) + { + bcd1 = bcd10 = (unsigned char) input[length++]; + bcd1 &= 0x0F; + bcd10 >>= 4; + if (bcd1 > 9 || shift * bcd1 < bcd1) break; + if (width == 0 && fmt.flags & sign_flag) + { + val += bcd1 * shift; + if (bcd10 != 0) val = -val; + break; + } + if (bcd10 > 9 || shift * bcd10 < bcd10) break; + val += (bcd1 + 10 * bcd10) * shift; + if (shift <= 100000000) shift *= 100; + else shift = 0; + } + } + else + { + // big endian + int sign = 1; + while (width--) + { + long temp; + bcd1 = bcd10 = (unsigned char) input[length]; + bcd1 &= 0x0F; + bcd10 >>= 4; + if (length == 0 && fmt.flags & sign_flag && bcd10) + { + sign = -1; + bcd10 = 0; + } + if (bcd1 > 9 || bcd10 > 9) break; + temp = val * 100 + (bcd1 + 10 * bcd10); + if (temp < val) + { + length = 0; + break; + } + val = temp; + length++; + } + val *= sign; + } + if (length == 0) return -1; + value = val; + return length; +} + +RegisterConverter (BCDConverter, "D"); diff --git a/src/BinaryConverter.cc b/src/BinaryConverter.cc new file mode 100644 index 0000000..cbe9449 --- /dev/null +++ b/src/BinaryConverter.cc @@ -0,0 +1,118 @@ +/*************************************************************** +* StreamDevice Support * +* * +* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) * +* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) * +* * +* This is the binary format converter of StreamDevice. * +* Please refer to the HTML files in ../doc/ for a detailed * +* documentation. * +* * +* If you do any changes in this file, you are not allowed to * +* redistribute it any more. If there is a bug or a missing * +* feature, send me an email and/or your patch. If I accept * +* your changes, they will go to the next release. * +* * +* DISCLAIMER: If this software breaks something or harms * +* someone, it's your problem. * +* * +***************************************************************/ + +#include +#include "StreamFormatConverter.h" +#include "StreamError.h" + +// Binary ASCII Converter %b and %B + +class BinaryConverter : public StreamFormatConverter +{ + int parse(const StreamFormat&, StreamBuffer&, const char*&, bool); + bool printLong(const StreamFormat&, StreamBuffer&, long); + int scanLong(const StreamFormat&, const char*, long&); +}; + +int BinaryConverter:: +parse(const StreamFormat& format, StreamBuffer& info, + const char*& source, bool) +{ + if (format.conv == 'B') + { + // user defined characters for %B (next 2 in source) + if (!*source ) + { + if (*source == esc) source++; + info.append(*source++); + if (!*source) + { + if (*source == esc) source++; + info.append(*source++); + return long_format; + } + } + error("Missing characters after %%B format conversion\n"); + return false; + } + // default characters for %b + info.append("01"); + return long_format; +} + +bool BinaryConverter:: +printLong(const StreamFormat& format, StreamBuffer& output, long value) +{ + int prec = format.prec; + if (prec == -1) + { + // find number of significant bits + prec = sizeof (value) * 8; + while (prec && (value & (1 << (prec - 1))) == 0) prec--; + } + if (prec == 0) prec++; // print at least one bit + int width = prec; + if (format.width > width) width = format.width; + char zero = format.info[0]; + char one = format.info[1]; + if (!(format.flags & left_flag)) + { + // pad left + char fill = (format.flags & zero_flag) ? zero : ' '; + while (width > prec) + { + output.append(fill); + width--; + } + } + while (prec--) + { + output.append((value & (1 << prec)) ? one : zero); + width--; + } + while (width--) + { + // pad right + output.append(' '); + } + return true; +} + +int BinaryConverter:: +scanLong(const StreamFormat& format, const char* input, long& value) +{ + long val = 0; + int width = format.width; + if (width == 0) width = -1; + int length = 0; + while (isspace(input[++length])); // skip whitespaces + char zero = format.info[0]; + char one = format.info[1]; + if (input[length] != zero && input[length] != one) return -1; + while (width-- && (input[length] == zero || input[length] == one)) + { + val <<= 1; + if (input[length++] == one) val |= 1; + } + value = val; + return length; +} + +RegisterConverter (BinaryConverter, "bB"); diff --git a/src/CONFIG_STREAM b/src/CONFIG_STREAM new file mode 100644 index 0000000..d487bd9 --- /dev/null +++ b/src/CONFIG_STREAM @@ -0,0 +1,65 @@ +# Want debugging? +# HOST_OPT = NO + +# You may add more record interfaces +# This requires the naming conventions +# dev$(RECORD)Stream.c + +RECORDS += ao ai +RECORDS += bo bi +RECORDS += mbbo mbbi +RECORDS += mbboDirect mbbiDirect +RECORDS += longout longin +RECORDS += stringout stringin +RECORDS += waveform +RECORDS += calcout +#RECORDS += aai aao + +# Do you have synApps and want support for scalcout? +# Then define CALC or SYNAPPS in your RELEASE file +# pointing to the 'calc' module of synApps. +# Due to strange cross dependencies in synApps +# you have to build the 'sscan' and 'genSub' +# modules before building 'calc'. +# See doc/scalcout.html for a required fix in scalcout. +SYNAPPS_RECORDS += scalcout + +# You may add more bus interfaces +# This requires the naming convention +# $(BUS)Interface.cc +# asynDriver interface is added automatically +# if ASYN is defined in your RELEASE file. +# BUSSES += Debug + +# You may add more format converters +# This requires the naming convention +# $(FORMAT)Converter.cc + +FORMATS += Enum +FORMATS += BCD +FORMATS += Raw +FORMATS += Binary +FORMATS += Checksum + +# Want a loadable module? +# For Tornado 2.0.2, a fix is needed in the +# registerRecordDeviceDriver.pl script in base: +# at the end replace the line "IoccrfReg iocshReg;" +# by "static IoccrfReg iocshReg;" +# LOADABLE_MODULE = YES + +# Want to add some memory tracing +# to find memory leaks? +# USE_MEMGUARD = YES + +# Sources of Stream Kernel +# Do not modify + +STREAM_SRCS += StreamVersion.c +STREAM_SRCS += StreamBuffer.cc +STREAM_SRCS += StreamError.cc +STREAM_SRCS += StreamProtocol.cc +STREAM_SRCS += StreamFormatConverter.cc +STREAM_SRCS += StreamCore.cc +STREAM_SRCS += StreamBusInterface.cc +STREAM_SRCS += StreamEpics.cc diff --git a/src/ChecksumConverter.cc b/src/ChecksumConverter.cc new file mode 100644 index 0000000..2a5ea72 --- /dev/null +++ b/src/ChecksumConverter.cc @@ -0,0 +1,645 @@ +/*************************************************************** +* StreamDevice Support * +* * +* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) * +* (C) 2006 Dirk Zimoch (dirk.zimoch@psi.ch) * +* * +* This is the checksum pseudo-converter of StreamDevice. * +* Please refer to the HTML files in ../doc/ for a detailed * +* documentation. * +* * +* If you do any changes in this file, you are not allowed to * +* redistribute it any more. If there is a bug or a missing * +* feature, send me an email and/or your patch. If I accept * +* your changes, they will go to the next release. * +* * +* DISCLAIMER: If this software breaks something or harms * +* someone, it's your problem. * +* * +***************************************************************/ + +#include "StreamFormatConverter.h" +#include "StreamError.h" +#if defined(__vxworks) || defined(vxWorks) || defined(_WIN32) || defined(__rtems__) +// These systems have no strncasecmp +#include +#define strncasecmp epicsStrnCaseCmp +#endif +#include + +typedef unsigned long ulong; +typedef unsigned char uchar; + +typedef ulong (*checksumFunc)(const uchar* data, ulong len, ulong init); + +static ulong sum(const uchar* data, ulong len, ulong sum) +{ + while (len--) + { + sum += *data++; + } + return sum; +} + +static ulong xor8(const uchar* data, ulong len, ulong sum) +{ + while (len--) + { + sum ^= *data++; + } + return sum; +} + +static ulong crc_0x07(const uchar* data, ulong len, ulong crc) +{ + // x^8 + x^2 + x^1 + x^0 (0x07) + const static uchar table[256] = { + 0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, + 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D, + 0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, + 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D, + 0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5, + 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD, + 0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, + 0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD, + 0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, + 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA, + 0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2, + 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A, + 0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, + 0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A, + 0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, + 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A, + 0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C, + 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4, + 0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, + 0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4, + 0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C, + 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44, + 0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C, + 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34, + 0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, + 0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63, + 0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, + 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13, + 0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB, + 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83, + 0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, + 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3 }; + + while (len--) crc = table[(crc ^ *data++) & 0xFF]; + return crc; +} + +static ulong crc_0x31(const uchar* data, ulong len, ulong crc) +{ + // x^8 + x^5 + x^4 + x^0 (0x31) + const static uchar table[256] = { + 0x00, 0x5e, 0xbc, 0xe2, 0x61, 0x3f, 0xdd, 0x83, + 0xc2, 0x9c, 0x7e, 0x20, 0xa3, 0xfd, 0x1f, 0x41, + 0x9d, 0xc3, 0x21, 0x7f, 0xfc, 0xa2, 0x40, 0x1e, + 0x5f, 0x01, 0xe3, 0xbd, 0x3e, 0x60, 0x82, 0xdc, + 0x23, 0x7d, 0x9f, 0xc1, 0x42, 0x1c, 0xfe, 0xa0, + 0xe1, 0xbf, 0x5d, 0x03, 0x80, 0xde, 0x3c, 0x62, + 0xbe, 0xe0, 0x02, 0x5c, 0xdf, 0x81, 0x63, 0x3d, + 0x7c, 0x22, 0xc0, 0x9e, 0x1d, 0x43, 0xa1, 0xff, + 0x46, 0x18, 0xfa, 0xa4, 0x27, 0x79, 0x9b, 0xc5, + 0x84, 0xda, 0x38, 0x66, 0xe5, 0xbb, 0x59, 0x07, + 0xdb, 0x85, 0x67, 0x39, 0xba, 0xe4, 0x06, 0x58, + 0x19, 0x47, 0xa5, 0xfb, 0x78, 0x26, 0xc4, 0x9a, + 0x65, 0x3b, 0xd9, 0x87, 0x04, 0x5a, 0xb8, 0xe6, + 0xa7, 0xf9, 0x1b, 0x45, 0xc6, 0x98, 0x7a, 0x24, + 0xf8, 0xa6, 0x44, 0x1a, 0x99, 0xc7, 0x25, 0x7b, + 0x3a, 0x64, 0x86, 0xd8, 0x5b, 0x05, 0xe7, 0xb9, + 0x8c, 0xd2, 0x30, 0x6e, 0xed, 0xb3, 0x51, 0x0f, + 0x4e, 0x10, 0xf2, 0xac, 0x2f, 0x71, 0x93, 0xcd, + 0x11, 0x4f, 0xad, 0xf3, 0x70, 0x2e, 0xcc, 0x92, + 0xd3, 0x8d, 0x6f, 0x31, 0xb2, 0xec, 0x0e, 0x50, + 0xaf, 0xf1, 0x13, 0x4d, 0xce, 0x90, 0x72, 0x2c, + 0x6d, 0x33, 0xd1, 0x8f, 0x0c, 0x52, 0xb0, 0xee, + 0x32, 0x6c, 0x8e, 0xd0, 0x53, 0x0d, 0xef, 0xb1, + 0xf0, 0xae, 0x4c, 0x12, 0x91, 0xcf, 0x2d, 0x73, + 0xca, 0x94, 0x76, 0x28, 0xab, 0xf5, 0x17, 0x49, + 0x08, 0x56, 0xb4, 0xea, 0x69, 0x37, 0xd5, 0x8b, + 0x57, 0x09, 0xeb, 0xb5, 0x36, 0x68, 0x8a, 0xd4, + 0x95, 0xcb, 0x29, 0x77, 0xf4, 0xaa, 0x48, 0x16, + 0xe9, 0xb7, 0x55, 0x0b, 0x88, 0xd6, 0x34, 0x6a, + 0x2b, 0x75, 0x97, 0xc9, 0x4a, 0x14, 0xf6, 0xa8, + 0x74, 0x2a, 0xc8, 0x96, 0x15, 0x4b, 0xa9, 0xf7, + 0xb6, 0xe8, 0x0a, 0x54, 0xd7, 0x89, 0x6b, 0x35 }; + + while (len--) crc = table[(crc ^ *data++) & 0xFF]; + return crc; +} + +static ulong crc_0x8005(const uchar* data, ulong len, ulong crc) +{ + // x^16 + x^15 + x^2 + x^0 (0x8005) + const static unsigned short table[256] = { + 0x0000,0x8005,0x800f,0x000a,0x801b,0x001e,0x0014,0x8011, + 0x8033,0x0036,0x003c,0x8039,0x0028,0x802d,0x8027,0x0022, + 0x8063,0x0066,0x006c,0x8069,0x0078,0x807d,0x8077,0x0072, + 0x0050,0x8055,0x805f,0x005a,0x804b,0x004e,0x0044,0x8041, + 0x80c3,0x00c6,0x00cc,0x80c9,0x00d8,0x80dd,0x80d7,0x00d2, + 0x00f0,0x80f5,0x80ff,0x00fa,0x80eb,0x00ee,0x00e4,0x80e1, + 0x00a0,0x80a5,0x80af,0x00aa,0x80bb,0x00be,0x00b4,0x80b1, + 0x8093,0x0096,0x009c,0x8099,0x0088,0x808d,0x8087,0x0082, + 0x8183,0x0186,0x018c,0x8189,0x0198,0x819d,0x8197,0x0192, + 0x01b0,0x81b5,0x81bf,0x01ba,0x81ab,0x01ae,0x01a4,0x81a1, + 0x01e0,0x81e5,0x81ef,0x01ea,0x81fb,0x01fe,0x01f4,0x81f1, + 0x81d3,0x01d6,0x01dc,0x81d9,0x01c8,0x81cd,0x81c7,0x01c2, + 0x0140,0x8145,0x814f,0x014a,0x815b,0x015e,0x0154,0x8151, + 0x8173,0x0176,0x017c,0x8179,0x0168,0x816d,0x8167,0x0162, + 0x8123,0x0126,0x012c,0x8129,0x0138,0x813d,0x8137,0x0132, + 0x0110,0x8115,0x811f,0x011a,0x810b,0x010e,0x0104,0x8101, + 0x8303,0x0306,0x030c,0x8309,0x0318,0x831d,0x8317,0x0312, + 0x0330,0x8335,0x833f,0x033a,0x832b,0x032e,0x0324,0x8321, + 0x0360,0x8365,0x836f,0x036a,0x837b,0x037e,0x0374,0x8371, + 0x8353,0x0356,0x035c,0x8359,0x0348,0x834d,0x8347,0x0342, + 0x03c0,0x83c5,0x83cf,0x03ca,0x83db,0x03de,0x03d4,0x83d1, + 0x83f3,0x03f6,0x03fc,0x83f9,0x03e8,0x83ed,0x83e7,0x03e2, + 0x83a3,0x03a6,0x03ac,0x83a9,0x03b8,0x83bd,0x83b7,0x03b2, + 0x0390,0x8395,0x839f,0x039a,0x838b,0x038e,0x0384,0x8381, + 0x0280,0x8285,0x828f,0x028a,0x829b,0x029e,0x0294,0x8291, + 0x82b3,0x02b6,0x02bc,0x82b9,0x02a8,0x82ad,0x82a7,0x02a2, + 0x82e3,0x02e6,0x02ec,0x82e9,0x02f8,0x82fd,0x82f7,0x02f2, + 0x02d0,0x82d5,0x82df,0x02da,0x82cb,0x02ce,0x02c4,0x82c1, + 0x8243,0x0246,0x024c,0x8249,0x0258,0x825d,0x8257,0x0252, + 0x0270,0x8275,0x827f,0x027a,0x826b,0x026e,0x0264,0x8261, + 0x0220,0x8225,0x822f,0x022a,0x823b,0x023e,0x0234,0x8231, + 0x8213,0x0216,0x021c,0x8219,0x0208,0x820d,0x8207,0x0202 }; + + while (len--) crc = table[((crc>>8) ^ *data++) & 0xFF] ^ (crc << 8); + return crc; +} + +static ulong crc_0x8005_r(const uchar* data, ulong len, ulong crc) +{ + // x^16 + x^15 + x^2 + x^0 (0x8005) + // reflected + const static unsigned short table[256] = { + 0x0000,0xC0C1,0xC181,0x0140,0xC301,0x03C0,0x0280,0xC241, + 0xC601,0x06C0,0x0780,0xC741,0x0500,0xC5C1,0xC481,0x0440, + 0xCC01,0x0CC0,0x0D80,0xCD41,0x0F00,0xCFC1,0xCE81,0x0E40, + 0x0A00,0xCAC1,0xCB81,0x0B40,0xC901,0x09C0,0x0880,0xC841, + 0xD801,0x18C0,0x1980,0xD941,0x1B00,0xDBC1,0xDA81,0x1A40, + 0x1E00,0xDEC1,0xDF81,0x1F40,0xDD01,0x1DC0,0x1C80,0xDC41, + 0x1400,0xD4C1,0xD581,0x1540,0xD701,0x17C0,0x1680,0xD641, + 0xD201,0x12C0,0x1380,0xD341,0x1100,0xD1C1,0xD081,0x1040, + 0xF001,0x30C0,0x3180,0xF141,0x3300,0xF3C1,0xF281,0x3240, + 0x3600,0xF6C1,0xF781,0x3740,0xF501,0x35C0,0x3480,0xF441, + 0x3C00,0xFCC1,0xFD81,0x3D40,0xFF01,0x3FC0,0x3E80,0xFE41, + 0xFA01,0x3AC0,0x3B80,0xFB41,0x3900,0xF9C1,0xF881,0x3840, + 0x2800,0xE8C1,0xE981,0x2940,0xEB01,0x2BC0,0x2A80,0xEA41, + 0xEE01,0x2EC0,0x2F80,0xEF41,0x2D00,0xEDC1,0xEC81,0x2C40, + 0xE401,0x24C0,0x2580,0xE541,0x2700,0xE7C1,0xE681,0x2640, + 0x2200,0xE2C1,0xE381,0x2340,0xE101,0x21C0,0x2080,0xE041, + 0xA001,0x60C0,0x6180,0xA141,0x6300,0xA3C1,0xA281,0x6240, + 0x6600,0xA6C1,0xA781,0x6740,0xA501,0x65C0,0x6480,0xA441, + 0x6C00,0xACC1,0xAD81,0x6D40,0xAF01,0x6FC0,0x6E80,0xAE41, + 0xAA01,0x6AC0,0x6B80,0xAB41,0x6900,0xA9C1,0xA881,0x6840, + 0x7800,0xB8C1,0xB981,0x7940,0xBB01,0x7BC0,0x7A80,0xBA41, + 0xBE01,0x7EC0,0x7F80,0xBF41,0x7D00,0xBDC1,0xBC81,0x7C40, + 0xB401,0x74C0,0x7580,0xB541,0x7700,0xB7C1,0xB681,0x7640, + 0x7200,0xB2C1,0xB381,0x7340,0xB101,0x71C0,0x7080,0xB041, + 0x5000,0x90C1,0x9181,0x5140,0x9301,0x53C0,0x5280,0x9241, + 0x9601,0x56C0,0x5780,0x9741,0x5500,0x95C1,0x9481,0x5440, + 0x9C01,0x5CC0,0x5D80,0x9D41,0x5F00,0x9FC1,0x9E81,0x5E40, + 0x5A00,0x9AC1,0x9B81,0x5B40,0x9901,0x59C0,0x5880,0x9841, + 0x8801,0x48C0,0x4980,0x8941,0x4B00,0x8BC1,0x8A81,0x4A40, + 0x4E00,0x8EC1,0x8F81,0x4F40,0x8D01,0x4DC0,0x4C80,0x8C41, + 0x4400,0x84C1,0x8581,0x4540,0x8701,0x47C0,0x4680,0x8641, + 0x8201,0x42C0,0x4380,0x8341,0x4100,0x81C1,0x8081,0x4040 }; + + while (len--) crc = table[(crc ^ *data++) & 0xFF] ^ (crc >> 8); + return crc; +} + +static ulong crc_0x1021(const uchar* data, ulong len, ulong crc) +{ + // x^16 + x^12 + x^5 + x^0 (0x1021) + const static unsigned short table[256] = { + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, + 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, + 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, + 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, + 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, + 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, + 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, + 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, + 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, + 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, + 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, + 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, + 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, + 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, + 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, + 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, + 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, + 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, + 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, + 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, + 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, + 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, + 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0 }; + + while (len--) crc = table[((crc>>8) ^ *data++) & 0xFF] ^ (crc << 8); + return crc; +} + +static ulong crc_0x04C11DB7(const uchar* data, ulong len, ulong crc) +{ + // x^32 + x^26 + x^23 + x^22 + x^16 + x^12 + x^11 + x^10 + + // x^8 + x^7 + x^5 + x^4 + x^2 + x^1 + x^0 (0x04C11DB7) + const static ulong table[] = { + 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, + 0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005, + 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, + 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, + 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, + 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75, + 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, + 0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd, + 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, + 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, + 0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81, + 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d, + 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, + 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95, + 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, + 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, + 0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae, + 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072, + 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, + 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, + 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, + 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, + 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066, + 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, + 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, + 0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692, + 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, + 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, + 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, + 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, + 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, + 0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a, + 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, + 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, + 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, + 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53, + 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, + 0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b, + 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, + 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, + 0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, + 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b, + 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, + 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3, + 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, + 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, + 0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f, + 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3, + 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, + 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, + 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, + 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, + 0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30, + 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, + 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, + 0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654, + 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, + 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, + 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, + 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, + 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, + 0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c, + 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, + 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4 }; + + while (len--) crc = table[((crc>>24) ^ *data++) & 0xFF] ^ (crc << 8); + return crc; +} + +static ulong crc_0x04C11DB7_r(const uchar* data, ulong len, ulong crc) +{ + // x^32 + x^26 + x^23 + x^22 + x^16 + x^12 + x^11 + x^10 + + // x^8 + x^7 + x^5 + x^4 + x^2 + x^1 + x^0 (0x04C11DB7) + // reflected + const static ulong table[] = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, + 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, + 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, + 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, + 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, + 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, + 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, + 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, + 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, + 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, + 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, + 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, + 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, + 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, + 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, + 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, + 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, + 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, + 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, + 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, + 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, + 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, + 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, + 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, + 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, + 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, + 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, + 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, + 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, + 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, + 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, + 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, + 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, + 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, + 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, + 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, + 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, + 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, + 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, + 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, + 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, + 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, + 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, + 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, + 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, + 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, + 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, + 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d }; + + while (len--) crc = table[(crc ^ *data++) & 0xFF] ^ (crc >> 8); + return crc; +} + +static ulong adler32(const uchar* data, ulong len, ulong init) +{ + ulong a = init & 0xFFFF; + ulong b = (init >> 16) & 0xFFFF; + + while (len) { + ulong tlen = len > 5550 ? 5550 : len; + len -= tlen; + do { + a += *data++; + b += a; + } while (--tlen); + a = (a & 0xFFFF) + (a >> 16) * 15; + b = (b & 0xFFFF) + (b >> 16) * 15; + } + if (a >= 65521) a -= 65521; + b = (b & 0xFFFF) + (b >> 16) * 15; + if (b >= 65521) b -= 65521; + return b << 16 | a; +} + +static ulong hexsum(const uchar* data, ulong len, ulong sum) +{ + // Add all hex digits, ignore all other bytes. + ulong d; + while (len--) + { + d = toupper(*data++); + if (isxdigit(d)) + { + if (isdigit(d)) d -= '0'; + else d -= 'A' - 0x0A; + sum += d; + } + } + return sum; +} + +struct checksum +{ + const char* name; + checksumFunc func; + ulong init; + ulong xorout; + signed char bytes; +}; + +static checksum checksumMap[] = +// You may add your own checksum functions to this map. +{ +// name func init xorout bytes chk("123456789") + {"sum", sum, 0x00, 0x00, 1}, // 0xDD + {"sum8", sum, 0x00, 0x00, 1}, // 0xDD + {"sum16", sum, 0x0000, 0x0000, 2}, // 0x01DD + {"sum32", sum, 0x00000000, 0x00000000, 4}, // 0x000001DD + {"nsum", sum, 0xff, 0xff, 1}, // 0x23 + {"negsum", sum, 0xff, 0xff, 1}, // 0x23 + {"-sum", sum, 0xff, 0xff, 1}, // 0x23 + {"notsum", sum, 0x00, 0xff, 1}, // 0x22 + {"~sum", sum, 0x00, 0xff, 1}, // 0x22 + {"xor", xor8, 0x00, 0x00, 1}, // 0x31 + {"crc8", crc_0x07, 0x00, 0x00, 1}, // 0xF4 + {"ccitt8", crc_0x31, 0x00, 0x00, 1}, // 0xA1 + {"crc16", crc_0x8005, 0x0000, 0x0000, 2}, // 0xFEE8 + {"crc16r", crc_0x8005_r, 0x0000, 0x0000, 2}, // 0xBB3D + {"ccitt16", crc_0x1021, 0xFFFF, 0x0000, 2}, // 0x29B1 + {"ccitt16a",crc_0x1021, 0x1D0F, 0x0000, 2}, // 0xE5CC + {"crc32", crc_0x04C11DB7, 0xFFFFFFFF, 0xFFFFFFFF, 4}, // 0xFC891918 + {"crc32r", crc_0x04C11DB7_r, 0xFFFFFFFF, 0xFFFFFFFF, 4}, // 0xCBF43926 + {"jamcrc", crc_0x04C11DB7_r, 0xFFFFFFFF, 0x00000000, 4}, // 0x340BC6D9 + {"adler32", adler32, 0x00000001, 0x00000000, 4}, // 0x091E01DE + {"hexsum8", hexsum, 0x00, 0x00, 1} // 0x2D +}; + +static ulong mask[5] = {0, 0xFF, 0xFFFF, 0xFFFFFF, 0xFFFFFFFF}; + +class ChecksumConverter : public StreamFormatConverter +{ + int parse (const StreamFormat&, StreamBuffer&, const char*&, bool); + bool printPseudo(const StreamFormat&, StreamBuffer&); + int scanPseudo(const StreamFormat&, StreamBuffer&, long& cursor); +}; + +int ChecksumConverter:: +parse(const StreamFormat&, StreamBuffer& info, const char*& source, bool) +{ + const char* p = strchr(source, '>'); + if (!p) + { + error ("Missing terminating '>' in checksum format.\n"); + return false; + } + + size_t fnum; + for (fnum = 0; fnum < sizeof(checksumMap)/sizeof(checksum); fnum++) + { + if (strncasecmp(source, checksumMap[fnum].name, p-source) == 0) + { + info.append(fnum); + source = p+1; + return pseudo_format; + } + } + + error ("Unknown checksum algorithm \"%.*s\"\n", + static_cast(p-source), source); + return false; +} + +bool ChecksumConverter:: +printPseudo(const StreamFormat& format, StreamBuffer& output) +{ + ulong sum; + int fnum = format.info[0]; + int start = format.width; + int length = output.length()-format.width; + if (format.prec > 0) length -= format.prec; + + debug("ChecksumConverter %s: output to check: \"%s\"\n", + checksumMap[fnum].name, output.expand(start,length)()); + + sum = checksumMap[fnum].xorout ^ checksumMap[fnum].func( + reinterpret_cast(output(start)), length, + checksumMap[fnum].init) & mask[checksumMap[fnum].bytes]; + + debug("ChecksumConverter %s: output checksum is 0x%lX\n", + checksumMap[fnum].name, sum); + + int i; + unsigned outchar; + + if (format.flags & alt_flag) // lsb first (little endian) + { + for (i = 0; i < checksumMap[fnum].bytes; i++) + { + outchar = sum & 0xff; + debug("ChecksumConverter %s: little endian appending 0x%X\n", + checksumMap[fnum].name, outchar); + if (format.flags & zero_flag) // ASCII + output.printf("%02X", outchar); + else // binary + output.append(outchar); + sum >>= 8; + } + } + else // msb first (big endian) + { + sum <<= 8*(4-checksumMap[fnum].bytes); + for (i = 0; i < checksumMap[fnum].bytes; i++) + { + outchar = (sum >> 24) & 0xff; + debug("ChecksumConverter %s: big endian appending 0x%X\n", + checksumMap[fnum].name, outchar); + if (format.flags & zero_flag) // ASCII + output.printf("%02X", outchar); + else // binary + output.append(outchar); + sum <<= 8; + } + } + return true; +} + +int ChecksumConverter:: +scanPseudo(const StreamFormat& format, StreamBuffer& input, long& cursor) +{ + int fnum = format.info[0]; + ulong sum; + int start = format.width; + int length = cursor-format.width; + if (format.prec > 0) length -= format.prec; + + debug("ChecksumConverter %s: input to check: \"%s\n", + checksumMap[fnum].name, input.expand(start,length)()); + + if (input.length() - cursor < + (format.flags & zero_flag ? 2 : 1) * checksumMap[fnum].bytes) + { + error("Input too short for checksum\n"); + return -1; + } + + sum = checksumMap[fnum].xorout ^ checksumMap[fnum].func( + reinterpret_cast(input(start)), length, + checksumMap[fnum].init) & mask[checksumMap[fnum].bytes]; + + debug("ChecksumConverter %s: input checksum is 0x%0*lX\n", + checksumMap[fnum].name, 2*checksumMap[fnum].bytes, sum); + + int i,j; + unsigned inchar; + + if (format.flags & alt_flag) // lsb first (little endian) + { + for (i = 0; i < checksumMap[fnum].bytes; i++) + { + if (format.flags & zero_flag) // ASCII + { + sscanf(input(cursor+2*i), "%2X", &inchar); + } + else // binary + { + inchar = input[cursor+i] & 0xff; + } + if (inchar != ((sum >> 8*i) & 0xff)) + { + error("Input byte 0x%02X does not match checksum 0x%0*lX\n", + inchar, 2*checksumMap[fnum].bytes, sum); + return -1; + } + } + } + else // msb first (big endian) + { + for (i = checksumMap[fnum].bytes-1, j = 0; i >= 0; i--, j++) + { + if (format.flags & zero_flag) // ASCII + { + sscanf(input(cursor+2*i), "%2x", &inchar); + } + else // binary + { + inchar = input[cursor+i] & 0xff; + } + if (inchar != ((sum >> 8*j) & 0xff)) + { + error("Input byte 0x%02X does not match checksum 0x%0*lX\n", + inchar, 2*checksumMap[fnum].bytes, sum); + return -1; + } + } + } + if (format.flags & zero_flag) // ASCII + return 2*checksumMap[fnum].bytes; + return checksumMap[fnum].bytes; +} + +RegisterConverter (ChecksumConverter, "<"); diff --git a/src/DebugInterface.cc b/src/DebugInterface.cc new file mode 100644 index 0000000..51da2a6 --- /dev/null +++ b/src/DebugInterface.cc @@ -0,0 +1,229 @@ +/*************************************************************** +* StreamDevice Support * +* * +* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) * +* * +* This is the interface to a debug and example bus drivers for * +* StreamDevice. Please refer to the HTML files in ../doc/ for * +* a detailed documentation. * +* * +* If you do any changes in this file, you are not allowed to * +* redistribute it any more. If there is a bug or a missing * +* feature, send me an email and/or your patch. If I accept * +* your changes, they will go to the next release. * +* * +* DISCLAIMER: If this software breaks something or harms * +* someone, it's your problem. * +* * +***************************************************************/ + +#include "StreamBusInterface.h" +#include "StreamError.h" +#include "StreamBuffer.h" + +// This is a non-blocking bus interface for debugging purpose. +// Normally, a bus interface will use blocking I/O and thus require +// a separate thread. + +class DebugInterface : StreamBusInterface +{ + DebugInterface(Client* client); + + // StreamBusInterface methods + bool lockRequest(unsigned long lockTimeout_ms); + bool unlock(); + bool writeRequest(const void* output, size_t size, + unsigned long writeTimeout_ms); + bool readRequest(unsigned long replyTimeout_ms, + unsigned long readTimeout_ms, long expectedLength, bool async); + +protected: + ~DebugInterface(); + +public: + // static creator method + static StreamBusInterface* getBusInterface(Client* client, + const char* busname, int addr, const char* param); +}; + +RegisterStreamBusInterface(DebugInterface); + +DebugInterface:: +DebugInterface(Client* client) : StreamBusInterface(client) +{ + // Create or attach to interface thread +} + +DebugInterface:: +~DebugInterface() +{ + // Abort any running I/O + // Disconnect low-level bus driver + // Free all resources +} + +// Interface method getBusInterface(): +// Do we have this bus/addr ? +StreamBusInterface* DebugInterface:: +getBusInterface(Client* client, + const char* busname, int addr, const char*) +{ + if (strcmp(busname, "debug") == 0) + { + DebugInterface* interface = new DebugInterface(client); + debug ("DebugInterface::getBusInterface(%s, %d): " + "new Interface allocated\n", + busname, addr); + // Connect to low-level bus driver here or in constructor + // Delete interface and return NULL if connect fails. + return interface; + } + return NULL; +} + +// Interface method lockRequest(): +// We want exclusive access to the device +// lockTimeout_ms=0 means "block here" (used in @init) +// Return false if the lock request cannot be accepted. +bool DebugInterface:: +lockRequest(unsigned long lockTimeout_ms) +{ + debug("DebugInterface::lockRequest(%s, %ld msec)\n", + clientName(), lockTimeout_ms); + + // Debug interface is non-blocking, + // thus we can call lockCallback() immediately. + lockCallback(StreamIoSuccess); + + // A blocking interface would send a message that requests + // exclusive access to the interface thread. + // Once the interface is available, the interface thread + // would call lockCallback(StreamIoSuccess). + // If lockTimeout_ms expires, the interface would + // call lockCallback(ioTimeout). + + // Only if lockTimeout_ms==0, a blocking interface is allowed, + // even required, to block here until the interface is available. + + return true; +} + +// Interface method unlock(): +// We don't need exclusive access any more +// Return false if the unlock fails. (How can that be?) +bool DebugInterface:: +unlock() +{ + debug("DebugInterface::unlock(%s)\n", + clientName()); + + // debug interface is non-blocking, thus unlock does nothing. + + // A blocking interface would call lockCallback(StreamIoSuccess) now + // for the next interface that had called lockRequest. + + return true; +} + +// Interface method writeRequest(): +// We want to write something +// This method will only be called by StreamDevice when locked. +// I.e. lockRequest() has been called and the interface +// has called lockCallback(StreamIoSuccess). +// It may be called several times before unlock() is called. +// Return false if the write request cannot be accepted. +bool DebugInterface:: +writeRequest(const void* output, size_t size, unsigned long writeTimeout_ms) +{ + debug("DebugInterface::writeRequest(%s, \"%.*s\", %ld msec)\n", + clientName(), (int)size, (char*)output, writeTimeout_ms); + + // Debug interface is non-blocking, + // thus we can call writeCallback() immediately. + writeCallback(StreamIoSuccess); + + // A blocking interface would send a message that requests + // write access to the interface thread. + // That thread would handle the actual write. + // Writing all output might take some time and might be + // done in several chunks. + // If any chunk cannot be written within writeTimeout_ms milliseconds, + // the interface thread would call writeCallback(ioTimeout). + // If anything is wrong with the bus (e.g. unpugged cable), + // the interface would return false now or call writeCallback(ioFault) + // later when it finds out. + // Once the interface has completed writing, the interface thread + // would call writeCallback(StreamIoSuccess). + + return true; +} + +// Interface method readRequest(): +// We want to read something +// This method may be called in async mode, if a previous call to +// supportsAsyncRead() had returned true. +// It may be called unlocked. +// If expectedLength!=0, it is a hint when to stop reading input. +// Return false if the read request cannot be accepted. +bool DebugInterface:: +readRequest(unsigned long replyTimeout_ms, unsigned long readTimeout_ms, + long expectedLength, bool async) +{ + debug("DebugInterface::readRequest(%s, %ld msec reply, %ld msec read, expect %ld bytes, asyn=%s)\n", + clientName(), replyTimeout_ms, readTimeout_ms, expectedLength, async?"yes":"no"); + + // Debug interface does not support async mode. + // Since we have not implemented supportsAsyncRead(), + // this method is never called with async=true. + if (async) return false; + + // Debug interface is non-blocking, + // thus we can call writeCallback() immediately. + const char input [] = "Receviced input 3.1415\r\n"; + readCallback(StreamIoEnd, input, sizeof(input)); + + // A blocking interface would send a message that requests + // read access to the interface thread. + // That thread would handle the actual read. + // Reading all input might take some time and might be + // done in several chunks. + // If no data arrives within replyTimeout_ms milliseconds, + // the interface thread would call + // readCallback(ioNoReply). + // If any following chunk cannot be read within readTimeout_ms + // milliseconds, the interface thread would call + // readCallback(ioTimeout, input, inputlength) + // with all input received so far. + // For each reveived chunk, the interface would call + // readCallback(StreamIoSuccess, input, inputlength) + // and check its return value. + // The return value is 0 if no more input is expected, i.e. + // the interface would tell the low-level driver that + // reading has finished (if necessary) and return. + // If the return value is >0, this number of additional bytes is + // expected at maximum (this replaces the original expectedLengh). + // If the return value is -1, an unknown number of additional + // bytes is expected. + // In the both cases where more input is expected, the interface + // would do another read and call readCallback() again. + // The return value needs only to be checked if readCallback() + // is called with StreamIoSuccess as the first argument. + // If the interface received a signal from the low-level driver + // telling that input has terminated (e.g. EOS in GPIB, EOF when + // reading file or socket, ...) it would call + // readCallback(StreamIoEnd, input, inputlength). + // If anything is wrong with the bus (e.g. unpugged cable), + // the interface would immediately return false now or + // call readCallback(ioFault) later when it finds out. + + // On some busses, input may arrive even before readRequest() + // has been called, e.g. when a device replies very fast. + // In this case, the interface would buffer the input or call + // readCallback() even without beeing asked to do so. + + // In async mode, the interface would arrange that the client + // gets a copy of the next input on the same bus/address, + // even when the input is meant for an other client. + + return true; +} diff --git a/src/EnumConverter.cc b/src/EnumConverter.cc new file mode 100644 index 0000000..0a89fee --- /dev/null +++ b/src/EnumConverter.cc @@ -0,0 +1,142 @@ +/*************************************************************** +* StreamDevice Support * +* * +* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) * +* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) * +* * +* This is the enum format converter of StreamDevice. * +* Please refer to the HTML files in ../doc/ for a detailed * +* documentation. * +* * +* If you do any changes in this file, you are not allowed to * +* redistribute it any more. If there is a bug or a missing * +* feature, send me an email and/or your patch. If I accept * +* your changes, they will go to the next release. * +* * +* DISCLAIMER: If this software breaks something or harms * +* someone, it's your problem. * +* * +***************************************************************/ + +#include "StreamFormatConverter.h" +#include "StreamError.h" +#include "StreamProtocol.h" + +// Enum %{string0|string1|...} + +class EnumConverter : public StreamFormatConverter +{ + int parse(const StreamFormat&, StreamBuffer&, const char*&, bool); + bool printLong(const StreamFormat&, StreamBuffer&, long); + int scanLong(const StreamFormat&, const char*, long&); +}; + +int EnumConverter:: +parse(const StreamFormat& fmt, StreamBuffer& info, + const char*& source, bool) +{ + if (fmt.flags & (left_flag|sign_flag|space_flag|zero_flag|alt_flag)) + { + error("Use of modifiers '-', '+', ' ', '0', '#'" + "not allowed with %%{ conversion\n"); + return false; + } + int i = info.length(); // put maxValue here later + info.append('\0'); + int maxValue = 0; + while (*source) + { + switch (*source) + { + case '|': + info.append('\0'); + if (++maxValue > 255) + { + error("Too many enums (max 256)\n"); + return false; + } + break; + case '}': + source++; + info.append('\0'); + info[i] = maxValue; + debug("EnumConverter::parse %d choices: %s\n", + maxValue+1, info.expand(i+1)()); + return enum_format; + case esc: + info.append(*source++); + default: + info.append(*source); + } + source++; + } + error("Missing '}' after %%{ format conversion\n"); + return false; +} + +bool EnumConverter:: +printLong(const StreamFormat& fmt, StreamBuffer& output, long value) +{ + long maxValue = fmt.info[0]; // number of enums + const char* s = fmt.info+1; // first enum string + if (value < 0 || value > maxValue) + { + error("Value %li out of range [0...%li]\n", value, maxValue); + return false; + } + while (value--) + { + while(*s) + { + if (*s == esc) s++; + s++; + } + s++; + } + while(*s) + { + if (*s == esc) s++; + output.append(*s++); + } + return true; +} + +int EnumConverter:: +scanLong(const StreamFormat& fmt, const char* input, long& value) +{ + debug("EnumConverter::scanLong(%%%c, \"%s\")\n", + fmt.conv, input); + long maxValue = fmt.info[0]; // number of enums + const char* s = fmt.info+1; // first enum string + int length; + long val; + bool match; + for (val = 0; val <= maxValue; val++) + { + debug("EnumConverter::scanLong: check #%ld \"%s\"\n", val, s); + length = 0; + match = true; + while(*s) + { + if (*s == StreamProtocolParser::skip) + { + s++; + length++; + continue; + } + if (*s == esc) s++; + if (*s++ != input[length++]) match = false; + } + if (match) + { + debug("EnumConverter::scanLong: value %ld matches\n", val); + value = val; + return length; + } + s++; + } + debug("EnumConverter::scanLong: no value matches\n"); + return -1; +} + +RegisterConverter (EnumConverter, "{"); diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..7940e8c --- /dev/null +++ b/src/Makefile @@ -0,0 +1,100 @@ +################################################################ +# StreamDevice Support # +# # +# (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) # +# (C) 2006 Dirk Zimoch (dirk.zimoch@psi.ch) # +# # +# This is the EPICS 3.14 Makefile of StreamDevice. # +# Normally it should not be necessary to modify this file. # +# All configuration can be done in CONFIG_STREAM # +# # +# If you do any changes in this file, you are not allowed to # +# redistribute it any more. If there is a bug or a missing # +# feature, send me an email and/or your patch. If I accept # +# your changes, they will go to the next release. # +# # +# DISCLAIMER: If this software breaks something or harms # +# someone, it's your problem. # +# # +################################################################ + +TOP=../.. + +# Look if we have EPICS R3.13 or R3.14 +ifeq ($(wildcard $(TOP)/configure),) +# EPICS R3.13 +include $(TOP)/config/CONFIG_APP +# The real work is in Makefile.Vx +include $(TOP)/config/RULES_ARCHS +else + +# EPICS R3.14 +include $(TOP)/configure/CONFIG + +-include CONFIG_STREAM +-include ../CONFIG_STREAM + +LIBRARY_DEFAULT = stream + +DBD += $(LIBRARY_DEFAULT).dbd + +ifdef ASYN +LIB_LIBS += asyn +BUSSES += AsynDriver +endif + +ifdef T_A +ifndef BUSSES +$(error No bus interface defined! Didn't you set ASYN in your RELEASE file?) +endif +endif + +ifeq ($(LOADABLE_MODULE),YES) +SRCS += $(LIBRARY_DEFAULT)_registerRecordDeviceDriver.cpp +endif +SRCS += $(BUSSES:%=%Interface.cc) +SRCS += $(FORMATS:%=%Converter.cc) +SRCS += $(RECORDS:%=dev%Stream.c) +SRCS += $(STREAM_SRCS) + +LIB_LIBS += Com dbIoc dbStaticIoc registryIoc iocsh + +ifeq ($(USE_MEMGUARD),YES) +# memguard looks for memory leaks (gcc only) +CPPFLAGS += -include ../memguard.h +LIB_SRCS += memguard.cc +endif + +INC += devStream.h + +include $(TOP)/configure/RULES + +# Update version string (contains __DATE__ and __TIME__) +# each time make runs. +StreamVersion$(OBJ): FORCE +FORCE: + +# Add references to all registrars to main file to avoid +# missing initialization. +StreamCore$(OBJ): streamReferences + +streamReferences: ../CONFIG_STREAM + @for i in $(BUSSES); \ + do echo "extern void* ref_$${i}Interface;"; \ + echo "void* p$$i = ref_$${i}Interface;"; \ + done > $@ + @for i in $(FORMATS); \ + do echo "extern void* ref_$${i}Converter;"; \ + echo "void* p$$i = ref_$${i}Converter;"; \ + done >> $@ + +# create stream.dbd from all RECORDS +$(COMMON_DIR)/$(LIBRARY_DEFAULT).dbd: ../CONFIG_STREAM + @for r in $(RECORDS); \ + do echo "device($$r,INST_IO,dev$${r}Stream,\"stream\")"; \ + done > $@ + @echo "driver(stream)" >> $@ + @echo "variable(streamDebug, int)" >> $@ + @echo "registrar(streamRegistrar)" >> $@ + +endif diff --git a/src/Makefile.Host b/src/Makefile.Host new file mode 100644 index 0000000..659ea6d --- /dev/null +++ b/src/Makefile.Host @@ -0,0 +1,45 @@ +################################################################ +# StreamDevice Support # +# # +# (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) # +# (C) 2006 Dirk Zimoch (dirk.zimoch@psi.ch) # +# # +# This is the EPICS 3.13 Makefile of StreamDevice. # +# Normally it should not be necessary to modify this file. # +# All configuration can be done in CONFIG_STREAM # +# # +# If you do any changes in this file, you are not allowed to # +# redistribute it any more. If there is a bug or a missing # +# feature, send me an email and/or your patch. If I accept # +# your changes, they will go to the next release. # +# # +# DISCLAIMER: If this software breaks something or harms # +# someone, it's your problem. # +# # +################################################################ + +TOP = ../../.. + +include $(TOP)/config/CONFIG_APP +include ../CONFIG_STREAM + +# In 3.13, calcout has no device support +RECORDS_3_13 = $(filter-out calcout,$(RECORDS)) + +DBDNAME = stream.dbd + +INC += devStream.h + +# This is the munch.pl taken from EPICS 3.14.8.2 +# Install script and rule. +CONFIGS = RULES.munch +SCRIPTS = munch.pl + +include $(TOP)/config/RULES.Host + +# create stream.dbd from all RECORDS +stream.dbd: ../CONFIG_STREAM + @for r in $(RECORDS_3_13); \ + do echo "device($$r,INST_IO,dev$${r}Stream,\"stream\")"; \ + done > $@ + @echo "driver(stream)" >> $@ diff --git a/src/Makefile.Vx b/src/Makefile.Vx new file mode 100644 index 0000000..e95ce60 --- /dev/null +++ b/src/Makefile.Vx @@ -0,0 +1,58 @@ +################################################################ +# StreamDevice Support # +# # +# (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) # +# (C) 2006 Dirk Zimoch (dirk.zimoch@psi.ch) # +# # +# This is the EPICS 3.13 Makefile of StreamDevice. # +# Normally it should not be necessary to modify this file. # +# All configuration can be done in CONFIG_STREAM # +# # +# If you do any changes in this file, you are not allowed to # +# redistribute it any more. If there is a bug or a missing # +# feature, send me an email and/or your patch. If I accept # +# your changes, they will go to the next release. # +# # +# DISCLAIMER: If this software breaks something or harms # +# someone, it's your problem. # +# # +################################################################ + +TOP = ../../.. + +include $(TOP)/config/CONFIG_APP +include ../CONFIG_STREAM + +LIBNAME = streamLib + +# In 3.13, calcout has no device support +RECORDS_3_13 = $(filter-out calcout,$(RECORDS)) + +ifdef ASYN +BUSSES += AsynDriver +endif + +SRCS.cc += $(patsubst %,../%,$(filter %.cc,$(STREAM_SRCS))) +SRCS.cc += $(BUSSES:%=../%Interface.cc) +SRCS.cc += $(FORMATS:%=../%Converter.cc) +SRCS.c += $(patsubst %,../%,$(filter %.c,$(STREAM_SRCS))) +SRCS.c += $(RECORDS_3_13:%=../dev%Stream.c) + +LIBOBJS = $(patsubst ../%,%.o,$(basename $(SRCS.cc) $(SRCS.c))) + +include $(TOP)/config/RULES.Vx +include $(TOP)/config/RULES.munch + +build:: depends + +-include DEPENDS + +# Update version string (contains __DATE__ and __TIME__) +# each time make runs. +StreamVersion.o: FORCE +FORCE: + +StreamCore.o: streamReferences + +streamReferences: + touch $@ diff --git a/src/RULES.munch b/src/RULES.munch new file mode 100644 index 0000000..816a3b3 --- /dev/null +++ b/src/RULES.munch @@ -0,0 +1,14 @@ +MUNCH = $(PERL) $(INSTALL_LOCATION)/bin/$(HOST_ARCH)/munch.pl + +# The original 3.13.10 munching rule does not really work well + +build:: $(LIBNAME).munch + +buildInstall:: $(INSTALL_BIN)/$(LIBNAME).munch + +%.munch: % + $(RM) $*_ctct.o $*_ctdt.c + $(NM) $< | $(MUNCH) > $*_ctdt.c + $(GCC) -traditional $(CFLAGS) -fdollars-in-identifiers -c $(SOURCE_FLAG) $*_ctdt.c + $(LINK.c) $@ $< $*_ctdt.o + diff --git a/src/RawConverter.cc b/src/RawConverter.cc new file mode 100644 index 0000000..d057665 --- /dev/null +++ b/src/RawConverter.cc @@ -0,0 +1,119 @@ +/*************************************************************** +* StreamDevice Support * +* * +* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) * +* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) * +* * +* This is the raw format converter of StreamDevice. * +* Please refer to the HTML files in ../doc/ for a detailed * +* documentation. * +* * +* If you do any changes in this file, you are not allowed to * +* redistribute it any more. If there is a bug or a missing * +* feature, send me an email and/or your patch. If I accept * +* your changes, they will go to the next release. * +* * +* DISCLAIMER: If this software breaks something or harms * +* someone, it's your problem. * +* * +***************************************************************/ + +#include "StreamFormatConverter.h" +#include "StreamError.h" + +// Raw Bytes Converter %r + +class RawConverter : public StreamFormatConverter +{ + int parse(const StreamFormat&, StreamBuffer&, const char*&, bool); + bool printLong(const StreamFormat&, StreamBuffer&, long); + int scanLong(const StreamFormat&, const char*, long&); +}; + +int RawConverter:: +parse(const StreamFormat&, StreamBuffer&, + const char*&, bool) +{ + return long_format; +} + +bool RawConverter:: +printLong(const StreamFormat& format, StreamBuffer& output, long value) +{ + int prec = format.prec; // number of bytes from value + if (prec == -1) prec = 1; // default: 1 byte + int width = prec; // number of bytes in output + if (format.width > width) width = format.width; + char byte = 0; + if (format.flags & alt_flag) // lsb first (little endian) + { + while (prec--) + { + byte = static_cast(value); + output.append(byte); + value >>= 8; + width--; + } + byte = (byte & 0x80) ? 0xFF : 0x00; // fill with sign + while (width--) + { + output.append(byte); + } + } + else // msb first (big endian) + { + byte = ((value >> (8 * (prec-1))) & 0x80) ? 0xFF : 0x00; + while (width > prec) // fill with sign + { + output.append(byte); + width--; + } + while (prec--) + { + output.append(static_cast(value >> (8 * prec))); + } + } + return true; +} + +int RawConverter:: +scanLong(const StreamFormat& format, const char* input, long& value) +{ + long length = 0; + long val = 0; + int width = format.width; + if (width == 0) width = 1; // default: 1 byte + if (format.flags & skip_flag) + { + return width; // just skip input + } + if (format.flags & alt_flag) + { + // little endian (sign extended)*/ + unsigned int shift = 0; + while (--width && shift < sizeof(long)*8) + { + val |= ((unsigned char) input[length++]) << shift; + shift += 8; + } + if (width == 0) + { + val |= ((signed char) input[length++]) << shift; + } + length += width; // ignore upper bytes not fitting in long + } + else + { + // big endian (sign extended)*/ + val = (signed char) input[length++]; + while (--width) + { + val <<= 8; + val |= (unsigned char) input[length++]; + } + } + value = val; + return length; +} + +RegisterConverter (RawConverter, "r"); diff --git a/src/StreamBuffer.cc b/src/StreamBuffer.cc new file mode 100644 index 0000000..34c3eab --- /dev/null +++ b/src/StreamBuffer.cc @@ -0,0 +1,317 @@ +/*************************************************************** +* StreamBuffer * +* * +* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) * +* * +* This is a buffer class used in StreamDevice for I/O. * +* Please refer to the HTML files in ../doc/ for a detailed * +* documentation. * +* * +* If you do any changes in this file, you are not allowed to * +* redistribute it any more. If there is a bug or a missing * +* feature, send me an email and/or your patch. If I accept * +* your changes, they will go to the next release. * +* * +* DISCLAIMER: If this software breaks something or harms * +* someone, it's your problem. * +* * +***************************************************************/ + +#include "StreamBuffer.h" +#include "StreamError.h" + +#include +#include +#include + +#if defined(__vxworks) || defined(vxWorks) || defined(_WIN32) || defined(__rtems__) +// These systems have no vsnprintf +#include +#define vsnprintf epicsVsnprintf +#endif + +void StreamBuffer:: +init(const void* s, long minsize) +{ + len = 0; + offs = 0; + buffer = local; + cap = sizeof(local); + if (minsize < 0) minsize = 0; + if (minsize >= cap) + { + // use allocated buffer + grow(minsize); + } + else + { + // clear local buffer + memset(buffer+minsize, 0, cap-minsize); + } + if (s) { + len = minsize; + memcpy(buffer, s, minsize); + } +} + +void StreamBuffer:: +grow(long minsize) +{ + // make space for minsize + 1 (for termination) bytes + char* newbuffer; + long newcap; + if (minsize > 10000) + { + // crude trap against infinite grow + error ("StreamBuffer exploded growing from %ld to %ld chars. Exiting\n", + cap, minsize); + int i; + char c; + fprintf(stderr, "String contents (len=%ld):\n", len); + for (i = offs; i < len; i++) + { + c = buffer[i]; + if ((c & 0x7f) < 0x20 || (c & 0x7f) == 0x7f) + { + fprintf(stderr, "<%02x>", c & 0xff); + } + else + { + fprintf(stderr, "%c", c); + } + } + fprintf(stderr, "\n"); + abort(); + } + if (minsize < cap) + { + // just move contents to start of buffer and clear end + // to avoid reallocation + memmove(buffer, buffer+offs, len); + memset(buffer+len, 0, offs); + offs = 0; + return; + } + // allocate new buffer + for (newcap = sizeof(local)*2; newcap <= minsize; newcap *= 2); + newbuffer = new char[newcap]; + // copy old buffer to new buffer and clear end + memcpy(newbuffer, buffer+offs, len); + memset(newbuffer+len, 0, newcap-len); + if (buffer != local) + { + delete buffer; + } + buffer = newbuffer; + cap = newcap; + offs = 0; +} + +StreamBuffer& StreamBuffer:: +append(const void* s, long size) +{ + if (size <= 0) + { + // append negative number of bytes? let's delete some + if (size < -len) size = -len; + memset (buffer+offs+len+size, 0, -size); + } + else + { + check(size); + memcpy(buffer+offs+len, s, size); + } + len += size; + return *this; +} + +long int StreamBuffer:: +find(const void* m, long size, long start) const +{ + if (start < 0) + { + start += len; + if (start < 0) start = 0; + } + if (start >= len-size+1) return -1; // find nothing after end + if (!m || size <= 0) return start; // find empty string at start + const char* s = static_cast(m); + char* b = buffer+offs; + char* p = b+start; + long i; + while ((p = static_cast(memchr(p, s[0], b-p+len-size+1)))) + { + i = 1; + while (p[i] == s[i]) + { + if (++i >= size) return p-b; + } + p++; + } + return -1; +} + +StreamBuffer& StreamBuffer:: +replace(long remstart, long remlen, const void* ins, long inslen) +{ + if (remstart < 0) + { + // remove from end + remstart += len; + } + if (remlen < 0) + { + // remove left of remstart + remstart += remlen; + remlen = -remlen; + } + if (inslen < 0) + { + // handle negative inserts as additional remove + remstart += inslen; + remlen -= inslen; + inslen = 0; + } + if (remstart < 0) + { + // truncate remove before bufferstart + remlen += remstart; + remstart = 0; + } + if (remstart > len) + { + // remove begins after bufferend + remstart = len; + } + if (remlen >= len-remstart) + { + // truncate remove after bufferend + remlen = len-remstart; + } + if (inslen == 0 && remstart == 0) + { + // optimize remove of bufferstart + offs += remlen; + return *this; + } + if (inslen < 0) inslen = 0; + long remend = remstart+remlen; + long newlen = len+inslen-remlen; + if (cap <= newlen) + { + // buffer too short + long newcap; + for (newcap = sizeof(local)*2; newcap <= newlen; newcap *= 2); + char* newbuffer = new char[newcap]; + memcpy(newbuffer, buffer+offs, remstart); + memcpy(newbuffer+remstart, ins, inslen); + memcpy(newbuffer+remstart+inslen, buffer+offs+remend, len-remend); + memset(newbuffer+newlen, 0, newcap-newlen); + if (buffer != local) + { + delete buffer; + } + buffer = newbuffer; + cap = newcap; + offs = 0; + } + else + { + if (newlen+offs<=cap) + { + // move to start of buffer + memmove(buffer+offs+remstart+inslen, buffer+offs+remend, len-remend); + memcpy(buffer+offs+remstart, ins, inslen); + if (newlen -1 && printed < (int)(cap-offs-len)) + { + len += printed; + return *this; + } + if (printed > -1) grow(len+printed); + else grow(len); + } +} + +StreamBuffer StreamBuffer::expand(long start, long length) const +{ + long end; + if (start < 0) + { + start += len; + if (start < 0) start = 0; + } + end = length >= 0 ? start+length : len; + if (end > len) end = len; + StreamBuffer result((end-start)*2); + start += offs; + end += offs; + long i; + char c; + for (i = start; i < end; i++) + { + c = buffer[i]; + if ((c & 0x7f) < 0x20 || (c & 0x7f) == 0x7f) + { + result.printf("<%02x>", c & 0xff); + } + else + { + result.append(c); + } + } + return result; +} + +StreamBuffer StreamBuffer:: +dump() const +{ + StreamBuffer result(256+cap*5); + result.append("\033[0m"); + long i; + result.printf("%ld,%ld,%ld:\033[37m", offs, len, cap); + for (i = 0; i < cap; i++) + { + if (i == offs) result.append("\033[34m[\033[0m"); + if ((buffer[i] & 0x7f) < 0x20 || (buffer[i] & 0x7f) == 0x7f) + { + if (i < offs || i >= offs+len) + result.printf( + "<%02x>", buffer[i] & 0xff); + else + result.printf( + "\033[34m<%02x>\033[37m", buffer[i] & 0xff); + } + else + { + result.append(buffer[i]); + } + if (i == offs+len-1) result.append("\033[34m]\033[37m"); + } + result.append("\033[0m"); + return result; +} diff --git a/src/StreamBuffer.h b/src/StreamBuffer.h new file mode 100644 index 0000000..6789c09 --- /dev/null +++ b/src/StreamBuffer.h @@ -0,0 +1,208 @@ +/*************************************************************** +* StreamBuffer * +* * +* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) * +* * +* This is a buffer class used in StreamDevice for I/O. * +* Please refer to the HTML files in ../doc/ for a detailed * +* documentation. * +* * +* If you do any changes in this file, you are not allowed to * +* redistribute it any more. If there is a bug or a missing * +* feature, send me an email and/or your patch. If I accept * +* your changes, they will go to the next release. * +* * +* DISCLAIMER: If this software breaks something or harms * +* someone, it's your problem. * +* * +***************************************************************/ + +#ifndef StreamBuffer_h +#define StreamBuffer_h + +#include +#include + +#ifndef __GNUC__ +#define __attribute__(x) +#endif + +class StreamBuffer +{ + long len; + long cap; + long offs; + char* buffer; + char local[64]; + + void grow(long); + void init(const void*, long); + + void check(long size) + {if (len+offs+size >= cap) grow(len+size);} + +public: + // Hints: + // * Any index parameter (long) can be negative + // meaning "count from end" (-1 is the last byte) + // * Any returned char* pointer becomes invalid when + // the StreamBuffer is modified. + + StreamBuffer() + {init(NULL, 0);} + + StreamBuffer(const void*s, long size) + {init(s, size);} + + StreamBuffer(const char*s) + {init(s, s?strlen(s):0);} + + StreamBuffer(const StreamBuffer& s) + {init(s.buffer+s.offs, s.len);} + + StreamBuffer(long size) + {init(NULL, size);} + + ~StreamBuffer() + {if (buffer != local) delete buffer;} + + // operator (): get char* pointing to index + const char* operator()(long index=0) const + {buffer[offs+len]=0; return buffer+offs+(index<0?index+len:index);} + + char* operator()(long index=0) + {buffer[offs+len]=0; return buffer+offs+(index<0?index+len:index);} + + // operator []: get byte at index + char operator[](long index) const + {return buffer[offs+(index<0?index+len:index)];} + + char& operator[](long index) + {return buffer[offs+(index<0?index+len:index)];} + + // cast to bool: not empty? + operator bool() const + {return len>0;} + + // length: get current data length + long length() const + {return len;} + + // capacity: get current max data length (spare one byte for end) + long capacity() const + {return cap-1;} + + // end: get pointer to byte after last data byte + const char* end() const + {return buffer+offs+len;} + + // clear: set length to 0, don't free or blank memory (fast!) + StreamBuffer& clear() + {offs+=len; len=0; return *this;} + + // reserve: reserve size bytes of memory and return + // pointer to that memory (for copying something to it) + char* reserve(long size) + {check(size); char* p=buffer+offs+len; len+=size; return p; } + + // append: append data at the end of the buffer + StreamBuffer& append(char c) + {check(1); buffer[offs+len++]=c; return *this;} + + StreamBuffer& append(const void* s, long size); + + StreamBuffer& append(const char* s) + {return append(s, s?strlen(s):0);} + + StreamBuffer& append(const StreamBuffer& s) + {return append(s.buffer+s.offs, s.len);} + + // set: clear buffer and fill with new data + StreamBuffer& set(const void* s, long size) + {clear(); return append(s, size);} + + StreamBuffer& set(const char* s) + {clear(); return append(s, s?strlen(s):0);} + + StreamBuffer& set(const StreamBuffer& s) + {clear(); return append(s.buffer+s.offs, s.len);} + + // operator =: alias for set + StreamBuffer& operator=(const char* s) + {return set(s);} + + StreamBuffer& operator=(const StreamBuffer& s) + {return set(s);} + + // replace: delete part of buffer (pos/length) and insert new data + StreamBuffer& replace( + long pos, long length, const void* s, long size); + + StreamBuffer& replace(long pos, long length, const char* s) + {return replace(pos, length, s, s?strlen(s):0);} + + StreamBuffer& replace(long pos, long length, const StreamBuffer& s) + {return replace(pos, length, s.buffer+s.offs, s.len);} + + // replace: delete part of buffer + StreamBuffer& remove(long pos, long length) + {return replace(pos, length, NULL, 0);} + + StreamBuffer& remove(long length) + {if (length>len) length=len; + offs+=length; len-=length; return *this;} + + // replace: delete end of buffer + StreamBuffer& truncate(long pos) + {return replace(pos, len, NULL, 0);} + + // insert: insert new data into buffer + StreamBuffer& insert(long pos, const void* s, long size) + {return replace(pos, 0, s, size);} + + StreamBuffer& insert(long pos, const char* s) + {return replace(pos, 0, s, s?strlen(s):0);} + + StreamBuffer& insert(long pos, const StreamBuffer& s) + {return replace(pos, 0, s.buffer+s.offs, s.len);} + + StreamBuffer& insert(long pos, char c) + {return replace(pos, 0, &c, 1);} + + StreamBuffer& printf(const char* fmt, ...) + __attribute__ ((format(printf,2,3))); + + // find: get index of data in buffer or -1 + long find(char c, long start=0) const + {char* p; + return (p = static_cast( + memchr(buffer+offs+(start<0?start+len:start), + c, start<0?-start:len-start)))? + p-(buffer+offs) : -1;} + + long find(const void* s, long size, long start=0) const; + + long find(const char* s, long start=0) const + {return find(s, s?strlen(s):0, start);} + + long int find(const StreamBuffer& s, long start=0) const + {return find(s.buffer+s.offs, s.len, start);} + + // equals: returns true if first size bytes are equal + bool equals(const void* s, long size) const + {return len>=size ? memcmp(buffer+offs, s, size) == 0 : false;} + + // equals: returns true if first string is equal (empty string match) + bool equals(const char* s) const + {return len ? strcmp(buffer+offs, s) == 0 : !s || !*s;} + +// expand: create copy of StreamBuffer where all nonprintable characters +// are replaced by with xx being the hex code of the characters + StreamBuffer expand(long start=0, long length=-1) const; + +// dump: debug function, like expand but also show the 'hidden' memory +// before and after the real data. Uses colours. + StreamBuffer dump() const; +}; + +#endif diff --git a/src/StreamBusInterface.cc b/src/StreamBusInterface.cc new file mode 100644 index 0000000..d3d7f8d --- /dev/null +++ b/src/StreamBusInterface.cc @@ -0,0 +1,144 @@ +/*************************************************************** +* StreamDevice Support * +* * +* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) * +* * +* This is the interface to bus drivers for StreamDevice. * +* Please refer to the HTML files in ../doc/ for a detailed * +* documentation. * +* * +* If you do any changes in this file, you are not allowed to * +* redistribute it any more. If there is a bug or a missing * +* feature, send me an email and/or your patch. If I accept * +* your changes, they will go to the next release. * +* * +* DISCLAIMER: If this software breaks something or harms * +* someone, it's your problem. * +* * +***************************************************************/ + +#include "StreamBusInterface.h" + +const char* StreamIoStatusStr[] = { + "StreamIoSuccess", "ioTimeout", "ioNoReply", "ioEnd", "ioFault" +}; + +StreamBusInterfaceRegistrarBase* StreamBusInterfaceRegistrarBase::first; + +StreamBusInterfaceRegistrarBase:: +StreamBusInterfaceRegistrarBase(const char* name) : name(name) +{ + next = NULL; + StreamBusInterfaceRegistrarBase** pr; + for (pr = &first; *pr; pr = &(*pr)->next); + *pr = this; +} + +StreamBusInterfaceRegistrarBase:: +~StreamBusInterfaceRegistrarBase() +{ +} + +StreamBusInterface:: +StreamBusInterface(Client* client) : + client(client) +{ +} + +bool StreamBusInterface:: +supportsEvent() +{ + return false; +} + +bool StreamBusInterface:: +supportsAsyncRead() +{ + return false; +} + +StreamBusInterface* StreamBusInterface:: +find(Client* client, const char* busname, int addr, const char* param) +{ + StreamBusInterfaceRegistrarBase* r; + StreamBusInterface* bus; + for (r = r->first; r; r = r->next) + { + bus = r->find(client, busname, addr, param); + if (bus) return bus; + } + return NULL; +} + +bool StreamBusInterface:: +acceptEvent(unsigned long, unsigned long) +{ + return false; +} + +void StreamBusInterface:: +release () +{ + delete this; +} + +bool StreamBusInterface:: +connectRequest (unsigned long) +{ + return false; +} + +bool StreamBusInterface:: +disconnect () +{ + return false; +} + +bool StreamBusInterface:: +writeRequest(const void*, size_t, unsigned long) +{ + return false; +} + +bool StreamBusInterface:: +readRequest(unsigned long, unsigned long, long, bool) +{ + return false; +} + +void StreamBusInterface:: +finish() +{ +} + +StreamBusInterface::Client:: +~Client() +{ +} + +void StreamBusInterface::Client:: +writeCallback(StreamIoStatus) +{ +} + +long StreamBusInterface::Client:: +readCallback(StreamIoStatus, const void*, long) +{ + return 0; +} + +void StreamBusInterface::Client:: +eventCallback(StreamIoStatus) +{ +} + +void StreamBusInterface::Client:: +connectCallback(StreamIoStatus) +{ +} + +long StreamBusInterface::Client:: +priority() +{ + return 0; +} diff --git a/src/StreamBusInterface.h b/src/StreamBusInterface.h new file mode 100644 index 0000000..17f0c66 --- /dev/null +++ b/src/StreamBusInterface.h @@ -0,0 +1,200 @@ +/*************************************************************** +* StreamDevice Support * +* * +* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) * +* * +* This is the interface to bus drivers for StreamDevice. * +* Please refer to the HTML files in ../doc/ for a detailed * +* documentation. * +* * +* If you do any changes in this file, you are not allowed to * +* redistribute it any more. If there is a bug or a missing * +* feature, send me an email and/or your patch. If I accept * +* your changes, they will go to the next release. * +* * +* DISCLAIMER: If this software breaks something or harms * +* someone, it's your problem. * +* * +***************************************************************/ + +#ifndef StreamBusInterface_h +#define StreamBusInterface_h + +#include + +enum StreamIoStatus { + StreamIoSuccess, StreamIoTimeout, StreamIoNoReply, + StreamIoEnd, StreamIoFault +}; + +extern const char* StreamIoStatusStr[]; + +class StreamBusInterface +{ +public: + + class Client + { + friend class StreamBusInterface; + virtual void lockCallback(StreamIoStatus status) = 0; + virtual void writeCallback(StreamIoStatus status); + virtual long readCallback(StreamIoStatus status, + const void* input, long size); + virtual void eventCallback(StreamIoStatus status); + virtual void connectCallback(StreamIoStatus status); + virtual long priority(); + virtual const char* name() = 0; + virtual const char* getInTerminator(size_t& length) = 0; + virtual const char* getOutTerminator(size_t& length) = 0; + public: + virtual ~Client(); + protected: + StreamBusInterface* businterface; + bool busSupportsEvent() { + return businterface->supportsEvent(); + } + bool busSupportsAsyncRead() { + return businterface->supportsAsyncRead(); + } + bool busAcceptEvent(unsigned long mask, + unsigned long replytimeout_ms) { + return businterface->acceptEvent(mask, replytimeout_ms); + } + void busRelease() { + businterface->release(); + } + bool busLockRequest(unsigned long timeout_ms) { + return businterface->lockRequest(timeout_ms); + } + bool busUnlock() { + return businterface->unlock(); + } + bool busWriteRequest(const void* output, size_t size, + unsigned long timeout_ms) { + return businterface->writeRequest(output, size, timeout_ms); + } + bool busReadRequest(unsigned long replytimeout_ms, + unsigned long readtimeout_ms, long expectedLength, + bool async) { + return businterface->readRequest(replytimeout_ms, + readtimeout_ms, expectedLength, async); + } + void busFinish() { + businterface->finish(); + } + bool busConnectRequest(unsigned long timeout_ms) { + return businterface->connectRequest(timeout_ms); + } + bool busDisconnect() { + return businterface->disconnect(); + } + }; + +private: + friend class StreamBusInterfaceClass; // the iterator + friend class Client; + +public: + Client* client; + virtual ~StreamBusInterface() {}; + +protected: + StreamBusInterface(Client* client); + +// map client functions into StreamBusInterface namespace + void lockCallback(StreamIoStatus status) + { client->lockCallback(status); } + void writeCallback(StreamIoStatus status) + { client->writeCallback(status); } + long readCallback(StreamIoStatus status, + const void* input = NULL, long size = 0) + { return client->readCallback(status, input, size); } + void eventCallback(StreamIoStatus status) + { client->eventCallback(status); } + void connectCallback(StreamIoStatus status) + { client->connectCallback(status); } + const char* getInTerminator(size_t& length) + { return client->getInTerminator(length); } + const char* getOutTerminator(size_t& length) + { return client->getOutTerminator(length); } + long priority() { return client->priority(); } + const char* clientName() { return client->name(); } + +// default implementations + virtual bool writeRequest(const void* output, size_t size, + unsigned long timeout_ms); + virtual bool readRequest(unsigned long replytimeout_ms, + unsigned long readtimeout_ms, long expectedLength, + bool async); + virtual bool supportsEvent(); // defaults to false + virtual bool supportsAsyncRead(); // defaults to false + virtual bool acceptEvent(unsigned long mask, // implement if + unsigned long replytimeout_ms); // supportsEvents() returns true + virtual void release(); + virtual bool connectRequest(unsigned long connecttimeout_ms); + virtual bool disconnect(); + virtual void finish(); + +// pure virtual + virtual bool lockRequest(unsigned long timeout_ms) = 0; + virtual bool unlock() = 0; + +public: +// static methods + static StreamBusInterface* find(Client*, const char* busname, + int addr, const char* param); +}; + +class StreamBusInterfaceRegistrarBase +{ + friend class StreamBusInterfaceClass; // the iterator + friend class StreamBusInterface; // the implementation + static StreamBusInterfaceRegistrarBase* first; + StreamBusInterfaceRegistrarBase* next; + virtual StreamBusInterface* find(StreamBusInterface::Client* client, + const char* busname, int addr, const char* param) = 0; +protected: + const char* name; + StreamBusInterfaceRegistrarBase(const char* name); + virtual ~StreamBusInterfaceRegistrarBase(); +}; + +template +class StreamBusInterfaceRegistrar : protected StreamBusInterfaceRegistrarBase +{ + StreamBusInterface* find(StreamBusInterface::Client* client, + const char* busname, int addr, const char* param) + { return C::getBusInterface(client, busname, addr, param); } +public: + StreamBusInterfaceRegistrar(const char* name) : + StreamBusInterfaceRegistrarBase(name) {}; +}; + + +#define RegisterStreamBusInterface(interface) \ +template class StreamBusInterfaceRegistrar; \ +StreamBusInterfaceRegistrar \ +registrar_##interface(#interface); \ +void* ref_##interface = ®istrar_##interface\ + +// Interface class iterator + +class StreamBusInterfaceClass +{ + StreamBusInterfaceRegistrarBase* ptr; +public: + StreamBusInterfaceClass () { + ptr = StreamBusInterfaceRegistrarBase::first; + } + StreamBusInterfaceClass& operator ++ () { + ptr = ptr->next; return *this; + } + const char* name() { + return ptr->name; + } + operator bool () { + return ptr != NULL; + } +}; + +#endif diff --git a/src/StreamCore.cc b/src/StreamCore.cc new file mode 100644 index 0000000..47ee904 --- /dev/null +++ b/src/StreamCore.cc @@ -0,0 +1,1517 @@ +/*************************************************************** +* StreamDevice Support * +* * +* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) * +* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) * +* * +* This is the kernel of StreamDevice. * +* Please refer to the HTML files in ../doc/ for a detailed * +* documentation. * +* * +* If you do any changes in this file, you are not allowed to * +* redistribute it any more. If there is a bug or a missing * +* feature, send me an email and/or your patch. If I accept * +* your changes, they will go to the next release. * +* * +* DISCLAIMER: If this software breaks something or harms * +* someone, it's your problem. * +* * +***************************************************************/ + +#include "StreamCore.h" +#include "StreamError.h" +#include +#include + +enum Commands { end_cmd, in_cmd, out_cmd, wait_cmd, event_cmd, exec_cmd, + connect_cmd, disconnect_cmd }; +const char* commandStr[] = { "end", "in", "out", "wait", "event", "exec", + "connect", "disconnect" }; + +inline const char* commandName(unsigned char i) +{ + return i > exec_cmd ? "invalid" : commandStr[i]; +} + +/// debug functions ///////////////////////////////////////////// + +static char* printCommands(StreamBuffer& buffer, const char* c) +{ + unsigned long timeout; + unsigned long eventnumber; + unsigned short cmdlen; + while (1) + { + switch(*c++) + { + case end_cmd: + return buffer(); + case in_cmd: + buffer.append(" in \""); + c = StreamProtocolParser::printString(buffer, c); + buffer.append("\";\n"); + break; + case out_cmd: + buffer.append(" out \""); + c = StreamProtocolParser::printString(buffer, c); + buffer.append("\";\n"); + break; + case wait_cmd: + timeout = extract(c); + buffer.printf(" wait %ld;\n # ms", timeout); + break; + case event_cmd: + eventnumber = extract(c); + timeout = extract(c); + buffer.printf(" event(%ld) %ld; # ms\n", eventnumber, timeout); + break; + case exec_cmd: + buffer.append(" exec \""); + cmdlen = extract(c); + c = StreamProtocolParser::printString(buffer, c); + buffer.append("\";\n"); + break; + case connect_cmd: + timeout = extract(c); + buffer.printf(" connect %ld; # ms\n", timeout); + break; + case disconnect_cmd: + buffer.append(" disconnect;\n"); + break; + default: + buffer.append("\033[31;1mGARBAGE: "); + c = StreamProtocolParser::printString(buffer, c-1); + buffer.append("\033[0m\n"); + } + } +} + +void StreamCore:: +printProtocol() +{ + StreamBuffer buffer; + printf("%s {\n", protocolname()); + printf(" extraInput = %s;\n", + (flags & IgnoreExtraInput) ? "ignore" : "error"); + printf(" lockTimeout = %ld; # ms\n", lockTimeout); + printf(" readTimeout = %ld; # ms\n", readTimeout); + printf(" replyTimeout = %ld; # ms\n", replyTimeout); + printf(" writeTimeout = %ld; # ms\n", writeTimeout); + printf(" pollPeriod = %ld; # ms\n", pollPeriod); + printf(" maxInput = %ld; # bytes\n", maxInput); + StreamProtocolParser::printString(buffer.clear(), inTerminator()); + printf(" inTerminator = \"%s\";\n", buffer()); + StreamProtocolParser::printString(buffer.clear(), outTerminator()); + printf(" outTerminator = \"%s\";\n", buffer()); + StreamProtocolParser::printString(buffer.clear(), separator()); + printf(" separator = \"%s\";\n", buffer()); + if (onInit) + printf(" @Init {\n%s }\n", + printCommands(buffer.clear(), onInit())); + if (onReplyTimeout) + printf(" @ReplyTimeout {\n%s }\n", + printCommands(buffer.clear(), onReplyTimeout())); + if (onReadTimeout) + printf(" @ReadTimeout {\n%s }\n", + printCommands(buffer.clear(), onReadTimeout())); + if (onWriteTimeout) + printf(" @WriteTimeout {\n%s }\n", + printCommands(buffer.clear(), onWriteTimeout())); + if (onMismatch) + printf(" @Mismatch {\n%s }\n", + printCommands(buffer.clear(), onMismatch())); + debug("StreamCore::printProtocol: commands=%s\n", commands.expand()()); + printf("\n%s}\n", + printCommands(buffer.clear(), commands())); +} + +/////////////////////////////////////////////////////////////////////////// + +StreamCore* StreamCore::first = NULL; + +StreamCore:: +StreamCore() +{ + businterface = NULL; + flags = None; + next = NULL; + unparsedInput = false; + // add myself to list of streams + StreamCore** pstream; + for (pstream = &first; *pstream; pstream = &(*pstream)->next); + *pstream = this; +} + +StreamCore:: +~StreamCore() +{ + debug("~StreamCore(%s) %p\n", name(), (void*)this); + releaseBus(); + // remove myself from list of all streams + StreamCore** pstream; + for (pstream = &first; *pstream; pstream = &(*pstream)->next) + { + if (*pstream == this) + { + *pstream = next; + break; + } + } +} + +bool StreamCore:: +attachBus(const char* busname, int addr, const char* param) +{ + releaseBus(); + businterface = StreamBusInterface::find(this, busname, addr, param); + if (!businterface) + { + error("Businterface '%s' not found for '%s'\n", + busname, name()); + return false; + } + debug("StreamCore::attachBus(busname=\"%s\", addr=%i, param=\"%s\") businterface=%p\n", + busname, addr, param, (void*)businterface); + return true; +} + +void StreamCore:: +releaseBus() +{ + if (businterface) + { + if (flags & BusOwner) + { + busUnlock(); + } + busRelease(); + businterface = NULL; + } +} + +// Parse the protocol + +bool StreamCore:: +parse(const char* filename, const char* _protocolname) +{ + protocolname = _protocolname; + // extract substitutions from protocolname "name(sub1,sub2)" + int i = protocolname.find('('); + if (i >= 0) + { + while (i >= 0) + { + protocolname[i] = '\0'; // replace '(' and ',' with '\0' + i = protocolname.find(',', i+1); + } + // should have closing parentheses + if (protocolname[-1] != ')') + { + error("Missing ')' after substitutions '%s'\n", _protocolname); + return false; + } + protocolname.truncate(-1); // remove ')' + } + StreamProtocolParser::Protocol* protocol; + protocol = StreamProtocolParser::getProtocol(filename, protocolname); + if (!protocol) + { + error("while reading protocol '%s' for '%s'\n", protocolname(), name()); + return false; + } + if (!compile(protocol)) + { + delete protocol; + error("while compiling protocol '%s' for '%s'\n", _protocolname, name()); + return false; + } + delete protocol; + return true; +} + +bool StreamCore:: +compile(StreamProtocolParser::Protocol* protocol) +{ + const char* extraInputNames [] = {"error", "ignore", NULL}; + + // default values for protocol variables + flags &= ~IgnoreExtraInput; + lockTimeout = 5000; + readTimeout = 100; + replyTimeout = 1000; + writeTimeout = 100; + maxInput = 0; + pollPeriod = 1000; + inTerminatorDefined = false; + outTerminatorDefined = false; + + unsigned short ignoreExtraInput = false; + if (!protocol->getEnumVariable("extrainput", ignoreExtraInput, + extraInputNames)) + { + return false; + } + if (ignoreExtraInput) flags |= IgnoreExtraInput; + if (!(protocol->getNumberVariable("locktimeout", lockTimeout) && + protocol->getNumberVariable("readtimeout", readTimeout) && + protocol->getNumberVariable("replytimeout", replyTimeout) && + protocol->getNumberVariable("writetimeout", writeTimeout) && + protocol->getNumberVariable("maxinput", maxInput) && + // use replyTimeout as default for pollPeriod + protocol->getNumberVariable("replytimeout", pollPeriod) && + protocol->getNumberVariable("pollperiod", pollPeriod))) + { + return false; + } + if (!(protocol->getStringVariable("terminator", inTerminator, &inTerminatorDefined) && + protocol->getStringVariable("terminator", outTerminator, &outTerminatorDefined) && + protocol->getStringVariable("interminator", inTerminator, &inTerminatorDefined) && + protocol->getStringVariable("outterminator", outTerminator, &outTerminatorDefined) && + protocol->getStringVariable("separator", separator))) + { + return false; + } + if (!(protocol->getCommands(NULL, commands, this) && + protocol->getCommands("@init", onInit, this) && + protocol->getCommands("@writetimeout", onWriteTimeout, this) && + protocol->getCommands("@replytimeout", onReplyTimeout, this) && + protocol->getCommands("@readtimeout", onReadTimeout, this) && + protocol->getCommands("@mismatch", onMismatch, this))) + { + return false; + } + return protocol->checkUnused(); +} + +bool StreamCore:: +compileCommand(StreamProtocolParser::Protocol* protocol, + StreamBuffer& buffer, const char* command, const char*& args) +{ + unsigned long timeout = 0; + + if (strcmp(command, commandStr[in_cmd]) == 0) + { + buffer.append(in_cmd); + if (!protocol->compileString(buffer, args, + ScanFormat, this)) + { + return false; + } + buffer.append(StreamProtocolParser::eos); + return true; + } + if (strcmp(command, commandStr[out_cmd]) == 0) + { + buffer.append(out_cmd); + if (!protocol->compileString(buffer, args, + PrintFormat, this)) + { + return false; + } + buffer.append(StreamProtocolParser::eos); + return true; + } + if (strcmp(command, commandStr[wait_cmd]) == 0) + { + buffer.append(wait_cmd); + if (!protocol->compileNumber(timeout, args)) + { + return false; + } + buffer.append(&timeout, sizeof(timeout)); + return true; + } + if (strcmp(command, commandStr[event_cmd]) == 0) + { + if (!busSupportsEvent()) + { + protocol->errorMsg(getLineNumber(command), + "Events not supported by businterface.\n"); + return false; + } + unsigned long eventmask = 0xffffffff; + buffer.append(event_cmd); + if (*args == '(') + { + if (!protocol->compileNumber(eventmask, ++args)) + { + return false; + } + if (*args != ')') + { + protocol->errorMsg(getLineNumber(command), + "Expect ')' instead of: '%s'\n", args); + return false; + } + args++; + } + buffer.append(&eventmask, sizeof(eventmask)); + if (*args) + { + if (!protocol->compileNumber(timeout, args)) + { + return false; + } + } + buffer.append(&timeout, sizeof(timeout)); + return true; + } + if (strcmp(command, commandStr[exec_cmd]) == 0) + { + buffer.append(exec_cmd); + if (!protocol->compileString(buffer, args, + NoFormat, this)) + { + return false; + } + buffer.append(StreamProtocolParser::eos); + return true; + } + if (strcmp(command, commandStr[connect_cmd]) == 0) + { + buffer.append(connect_cmd); + if (!protocol->compileNumber(timeout, args)) + { + return false; + } + buffer.append(&timeout, sizeof(timeout)); + return true; + } + if (strcmp(command, commandStr[disconnect_cmd]) == 0) + { + buffer.append(disconnect_cmd); + return true; + } + + protocol->errorMsg(getLineNumber(command), + "Unknown command name '%s'\n", command); + return false; +} + +// Run the protocol + +bool StreamCore:: +startProtocol(StartMode startMode) +{ + MutexLock lock(this); + debug("StreamCore::startProtocol(%s, startMode=%s)\n", name(), + startMode == StartNormal ? "StartNormal" : + startMode == StartInit ? "StartInit" : + startMode == StartAsync ? "StartAsync" : "Invalid"); + if (!businterface) + { + error("%s: No businterface attached\n", name()); + return false; + } + flags &= ~ClearOnStart; + switch (startMode) + { + case StartInit: + flags |= InitRun; + break; + case StartAsync: + if (!busSupportsAsyncRead()) + { + error("%s: Businterface does not support async mode\n", name()); + return false; + } + flags |= AsyncMode; + break; + case StartNormal: + break; + } + if (!commands) + { + error ("%s: No protocol loaded\n", name()); + return false; + } + commandIndex = (startMode == StartInit) ? onInit() : commands(); + runningHandler = Success; + protocolStartHook(); + return evalCommand(); +} + +void StreamCore:: +finishProtocol(ProtocolResult status) +{ + if (flags & BusPending) + { + error("StreamCore::finishProtocol(%s): Still waiting for %s%s%s\n", + name(), + flags & LockPending ? "lockSuccess() " : "", + flags & WritePending ? "writeSuccess() " : "", + flags & WaitPending ? "timerCallback()" : ""); + status = Fault; + } + flags &= ~(AcceptInput|AcceptEvent); + if (runningHandler) + { + // get original error status + if (status == Success) status = runningHandler; + } + else + { + // save original error status + runningHandler = status; + // look for error handler + char* handler; + switch (status) + { + case Success: + handler = NULL; + break; + case WriteTimeout: + handler = onWriteTimeout(); + break; + case ReplyTimeout: + handler = onReplyTimeout(); + break; + case ReadTimeout: + handler = onReadTimeout(); + break; + case ScanError: + handler = onMismatch(); + /* reparse old input if first command in handler is 'in' */ + if (*handler == in_cmd) + { + debug("reparsing input \"%s\"\n", + inputLine.expand()()); + commandIndex = handler + 1; + if (matchInput()) + { + evalCommand(); + return; + } + handler = NULL; + break; + } + break; + default: + // get rid of all the rubbish whe might have collected + inputBuffer.clear(); + handler = NULL; + } + if (handler) + { + debug("starting exception handler\n"); + // execute handler + commandIndex = handler; + evalCommand(); + return; + } + } + debug("StreamCore::finishProtocol(%s, status=%s) %sbus owner\n", + name(), + status==0 ? "Success" : + status==1 ? "LockTimeout" : + status==2 ? "WriteTimeout" : + status==3 ? "ReplyTimeout" : + status==4 ? "ReadTimeout" : + status==5 ? "ScanError" : + status==6 ? "FormatError" : + status==7 ? "Abort" : + status==8 ? "Fault" : "Invalid", + flags & BusOwner ? "" : "not "); + if (flags & BusOwner) + { + busUnlock(); + flags &= ~BusOwner; + } + busFinish(); + protocolFinishHook(status); +} + +bool StreamCore:: +evalCommand() +{ + if (flags & BusPending) + { + error("StreamCore::evalCommand(%s): Still waiting for %s%s%s", + name(), + flags & LockPending ? "lockSuccess() " : "", + flags & WritePending ? "writeSuccess() " : "", + flags & WaitPending ? "timerCallback()" : ""); + return false; + } + activeCommand = commandIndex; + debug("StreamCore::evalCommand(%s): activeCommand = %s\n", + name(), commandName(*activeCommand)); + switch (*commandIndex++) + { + case out_cmd: + flags &= ~(AcceptInput|AcceptEvent); + return evalOut(); + case in_cmd: + flags &= ~AcceptEvent; + return evalIn(); + case wait_cmd: + flags &= ~(AcceptInput|AcceptEvent); + return evalWait(); + case event_cmd: + flags &= ~AcceptInput; + return evalEvent(); + case exec_cmd: + return evalExec(); + case end_cmd: + finishProtocol(Success); + return true; + case connect_cmd: + return evalConnect(); + case disconnect_cmd: + return evalDisconnect(); + default: + error("INTERNAL ERROR (%s): illegal command code 0x%02x\n", + name(), *activeCommand); + flags &= ~BusPending; + finishProtocol(Fault); + return false; + } +} + +// Handle 'out' command + +bool StreamCore:: +evalOut() +{ + inputBuffer.clear(); // flush all unread input + unparsedInput = false; + outputLine.clear(); + if (!formatOutput()) + { + finishProtocol(FormatError); + return false; + } + outputLine.append(outTerminator); + debug ("StreamCore::evalOut: outputLine = \"%s\"\n", outputLine.expand()()); + if (*commandIndex == in_cmd) // prepare for early input + { + flags |= AcceptInput; + } + if (*commandIndex == event_cmd) // prepare for early event + { + flags |= AcceptEvent; + } + if (!(flags & BusOwner)) + { + debug ("StreamCore::evalOut(%s): lockRequest(%li)\n", + name(), flags & InitRun ? 0 : lockTimeout); + flags |= LockPending; + if (!busLockRequest(flags & InitRun ? 0 : lockTimeout)) + { + return false; + } + return true; + } + flags |= WritePending; + if (!busWriteRequest(outputLine(), outputLine.length(), writeTimeout)) + { + return false; + } + return true; +} + +bool StreamCore:: +formatOutput() +{ + char command; + const char* fieldName = NULL; + const char* formatstring; + while ((command = *commandIndex++) != StreamProtocolParser::eos) + { + switch (command) + { + case StreamProtocolParser::format_field: + { + debug("StreamCore::formatOutput(%s): StreamProtocolParser::format_field\n", + name()); + // code layout: + // field addrlen AddressStructure formatstring StreamFormat [info] + fieldName = commandIndex; + commandIndex += strlen(commandIndex)+1; + unsigned short addrlen = extract(commandIndex); + fieldAddress.set(commandIndex, addrlen); + commandIndex += addrlen; + } + case StreamProtocolParser::format: + { + // code layout: + // formatstring StreamFormat [info] + formatstring = commandIndex; + while (*commandIndex++); // jump after + StreamFormat fmt = extract(commandIndex); + fmt.info = commandIndex; // point to info string + commandIndex += fmt.infolen; + debug("StreamCore::formatOutput(%s): format = %%%s\n", + name(), formatstring); + if (fmt.type == pseudo_format) + { + if (!StreamFormatConverter::find(fmt.conv)-> + printPseudo(fmt, outputLine)) + { + error("%s: Can't print pseudo value '%%%s'\n", + name(), formatstring); + return false; + } + continue; + } + flags &= ~Separator; + if (!formatValue(fmt, fieldAddress ? fieldAddress() : NULL)) + { + if (fieldName) + error("%s: Cannot format field '%s' with '%%%s'\n", + name(), fieldName, formatstring); + else + error("%s: Cannot format value with '%%%s'\n", + name(), formatstring); + return false; + } + fieldAddress.clear(); + fieldName = NULL; + continue; + } + case esc: + // escaped literal byte + command = *commandIndex++; + default: + // literal byte + outputLine.append(command); + } + } + return true; +} + +void StreamCore:: +printSeparator() +{ + if (!(flags & Separator)) + { + flags |= Separator; + return; + } + if (!separator) return; + long i = 0; + if (separator[0] == ' ') i++; // ignore leading space + for (; i < separator.length(); i++) + { + if (separator[i] == StreamProtocolParser::skip) continue; // wildcard + if (separator[i] == esc) i++; // escaped literal byte + outputLine.append(separator[i]); + } +} + +bool StreamCore:: +printValue(const StreamFormat& fmt, long value) +{ + if (fmt.type != long_format && fmt.type != enum_format) + { + error("%s: printValue(long) called with %%%c format\n", + name(), fmt.conv); + return false; + } + printSeparator(); + if (!StreamFormatConverter::find(fmt.conv)-> + printLong(fmt, outputLine, value)) + { + error("%s: Formatting value %li failed\n", + name(), value); + return false; + } + return true; +} + +bool StreamCore:: +printValue(const StreamFormat& fmt, double value) +{ + if (fmt.type != double_format) + { + error("%s: printValue(double) called with %%%c format\n", + name(), fmt.conv); + return false; + } + printSeparator(); + if (!StreamFormatConverter::find(fmt.conv)-> + printDouble(fmt, outputLine, value)) + { + error("%s: Formatting value %#g failed\n", + name(), value); + return false; + } + return true; +} + +bool StreamCore:: +printValue(const StreamFormat& fmt, char* value) +{ + if (fmt.type != string_format) + { + error("%s: printValue(char*) called with %%%c format\n", + name(), fmt.conv); + return false; + } + printSeparator(); + if (!StreamFormatConverter::find(fmt.conv)-> + printString(fmt, outputLine, value)) + { + StreamBuffer buffer(value); + error("%s: Formatting value \"%s\" failed\n", + name(), buffer.expand()()); + return false; + } + return true; +} + +void StreamCore:: +lockCallback(StreamIoStatus status) +{ + MutexLock lock(this); + debug("StreamCore::lockCallback(%s, status=%s)\n", + name(), status ? "Timeout" : "Success"); + if (!(flags & LockPending)) + { + error("StreamCore::lockCallback(%s) called unexpectedly\n", + name()); + return; + } + flags &= ~LockPending; + flags |= BusOwner; + if (status != StreamIoSuccess) + { + finishProtocol(LockTimeout); + return; + } + flags |= WritePending; + if (!busWriteRequest(outputLine(), outputLine.length(), writeTimeout)) + { + finishProtocol(Fault); + } +} + +void StreamCore:: +writeCallback(StreamIoStatus status) +{ + MutexLock lock(this); + debug("StreamCore::writeCallback(%s, status=%s)\n", + name(), status ? "Timeout" : "Success"); + if (!(flags & WritePending)) + { + error("StreamCore::writeCallback(%s) called unexpectedly\n", + name()); + return; + } + flags &= ~WritePending; + if (status != StreamIoSuccess) + { + finishProtocol(WriteTimeout); + return; + } + evalCommand(); +} + +const char* StreamCore:: +getOutTerminator(size_t& length) +{ + if (outTerminatorDefined) + { + length = outTerminator.length(); + return outTerminator(); + } + else + { + length = 0; + return NULL; + } +} + +// Handle 'in' command + +bool StreamCore:: +evalIn() +{ + flags |= AcceptInput; + long expectedInput; + + expectedInput = maxInput; + if (unparsedInput) + { + // handle early input + debug("StreamCore::evalIn(%s): early input: %s\n", + name(), inputBuffer.expand()()); + expectedInput = readCallback(lastInputStatus, NULL, 0); + if (!expectedInput) + { + // no more input needed + return true; + } + } + if (flags & AsyncMode) + { + // release bus + if (flags & BusOwner) + { + debug("StreamCore::evalIn(%s): unlocking bus\n", + name()); + busUnlock(); + flags &= ~BusOwner; + } + busReadRequest(pollPeriod, readTimeout, + expectedInput, true); + return true; + } + busReadRequest(replyTimeout, readTimeout, + expectedInput, false); + // continue with readCallback() in another thread + return true; +} + +long StreamCore:: +readCallback(StreamIoStatus status, + const void* input, long size) +// returns number of bytes to read additionally + +{ + if (status < 0 || status > StreamIoFault) + { + error("StreamCore::readCallback(%s) called with illegal StreamIoStatus %d\n", + name(), status); + return 0; + } + MutexLock lock(this); + lastInputStatus = status; + +#ifndef NO_TEMPORARY + debug("StreamCore::readCallback(%s, status=%s input=\"%s\", size=%ld)\n", + name(), StreamIoStatusStr[status], + StreamBuffer(input, size).expand()(), size); +#endif + + if (!(flags & AcceptInput)) + { + error("StreamCore::readCallback(%s) called unexpectedly\n", + name()); + return 0; + } + flags &= ~AcceptInput; + unparsedInput = false; + switch (status) + { + case StreamIoTimeout: + // timeout is valid end if we have no terminator + // and number of input bytes is not limited + if (!inTerminator && !maxInput) + { + status = StreamIoEnd; + } + // else timeout might be ok if we find a terminator + break; + case StreamIoSuccess: + case StreamIoEnd: + break; + case StreamIoNoReply: + if (flags & AsyncMode) + { + // just restart in asyn mode + debug("StreamCore::readCallback(%s) no async input: just restart\n", + name()); + evalIn(); + return 0; + } + error("%s: No reply from device within %ld ms\n", + name(), replyTimeout); + inputBuffer.clear(); + finishProtocol(ReplyTimeout); + return 0; + case StreamIoFault: + error("%s: I/O error when reading from device\n", name()); + finishProtocol(Fault); + return 0; + } + inputBuffer.append(input, size); + debug("StreamCore::readCallback(%s) inputBuffer=\"%s\", size %ld\n", + name(), inputBuffer.expand()(), inputBuffer.length()); + if (*activeCommand != in_cmd) + { + // early input, stop here and wait for in command + // -- Should we limit size of inputBuffer? -- + if (inputBuffer) unparsedInput = true; + return 0; + } + + // prepare to parse the input + const char *commandStart = commandIndex; + long end = -1; + long termlen = 0; + + if (inTerminator) + { + // look for terminator + end = inputBuffer.find(inTerminator); + if (end >= 0) termlen = inTerminator.length(); + debug("StreamCore::readCallback(%s) inTerminator %sfound\n", + name(), end >= 0 ? "" : "not "); + } + if (status == StreamIoEnd && end < 0) + { + // no terminator but end flag + debug("StreamCore::readCallback(%s) end flag received\n", + name()); + end = inputBuffer.length(); + } + if (maxInput && end < 0 && (long)maxInput <= inputBuffer.length()) + { + // no terminator but maxInput bytes read + debug("StreamCore::readCallback(%s) maxInput size reached\n", + name()); + end = maxInput; + } + if (maxInput && end > (long)maxInput) + { + // limit input length to maxInput (ignore terminator) + end = maxInput; + termlen = 0; + } + if (end < 0) + { + // no end found + if (status != StreamIoTimeout) + { + // input is incomplete - wait for more + debug("StreamCore::readCallback(%s) wait for more input\n", + name()); + flags |= AcceptInput; + if (maxInput) + return maxInput - inputBuffer.length(); + else + return -1; + } + // try to parse what we got + end = inputBuffer.length(); + if (!(flags & AsyncMode)) + { + error("%s: Timeout after reading %ld byte%s \"%s%s\"\n", + name(), end, end==1 ? "" : "s", end > 20 ? "..." : "", + inputBuffer.expand(-20)()); + } + } + + if (status == StreamIoTimeout && (flags & AsyncMode)) + { + debug("StreamCore::readCallback(%s) async timeout: just restart\n", + name()); + inputBuffer.clear(); + commandIndex = commandStart; + evalIn(); + return 0; + } + + inputLine.set(inputBuffer(), end); + debug("StreamCore::readCallback(%s) input line: \"%s\"\n", + name(), inputLine.expand()()); + bool matches = matchInput(); + inputBuffer.remove(end + termlen); + if (inputBuffer) + { + debug("StreamCore::readCallback(%s) unpared input left: \"%s\"\n", + name(), inputBuffer.expand()()); + unparsedInput = true; + } + if (!matches) + { + if (status == StreamIoTimeout) + { + // we have not forgotten the timeout + finishProtocol(ReadTimeout); + return 0; + } + if (flags & AsyncMode) + { + debug("StreamCore::readCallback(%s) async match failure: just restart\n", + name()); + commandIndex = commandStart; + evalIn(); + return 0; + } + debug("StreamCore::readCallback(%s) match failure\n", + name()); + finishProtocol(ScanError); + return 0; + } + if (status == StreamIoTimeout) + { + // we have not forgotten the timeout + finishProtocol(ReadTimeout); + return 0; + } + // end input mode and do next command + flags &= ~(AsyncMode|AcceptInput); + // -- should we tell someone that input has finished? -- + evalCommand(); + return 0; +} + +bool StreamCore:: +matchInput() +{ + /* Don't write messages about matching errors if either in asynchronous + mode (then we just wait for new matching input) or if @mismatch handler + is installed and starts with 'in' (then we reparse the input). + */ + char command; + const char* formatstring; + + consumedInput = 0; + + while ((command = *commandIndex++) != StreamProtocolParser::eos) + { + switch (command) + { + case StreamProtocolParser::format_field: + { + // code layout: + // field addrlen AddressStructure formatstring StreamFormat [info] + commandIndex += strlen(commandIndex)+1; + unsigned short addrlen = extract(commandIndex); + fieldAddress.set(commandIndex, addrlen); + commandIndex += addrlen; + } + case StreamProtocolParser::format: + { + int consumed; + // code layout: + // formatstring StreamFormat [info] + formatstring = commandIndex; + while (*commandIndex++ != StreamProtocolParser::eos); // jump after + + StreamFormat fmt = extract(commandIndex); + fmt.info = commandIndex; + commandIndex += fmt.infolen; + if (fmt.flags & skip_flag || fmt.type == pseudo_format) + { + long ldummy; + double ddummy; + switch (fmt.type) + { + case long_format: + case enum_format: + consumed = StreamFormatConverter::find(fmt.conv)-> + scanLong(fmt, inputLine(consumedInput), ldummy); + break; + case double_format: + consumed = StreamFormatConverter::find(fmt.conv)-> + scanDouble(fmt, inputLine(consumedInput), ddummy); + break; + case string_format: + consumed = StreamFormatConverter::find(fmt.conv)-> + scanString(fmt, inputLine(consumedInput), NULL, 0); + break; + case pseudo_format: + // pass complete input + consumed = StreamFormatConverter::find(fmt.conv)-> + scanPseudo(fmt, inputLine, consumedInput); + break; + default: + error("INTERNAL ERROR (%s): illegal format.type 0x%02x\n", + name(), fmt.type); + return false; + } + if (consumed < 0) + { + if (!(flags & AsyncMode) && onMismatch[0] != in_cmd) + { + error("%s: Input \"%s%s\" does not match format %%%s\n", + name(), inputLine.expand(consumedInput, 20)(), + inputLine.length()-consumedInput > 20 ? "..." : "", + formatstring); + } + return false; + } + consumedInput += consumed; + break; + } + flags &= ~Separator; + if (!matchValue(fmt, fieldAddress ? fieldAddress() : NULL)) + { + if (!(flags & AsyncMode) && onMismatch[0] != in_cmd) + { + if (flags & ScanTried) + error("%s: Input \"%s%s\" does not match format %%%s\n", + name(), inputLine.expand(consumedInput, 20)(), + inputLine.length()-consumedInput > 20 ? "..." : "", + formatstring); + else + error("%s: Can't scan value with format %%%s\n", + name(), formatstring); + } + return false; + } + // matchValue() has already removed consumed bytes from inputBuffer + fieldAddress = NULL; + break; + } + case StreamProtocolParser::skip: + // ignore next input byte + consumedInput++; + break; + case esc: + // escaped literal byte + command = *commandIndex++; + default: + // literal byte + if (consumedInput >= inputLine.length()) + { + int i = 0; + while (commandIndex[i] >= ' ') i++; + if (!(flags & AsyncMode) && onMismatch[0] != in_cmd) + { + error("%s: Input \"%s%s\" too short." + " No match for \"%s\"\n", + name(), + inputLine.length() > 20 ? "..." : "", + inputLine.expand(-20)(), + StreamBuffer(commandIndex-1,i+1).expand()()); + } + return false; + } + if (command != inputLine[consumedInput]) + { + if (!(flags & AsyncMode) && onMismatch[0] != in_cmd) + { + int i = 0; + while (commandIndex[i] >= ' ') i++; + error("%s: Input \"%s%s\" mismatch after %ld byte%s\n", + name(), + consumedInput > 10 ? "..." : "", + inputLine.expand(consumedInput > 10 ? + consumedInput-10 : 0,20)(), + consumedInput, + consumedInput==1 ? "" : "s"); + + error("%s: got \"%s\" where \"%s\" was expected\n", + name(), + inputLine.expand(consumedInput, 20)(), + StreamBuffer(commandIndex-1,i+1).expand()()); + } + return false; + } + consumedInput++; + } + } + long surplus = inputLine.length()-consumedInput; + if (surplus > 0 && !(flags & IgnoreExtraInput)) + { + if (!(flags & AsyncMode) && onMismatch[0] != in_cmd) + { + error("%s: %ld byte%s surplus input \"%s%s\"\n", + name(), surplus, surplus==1 ? "" : "s", + inputLine.expand(consumedInput, 20)(), + surplus > 20 ? "..." : ""); + + if (consumedInput>20) + error("%s: after %ld byte%s \"...%s\"\n", + name(), consumedInput, + consumedInput==1 ? "" : "s", + inputLine.expand(consumedInput-20, 20)()); + else + error("%s: after %ld byte%s: \"%s\"\n", + name(), consumedInput, + consumedInput==1 ? "" : "s", + inputLine.expand(0, consumedInput)()); + } + return false; + } + return true; +} + +bool StreamCore:: +matchSeparator() +{ + if (!(flags & Separator)) + { + flags |= Separator; + return true; + } + if (!separator) return true; + long i = 0; + if (separator[0] == ' ') + { + i++; + // skip leading whitespaces + while (isspace(inputLine[consumedInput++])); + } + for (; i < separator.length(); i++,consumedInput++) + { + if (!inputLine[consumedInput]) return false; + if (separator[i] == StreamProtocolParser::skip) continue; // wildcard + if (separator[i] == esc) i++; // escaped literal byte + if (separator[i] != inputLine[consumedInput]) return false; + } + return true; +} + +bool StreamCore:: +scanSeparator() +{ + // for compatibility only + // read and remove separator + if (!matchSeparator()) return false; + flags &= ~Separator; + return true; +} + +long StreamCore:: +scanValue(const StreamFormat& fmt, long& value) +{ + if (fmt.type != long_format && fmt.type != enum_format) + { + error("%s: scanValue(long&) called with %%%c format\n", + name(), fmt.conv); + return -1; + } + flags |= ScanTried; + if (!matchSeparator()) return -1; + long consumed = StreamFormatConverter::find(fmt.conv)-> + scanLong(fmt, inputLine(consumedInput), value); + debug("StreamCore::scanValue(%s, format=%%%c, long) input=\"%s\"\n", + name(), fmt.conv, inputLine.expand(consumedInput)()); + if (consumed < 0 || + consumed > inputLine.length()-consumedInput) return -1; + debug("StreamCore::scanValue(%s) scanned %li\n", + name(), value); + flags |= GotValue; + return consumed; +} + +long StreamCore:: +scanValue(const StreamFormat& fmt, double& value) +{ + if (fmt.type != double_format) + { + error("%s: scanValue(double&) called with %%%c format\n", + name(), fmt.conv); + return -1; + } + flags |= ScanTried; + if (!matchSeparator()) return -1; + long consumed = StreamFormatConverter::find(fmt.conv)-> + scanDouble(fmt, inputLine(consumedInput), value); + debug("StreamCore::scanValue(%s, format=%%%c, double) input=\"%s\"\n", + name(), fmt.conv, inputLine.expand(consumedInput)()); + if (consumed < 0 || + consumed > inputLine.length()-consumedInput) return -1; + debug("StreamCore::scanValue(%s) scanned %#g\n", + name(), value); + flags |= GotValue; + return consumed; +} + +long StreamCore:: +scanValue(const StreamFormat& fmt, char* value, long maxlen) +{ + if (fmt.type != string_format) + { + error("%s: scanValue(char*) called with %%%c format\n", + name(), fmt.conv); + return -1; + } + if (maxlen < 0) maxlen = 0; + flags |= ScanTried; + if (!matchSeparator()) return -1; + long consumed = StreamFormatConverter::find(fmt.conv)-> + scanString(fmt, inputLine(consumedInput), value, maxlen); + debug("StreamCore::scanValue(%s, format=%%%c, char*, maxlen=%ld) input=\"%s\"\n", + name(), fmt.conv, maxlen, inputLine.expand(consumedInput)()); + if (consumed < 0 || + consumed > inputLine.length()-consumedInput) return -1; +#ifndef NO_TEMPORARY + debug("StreamCore::scanValue(%s) scanned \"%s\"\n", + name(), StreamBuffer(value, consumed).expand()()); +#endif + flags |= GotValue; + return consumed; +} + +const char* StreamCore:: +getInTerminator(size_t& length) +{ + if (inTerminatorDefined) + { + length = inTerminator.length(); + return inTerminator(); + } + else + { + length = 0; + return NULL; + } +} + +// Handle 'event' command + +bool StreamCore:: +evalEvent() +{ + // code layout: + // eventmask timeout + unsigned long eventMask = extract(commandIndex); + unsigned long eventTimeout = extract(commandIndex); + if (flags & AsyncMode && eventTimeout == 0) + { + if (flags & BusOwner) + { + busUnlock(); + flags &= ~BusOwner; + } + } + flags |= AcceptEvent; + busAcceptEvent(eventMask, eventTimeout); + return true; +} + +void StreamCore:: +eventCallback(StreamIoStatus status) +{ + if (status < 0 || status > StreamIoFault) + { + error("StreamCore::eventCallback(%s) called with illegal StreamIoStatus %d\n", + name(), status); + return; + } + if (!(flags & AcceptEvent)) + { + error("StreamCore::eventCallback(%s) called unexpectedly\n", + name()); + return; + } + debug("StreamCore::eventCallback(%s, status=%s)\n", + name(), StreamIoStatusStr[status]); + MutexLock lock(this); + flags &= ~AcceptEvent; + switch (status) + { + case StreamIoTimeout: + error("%s: No event from device\n", name()); + finishProtocol(ReplyTimeout); + return; + case StreamIoSuccess: + evalCommand(); + return; + default: + error("%s: Event error from device: %s\n", + name(), StreamIoStatusStr[status]); + finishProtocol(Fault); + return; + } +} + +// Handle 'wait' command + +bool StreamCore:: +evalWait() +{ + unsigned long waitTimeout = extract(commandIndex); + flags |= WaitPending; + startTimer(waitTimeout); + return true; +} + +void StreamCore:: +timerCallback() +{ + MutexLock lock(this); + debug ("StreamCore::timerCallback(%s)\n", name()); + if (!(flags & WaitPending)) + { + error("StreamCore::timerCallback(%s) called unexpectedly\n", + name()); + return; + } + flags &= ~WaitPending; + evalCommand(); +} + + +bool StreamCore:: +evalExec() +{ + outputLine.clear(); + formatOutput(); + // release bus + if (flags & BusOwner) + { + debug("StreamCore::evalExec(%s): unlocking bus\n", + name()); + busUnlock(); + flags &= ~BusOwner; + } + if (!execute()) + { + error("%s: executing command \"%s\"\n", name(), outputLine()); + return false; + } + return true; +} + +void StreamCore:: +execCallback(StreamIoStatus status) +{ + switch (status) + { + case StreamIoSuccess: + evalCommand(); + return; + default: + error("%s: Shell command \"%s\" failed\n", + name(), outputLine()); + finishProtocol(Fault); + return; + } +} + +bool StreamCore::execute() +{ + error("%s: Command 'exec' not implemented on this system\n", + name()); + return false; +} + +bool StreamCore::evalConnect() +{ + unsigned long connectTimeout = extract(commandIndex); + if (!busConnectRequest(connectTimeout)) + { + error("%s: Connect not supported for this bus\n", + name()); + return false; + } + return true; +} + +void StreamCore:: +connectCallback(StreamIoStatus status) +{ + switch (status) + { + case StreamIoSuccess: + evalCommand(); + return; + default: + error("%s: Connect failed\n", + name()); + finishProtocol(Fault); + return; + } +} + +bool StreamCore::evalDisconnect() +{ + if (!busDisconnect()) + { + error("%s: Cannot disconnect from this bus\n", + name()); + finishProtocol(Fault); + return false; + } + evalCommand(); + return true; +} + +#include "streamReferences" diff --git a/src/StreamCore.h b/src/StreamCore.h new file mode 100644 index 0000000..2fe078c --- /dev/null +++ b/src/StreamCore.h @@ -0,0 +1,224 @@ +/*************************************************************** +* StreamDevice Support * +* * +* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) * +* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) * +* * +* This is the kernel of StreamDevice. * +* Please refer to the HTML files in ../doc/ for a detailed * +* documentation. * +* * +* If you do any changes in this file, you are not allowed to * +* redistribute it any more. If there is a bug or a missing * +* feature, send me an email and/or your patch. If I accept * +* your changes, they will go to the next release. * +* * +* DISCLAIMER: If this software breaks something or harms * +* someone, it's your problem. * +* * +***************************************************************/ + +#ifndef StreamCore_h +#define StreamCore_h + +#include "StreamProtocol.h" +#include "StreamFormatConverter.h" +#include "StreamBusInterface.h" + +/************************************** + virtual methods: + +bool getFieldAddress(const char* fieldname, StreamBuffer& address) + If a format sting contains a field name (like "%(EGU)s") a value should + be taken from / put to that field instead of the default location. + This function must convert the fieldname into some address structure and + put the structure into the address buffer (e.g. address.set(addressStruct)). + The address structure must be suitable for bytewise copying. I.e. memcpy() + to so some other memory location must not invalidate its contents. No + constructor, destructor or copy operator will be called. + formatValue() and matchValue() get a pointer to the structure. + getFieldAddress() must return true on success and false on failure. + +bool formatValue(const StreamFormat& format, const void* fieldaddress) + This function should first get a value from fieldaddress (if not NULL) or + the default location (which may depend on format.type). + The printValue(format,XXX) function suitable for format.type should be + called to print value. If value is an array, printValue() should be called + for each element. The separator string will be added automatically. + formatValue() must return true on success and false on failure. + +bool matchValue(const StreamFormat& format, const void* fieldaddress) + This function should first call the scanValue(format,XXX) function + suitable for format.type. If scanValue() returns true, the scanned value + should be put into fieldaddress (if not NULL) or the default location + (which may depend on format.type). + If value is an array, scanValue() should be called for each element. It + returns false if there is no more element available. The separator string + is matched automatically. + matchValue() must return true on success and false on failure. + + +void protocolStartHook() +void protocolFinishHook(ProtocolResult) +void startTimer(unsigned short timeout) +void lockRequest(unsigned short timeout) +void unlock() +void writeRequest(unsigned short timeout) +void readRequest(unsigned short replytimeout, unsigned short timeout) +void readAsyn(unsigned short timeout) +void acceptEvent(unsigned short mask, unsigned short timeout) + +***************************************/ + +enum Flags { + // 0x00FFFFFF reserved for StreamCore + None = 0x0000, + IgnoreExtraInput = 0x0001, + InitRun = 0x0002, + AsyncMode = 0x0004, + GotValue = 0x0008, + BusOwner = 0x0010, + Separator = 0x0020, + ScanTried = 0x0040, + AcceptInput = 0x0100, + AcceptEvent = 0x0200, + LockPending = 0x0400, + WritePending = 0x0800, + WaitPending = 0x1000, + BusPending = LockPending|WritePending|WaitPending, + ClearOnStart = InitRun|AsyncMode|GotValue|BusOwner|Separator|ScanTried| + AcceptInput|AcceptEvent|BusPending +}; + +struct StreamFormat; + +class StreamCore : + StreamProtocolParser::Client, + StreamBusInterface::Client +{ +protected: + enum ProtocolResult { + Success, LockTimeout, WriteTimeout, ReplyTimeout, ReadTimeout, + ScanError, FormatError, Abort, Fault + }; + + enum StartMode { + StartNormal, StartInit, StartAsync + }; + + class MutexLock + { + StreamCore* stream; + + public: + MutexLock(StreamCore* _stream) : stream(_stream) + { _stream->lockMutex(); } + ~MutexLock() + { stream->releaseMutex(); } + }; + + friend class MutexLock; + + StreamCore* next; + static StreamCore* first; + + char* streamname; + unsigned long flags; + + bool attachBus(const char* busname, int addr, const char* param); + void releaseBus(); + + bool startProtocol(StartMode); + void finishProtocol(ProtocolResult); + void timerCallback(); + + bool printValue(const StreamFormat& format, long value); + bool printValue(const StreamFormat& format, double value); + bool printValue(const StreamFormat& format, char* value); + long scanValue(const StreamFormat& format, long& value); + long scanValue(const StreamFormat& format, double& value); + long scanValue(const StreamFormat& format, char* value, long maxlen); + long scanValue(const StreamFormat& format); + bool scanSeparator(); // disencouraged + + StreamBuffer protocolname; + unsigned long lockTimeout; + unsigned long writeTimeout; + unsigned long replyTimeout; + unsigned long readTimeout; + unsigned long pollPeriod; + unsigned long maxInput; + bool inTerminatorDefined; + bool outTerminatorDefined; + StreamBuffer inTerminator; + StreamBuffer outTerminator; + StreamBuffer separator; + StreamBuffer commands; // the normal protocol + StreamBuffer onInit; // init protocol (optional) + StreamBuffer onWriteTimeout; // error handler (optional) + StreamBuffer onReplyTimeout; // error handler (optional) + StreamBuffer onReadTimeout; // error handler (optional) + StreamBuffer onMismatch; // error handler (optional) + const char* commandIndex; // current position + const char* activeCommand; // start of current command + StreamBuffer outputLine; + StreamBuffer inputBuffer; + StreamBuffer inputLine; + long consumedInput; + ProtocolResult runningHandler; + StreamBuffer fieldAddress; + + StreamIoStatus lastInputStatus; + bool unparsedInput; + + StreamCore(const StreamCore&); // undefined + bool compile(StreamProtocolParser::Protocol*); + bool evalCommand(); + bool evalOut(); + bool evalIn(); + bool evalEvent(); + bool evalWait(); + bool evalExec(); + bool evalConnect(); + bool evalDisconnect(); + bool formatOutput(); + bool matchInput(); + bool matchSeparator(); + void printSeparator(); + +// StreamProtocolParser::Client methods + bool compileCommand(StreamProtocolParser::Protocol*, + StreamBuffer&, const char* command, const char*& args); + bool getFieldAddress(const char* fieldname, + StreamBuffer& address) = 0; + +// StreamBusInterface::Client methods + void lockCallback(StreamIoStatus status); + void writeCallback(StreamIoStatus status); + long readCallback(StreamIoStatus status, + const void* input, long size); + void eventCallback(StreamIoStatus status); + void execCallback(StreamIoStatus status); + void connectCallback(StreamIoStatus status); + const char* getInTerminator(size_t& length); + const char* getOutTerminator(size_t& length); + +// virtual methods + virtual void protocolStartHook() {} + virtual void protocolFinishHook(ProtocolResult) {} + virtual void startTimer(unsigned long timeout) = 0; + virtual bool formatValue(const StreamFormat&, const void* fieldaddress) = 0; + virtual bool matchValue (const StreamFormat&, const void* fieldaddress) = 0; + virtual void lockMutex() = 0; + virtual void releaseMutex() = 0; + virtual bool execute(); + +public: + StreamCore(); + virtual ~StreamCore(); + bool parse(const char* filename, const char* protocolname); + void printProtocol(); + const char* name() { return streamname; } +}; + +#endif diff --git a/src/StreamEpics.cc b/src/StreamEpics.cc new file mode 100644 index 0000000..2d914e3 --- /dev/null +++ b/src/StreamEpics.cc @@ -0,0 +1,1218 @@ +/*************************************************************** +* StreamDevice Support * +* * +* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) * +* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) * +* * +* This is the interface to EPICS for StreamDevice. * +* Please refer to the HTML files in ../doc/ for a detailed * +* documentation. * +* * +* If you do any changes in this file, you are not allowed to * +* redistribute it any more. If there is a bug or a missing * +* feature, send me an email and/or your patch. If I accept * +* your changes, they will go to the next release. * +* * +* DISCLAIMER: If this software breaks something or harms * +* someone, it's your problem. * +* * +***************************************************************/ + +#include "StreamCore.h" +#include "StreamError.h" +#include "devStream.h" + +#ifndef EPICS_3_14 +extern "C" { +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef EPICS_3_14 + +#include +#include + +extern DBBASE *pdbbase; + +} // extern "C" + +#else + +#include +#include +#include +#include +#include +#include + +#if EPICS_MODIFICATION<9 +extern "C" { +// iocshCmd() is missing in iocsh.h (up to R3.14.8.2) +// To build with win32-x86, you MUST fix iocsh.h. +// Move the declaration below to iocsh.h and rebuild base. +epicsShareFunc int epicsShareAPI iocshCmd(const char *command); +} +#endif + +#include + +#endif + +#if defined(__vxworks) || defined(vxWorks) +#include +#include +#endif + +enum MoreFlags { + // 0x00FFFFFF used by StreamCore + InDestructor = 0x0100000, + ValueReceived = 0x0200000 +}; + +extern "C" void streamExecuteCommand(CALLBACK *pcallback); +extern "C" long streamReload(char* recordname); + +class Stream : protected StreamCore +#ifdef EPICS_3_14 + , epicsTimerNotify +#endif +{ + dbCommon* record; + struct link *ioLink; + streamIoFunction readData; + streamIoFunction writeData; +#ifdef EPICS_3_14 + epicsTimerQueueActive* timerQueue; + epicsTimer* timer; + epicsMutex mutex; + epicsEvent initDone; +#else + WDOG_ID timer; + CALLBACK timeoutCallback; + SEM_ID mutex; + SEM_ID initDone; +#endif + StreamBuffer fieldBuffer; + int status; + int convert; + long currentValueLength; + IOSCANPVT ioscanpvt; + CALLBACK commandCallback; + + +#ifdef EPICS_3_14 +// epicsTimerNotify method + expireStatus expire(const epicsTime&); +#else + static void expire(CALLBACK *pcallback); +#endif + +// StreamCore methods + // void protocolStartHook(); // Nothing to do here? + void protocolFinishHook(ProtocolResult); + void startTimer(unsigned long timeout); + bool getFieldAddress(const char* fieldname, + StreamBuffer& address); + bool formatValue(const StreamFormat&, + const void* fieldaddress); + bool matchValue(const StreamFormat&, + const void* fieldaddress); + void lockMutex(); + void releaseMutex(); + bool execute(); + friend void streamExecuteCommand(CALLBACK *pcallback); + +// Stream Epics methods + long initRecord(); + Stream(dbCommon* record, struct link *ioLink, + streamIoFunction readData, streamIoFunction writeData); + ~Stream(); + bool print(format_t *format, va_list ap); + bool scan(format_t *format, void* pvalue, size_t maxStringSize); + bool process(); + +// device support functions + friend long streamInitRecord(dbCommon *record, struct link *ioLink, + streamIoFunction readData, streamIoFunction writeData); + friend long streamReadWrite(dbCommon *record); + friend long streamGetIointInfo(int cmd, dbCommon *record, + IOSCANPVT *ppvt); + friend long streamPrintf(dbCommon *record, format_t *format, ...); + friend long streamScanfN(dbCommon *record, format_t *format, + void*, size_t maxStringSize); + friend long streamScanSep(dbCommon *record); + friend long streamReload(char* recordname); + +public: + long priority() { return record->prio; }; + static long report(int interest); + static long drvInit(); +}; + + +// shell functions /////////////////////////////////////////////////////// +#ifdef EPICS_3_14 +extern "C" { +epicsExportAddress(int, streamDebug); +} +#endif + +#ifdef MEMGUARD +static const iocshFuncDef memguardReportDef = + { "memguardReport", 0, NULL }; + +static void memguardReportFunc (const iocshArgBuf *args) +{ + memguardReport(); +} +#endif + +// for subroutine record +extern "C" long streamReloadSub() +{ + return streamReload(NULL); +} + +extern "C" long streamReload(char* recordname) +{ + DBENTRY dbentry; + dbCommon* record; + long status; + + if(!pdbbase) { + error("No database has been loaded\n"); + return ERROR; + } + debug("streamReload(%s)\n", recordname); + dbInitEntry(pdbbase,&dbentry); + for (status = dbFirstRecordType(&dbentry); status == OK; + status = dbNextRecordType(&dbentry)) + { + for (status = dbFirstRecord(&dbentry); status == OK; + status = dbNextRecord(&dbentry)) + { + char* value; + if (dbFindField(&dbentry, "DTYP") != OK) + continue; + if ((value = dbGetString(&dbentry)) == NULL) + continue; + if (strcmp(value, "stream") != 0) + continue; + record=(dbCommon*)dbentry.precnode->precord; + if (recordname && strcmp(recordname, record->name) != 0) + continue; + + // This cancels any running protocol and reloads + // the protocol file + status = record->dset->init_record(record); + if (status == OK || status == DO_NOT_CONVERT) + { + printf("%s: Protocol reloaded\n", record->name); + } + else + { + error("%s: Protocol reload failed\n", record->name); + } + } + } + dbFinishEntry(&dbentry); + StreamProtocolParser::free(); + return OK; +} + +#ifdef EPICS_3_14 +static const iocshArg streamReloadArg0 = + { "recordname", iocshArgString }; +static const iocshArg * const streamReloadArgs[] = + { &streamReloadArg0 }; +static const iocshFuncDef reloadDef = + { "streamReload", 1, streamReloadArgs }; + +extern "C" void streamReloadFunc (const iocshArgBuf *args) +{ + streamReload(args[0].sval); +} + +static void streamRegistrar () +{ +#ifdef MEMGUARD + iocshRegister(&memguardReportDef, memguardReportFunc); +#endif + iocshRegister(&reloadDef, streamReloadFunc); + // make streamReload available for subroutine records + registryFunctionAdd("streamReload", + (REGISTRYFUNCTION)streamReloadSub); + registryFunctionAdd("streamReloadSub", + (REGISTRYFUNCTION)streamReloadSub); +} + +extern "C" { +epicsExportRegistrar(streamRegistrar); +} +#endif // EPICS_3_14 + +// driver support //////////////////////////////////////////////////////// + +struct stream_drvsup { + long number; + long (*report)(int); + DRVSUPFUN init; +} stream = { + 2, + Stream::report, + Stream::drvInit +}; + +#ifdef EPICS_3_14 +extern "C" { +epicsExportAddress(drvet, stream); +} + +void streamEpicsPrintTimestamp(char* buffer, int size) +{ + epicsTime tm = epicsTime::getCurrent(); + tm.strftime(buffer, size, "%Y/%m/%d %H:%M:%S.%03f"); +} +#else +void streamEpicsPrintTimestamp(char* buffer, int size) +{ + char* c; + TS_STAMP tm; + tsLocalTime (&tm); + tsStampToText(&tm, TS_TEXT_MMDDYY, buffer); + c = strchr(buffer,'.'); + if (c) { + c[4] = 0; + } +} +#endif + +long Stream:: +report(int interest) +{ + debug("Stream::report(interest=%d)\n", interest); + printf(" %s\n", StreamVersion); + + printf(" registered bus interfaces:\n"); + StreamBusInterfaceClass interface; + while (interface) + { + printf(" %s\n", interface.name()); + ++interface; + } + + if (interest < 1) return OK; + printf(" registered converters:\n"); + StreamFormatConverter* converter; + int c; + for (c=0; c < 256; c++) + { + converter = StreamFormatConverter::find(c); + if (converter) + { + printf(" %%%c %s\n", c, converter->name()); + } + } + + Stream* pstream; + printf(" connected records:\n"); + for (pstream = static_cast(first); pstream; + pstream = static_cast(pstream->next)) + { + if (interest == 2) + { + printf("\n%s: %s\n", pstream->name(), + pstream->ioLink->value.instio.string); + pstream->printProtocol(); + } + else + { + printf(" %s: %s\n", pstream->name(), + pstream->ioLink->value.instio.string); + } + } + StreamPrintTimestampFunction = streamEpicsPrintTimestamp; + return OK; +} + +long Stream:: +drvInit() +{ + char* path; + debug("drvStreamInit()\n"); + path = getenv("STREAM_PROTOCOL_PATH"); +#if defined(__vxworks) || defined(vxWorks) + // for compatibility reasons look for global symbols + if (!path) + { + char* symbol; + SYM_TYPE type; + if (symFindByName(sysSymTbl, + "STREAM_PROTOCOL_PATH", &symbol, &type) == OK) + { + path = *(char**)symbol; + } + else + if (symFindByName(sysSymTbl, + "STREAM_PROTOCOL_DIR", &symbol, &type) == OK) + { + path = *(char**)symbol; + } + } +#endif + if (!path) + { + fprintf(stderr, + "drvStreamInit: Warning! STREAM_PROTOCOL_PATH not set. " + "Defaults to \"%s\"\n", StreamProtocolParser::path); + } + else + { + StreamProtocolParser::path = path; + } + debug("StreamProtocolParser::path = %s\n", + StreamProtocolParser::path); + StreamPrintTimestampFunction = streamEpicsPrintTimestamp; + return OK; +} + +// device support (C interface) ////////////////////////////////////////// + +long streamInit(int after) +{ + if (after) + { + StreamProtocolParser::free(); + } + return OK; +} + +long streamInitRecord(dbCommon* record, struct link *ioLink, + streamIoFunction readData, streamIoFunction writeData) +{ + debug("streamInitRecord(%s): SEVR=%d\n", record->name, record->sevr); + Stream* pstream = (Stream*)record->dpvt; + if (!pstream) + { + // initialize the first time + pstream = new Stream(record, ioLink, readData, writeData); + record->dpvt = pstream; + } else { + // stop any running protocol + pstream->finishProtocol(Stream::Abort); + } + // (re)initialize bus and protocol + long status = pstream->initRecord(); + if (status != OK && status != DO_NOT_CONVERT) + { + error("%s: Record initialization failed\n", record->name); + } + if (!pstream->ioscanpvt) + { + scanIoInit(&pstream->ioscanpvt); + } + return status; +} + +long streamReadWrite(dbCommon *record) +{ + Stream* pstream = (Stream*)record->dpvt; + if (!pstream || pstream->status == ERROR) + { + (void) recGblSetSevr(record, UDF_ALARM, INVALID_ALARM); + error("%s: Record not initialised correctly\n", record->name); + return ERROR; + } + return pstream->process() ? pstream->convert : ERROR; +} + +long streamGetIointInfo(int cmd, dbCommon *record, IOSCANPVT *ppvt) +{ + Stream* pstream = (Stream*)record->dpvt; + debug("streamGetIointInfo(%s,cmd=%d): pstream=%p, ioscanpvt=%p\n", + record->name, cmd, (void*)pstream, pstream ? pstream->ioscanpvt : NULL); + if (!pstream) + { + error("streamGetIointInfo called without stream instance\n"); + return ERROR; + } + *ppvt = pstream->ioscanpvt; + if (cmd == 0) + { + debug("streamGetIointInfo: starting protocol\n"); + // SCAN has been set to "I/O Intr" + if (!pstream->startProtocol(Stream::StartAsync)) + { + error("%s: Can't start \"I/O Intr\" protocol\n", + record->name); + } + } + else + { + // SCAN is no longer "I/O Intr" + pstream->finishProtocol(Stream::Abort); + } + return OK; +} + +long streamPrintf(dbCommon *record, format_t *format, ...) +{ + debug("streamPrintf(%s,format=%%%c)\n", + record->name, format->priv->conv); + Stream* pstream = (Stream*)record->dpvt; + if (!pstream) return ERROR; + va_list ap; + va_start(ap, format); + bool success = pstream->print(format, ap); + va_end(ap); + return success ? OK : ERROR; +} + +long streamScanSep(dbCommon* record) +{ + // depreciated + debug("streamScanSep(%s)\n", record->name); + Stream* pstream = (Stream*)record->dpvt; + if (!pstream) return ERROR; + return pstream->scanSeparator() ? OK : ERROR; +} + +long streamScanfN(dbCommon* record, format_t *format, + void* value, size_t maxStringSize) +{ + debug("streamScanfN(%s,format=%%%c,maxStringSize=%d)\n", + record->name, format->priv->conv, maxStringSize); + Stream* pstream = (Stream*)record->dpvt; + if (!pstream) return ERROR; + if (!pstream->scan(format, value, maxStringSize)) + { + return ERROR; + } +#ifndef NO_TEMPORARY + debug("streamScanfN(%s) success, value=\"%s\"\n", + record->name, StreamBuffer((char*)value).expand()()); +#endif + return OK; +} + +// Stream methods //////////////////////////////////////////////////////// + +Stream:: +Stream(dbCommon* _record, struct link *ioLink, + streamIoFunction readData, streamIoFunction writeData) +:record(_record), ioLink(ioLink), readData(readData), writeData(writeData) +{ + streamname = record->name; +#ifdef EPICS_3_14 + timerQueue = &epicsTimerQueueActive::allocate(true); + timer = &timerQueue->createTimer(); +#else + timer = wdCreate(); + mutex = semMCreate(SEM_INVERSION_SAFE | SEM_Q_PRIORITY); + initDone = semBCreate(SEM_Q_FIFO, SEM_EMPTY); + callbackSetCallback(expire, &timeoutCallback); + callbackSetUser(this, &timeoutCallback); +#endif + callbackSetCallback(streamExecuteCommand, &commandCallback); + callbackSetUser(this, &commandCallback); + status = ERROR; + convert = DO_NOT_CONVERT; + ioscanpvt = NULL; +} + +Stream:: +~Stream() +{ + lockMutex(); + flags |= InDestructor;; + debug("~Stream(%s) %p\n", name(), (void*)this); + if (record->dpvt) + { + finishProtocol(Abort); + debug("~Stream(%s): protocol finished\n", name()); + record->dpvt = NULL; + debug("~Stream(%s): dpvt cleared\n", name()); + } +#ifdef EPICS_3_14 + timer->destroy(); + debug("~Stream(%s): timer destroyed\n", name()); + timerQueue->release(); + debug("~Stream(%s): timer queue released\n", name()); +#else + wdDelete(timer); + debug("~Stream(%s): watchdog destroyed\n", name()); +#endif + releaseMutex(); +} + +long Stream:: +initRecord() +{ + // scan link parameters: filename protocol busname addr busparam + // It is safe to call this function again with different + // link text or different protocol file. + + char filename[80]; + char protocol[80]; + char busname[80]; + int addr = -1; + char busparam[80]; + int n; + + if (ioLink->type != INST_IO) + { + error("%s: Wrong link type %s\n", name(), + pamaplinkType[ioLink->type].strvalue); + return S_dev_badInitRet; + } + int items = sscanf(ioLink->value.instio.string, "%79s%79s%79s%n%i%n", + filename, protocol, busname, &n, &addr, &n); + if (items <= 0) + { + error("%s: Empty link. Forgot the leading '@' or confused INP with OUT ?\n", + name()); + return S_dev_badInitRet; + } + if (items < 3) + { + error("%s: Wrong link format\n" + " expect \"@file protocol bus addr params\"\n" + " in \"@%s\"\n", name(), + ioLink->value.instio.string); + return S_dev_badInitRet; + } + memset(busparam, 0 ,80); + for (n = 0; isspace((unsigned char)ioLink->value.instio.string[n]); n++); + strncpy (busparam, ioLink->value.constantStr+n, 79); + + // attach to bus interface + if (!attachBus(busname, addr, busparam)) + { + error("%s: Can't attach to bus %s %d\n", + name(), busname, addr); + return S_dev_noDevice; + } + + // parse protocol file + if (!parse(filename, protocol)) + { + error("%s: Protocol parse error\n", + name()); + return S_dev_noDevice; + } + + // record is ready to use + status = NO_ALARM; + + if (ioscanpvt) + { + // we have been called by streamReload + debug("Stream::initRecord %s: initialize after streamReload\n", + name()); + if (record->scan == SCAN_IO_EVENT) + { + debug("Stream::initRecord %s: restarting \"I/O Intr\" after reload\n", + name()); + if (!startProtocol(StartAsync)) + { + error("%s: Can't restart \"I/O Intr\" protocol\n", + name()); + } + } + return OK; + } + + debug("Stream::initRecord %s: initialize the first time\n", + name()); + + if (!onInit) return DO_NOT_CONVERT; // no @init handler, keep DOL + + // initialize the record from hardware + if (!startProtocol(StartInit)) + { + error("%s: Can't start init run\n", + name()); + return ERROR; + } + debug("Stream::initRecord %s: waiting for initDone\n", + name()); +#ifdef EPICS_3_14 + initDone.wait(); +#else + semTake(initDone, WAIT_FOREVER); +#endif + debug("Stream::initRecord %s: initDone\n", + name()); + + // init run has set status and convert + if (status != NO_ALARM) + { + record->stat = status; + error("%s: @init handler failed\n", + name()); + return ERROR; + } + debug("Stream::initRecord %s: initialized. convert=%d\n", + name(), convert); + return convert; +} + +bool Stream:: +process() +{ + MutexLock lock(this); + if (record->pact || record->scan == SCAN_IO_EVENT) + { + if (status != NO_ALARM) + { + debug("Stream::process(%s) error status=%d\n", + name(), status); + (void) recGblSetSevr(record, status, INVALID_ALARM); + return false; + } + debug("Stream::process(%s) ready. %s\n", + name(), convert==2 ? + "convert" : "don't convert"); + return true; + } + if (flags & InDestructor) + { + error("%s: Try to process while in stream destructor (try later)\n", + name()); + (void) recGblSetSevr(record, UDF_ALARM, INVALID_ALARM); + return false; + } + debug("Stream::process(%s) start\n", name()); + status = NO_ALARM; + convert = OK; + record->pact = true; + if (!startProtocol(StreamCore::StartNormal)) + { + debug("Stream::process(%s): could not start, status=%d\n", + name(), status); + (void) recGblSetSevr(record, status, INVALID_ALARM); + return false; + } + debug("Stream::process(%s): protocol started\n", name()); + return true; +} + +bool Stream:: +print(format_t *format, va_list ap) +{ + long lval; + double dval; + char* sval; + switch (format->type) + { + case DBF_ENUM: + case DBF_LONG: + lval = va_arg(ap, long); + return printValue(*format->priv, lval); + case DBF_DOUBLE: + dval = va_arg(ap, double); + return printValue(*format->priv, dval); + case DBF_STRING: + sval = va_arg(ap, char*); + return printValue(*format->priv, sval); + } + error("INTERNAL ERROR (%s): Illegal format type\n", name()); + return false; +} + +bool Stream:: +scan(format_t *format, void* value, size_t maxStringSize) +{ + // called by streamScanfN + long* lptr; + double* dptr; + char* sptr; + + // first remove old value from inputLine (if we are scanning arrays) + consumedInput += currentValueLength; + currentValueLength = 0; + switch (format->type) + { + case DBF_LONG: + case DBF_ENUM: + lptr = (long*)value; + currentValueLength = scanValue(*format->priv, *lptr); + break; + case DBF_DOUBLE: + dptr = (double*)value; + currentValueLength = scanValue(*format->priv, *dptr); + break; + case DBF_STRING: + sptr = (char*)value; + currentValueLength = scanValue(*format->priv, sptr, maxStringSize); + break; + default: + error("INTERNAL ERROR (%s): Illegal format type\n", name()); + return false; + } + if (currentValueLength < 0) + { + currentValueLength = 0; + return false; + } + // Don't remove scanned value from inputLine yet, because + // we might need the string in a later error message. + return true; +} + +// epicsTimerNotify virtual method /////////////////////////////////////// + +#ifdef EPICS_3_14 +epicsTimerNotify::expireStatus Stream:: +expire(const epicsTime&) +{ + timerCallback(); + return noRestart; +} +#else +void Stream:: +expire(CALLBACK *pcallback) +{ + Stream* pstream = static_cast(pcallback->user); + pstream->timerCallback(); +} +#endif + +// StreamCore virtual methods //////////////////////////////////////////// + +void Stream:: +protocolFinishHook(ProtocolResult result) +{ + switch (result) + { + case Success: + status = NO_ALARM; + if (flags & ValueReceived) + { + record->udf = false; + if (flags & InitRun) + { + // records start with UDF/INVALID, + // but now this record has a value + record->sevr = NO_ALARM; + record->stat = NO_ALARM; + } + } + break; + case LockTimeout: + case ReplyTimeout: + status = TIMEOUT_ALARM; + break; + case WriteTimeout: + status = WRITE_ALARM; + break; + case ReadTimeout: + status = READ_ALARM; + break; + case ScanError: + case FormatError: + status = CALC_ALARM; + break; + case Abort: + case Fault: + status = UDF_ALARM; + if (record->pact || record->scan == SCAN_IO_EVENT) + error("%s: Protocol aborted\n", name()); + break; + default: + status = UDF_ALARM; + error("INTERNAL ERROR (%s): Illegal protocol result\n", + name()); + break; + + } + if (flags & InitRun) + { +#ifdef EPICS_3_14 + initDone.signal(); +#else + semGive(initDone); +#endif + return; + } + if (record->pact || record->scan == SCAN_IO_EVENT) + { + debug("Stream::protocolFinishHook(stream=%s,result=%d) " + "processing record\n", name(), result); + // process record + // This will call streamReadWrite. + dbScanLock(record); + ((DEVSUPFUN)record->rset->process)(record); + dbScanUnlock(record); + + debug("Stream::protocolFinishHook(stream=%s,result=%d) done\n", + name(), result); + } + if (result != Abort && record->scan == SCAN_IO_EVENT) + { + // restart protocol for next turn + debug("Stream::process(%s) restart async protocol\n", + name()); + if (!startProtocol(StartAsync)) + { + error("%s: Can't restart \"I/O Intr\" protocol\n", + name()); + } + } +} + +void Stream:: +startTimer(unsigned long timeout) +{ + debug("Stream::startTimer(stream=%s, timeout=%lu) = %f seconds\n", + name(), timeout, timeout * 0.001); +#ifdef EPICS_3_14 + timer->start(*this, timeout * 0.001); +#else + callbackSetPriority(priority(), &timeoutCallback); + wdStart(timer, (timeout+1)*sysClkRateGet()/1000-1, + reinterpret_cast(callbackRequest), + reinterpret_cast(&timeoutCallback)); +#endif +} + +bool Stream:: +getFieldAddress(const char* fieldname, StreamBuffer& address) +{ + DBADDR dbaddr; + if (strchr(fieldname, '.') != NULL) + { + // record.FIELD (access to other record) + if (dbNameToAddr(fieldname, &dbaddr) != OK) return false; + } + else + { + // FIELD in this record + char fullname[PVNAME_SZ + 1]; + sprintf(fullname, "%s.%s", name(), fieldname); + if (dbNameToAddr(fullname, &dbaddr) != OK) return false; + } + address.append(&dbaddr, sizeof(dbaddr)); + return true; +} + +static const unsigned char dbfMapping[] = + {0, DBF_LONG, DBF_ENUM, DBF_DOUBLE, DBF_STRING}; +static const short typeSize[] = + {0, sizeof(epicsInt32), sizeof(epicsUInt16), + sizeof(epicsFloat64), MAX_STRING_SIZE}; + +bool Stream:: +formatValue(const StreamFormat& format, const void* fieldaddress) +{ + debug("Stream::formatValue(%s, format=%%%c, fieldaddr=%p\n", + name(), format.conv, fieldaddress); + +// -- TO DO: If SCAN is "I/O Intr" and record has not been processed, -- +// -- do it now to get the latest value (only for output records?) -- + + if (fieldaddress) + { + // Format like "%([record.]field)..." has requested to get value + // from field of this or other record. + DBADDR* pdbaddr = (DBADDR*)fieldaddress; + long i; + long nelem = pdbaddr->no_elements; + size_t size = nelem * typeSize[format.type]; + char* buffer = fieldBuffer.clear().reserve(size); + if (dbGet(pdbaddr, dbfMapping[format.type], buffer, + NULL, &nelem, NULL) != 0) + { + error("%s: dbGet(%s.%s, %s) failed\n", + name(), + pdbaddr->precord->name, + ((dbFldDes*)pdbaddr->pfldDes)->name, + pamapdbfType[dbfMapping[format.type]].strvalue); + return false; + } + for (i = 0; i < nelem; i++) + { + switch (format.type) + { + case enum_format: + if (!printValue(format, + (long)((epicsUInt16*)buffer)[i])) + return false; + break; + case long_format: + if (!printValue(format, + (long)((epicsInt32*)buffer)[i])) + return false; + break; + case double_format: + if (!printValue(format, + (double)((epicsFloat64*)buffer)[i])) + return false; + break; + case string_format: + if (!printValue(format, buffer+MAX_STRING_SIZE*i)) + return false; + break; + case pseudo_format: + error("%s: %%(FIELD) syntax not allowed with pseudo formats\n", + name()); + default: + error("INTERNAL ERROR %s: Illegal format.type=%d\n", + name(), format.type); + return false; + } + } + return true; + } + format_s fmt; + fmt.type = dbfMapping[format.type]; + fmt.priv = &format; + debug("Stream::formatValue(%s) format=%%%c type=%s\n", + name(), format.conv, pamapdbfType[fmt.type].strvalue); + if (!writeData) + { + error("%s: No writeData() function provided\n", name()); + return false; + } + if (writeData(record, &fmt) == ERROR) + { + debug("Stream::formatValue(%s): writeData failed\n", name()); + return false; + } + return true; +} + +bool Stream:: +matchValue(const StreamFormat& format, const void* fieldaddress) +{ + // this function must increase consumedInput + // [I use goto and feel very ashamed for it.] + long consumed; + long lval; + double dval; + char* buffer; + int status; + const char* putfunc; + + if (fieldaddress) + { + // Format like "%([record.]field)..." has requested to put value + // to field of this or other record. + DBADDR* pdbaddr = (DBADDR*)fieldaddress; + long nord; + long nelem = pdbaddr->no_elements; + size_t size = nelem * typeSize[format.type]; + buffer = fieldBuffer.clear().reserve(size); + for (nord = 0; nord < nelem; nord++) + { + switch (format.type) + { + case long_format: + { + consumed = scanValue(format, lval); + if (consumed < 0) goto noMoreElements; + ((epicsInt32*)buffer)[nord] = lval; + break; + } + case enum_format: + { + consumed = scanValue(format, lval); + if (consumed < 0) goto noMoreElements; + ((epicsUInt16*)buffer)[nord] = (epicsUInt16)lval; + break; + } + case double_format: + { + consumed = scanValue(format, dval); + if (consumed < 0) goto noMoreElements; + ((epicsFloat64*)buffer)[nord] = dval; + break; + } + case string_format: + { + consumed = scanValue(format, + buffer+MAX_STRING_SIZE*nord, MAX_STRING_SIZE); + if (consumed < 0) goto noMoreElements; + break; + } + default: + error("INTERNAL ERROR: Stream::matchValue %s: " + "Illegal format type\n", name()); + return false; + } + consumedInput += consumed; + } +noMoreElements: + if (!nord) + { + // scan error: set other record to alarm status + if (pdbaddr->precord != record) + { + recGblSetSevr(pdbaddr->precord, CALC_ALARM, INVALID_ALARM); + if (!INIT_RUN) + { + // process other record to send alarm monitor + dbProcess(pdbaddr->precord); + } + } + return false; + } + if (pdbaddr->precord == record || INIT_RUN) + { + // write into own record, thus don't process it + // in @init we must not process other record + debug("Stream::matchValue(%s): dbPut(%s.%s,...)\n", + name(), + pdbaddr->precord->name, + ((dbFldDes*)pdbaddr->pfldDes)->name); + putfunc = "dbPut"; + status = dbPut(pdbaddr, dbfMapping[format.type], fieldBuffer(), nord); + if (INIT_RUN && pdbaddr->precord != record) + { + // clean error status of other record in @init + pdbaddr->precord->udf = false; + pdbaddr->precord->sevr = NO_ALARM; + pdbaddr->precord->stat = NO_ALARM; + } + } + else + { + // write into other record, thus process it + debug("Stream::matchValue(%s): dbPutField(%s.%s,...)\n", + name(), + pdbaddr->precord->name, + ((dbFldDes*)pdbaddr->pfldDes)->name); + putfunc = "dbPutField"; + status = dbPutField(pdbaddr, dbfMapping[format.type], fieldBuffer(), nord); + } + if (status != 0) + { + flags &= ~ScanTried; + switch (format.type) + { + case long_format: + case enum_format: + error("%s: %s(%s.%s, %s, %li) failed\n", + putfunc, name(), pdbaddr->precord->name, + ((dbFldDes*)pdbaddr->pfldDes)->name, + pamapdbfType[dbfMapping[format.type]].strvalue, + lval); + return false; + case double_format: + error("%s: %s(%s.%s, %s, %#g) failed\n", + putfunc, name(), pdbaddr->precord->name, + ((dbFldDes*)pdbaddr->pfldDes)->name, + pamapdbfType[dbfMapping[format.type]].strvalue, + dval); + return false; + case string_format: + error("%s: %s(%s.%s, %s, \"%s\") failed\n", + putfunc, name(), pdbaddr->precord->name, + ((dbFldDes*)pdbaddr->pfldDes)->name, + pamapdbfType[dbfMapping[format.type]].strvalue, + buffer); + return false; + default: + return false; + } + } + return true; + } + // no fieldaddress (the "normal" case) + format_s fmt; + fmt.type = dbfMapping[format.type]; + fmt.priv = &format; + if (!readData) + { + error("%s: No readData() function provided\n", name()); + return false; + } + currentValueLength = 0; + convert = readData(record, &fmt); // this will call scan() + if (convert == ERROR) + { + debug("Stream::matchValue(%s): readData failed\n", name()); + if (currentValueLength > 0) + { + error("%s: Record does not accept input \"%s%s\"\n", + name(), inputLine.expand(consumedInput, 19)(), + inputLine.length()-consumedInput > 20 ? "..." : ""); + flags &= ~ScanTried; + } + return false; + } + flags |= ValueReceived; + consumedInput += currentValueLength; + return true; +} + +#ifdef EPICS_3_14 + +void streamExecuteCommand(CALLBACK *pcallback) +{ + Stream* pstream = static_cast(pcallback->user); + + if (iocshCmd(pstream->outputLine()) != OK) + { + pstream->execCallback(StreamIoFault); + } + else + { + pstream->execCallback(StreamIoSuccess); + } +} +#else +extern "C" int execute (const char *cmd); + +void streamExecuteCommand(CALLBACK *pcallback) +{ + Stream* pstream = static_cast(pcallback->user); + + if (execute(pstream->outputLine()) != OK) + { + pstream->execCallback(StreamBusInterface::ioFault); + } + else + { + pstream->execCallback(StreamBusInterface::ioSuccess); + } +} +#endif + +bool Stream:: +execute() +{ + callbackSetPriority(priority(), &commandCallback); + callbackRequest(&commandCallback); + return true; +} + +void Stream:: +lockMutex() +{ +#ifdef EPICS_3_14 + mutex.lock(); +#else + semTake(mutex, WAIT_FOREVER); +#endif +} + +void Stream:: +releaseMutex() +{ +#ifdef EPICS_3_14 + mutex.unlock(); +#else + semGive(mutex); +#endif +} diff --git a/src/StreamError.cc b/src/StreamError.cc new file mode 100644 index 0000000..d325854 --- /dev/null +++ b/src/StreamError.cc @@ -0,0 +1,103 @@ +/*************************************************************** +* StreamDevice Support * +* * +* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) * +* * +* This is error and debug message handling of StreamDevice. * +* Please refer to the HTML files in ../doc/ for a detailed * +* documentation. * +* * +* If you do any changes in this file, you are not allowed to * +* redistribute it any more. If there is a bug or a missing * +* feature, send me an email and/or your patch. If I accept * +* your changes, they will go to the next release. * +* * +* DISCLAIMER: If this software breaks something or harms * +* someone, it's your problem. * +* * +***************************************************************/ + +#include "StreamError.h" +#include +#include + +int streamDebug = 0; +extern "C" { +#ifdef _WIN32 +__declspec(dllexport) +#endif +FILE *StreamDebugFile = NULL; +} + +#ifndef va_copy +#ifdef __va_copy +#define va_copy __va_copy +#endif +#endif + +/* You can globally change the printTimestamp function + by setting the StreamPrintTimestampFunction variable + to your own function. +*/ +static void printTimestamp(char* buffer, int size) +{ + time_t t; + struct tm tm; + time(&t); +#ifdef _WIN32 + tm = *localtime(&t); +#else + localtime_r(&t, &tm); +#endif + strftime(buffer, size, "%Y/%m/%d %H:%M:%S", &tm); +} + +void (*StreamPrintTimestampFunction)(char* buffer, int size) = printTimestamp; + +void StreamError(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + StreamVError(fmt, args); + va_end(args); +} + +void StreamVError(const char* fmt, va_list args) +{ + char timestamp[40]; + StreamPrintTimestampFunction(timestamp, 40); +#ifdef va_copy + if (StreamDebugFile) + { + va_list args2; + va_copy(args2, args); + fprintf(StreamDebugFile, "%s ", timestamp); + vfprintf(StreamDebugFile, fmt, args2); + fflush(StreamDebugFile); + va_end(args2); + } +#endif + fprintf(stderr, "\033[31;1m"); + fprintf(stderr, "%s ", timestamp); + vfprintf(stderr, fmt, args); + fprintf(stderr, "\033[0m"); +} + +int StreamDebugClass:: +print(const char* fmt, ...) +{ + va_list args; + char timestamp[40]; + StreamPrintTimestampFunction(timestamp, 40); + va_start(args, fmt); + const char* f = strrchr(file, '/'); + if (f) f++; else f = file; + FILE* fp = StreamDebugFile ? StreamDebugFile : stderr; + fprintf(fp, "%s ", timestamp); + fprintf(fp, "%s:%d: ", f, line); + vfprintf(fp, fmt, args); + fflush(fp); + va_end(args); + return 1; +} + diff --git a/src/StreamError.h b/src/StreamError.h new file mode 100644 index 0000000..d36ad53 --- /dev/null +++ b/src/StreamError.h @@ -0,0 +1,62 @@ +/*************************************************************** +* StreamDevice Support * +* * +* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) * +* * +* This is error and debug message handling of StreamDevice. * +* Please refer to the HTML files in ../doc/ for a detailed * +* documentation. * +* * +* If you do any changes in this file, you are not allowed to * +* redistribute it any more. If there is a bug or a missing * +* feature, send me an email and/or your patch. If I accept * +* your changes, they will go to the next release. * +* * +* DISCLAIMER: If this software breaks something or harms * +* someone, it's your problem. * +* * +***************************************************************/ + +#ifndef StreamError_h +#define StreamError_h + +#include +#include + +#ifndef __GNUC__ +#define __attribute__(x) +#endif + +extern int streamDebug; +extern void (*StreamPrintTimestampFunction)(char* buffer, int size); + +void StreamError(const char* fmt, ...) +__attribute__ ((format(printf,1,2))); + +void StreamVError(const char* fmt, va_list args) +__attribute__ ((format(printf,1,0))); + +class StreamDebugClass +{ + const char* file; + int line; +public: + StreamDebugClass(const char* file, int line) : + file(file), line(line) {} + int print(const char* fmt, ...) + __attribute__ ((format(printf,2,3))); +}; + +inline StreamDebugClass +StreamDebugObject(const char* file, int line) +{ return StreamDebugClass(file, line); } + +#define error StreamError +#define debug (!streamDebug)?0:StreamDebugObject(__FILE__,__LINE__).print + +#if (__GNUC__ == 2 && __GNUC_MINOR__ == 7) +/* Bug in cygnus-2.7.2 compiler: temporary objects crash the compiler */ +#define NO_TEMPORARY +#endif + +#endif diff --git a/src/StreamFormat.h b/src/StreamFormat.h new file mode 100644 index 0000000..7e049e8 --- /dev/null +++ b/src/StreamFormat.h @@ -0,0 +1,55 @@ +/*************************************************************** +* StreamDevice Support * +* * +* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) * +* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) * +* * +* This header defines the format stucture used to interface * +* format converters and record interfaces to StreamDevice * +* Please refer to the HTML files in ../doc/ for a detailed * +* documentation. * +* * +* If you do any changes in this file, you are not allowed to * +* redistribute it any more. If there is a bug or a missing * +* feature, send me an email and/or your patch. If I accept * +* your changes, they will go to the next release. * +* * +* DISCLAIMER: If this software breaks something or harms * +* someone, it's your problem. * +* * +***************************************************************/ + +#ifndef StreamFormat_h +#define StreamFormat_h + +typedef enum { + left_flag = 0x01, + sign_flag = 0x02, + space_flag = 0x04, + alt_flag = 0x08, + zero_flag = 0x10, + skip_flag = 0x20 +} StreamFormatFlag; + +typedef enum { + long_format = 1, + enum_format = 2, + double_format = 3, + string_format = 4, + pseudo_format = 5 +} StreamFormatType; + +extern const char* StreamFormatTypeStr[]; + +typedef struct StreamFormat +{ + char conv; + StreamFormatType type; + unsigned char flags; + short prec; + unsigned short width; + unsigned short infolen; + const char* info; +} StreamFormat; + +#endif diff --git a/src/StreamFormatConverter.cc b/src/StreamFormatConverter.cc new file mode 100644 index 0000000..e4f9d80 --- /dev/null +++ b/src/StreamFormatConverter.cc @@ -0,0 +1,435 @@ +/*************************************************************** +* StreamDevice Support * +* * +* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) * +* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) * +* * +* This is the format converter base and includes the standard * +* format converters for StreamDevice. * +* Please refer to the HTML files in ../doc/ for a detailed * +* documentation. * +* * +* If you do any changes in this file, you are not allowed to * +* redistribute it any more. If there is a bug or a missing * +* feature, send me an email and/or your patch. If I accept * +* your changes, they will go to the next release. * +* * +* DISCLAIMER: If this software breaks something or harms * +* someone, it's your problem. * +* * +***************************************************************/ + +#include "StreamFormatConverter.h" +#include "StreamFormat.h" +#include "StreamError.h" + +StreamFormatConverter* StreamFormatConverter:: +registered [256]; + +StreamFormatConverter:: +~StreamFormatConverter() +{ +} + +void StreamFormatConverter:: +provides(const char* name, const char* provided) +{ + _name = name; + const unsigned char* p; + for (p = reinterpret_cast(provided); + *p; p++) + { + registered[*p] = this; + } +} + +bool StreamFormatConverter:: +printLong(const StreamFormat& fmt, StreamBuffer&, long) +{ + error("Unimplemented printLong method\n for %%%c format", + fmt.conv); + return false; +} + +bool StreamFormatConverter:: +printDouble(const StreamFormat& fmt, StreamBuffer&, double) +{ + error("Unimplemented printDouble method for %%%c format\n", + fmt.conv); + return false; +} + +bool StreamFormatConverter:: +printString(const StreamFormat& fmt, StreamBuffer&, const char*) +{ + error("Unimplemented printString method for %%%c format\n", + fmt.conv); + return false; +} + +bool StreamFormatConverter:: +printPseudo(const StreamFormat& fmt, StreamBuffer&) +{ + error("Unimplemented printPseudo method for %%%c format\n", + fmt.conv); + return false; +} + +int StreamFormatConverter:: +scanLong(const StreamFormat& fmt, const char*, long&) +{ + error("Unimplemented scanLong method for %%%c format\n", + fmt.conv); + return -1; +} + +int StreamFormatConverter:: +scanDouble(const StreamFormat& fmt, const char*, double&) +{ + error("Unimplemented scanDouble method for %%%c format\n", + fmt.conv); + return -1; +} + +int StreamFormatConverter:: +scanString(const StreamFormat& fmt, const char*, char*, size_t) +{ + error("Unimplemented scanString method for %%%c format\n", + fmt.conv); + return -1; +} + +int StreamFormatConverter:: +scanPseudo(const StreamFormat& fmt, StreamBuffer&, long&) +{ + error("Unimplemented scanPseudo method for %%%c format\n", + fmt.conv); + return -1; +} + +static void copyFormatString(StreamBuffer& info, const char* source) +{ + const char* p = source - 1; + while (*p != '%' && *p != ')') p--; + info.append('%'); + while (++p != source-1) info.append(*p); +} + +// Standard Long Converter for 'diouxX' + +class StdLongConverter : public StreamFormatConverter +{ + int parse(const StreamFormat& fmt, StreamBuffer& output, const char*& value, bool scanFormat); + bool printLong(const StreamFormat& fmt, StreamBuffer& output, long value); + int scanLong(const StreamFormat& fmt, const char* input, long& value); +}; + +int StdLongConverter:: +parse(const StreamFormat& fmt, StreamBuffer& info, + const char*& source, bool scanFormat) +{ + if (scanFormat && (fmt.flags & alt_flag)) + { + error("Use of modifier '#' not allowed with %%%c input conversion\n", + fmt.conv); + return false; + } + if (scanFormat && fmt.prec >= 0) + { + error("Use of precision field '.%d' not allowed with %%%c input conversion\n", + fmt.prec, fmt.conv); + return false; + } + copyFormatString(info, source); + info.append('l'); + info.append(fmt.conv); + if (scanFormat) info.append("%n"); + return long_format; +} + +bool StdLongConverter:: +printLong(const StreamFormat& fmt, StreamBuffer& output, long value) +{ + output.printf(fmt.info, value); + return true; +} + +int StdLongConverter:: +scanLong(const StreamFormat& fmt, const char* input, long& value) +{ + int length = -1; + if (fmt.flags & skip_flag) + { + if (sscanf(input, fmt.info, &length) < 0) return -1; + } + else + { + if (sscanf(input, fmt.info, &value, &length) < 1) return -1; + } + return length; +} + +RegisterConverter (StdLongConverter, "diouxX"); + +// Standard Double Converter for 'feEgG' + +class StdDoubleConverter : public StreamFormatConverter +{ + virtual int parse(const StreamFormat&, StreamBuffer&, const char*&, bool); + virtual bool printDouble(const StreamFormat&, StreamBuffer&, double); + virtual int scanDouble(const StreamFormat&, const char*, double&); +}; + +int StdDoubleConverter:: +parse(const StreamFormat& fmt, StreamBuffer& info, + const char*& source, bool scanFormat) +{ + if (scanFormat && (fmt.flags & alt_flag)) + { + error("Use of modifier '#' not allowed with %%%c input conversion\n", + fmt.conv); + return false; + } + if (scanFormat && fmt.prec >= 0) + { + error("Use of precision field '.%d' not allowed with %%%c input conversion\n", + fmt.prec, fmt.conv); + return false; + } + copyFormatString(info, source); + if (scanFormat) info.append('l'); + info.append(fmt.conv); + if (scanFormat) info.append("%n"); + return double_format; +} + +bool StdDoubleConverter:: +printDouble(const StreamFormat& fmt, StreamBuffer& output, double value) +{ + output.printf(fmt.info, value); + return true; +} + +int StdDoubleConverter:: +scanDouble(const StreamFormat& fmt, const char* input, double& value) +{ + int length = -1; + if (fmt.flags & skip_flag) + { + if (sscanf(input, fmt.info, &length) < 0) return -1; + } + else + { + if (sscanf(input, fmt.info, &value, &length) < 1) return -1; + } + return length; +} + +RegisterConverter (StdDoubleConverter, "feEgG"); + +// Standard String Converter for 's' + +class StdStringConverter : public StreamFormatConverter +{ + virtual int parse(const StreamFormat&, StreamBuffer&, const char*&, bool); + virtual bool printString(const StreamFormat&, StreamBuffer&, const char*); + virtual int scanString(const StreamFormat&, const char*, char*, size_t); +}; + +int StdStringConverter:: +parse(const StreamFormat& fmt, StreamBuffer& info, + const char*& source, bool scanFormat) +{ + if (fmt.flags & (sign_flag|space_flag|zero_flag|alt_flag)) + { + error("Use of modifiers '+', ' ', '0', '#' " + "not allowed with %%%c conversion\n", + fmt.conv); + return false; + } + if (scanFormat && fmt.prec >= 0) + { + error("Use of precision field '.%d' not allowed with %%%c input conversion\n", + fmt.prec, fmt.conv); + return false; + } + copyFormatString(info, source); + info.append(fmt.conv); + if (scanFormat) info.append("%n"); + return string_format; +} + +bool StdStringConverter:: +printString(const StreamFormat& fmt, StreamBuffer& output, const char* value) +{ + output.printf(fmt.info, value); + return true; +} + +int StdStringConverter:: +scanString(const StreamFormat& fmt, const char* input, + char* value, size_t maxlen) +{ + int length = -1; + if (*input == '\0') + { + // match empty string + value[0] = '\0'; + return 0; + } + if (fmt.flags & skip_flag) + { + if (sscanf(input, fmt.info, &length) < 0) return -1; + } + else + { + char tmpformat[10]; + const char* f; + if (maxlen <= fmt.width || fmt.width == 0) + { + // assure not to read too much + sprintf(tmpformat, "%%%d%c%%n", maxlen-1, fmt.conv); + f = tmpformat; + } + else + { + f = fmt.info; + } + if (sscanf(input, f, value, &length) < 1) return -1; + if (length < 0) return -1; + value[length] = '\0'; +#ifndef NO_TEMPORARY + debug("StdStringConverter::scanString: length=%d, value=%s\n", + length, StreamBuffer(value,length).expand()()); +#endif + } + return length; +} + +RegisterConverter (StdStringConverter, "s"); + +// Standard Characters Converter for 'c' + +class StdCharsConverter : public StdStringConverter +{ + virtual int parse(const StreamFormat&, StreamBuffer&, const char*&, bool); + virtual bool printLong(const StreamFormat&, StreamBuffer&, long); + // scanString is inherited from %s format +}; + +int StdCharsConverter:: +parse(const StreamFormat& fmt, StreamBuffer& info, + const char*& source, bool scanFormat) +{ + if (fmt.flags & (sign_flag|space_flag|zero_flag|alt_flag)) + { + error("Use of modifiers '+', ' ', '0', '#' " + "not allowed with %%%c conversion\n", + fmt.conv); + return false; + } + if (scanFormat && fmt.prec >= 0) + { + error("Use of precision field '.%d' not allowed with %%%c input conversion\n", + fmt.prec, fmt.conv); + return false; + } + copyFormatString(info, source); + info.append(fmt.conv); + if (scanFormat) + { + info.append("%n"); + return string_format; + } + return long_format; +} + +bool StdCharsConverter:: +printLong(const StreamFormat& fmt, StreamBuffer& output, long value) +{ + output.printf(fmt.info, value); + return true; +} + +RegisterConverter (StdCharsConverter, "c"); + +// Standard Charset Converter for '[' + +class StdCharsetConverter : public StreamFormatConverter +{ + virtual int parse(const StreamFormat&, StreamBuffer&, const char*&, bool); + virtual int scanString(const StreamFormat&, const char*, char*, size_t); + // no print method, %[ is readonly +}; + +int StdCharsetConverter:: +parse(const StreamFormat& fmt, StreamBuffer& info, + const char*& source, bool scanFormat) +{ + if (!scanFormat) + { + error("Format conversion %%[ is only allowed in input formats\n"); + return false; + } + if (fmt.flags & (left_flag|sign_flag|space_flag|zero_flag|alt_flag)) + { + error("Use of modifiers '-', '+', ' ', '0', '#'" + "not allowed with %%%c conversion\n", + fmt.conv); + return false; + } + if (scanFormat && fmt.prec >= 0) + { + error("Use of precision field '.%d' not allowed with %%%c input conversion\n", + fmt.prec, fmt.conv); + return false; + } + info.printf("%%%d[", fmt.width); + while (*source && *source != ']') + { + if (*source == esc) source++; + info.append(*source++); + } + if (!*source) { + error("Missing ']' after %%[ format conversion\n"); + return false; + } + source++; // consume ']' + info.append("]%n"); + return string_format; +} + +int StdCharsetConverter:: +scanString(const StreamFormat& fmt, const char* input, + char* value, size_t maxlen) +{ + int length = -1; + if (fmt.flags & skip_flag) + { + if (sscanf (input, fmt.info, &length) < 0) return -1; + } + else + { + char tmpformat[256]; + const char* f; + if (maxlen <= fmt.width || fmt.width == 0) + { + const char *p = strchr (fmt.info, '['); + // assure not to read too much + sprintf(tmpformat, "%%%d%s", maxlen-1, p); + f = tmpformat; + } + else + { + f = fmt.info; + } + if (sscanf(input, f, value, &length) < 1) return -1; + if (length < 0) return -1; + value[length] = '\0'; + debug("StdCharsetConverter::scanString: length=%d, value=%s\n", + length, value); + } + return length; +} + +RegisterConverter (StdCharsetConverter, "["); diff --git a/src/StreamFormatConverter.h b/src/StreamFormatConverter.h new file mode 100644 index 0000000..3bc03bb --- /dev/null +++ b/src/StreamFormatConverter.h @@ -0,0 +1,162 @@ +/*************************************************************** +* StreamDevice Support * +* * +* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) * +* * +* This is the format converter header of StreamDevice. * +* Please refer to the HTML files in ../doc/ for a detailed * +* documentation. * +* * +* If you do any changes in this file, you are not allowed to * +* redistribute it any more. If there is a bug or a missing * +* feature, send me an email and/or your patch. If I accept * +* your changes, they will go to the next release. * +* * +* DISCLAIMER: If this software breaks something or harms * +* someone, it's your problem. * +* * +***************************************************************/ + +#ifndef StreamFormatConverter_h +#define StreamFormatConverter_h + +#include "StreamFormat.h" +#include "StreamBuffer.h" + +#define esc (0x1b) + +template +class StreamFormatConverterRegistrar +{ +public: + StreamFormatConverterRegistrar(const char* name, const char* provided) { + static C prototype; + prototype.provides(name, provided); + } +}; + +class StreamFormatConverter +{ + static StreamFormatConverter* registered []; + const char* _name; +public: + virtual ~StreamFormatConverter(); + const char* name() { return _name; } + void provides(const char* name, const char* provided); + static StreamFormatConverter* find(unsigned char c); + virtual int parse(const StreamFormat& fmt, + StreamBuffer& info, const char*& source, bool scanFormat) = 0; + virtual bool printLong(const StreamFormat& fmt, + StreamBuffer& output, long value); + virtual bool printDouble(const StreamFormat& fmt, + StreamBuffer& output, double value); + virtual bool printString(const StreamFormat& fmt, + StreamBuffer& output, const char* value); + virtual bool printPseudo(const StreamFormat& fmt, + StreamBuffer& output); + virtual int scanLong(const StreamFormat& fmt, + const char* input, long& value); + virtual int scanDouble(const StreamFormat& fmt, + const char* input, double& value); + virtual int scanString(const StreamFormat& fmt, + const char* input, char* value, size_t maxlen); + virtual int scanPseudo(const StreamFormat& fmt, + StreamBuffer& inputLine, long& cursor); +}; + +inline StreamFormatConverter* StreamFormatConverter:: +find(unsigned char c) { + return registered[c]; +} + +#define RegisterConverter(converter, conversions) \ +template class StreamFormatConverterRegistrar; \ +StreamFormatConverterRegistrar \ +registrar_##converter(#converter,conversions); \ +void* ref_##converter = ®istrar_##converter\ + +/**************************************************************************** +* A user defined converter class inherits public from StreamFormatConverter +* and handles one or more conversion characters. +* Print and scan of the same conversion character must be handled by the +* same class but not both need to be supported. +* +* parse() +* ======= +* This function is called when the protocol is parsed (at initialisation) +* whenever one of the conversions handled by your converter is found. +* The fields fmt.conv, fmt.flags, fmt.prec, and fmt.width have +* already been filled in. If a scan format is parsed, scanFormat is true. If +* a print format is parsed, scanFormat is false. +* +* The source pointer points to the character of the format string just after +* the conversion character. You can parse additional characters if they +* belong to the format string handled by your class. Move the source pointer +* so that is points to the first character after the format string. +* +* Example: +* +* source source +* before after +* V V +* "%39[1-9]constant text" +* | +* conversion +* character +* +* You can write any string to info you need in print*() or scan*(). This will +* probably be necessary if you have taken characters from the format string. +* +* Return long_format, double_format, string_format, or enum_format +* depending on the datatype associated with the conversion character. +* You need not to return the same value for print and for scan formats. +* You can even return different values depending on the format string, but +* I can't imagine why anyone should do that. +* +* If the format is not a real data conversion but does other things with +* the data (append or check a checksum, encode or decode the data,...), +* return pseudo_format. +* +* Return false if there is any parse error or if print or scan is requested +* but not supported by this conversion. +* +* print[Long|Double|String|Pseudo](), scan[Long|Double|String|Pseudo]() +* ================= +* Provide a print*() and/or scan*() method appropriate for the data type +* you have returned in the parse() method. That method is called whenever +* the conversion appears in an output or input, resp. +* You only need to implement the flavour of print and/or scan suitable for +* the datatype returned by parse(). +* +* Now, fmt.type contains the value returned by parse(). With fmt.info() +* you will get the string you have written to info in parse() (null terminated). +* The length of the info string can be found in fmt.infolen. +* +* In print*(), append the converted value to output. Do not modify what is +* already in output (unless you really know what you're doing). +* Return true on success, false on failure. +* +* In scan*(), read the value from input and return the number of consumed +* bytes. In the string version, don't write more bytes than maxlen! If the +* skip_flag is set, you don't need to write to value, since the value will be +* discarded anyway. Return -1 on failure. +* +* +* Register your class +* =================== +* To register your class, write +* +* RegisterConverter (your_class, "conversions"); +* +* in the global context of your file. +* "conversions" is a string containing all conversion characters +* handled by your class. +* +* For examples see StreamFormatConverter.cc. +* +* HINT: Do not branch depending on the conversion character. +* Provide multiple classes, that's more efficient. +* +****************************************************************************/ + +#endif diff --git a/src/StreamProtocol.cc b/src/StreamProtocol.cc new file mode 100644 index 0000000..3781b6e --- /dev/null +++ b/src/StreamProtocol.cc @@ -0,0 +1,1661 @@ +/*************************************************************** +* StreamDevice Support * +* * +* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) * +* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) * +* * +* This is the protocol parser of StreamDevice. * +* Please refer to the HTML files in ../doc/ for a detailed * +* documentation. * +* * +* If you do any changes in this file, you are not allowed to * +* redistribute it any more. If there is a bug or a missing * +* feature, send me an email and/or your patch. If I accept * +* your changes, they will go to the next release. * +* * +* DISCLAIMER: If this software breaks something or harms * +* someone, it's your problem. * +* * +***************************************************************/ + +#include +#include +#include +#include "StreamProtocol.h" +#include "StreamFormatConverter.h" +#include "StreamError.h" + +const char* StreamFormatTypeStr[] = { + "none", "long", "enum", "double", "string", "pseudo" +}; + +class StreamProtocolParser::Protocol::Variable +{ + friend class Protocol; + friend class StreamProtocolParser; + + Variable* next; + const StreamBuffer name; + StreamBuffer value; + int line; + bool used; + + Variable(const char* name, int line, size_t startsize=0); + Variable(const Variable& v); + ~Variable(); +}; + +////////////////////////////////////////////////////////////////////////////// +// StreamProtocolParser + +StreamProtocolParser* StreamProtocolParser::parsers = NULL; +const char* StreamProtocolParser::path = "."; +static const char* specialChars = " ,;{}=()$'\"+-*/"; + +// Client destructor +StreamProtocolParser::Client:: +~Client() +{ +} + +// Private constructor +StreamProtocolParser:: +StreamProtocolParser(FILE* file, const char* filename) + : filename(filename), file(file), globalSettings(filename) +{ + + next = parsers; + parsers = this; + // start parsing in global context + protocols = NULL; + line = 1; + quote = false; + valid = parseProtocol(globalSettings, globalSettings.commands); +} + +// Private destructor +StreamProtocolParser:: +~StreamProtocolParser() +{ + delete protocols; + delete next; +} + +void StreamProtocolParser:: +errorMsg(const char* fmt, ...) +{ + char fmt2[200]; + sprintf (fmt2, "'%s' line %d: %s", + filename(), line, fmt); + va_list args; + va_start(args, fmt); + StreamVError(fmt2, args); + va_end(args); +} + +void StreamProtocolParser:: +report() +{ + Protocol* p; + printf("Report of protocol file '%s'\n", filename()); + printf(" GLOBAL:\n"); + globalSettings.report(); + printf(" PROTOCOLS:\n"); + for (p = protocols; p; p = p->next) + { + p->report(); + } +} + +// API function: read protocol from file, create parser if necessary +// RETURNS: a copy of a protocol that must be deleted by the caller +// SIDEEFFECTS: file IO, memory allocation for parsers +StreamProtocolParser::Protocol* StreamProtocolParser:: +getProtocol(const char* filename, const StreamBuffer& protocolAndParams) +{ + StreamProtocolParser* parser; + + // Have we already seen this file? + for (parser = parsers; parser; parser = parser->next) + { + if (parser->filename.equals(filename)) + { + if (!parser->valid) + { + error("Protocol file '%s' is invalid (see above)\n", + filename); + return NULL; + } + break; + } + } + if (!parser) + { + // If not, read it. + parser = readFile(filename); + if (!parser) + { + // We could not read the file. + return NULL; + } + } + return parser->getProtocol(protocolAndParams); +} + +// API function: free all parser resources allocated by any getProtocol() +// Call this function after the last getProtocol() to clean up. +void StreamProtocolParser:: +free() +{ + delete parsers; + parsers = NULL; +} + +/* +STEP 1: Parse the protocol file + +Read the complete file, check structure and break it into protocols, +handlers and assignments. +Substitute protocol references and variable references. +No replacement of positional parameters ($1...$9). +No coding of parameters, commands, functions, or formats yet. We'll do +this after protocol arguments have been replaced. +*/ + +StreamProtocolParser* StreamProtocolParser:: +readFile(const char* filename) +{ + FILE* file; +#ifdef windows + const char pathseparator = ';'; + const char dirseparator = '\\'; +#else + const char pathseparator = ':'; + const char dirseparator = '/'; +#endif + StreamProtocolParser* parser; + const char *p; + const char *s; + size_t n; + StreamBuffer dir; + + // look for filename in every dir in search path + for (p = path; *p; p += n) + { + dir.clear(); + // get next dir from search path (avoiding strtok, strsep, strcspn) + s = strchr(p, pathseparator); + if (s) n = s - p; + else n = strlen(p); + dir.append(p, n); + if (n && p[n-1] != dirseparator) dir.append(dirseparator); + if (s) p++; + // append filename + dir.append(filename); + // try to read the file + debug("StreamProtocolParser::readFile: try '%s'\n", dir()); + file = fopen(dir(), "r"); + if (file) + { + // file found; create a parser to read it + parser = new StreamProtocolParser(file, filename); + fclose(file); + if (!parser->valid) return NULL; +// printf( +// "/---------------------------------------------------------------------\\\n"); +// parser->report(); +// printf( +// "\\---------------------------------------------------------------------/\n"); + return parser; + } + } + error("Can't find readable file '%s' in '%s'\n", filename, path); + return NULL; +} + +/* +STEP 2: Compile protocols to executable format + +Make copy of one of the parsed protocols. +Replace positional parameters. +Ask client how to compile formats and commands. +*/ + +// Get a copy of a protocol +// Substitutions in protocolname replace positional parameters +StreamProtocolParser::Protocol* StreamProtocolParser:: +getProtocol(const StreamBuffer& protocolAndParams) +{ + StreamBuffer name = protocolAndParams; + // make name case insensitive + char* p; + for (p = name(); *p; p++) *p = tolower(*p); + // find and make a copy with parameters inserted + Protocol* protocol; + for (protocol = protocols; protocol; protocol = protocol->next) + { + if (protocol->protocolname.equals(name())) + // constructor also replaces parameters + return new Protocol(*protocol, name, 0); + } + error("Protocol '%s' not found in protocol file '%s'\n", + name(), filename()); + return NULL; +} + +inline bool StreamProtocolParser:: +isGlobalContext(const StreamBuffer* commands) +{ + return commands == globalSettings.commands; +} + +inline bool StreamProtocolParser:: +isHandlerContext(Protocol& protocol, const StreamBuffer* commands) +{ + return commands != protocol.commands; +} + +bool StreamProtocolParser:: +parseProtocol(Protocol& protocol, StreamBuffer* commands) +{ + StreamBuffer token; + int op; + int startline; + + commands->clear(); + while (true) // assignment and protocol parsing loop + { + // expect command name, protoclol name, + // handler name, or variable name + token.clear(); + startline = line; + if (!readToken(token, " ,;{}=()$'\"", isGlobalContext(commands))) + return false; + debug("StreamProtocolParser::parseProtocol:" + " token='%s'\n", token.expand()()); + + if (token[0] == 0) + { + // terminate at EOF in global context + return true; + } + if (strchr(" ;", token[0])) + { + // skip whitespaces, comments and ';' + // this must come after (token[0] == 0) check + continue; + } + if (token[0] == '}') + { + if (!isGlobalContext(commands)) + { + // end of protocol or handler definition + return true; + } + errorMsg("Stray '}' in global context\n"); + return false; + } + if (strchr("{=", token[0])) + { + errorMsg("Expect name before '%c'\n", token[0]); + return false; + } + do op = readChar(); while (op == ' '); // what comes after token? + if (op == EOF) + { + errorMsg("Unexpected end of file after: %s\n", token()); + return false; + } + if (op == '=') + { + // variable assignment + if (isHandlerContext(protocol, commands)) + { + errorMsg("Variables are not allowed in handlers: %s\n", + token()); + return false; + } + if (token[0] == '@' || (token[0] >= '0' && token[0] <= '9')) + { + errorMsg("Variable name cannot start with '%c': %s\n", + token[0], token()); + return false; + } + if (!parseAssignment(token(), protocol)) + { + line = startline; + errorMsg("in variable assignment '%s = ...'\n", token()); + return false; + } + continue; + } + if (op == '{') // protocol or handler definition + { + token.truncate(-1-(int)sizeof(int)); + if (token[0] == '@') // handler + { + if (isHandlerContext(protocol, commands)) + { + errorMsg("Handlers are not allowed in handlers: %s\n", + token()); + return false; + } + StreamBuffer* handler = protocol.createVariable(token(), line); + handler->clear(); // override existing handler + if (!parseProtocol(protocol, handler)) + { + line = startline; + errorMsg("in handler '%s'\n", token()); + return false; + } + continue; + } + // protocol definition + if (!isGlobalContext(commands)) + { + errorMsg("Definition of '%s' not in global context (missing '}' ?)\n", + token()); + return false; + } + Protocol** ppP; + for (ppP = &protocols; *ppP; ppP = &(*ppP)->next) + { + if ((*ppP)->protocolname.equals(token())) + { + errorMsg("Protocol '%s' redefined\n", token()); + return false; + } + } + Protocol* pP = new Protocol(protocol, token, startline); + if (!parseProtocol(*pP, pP->commands)) + { + line = startline; + errorMsg("in protocol '%s'\n", token()); + delete pP; + return false; + } + // append new protocol to parser + *ppP = pP; + continue; + } + // Must be a command or a protocol reference. + if (isGlobalContext(commands)) + { + errorMsg("Expect '=' or '{' instead of '%c' after '%s'\n", + op, token()); + return false; + } + if (op == ';' || op == '}') // no arguments + { + if (op == '}') ungetc(op, file); + // Check for protocol reference + Protocol* p; + for (p = protocols; p; p = p->next) + { + if (p->protocolname.equals(token())) + { + commands->append(*p->commands); + break; + } + } + if (p) continue; + } + // must be a command (validity will be checked later) + commands->append(token); // is null separated + ungetc(op, file); // put back first char of value + if (parseValue(*commands, true) == false) + { + line = startline; + errorMsg("after command '%s'\n", token()); + return false; + } + debug("parseProtocol: command '%s'\n", (*commands).expand()()); + commands->append('\0'); // terminate value with null byte + } +} + +int StreamProtocolParser:: +readChar() +{ + int c; + c = getc(file); + if (isspace(c) || c == '#') // blanks or comments + { + do { + if (c == '#') // comments + { + while ((c = getc(file)) != EOF && c != '\n'); + } + if (c == '\n') + { + ++line; // count newlines + } + c = getc(file); + } while (isspace(c) || c == '#'); + if (c != EOF) ungetc(c, file); // put back non-blank + c = ' '; // return one space for all spaces and comments + } + return c; +} + +bool StreamProtocolParser:: +readToken(StreamBuffer& buffer, const char* specialchars, bool eofAllowed) +{ +/* +a token is + - a string in "" or '' (possibly with escaped \" or \' inside) + - one of specialchars + - EOF + - a variable reference starting with $ (quoted or unquoted) + - a word of chars not containing any of the above mapped to lowercase + +All comments starting with # ending at end of line are whitespaces. +Sequences of whitespaces are be compressed into one blank. +EOF translates to a null byte. +Token is copied from file to buffer, variable names, words and strings are +terminated and the line number is stored for later use. +Each time newline is read, line is incremented. +*/ + if (!specialchars) specialchars = specialChars; + long token = buffer.length(); + int l = line; + + int c = readChar(); + if (c == '$') + { + // a variable + debug("StreamProtocolParser::readToken: Variable\n"); + buffer.append(c); + if (quote) buffer.append('"'); // mark as quoted variable + c = getc(file); + if (c >= '0' && c <= '9') + { + // positional parameter $0 ... $9 + buffer.append(c); + buffer.append('\0').append(&l, sizeof(l)); // append line number + return true; + } + if (c == '{') + { + int q = quote; + quote = false; + // variable like ${xyz} + if (!readToken(buffer, "{}=;")) return false; + debug("StreamProtocolParser::readToken: Variable '%s' in {}\n", + buffer(token)); + c = getc(file); + if (c != '}') + { + errorMsg("Expect '}' instead of '%c' after: %s\n", + c, buffer(token)); + return false; + } + quote = q; + return true; + } + if (c == EOF) + { + errorMsg("Unexpected end of file after '$'\n"); + return false; + } + if (strchr (specialchars, c)) + { + errorMsg("Unexpected '%c' after '$'\n,", c); + return false; + } + // variable like $xyz handled as word token + } + else if (quote || c == '\'' || c == '"') + { + debug("StreamProtocolParser::readToken: Quoted string\n"); + // quoted string + if (!quote) + { + quote = c; + c = getc(file); + } + buffer.append(quote); + while (quote) + { + if (c == EOF || c == '\n') + { + errorMsg("Unterminated quoted string: %s\n", + buffer(token)); + return false; + } + if (c == '$' && buffer[-1] == '\\') + { + // quoted variable reference + // terminate string here and do variable in next pass + buffer[-1] = quote; + ungetc(c, file); + break; + } + buffer.append(c); + if (c == quote && buffer[-1] != '\\') + { + quote = false; + break; + } + c = getc(file); + } + buffer.append('\0').append(&l, sizeof(l)); // append line number + return true; + } + else if (c == EOF) + { + // end of file + if (!eofAllowed) + { + errorMsg("Unexpected end of file\n"); + return false; + } + buffer.append('\0'); + return true; + } + else if (strchr (specialchars, c)) + { + debug("StreamProtocolParser::readToken: Special '%c'\n", c); + // a single character + buffer.append(c); + return true; + } + // a word or (variable name) + debug("StreamProtocolParser::readToken: word\n"); + while (1) + { + buffer.append(tolower(c)); + if ((c = readChar()) == EOF) break; + if (strchr (specialchars, c)) + { + ungetc(c, file); // put back char of next token + break; + } + } + debug("StreamProtocolParser::readToken: word='%s' c='%c'\n", + buffer(token), c); + buffer.append('\0').append(&l, sizeof(l)); // append line number + return true; +} + +bool StreamProtocolParser:: +parseAssignment(const char* name, Protocol& protocol) +{ + StreamBuffer value; + + if (!parseValue (value)) return false; + *protocol.createVariable(name, line) = value; // transfer value + return true; +} + +bool StreamProtocolParser:: +parseValue(StreamBuffer& buffer, bool lazy) +{ + long token; + int c; + + do c = readChar(); while (c == ' '); // skip leading spaces + ungetc (c, file); + while (true) + { + token = buffer.length(); // start of next token + if (!readToken(buffer)) return false; + debug("StreamProtocolParser::parseValue:%d: %s\n", + line, buffer.expand(token)()); + c = buffer[token]; + if (c == '$') // a variable + { + long varname = token+1; + if (buffer[varname] == '"') varname++; // quoted variable + if (lazy || (buffer[varname] >= '0' && buffer[varname] <= '9')) + { + // replace later + continue; + } + StreamBuffer value; + if (!globalSettings.replaceVariable(value, buffer(token))) + return false; + buffer.replace(token, buffer.length(), value); + continue; + } + if (c == '{' || c == '=') + { + errorMsg("Unexpected '%c' (missing ';' or '\"' ?)\n", c); + return false; + } + if (strchr (";}", c)) + { + buffer.truncate(-1); + if (c != ';') + { + // let's be generous with missing ';' before '}' + ungetc (c, file); + } + return true; + } + } +} + +// tools (static member functions) + +const char* StreamProtocolParser:: +printString(StreamBuffer& buffer, const char* s) +{ + while (*s) + { + switch (*s) + { + case esc: + buffer.printf("\\x%02x", (*++s) & 0xff); + break; + case '\r': + buffer.append("\\r"); + break; + case '\n': + buffer.append("\\n"); + break; + case skip: + buffer.append("\\?"); + break; + case '"': + buffer.append("\\\""); + break; + case '\\': + buffer.append("\\\\"); + break; + case format_field: + buffer.printf("%%(%s)", ++s); + while (*s++); + s += extract(s); // skip fieldaddress + goto format; + case format: + buffer.append('%'); + s++; +format: { + s = printString(buffer, s); + const StreamFormat& f = extract(s); + s += f.infolen; + } + continue; + default: + if ((*s & 0x7f) < 0x20 || (*s & 0x7f) == 0x7f) + buffer.printf("\\x%02x", *s & 0xff); + else + buffer.append(*s); + } + ++s; + } + return ++s; +} + +////////////////////////////////////////////////////////////////////////////// +// StreamProtocolParser::Protocol::Variable + +StreamProtocolParser::Protocol::Variable:: +Variable(const char* name, int line, size_t startsize) + : name(name), value(startsize), line(line) +{ + next = NULL; + used = false; +} + +StreamProtocolParser::Protocol::Variable:: +Variable(const Variable& v) + : name(v.name), value(v.value) +{ + line = v.line; + used = v.used; + next = NULL; +} + +StreamProtocolParser::Protocol::Variable:: +~Variable() +{ + delete next; +} + +////////////////////////////////////////////////////////////////////////////// +// StreamProtocolParser::Protocol + +// for global settings +StreamProtocolParser::Protocol:: +Protocol(const char* filename) + : filename(filename) +{ + line = 0; + next = NULL; + variables = new Variable(NULL, 0, 500); + commands = &variables->value; +} + +// make a deep copy +StreamProtocolParser::Protocol:: +Protocol(const Protocol& p, StreamBuffer& name, int _line) + : protocolname(name), filename(p.filename) +{ + next = NULL; + // copy all variables + Variable* pV; + Variable** ppNewV = &variables; + line = _line ? _line : p.line; + debug("new Protocol(name=\"%s\", line=%d)\n", name(), line); + for (pV = p.variables; pV; pV = pV->next) + { + *ppNewV = new Variable(*pV); + ppNewV = &(*ppNewV)->next; + } + commands = &variables->value; + if (line) variables->line = line; + // get parameters from name + memset(parameter, 0, sizeof(parameter)); + int i; + const char* nextparameter; + parameter[0] = protocolname(); + for (i = 0; i < 10; i++) + { + debug("StreamProtocolParser::Protocol::Protocol $%d=\"%s\"\n", + i, parameter[i]); + nextparameter = parameter[i] + strlen(parameter[i]) + 1; + if (nextparameter > name.length() + parameter[0]) break; + parameter[i+1] = nextparameter; + } +} + +StreamProtocolParser::Protocol:: +~Protocol() +{ + delete variables; + delete next; +} + +void StreamProtocolParser::Protocol:: +errorMsg(int l, const char* fmt, ...) +{ + char fmt2[200]; + sprintf (fmt2, "'%s' line %d: %s", + filename(), line, fmt); + va_list args; + va_start(args, fmt); + StreamVError(fmt2, args); + va_end(args); +} + +void StreamProtocolParser::Protocol:: +report() +{ + Variable* pV; + if (protocolname) printf(" Protocol %s\n", protocolname.expand()()); + printf(" Variables:\n"); + for (pV = variables->next; pV; pV = pV->next) // 1st var is commands + { + if (pV->name[0] != '@') + { + printf(" %s = %s;\n", pV->name.expand()(), pV->value.expand()()); + } + } + printf(" Handlers:\n"); + for (pV = variables->next; pV; pV = pV->next) + { + if (pV->name[0] == '@') + { + printf(" %s {%s}\n", pV->name.expand()(), pV->value.expand()()); + } + } + printf(" Commands:\n"); + printf(" { %s }\n", commands->expand()()); +} + +StreamBuffer* StreamProtocolParser::Protocol:: +createVariable(const char* name, int linenr) +{ + Variable** ppV; + for (ppV = &variables; *ppV; ppV = &(*ppV)->next) + { + if ((*ppV)->name.equals(name)) + { + (*ppV)->line = linenr; + return &(*ppV)->value; + } + } + *ppV = new Variable(name, linenr); + return &(*ppV)->value; +} + +const StreamProtocolParser::Protocol::Variable* + StreamProtocolParser::Protocol:: +getVariable(const char* name) +{ + Variable* pV; + + for (pV = variables; pV; pV = pV->next) + { + if (pV->name.equals(name)) + { + pV->used = true; + return pV; + } + } + return NULL; +} + +bool StreamProtocolParser::Protocol:: +getNumberVariable(const char* varname, unsigned long& value, unsigned long max) +{ + const Variable* pvar = getVariable(varname); + if (!pvar) return true; + const char* source = pvar->value(); + if (!compileNumber(value, source, max)) + { + int linenr = getLineNumber(source); + errorMsg(linenr, "in variable %s\n", varname); + return false; + } + if (source != pvar->value.end()) + { + errorMsg(getLineNumber(source), + "Garbage in variable '%s' after numeric value %ld: %s\n", + varname, value, source); + return false; + } + return true; +} + +bool StreamProtocolParser::Protocol:: +getEnumVariable(const char* varname, unsigned short& value, const char** enumstrings) +{ + const Variable* pvar = getVariable(varname); + if (!pvar) return true; + for (value = 0; enumstrings[value]; value++) + { + if (pvar->value.equals(enumstrings[value])) + { + return true; + } + } + error("Value '%s' must be one of", pvar->value()); + for (value = 0; enumstrings[value]; value++) + { + error("%s '%s'", value ? " or" : "", enumstrings[value]); + } + error("\n" + "in variable '%s' in protocol file '%s' line %d\n", + varname, filename(), getLineNumber(pvar->value())); + return false; +} + +bool StreamProtocolParser::Protocol:: +getStringVariable(const char* varname, StreamBuffer& value, bool* defined) +{ + const Variable* pvar = getVariable(varname); + if (!pvar) return true; + if (defined) *defined = true; + const StreamBuffer* pvalue = &pvar->value; + const char* source = (*pvalue)(); + value.clear(); + if (!compileString(value, source)) + { + error("in string variable '%s' in protocol file '%s' line %d\n", + varname, filename(), getLineNumber(source)); + debug("%s = %s\n", varname, pvalue->expand()()); + return false; + } + if (source != pvalue->end()) + { + debug("%s = %s\n", varname, pvalue->expand()()); + debug(" => %s\n", value.expand()()); + error("INTERNAL ERROR after '%s': source = %p != %p\n", + varname, source, pvalue->end()); + return false; + } + return true; +} + +bool StreamProtocolParser::Protocol:: +getCommands(const char* handlername,StreamBuffer& code, Client* client) +{ + const Variable* pvar = getVariable(handlername); + if (!pvar) return true; + if (!pvar->value) return true; + code.clear(); + const char* source = pvar->value(); + debug("StreamProtocolParser::Protocol::getCommands" + "(handlername=\"%s\", client=\"%s\"): source=%s\n", + handlername, client->name(), pvar->value.expand()()); + if (!compileCommands(code, source, client)) + { + if (handlername) + { + errorMsg(pvar->line, + "in handler '%s'\n", handlername); + errorMsg(variables->line, + "used by protocol '%s'\n", protocolname()); + return false; + } + errorMsg(pvar->line, "in protocol '%s'\n", protocolname()); + return false; + } + debug("commands %s: %s\n", handlername, pvar->value.expand()()); + debug("compiled to: %s\n", code.expand()()); + return true; +} + +bool StreamProtocolParser::Protocol:: +replaceVariable(StreamBuffer& buffer, const char* varname) +{ + debug("StreamProtocolParser::Protocol::replaceVariable %s\n", varname); + bool quoted = false; + if (*++varname == '"') + { + varname++; + quoted = true; + } + int linenr = getLineNumber(varname); + if (*varname >= '0' && *varname <= '9') + { + const char* p = parameter[*varname-'0']; + if (!p) + { + errorMsg(linenr, + "Missing value for parameter $%c\n", *varname); + return false; + } + if (!quoted) + { + buffer.append(p).append('\0'); + buffer.append(&linenr, sizeof(linenr)); + return true; + } + buffer.append('"'); + bool escaped = false; + while (*p) + { + if (*p == '"' && !escaped) buffer.append('\\'); + if (escaped) escaped = false; + else if (*p == '\\') escaped = true; + buffer.append(*p++); + } + buffer.append('"').append('\0'); + buffer.append(&linenr, sizeof(linenr)); + return true; + } + const Variable* v = getVariable(varname); + if (!v) + { + errorMsg(linenr, + "Undefined variable '%s' referenced\n", + varname); + return false; + } + if (!quoted) + { + buffer.append(v->value); + return true; + } + // quoted + buffer.append('"'); + long i; + bool escaped = false; + for (i = 0; i < v->value.length(); i++) + { + char c = v->value[i]; + if (c == '"' && !escaped) buffer.append('\\'); + if (c == 0 && !escaped) + { + // skip line info of token + i += sizeof(linenr); + continue; + } + if (escaped) escaped = false; + else if (c == '\\') escaped = true; + buffer.append(c); + } + buffer.append('"').append('\0'); + linenr = v->line; + buffer.append(&linenr, sizeof(linenr)); + return true; +} + +bool StreamProtocolParser::Protocol:: +compileNumber(unsigned long& number, const char*& source, unsigned long max) +{ + char* end; + unsigned long n; + StreamBuffer buffer; + + debug("StreamProtocolParser::Protocol::compileNumber source=\"%s\"\n", + source); + while (*source == '$' || (*source >= '0' && *source <= '9')) + { + debug("StreamProtocolParser::Protocol::compileNumber *source=%u source=\"%s\"\n", + *source, source); + if (*source == '$') + { + if(!replaceVariable(buffer, source)) return false; + debug("buffer=%s\n", buffer.expand()()); + buffer.truncate(-1-(int)sizeof(int)); + } + else + { + buffer.append(source); + } + source += strlen(source)+1+sizeof(int); // skip eos + line + }; + n = strtoul(buffer(), &end, 0); + if (end == buffer()) + { + debug("StreamProtocolParser::Protocol::compileNumber: %s\n", + buffer.expand()()); + errorMsg(getLineNumber(source), + "Unsigned numeric value expected: %s\n", buffer()); + return false; + } + if (*end) + { + debug("StreamProtocolParser::Protocol::compileNumber: %s\n", + buffer.expand()()); + errorMsg(getLineNumber(source), + "Garbage after numeric value: %s\n", buffer()); + return false; + } + if (n < 0 || n > max) + { + debug("StreamProtocolParser::Protocol::compileNumber: %s\n", + buffer.expand()()); + errorMsg(getLineNumber(source), + "Value %s out of range [0...%ld]\n", buffer(), max); + return false; + } + number = n; + debug("StreamProtocolParser::Protocol::compileNumber %s = %ld\n", + buffer(), n); + return true; +} + +// read a string (quoted or numerical byte values) from source code line +// and put it to buffer, replacing all variable references and code formats + +bool StreamProtocolParser::Protocol:: +compileString(StreamBuffer& buffer, const char*& source, + FormatType formatType, Client* client, int quoted) +{ + bool escaped = false; + int n; + long formatPos[20]; + int numFormats = 0; + int newline = 0; + StreamBuffer formatbuffer; + line = getLineNumber(source); + + debug("StreamProtocolParser::Protocol::compileString " + "line %d source=\"%s\"\n", + line, source); + + // coding is done in two steps: + // 1) read a line from protocol source and code quoted strings, + // numerical bytes, tokens, etc, and replace variables and parameters + // 2) replace the formats in the line + // thus variables can be replaces inside the format info string + + while (1) + { + // this is step 2: replacing the formats + if (!*source || (newline = getLineNumber(source)) != line) + { + // compile all formats in this line + // We do this here after all variables in this line + // have been replaced and after string has been coded. + int offs = 0; + for (n = 0; n < numFormats; n++) + { + int pos = formatPos[n] + offs; + debug("StreamProtocolParser::Protocol::compileString " + "format=\"%s\"\n", buffer.expand(pos)()); + formatbuffer.clear(); + const char* p = buffer(pos); + if (!compileFormat(formatbuffer, p, formatType, client)) + { + p = buffer(pos); + formatbuffer.clear(); + printString(formatbuffer, p); + errorMsg(line, "in format string: \"%s\"\n", + formatbuffer()); + return false; + } + int formatlen = p - buffer(pos); + buffer.replace(pos, formatlen, formatbuffer); + debug("StreamProtocolParser::Protocol::compileString " + "replaced by: \"%s\"\n", buffer.expand(pos)()); + offs += formatbuffer.length() - formatlen; + } + if (!*source) break; + numFormats = 0; + line = newline; + } + // this is step 1: coding the string + if ((*source & 0x7f) < 0x20) + { + error("Unexpected byte %#04x\n", *source & 0xFF); + return false; + } + if (escaped) // char after \ in quoted text + { + escaped = false; + unsigned int temp; + switch (*source) + { + case '$': // can't be: readToken would have made a token from this + errorMsg(line, + "INTERNAL ERROR: unconverted \\$ in quoted string\n"); + return false; + case '?': + if (formatType != ScanFormat) + { + errorMsg(line, + "Quoted \\? only allowed in input: %s\n", + source-1); + return false; + } + buffer.append(skip); + break; + case 'a': + buffer.append(7); + break; + case 'b': + buffer.append(8); + break; + case 't': + buffer.append(9); + break; + case 'n': + buffer.append('\n'); + break; + case 'r': + buffer.append('\r'); + break; + case 'e': + buffer.append(esc).append(esc); + break; + case '0': // octal numbers (max 3 digits after 0) + sscanf (source, "%3o%n", &temp, &n); + if (temp > 0xFF) + { + errorMsg(line, + "Octal source %#o does not fit in byte: %s\n", + temp, source-1); + return false; + } + if (formatType != NoFormat && + (temp < last_function_code || temp == esc)) + { + buffer.append(esc); + } + buffer.append(temp); + source += n; + continue; + case 'x': // hex numbers (max 2 digits after 0) + if (sscanf (source+1, "%2x%n", &temp, &n) < 1) + { + errorMsg(line, + "Hex digit expected after \\x: %s\n", + source-1); + return false; + } + if (formatType != NoFormat && + (temp < last_function_code || temp == esc)) + { + buffer.append(esc); + } + buffer.append(temp); + source += n+1; + continue; + case '1': // decimal numbers (max 3 digits) + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + sscanf (source, "%3u%n", &temp, &n); + if (temp > 0xFF) + { + errorMsg(line, + "Decimal source %d does not fit in byte: %s\n", + temp, source-1); + return false; + } + if (formatType != NoFormat && + (temp < last_function_code || temp == esc)) + { + buffer.append(esc); + } + buffer.append(temp); + source += n; + continue; + default: // escaped literals + buffer.append(esc).append(*source); + } + source++; + continue; + } + if (quoted) // look for ending quotes, escapes, and formats + { + switch (*source) + { + case '\\': // escape next character + escaped = true; + break; + case '%': // format specifier in string + if (formatType != NoFormat) + { + // format is allowed here + // just memorize position here and and do actual coding later + // after all variables and parameters have been replaced + // so that extra information is ready for format converter + if (numFormats+1 == sizeof(formatPos)) + { + errorMsg(line, "Too many formats in line"); + return false; + } + formatPos[numFormats++]=buffer.length(); + buffer.append(*source); + break; + } + case '"': + case '\'': + if (*source == quoted) // ending quote + { + source += 1+sizeof(int); // skip line number + quoted = false; + break; + } + default: + buffer.append(*source); // literal character + } + source++; + continue; + } + switch (*source) // tokens other than quoted string + { + const char* p; + case '$': // parameter or variable + { + StreamBuffer value; + if (!replaceVariable(value, source)) return false; + source += strlen(source)+1+sizeof(int); + p = value(); + int saveline = line; + if (!compileString(buffer, p, formatType, client)) + return false; + line = saveline; + continue; + } + case '\'': // starting single quotes + case '"': // starting double quotes + quoted = *source++; + continue; +// case '}': +// case ')': +// buffer.append(eos); + case ' ': // skip whitespace + case ',': // treat comma as whitespace + source++; + continue; + } + // try numeric byte value + char* p; + int temp = strtol(source, &p, 0); + if (p != source) + { + if (*p != 0) + { + errorMsg(line, + "Garbage after numeric source: %s", source); + return false; + } + if (temp > 0xFF || temp < -0x80) + { + errorMsg(line, + "Value %s does not fit in byte\n", source); + return false; + } + if (formatType != NoFormat && + (temp < last_function_code || temp == esc)) + { + buffer.append(esc); + } + buffer.append(temp); + source = p+1+sizeof(int); + continue; + } + // try constant token + struct {const char* name; char code;} codes [] = + { + {"skip", skip}, + {"?", skip}, + {"eot", 4 }, + {"ack", 6 }, + {"bel", 7 }, + {"bs", 8 }, + {"ht", 9 }, + {"tab", 9 }, + {"lf", '\n' }, + {"nl", '\n' }, + {"cr", '\r' }, + {"esc", esc }, + {"del", 0x7f} + }; + size_t i; + char c = 0; + for (i = 0; i < sizeof(codes)/sizeof(*codes); i++) + { + if (strcmp(source, codes[i].name) == 0) + { + c = codes[i].code; + if (c == skip && formatType != ScanFormat) + { + errorMsg(line, + "Use of '%s' only allowed in input formats\n", + source); + return false; + } + if (formatType != NoFormat && + (temp < last_function_code || temp == esc)) + { + buffer.append(esc); + } + buffer.append(c); + source += strlen(source)+1+sizeof(int); + break; + } + } + if (c) continue; + // source may contain a function name + errorMsg(line, + "Unexpected word: %s\n", source); + return false; + } + debug("StreamProtocolParser::Protocol::compileString buffer=%s\n", buffer.expand()()); + return true; +} + +bool StreamProtocolParser::Protocol:: +compileFormat(StreamBuffer& buffer, const char*& formatstr, + FormatType formatType, Client* client) +{ +/* + formatstr := '%' ['(' field ')'] [flags] [width] ['.' prec] conv [extra] + field := string + flags := '-' | '+' | ' ' | '#' | '0' | '*' + width := integer + prec := integer + conv := character + extra := string + only conv is required all other parts are optional. + + compiles to: + formatstr StreamFormat [info ] + or: + field addrLength AddressStructure formatstr StreamFormat [info ] +*/ + const char* source = formatstr; + StreamFormat streamFormat; + int fieldname = 0; + // look for fieldname + if (source[1] == '(') + { + buffer.append(format_field); + if (!client) + { + errorMsg(line, + "Using fieldname is not possible in this context\n"); + return false; + } + const char* fieldnameEnd = strchr(source+=2, ')'); + if (!fieldnameEnd) + { + errorMsg(line, + "Missing ')' after field name\n"); + return false; + } + // add fieldname for debug purpose + fieldname=buffer.length(); + buffer.append(source, fieldnameEnd - source).append(eos); + debug("StreamProtocolParser::Protocol::compileFormat: fieldname='%s'\n", + buffer(fieldname)); + StreamBuffer fieldAddress; + if (!client->getFieldAddress(buffer(fieldname), fieldAddress)) + { + errorMsg(line, + "Field '%s' not found\n", buffer(fieldname)); + return false; + } + source = fieldnameEnd; + unsigned short length = (unsigned short)fieldAddress.length(); + buffer.append(&length, sizeof(length)); + buffer.append(fieldAddress); + } + else + { + buffer.append(format); + } + const char* formatstart = source + 1; + // look for flags + streamFormat.flags = 0; + bool loop = true; + while (loop) + { + switch (*++source) + { + case '-': + streamFormat.flags |= left_flag; + break; + case '+': + streamFormat.flags |= sign_flag; + break; + case ' ': + streamFormat.flags |= space_flag; + break; + case '#': + streamFormat.flags |= alt_flag; + break; + case '0': + streamFormat.flags |= zero_flag; + break; + case '*': + if (formatType != ScanFormat) + { + errorMsg(line, + "Use of skip modifier '*' " + "only allowed in input formats\n"); + return false; + } + if (fieldname) + { + errorMsg(line, + "Use of skip modifier '*' not allowed " + "together with field name\n"); + return false; + } + streamFormat.flags |= skip_flag; + break; + default: + loop = false; + } + } +/* + if (formatType != PrintFormat && + streamFormat.flags & (left_flag|sign_flag|space_flag|zero_flag)) + { + errorMsg(line, + "Use of format modifiers '-', '+', ' ', '0' " + "only allowed in output formats\n"); + return false; + } + if (!(~streamFormat.flags & (left_flag|zero_flag))) + { + errorMsg(line, + "Can't use modifiers '-' and '0' together\n"); + return false; + } + if (!(~streamFormat.flags & (space_flag|sign_flag))) + { + errorMsg(line, + "Can't use modifiers ' ' and '+' together\n"); + return false; + } +*/ + // look for width + unsigned long val; + char* p; + val = strtoul (source, &p, 10); + source = p; + if (val > 0xFFFF) + { + errorMsg(line, + "Field width %ld out of range\n", val); + return false; + } + streamFormat.width = (unsigned short)val; + // look for prec + streamFormat.prec = -1; + if (*source == '.') + { + source++; + val = strtoul(source, &p, 10); + if (p == source) + { + debug("source = %s\n", source); + errorMsg(line, + "Numeric precision field expected after '.'\n"); + return false; + } + source = p; + if (val > 0x7FFF) + { + errorMsg(line, + "Precision %ld out of range\n", val); + return false; + } + streamFormat.prec = (short)val; + } + // look for converter + streamFormat.conv = *source++; + debug("StreamProtocolParser::Protocol::compileFormat: converter='%c'\n", + streamFormat.conv); + if (!streamFormat.conv || strchr("'\" (.0+-*", streamFormat.conv)) + { + debug("StreamProtocolParser::Protocol::compileFormat: formatstr='%s'\n", + formatstr); + errorMsg(line, + "Missing converter character\n"); + return false; + } + StreamFormatConverter* converter; + converter = StreamFormatConverter::find(streamFormat.conv); + if (!converter) + { + errorMsg(line, + "No converter registered for format '%%%c'\n", + streamFormat.conv); + return false; + } + // parse format and get info string + StreamBuffer infoString; + int type = converter->parse(streamFormat, infoString, + source, formatType == ScanFormat); + if (!type) + { + // parsing failed + return false; + } + if (type < long_format && type > pseudo_format) + { + errorMsg(line, + "Illegal format type %d returned from '%%%c' converter\n", + type, streamFormat.conv); + return false; + } + if (type == pseudo_format && fieldname) + { + errorMsg(line, + "Fieldname not allowed with pseudo format: '%%(%s)%c'\n", + buffer(fieldname), streamFormat.conv); + return false; + } + streamFormat.type = static_cast(type); + if (infoString && infoString[-1] != eos) + { + // terminate if necessary + infoString.append(eos); + } + streamFormat.infolen = (unsigned short)infoString.length(); + // add formatstr for debug purpose + buffer.append(formatstart, source-formatstart).append(eos); + +#ifndef NO_TEMPORARY + debug("StreamProtocolParser::Protocol::compileFormat: formatstring=\"%s\"\n", + StreamBuffer(formatstart, source-formatstart).expand()()); +#endif + + // add streamFormat structure and info + buffer.append(&streamFormat, sizeof(streamFormat)); + buffer.append(infoString); + + debug("StreamProtocolParser::Protocol::compileFormat: format.type=%s, infolen=%d\n", + StreamFormatTypeStr[streamFormat.type], streamFormat.infolen); + formatstr = source; // move pointer after parsed format + return true; +} + +bool StreamProtocolParser::Protocol:: +compileCommands(StreamBuffer& buffer, const char*& source, Client* client) +{ + const char* command; + const char* args; + + while (*source) + { + command = source; + args = source + strlen(source)+1+sizeof(int); + if (!client->compileCommand(this, buffer, command, args)) + { + errorMsg(getLineNumber(source), + "in command '%s'\n", command); + return false; + } + if (*args) + { + errorMsg(getLineNumber(source), + "Garbage after '%s' command: '%s'\n", + command, args); + return false; + } + source = args + 1; + } + buffer.append(eos); + return true; +} + +bool StreamProtocolParser::Protocol:: +checkUnused() +{ + Variable* pV; + + for (pV = variables; pV; pV = pV->next) + { + if (!pV->used) + { + if (pV->name[0] == '@') + { + error("Unknown handler %s defined in protocol file '%s' line %d\n", + pV->name(), filename(), pV->line); + return false; + } + debug("Unused variable %s in protocol file '%s' line %d\n", + pV->name(), filename(), pV->line); + } + } + return true; +} diff --git a/src/StreamProtocol.h b/src/StreamProtocol.h new file mode 100644 index 0000000..f5d6934 --- /dev/null +++ b/src/StreamProtocol.h @@ -0,0 +1,164 @@ +/*************************************************************** +* StreamDevice Support * +* * +* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) * +* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) * +* * +* This is the protocol parser of StreamDevice. * +* Please refer to the HTML files in ../doc/ for a detailed * +* documentation. * +* * +* If you do any changes in this file, you are not allowed to * +* redistribute it any more. If there is a bug or a missing * +* feature, send me an email and/or your patch. If I accept * +* your changes, they will go to the next release. * +* * +* DISCLAIMER: If this software breaks something or harms * +* someone, it's your problem. * +* * +***************************************************************/ + +#ifndef StreamProtocol_h +#define StreamProtocol_h + +#include "StreamBuffer.h" +#include + +enum FormatType {NoFormat, ScanFormat, PrintFormat}; + +class StreamProtocolParser +{ +public: + + enum Codes + { + eos = 0, skip, format, format_field, last_function_code + }; + + class Client; + + class Protocol + { + class Variable; + + friend class StreamProtocolParser; + + private: + Protocol* next; + Variable* variables; + const StreamBuffer protocolname; + StreamBuffer* commands; + int line; + const char* parameter[10]; + + Protocol(const char* filename); + Protocol(const Protocol& p, StreamBuffer& name, int line); + StreamBuffer* createVariable(const char* name, int line); + bool compileFormat(StreamBuffer&, const char*& source, + FormatType, Client*); + bool compileCommands(StreamBuffer&, const char*& source, Client*); + bool replaceVariable(StreamBuffer&, const char* varname); + const Variable* getVariable(const char* name); + + public: + + const StreamBuffer filename; + + bool getNumberVariable(const char* varname, unsigned long& value, + unsigned long max = 0xFFFFFFFF); + bool getEnumVariable(const char* varname, unsigned short& value, + const char ** enumstrings); + bool getStringVariable(const char* varname,StreamBuffer& value, bool* defined = NULL); + bool getCommands(const char* handlername, StreamBuffer& code, Client*); + bool compileNumber(unsigned long& number, const char*& source, + unsigned long max = 0xFFFFFFFF); + bool compileString(StreamBuffer& buffer, const char*& source, + FormatType formatType = NoFormat, Client* = NULL, int quoted = false); + bool checkUnused(); + void errorMsg(int line, const char* fmt, ...) + __attribute__ ((format(printf,3,4))); + ~Protocol(); + void report(); + }; + + class Client + { + friend class StreamProtocolParser::Protocol; + virtual bool compileCommand(Protocol*, StreamBuffer& buffer, + const char* command, const char*& args) = 0; + virtual bool getFieldAddress(const char* fieldname, + StreamBuffer& address) = 0; + virtual const char* name() = 0; + public: + virtual ~Client(); + }; + +private: + StreamBuffer filename; + FILE* file; + int line; + int quote; + Protocol globalSettings; + Protocol* protocols; + StreamProtocolParser* next; + static StreamProtocolParser* parsers; + bool valid; + + StreamProtocolParser(FILE* file, const char* filename); + Protocol* getProtocol(const StreamBuffer& protocolAndParams); + bool isGlobalContext(const StreamBuffer* commands); + bool isHandlerContext(Protocol&, const StreamBuffer* commands); + static StreamProtocolParser* readFile(const char* file); + bool parseProtocol(Protocol&, StreamBuffer* commands); + int readChar(); + bool readToken(StreamBuffer& buffer, + const char* specialchars = NULL, bool eofAllowed = false); + bool parseAssignment(const char* variable, Protocol&); + bool parseValue(StreamBuffer& buffer, bool lazy = false); + void errorMsg(const char* fmt, ...) + __attribute__ ((format(printf,2,3))); + +protected: + ~StreamProtocolParser(); // get rid of cygnus-2.7.2 compiler warning + +public: + static Protocol* getProtocol(const char* file, + const StreamBuffer& protocolAndParams); + static void free(); + static const char* path; + static const char* printString(StreamBuffer&, const char* string); + void report(); +}; + +inline int getLineNumber(const char* s) +{ + int l; + memcpy (&l, s+strlen(s)+1, sizeof(int)); + return l; +} + +template +inline const T extract(const char*& string) +{ + T p; + memcpy (&p, string, sizeof(T)); + string += sizeof(T); + return p; +} + +/* + +API functions: + +NAME: getProtocol() +PURPOSE: read protocol from file, create parser if necessary +RETURNS: a copy of a protocol that must be deleted by the caller +SIDEEFFECTS: file IO, memory allocation for parser + +NAME: free() +PURPOSE: free all parser resources allocated by getProtocol() +Call this function once after the last getProtocol() to clean up. + +*/ + +#endif diff --git a/src/StreamVersion.c b/src/StreamVersion.c new file mode 100644 index 0000000..8795910 --- /dev/null +++ b/src/StreamVersion.c @@ -0,0 +1,28 @@ +/*************************************************************** +* StreamDevice Support * +* * +* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) * +* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) * +* * +* This provides a version string for StreamDevice. * +* Please refer to the HTML files in ../doc/ for a detailed * +* documentation. * +* * +* If you do any changes in this file, you are not allowed to * +* redistribute it any more. If there is a bug or a missing * +* feature, send me an email and/or your patch. If I accept * +* your changes, they will go to the next release. * +* * +* DISCLAIMER: If this software breaks something or harms * +* someone, it's your problem. * +* * +***************************************************************/ + +#include "devStream.h" + +#define STR2(x) #x +#define STR(x) STR2(x) +const char StreamVersion [] = + "StreamDevice " STR(STREAM_MAJOR) "." STR(STREAM_MINOR) + " built " __DATE__ " " __TIME__; + diff --git a/src/devStream.h b/src/devStream.h new file mode 100644 index 0000000..d56bff6 --- /dev/null +++ b/src/devStream.h @@ -0,0 +1,115 @@ +/*************************************************************** +* StreamDevice Support * +* * +* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) * +* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) * +* * +* This is the header for the EPICS interface to StreamDevice. * +* Please refer to the HTML files in ../doc/ for a detailed * +* documentation. * +* * +* If you do any changes in this file, you are not allowed to * +* redistribute it any more. If there is a bug or a missing * +* feature, send me an email and/or your patch. If I accept * +* your changes, they will go to the next release. * +* * +* DISCLAIMER: If this software breaks something or harms * +* someone, it's your problem. * +* * +***************************************************************/ + +#ifndef devStream_h +#define devStream_h + +#define STREAM_MAJOR 2 +#define STREAM_MINOR 2 + +#if defined(__vxworks) || defined(vxWorks) +#include +#endif + +#ifndef OK +#define OK 0 +#endif + +#ifndef ERROR +#define ERROR -1 +#endif + +#define DO_NOT_CONVERT 2 +#define INIT_RUN (!interruptAccept) + +#include +#if (EPICS_VERSION == 3 && EPICS_REVISION == 14) +#define EPICS_3_14 +#endif + +#if defined(__cplusplus) && !defined(EPICS_3_14) +extern "C" { +#endif + +#include +#include +#include +#include +/* #include */ +#include + +#if defined(__cplusplus) && !defined(EPICS_3_14) +} +#endif + + +typedef const struct format_s { + unsigned char type; + const struct StreamFormat* priv; +} format_t; + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef _WIN32 +__declspec(dllimport) +#endif +extern FILE* StreamDebugFile; + +extern const char StreamVersion []; + +typedef long (*streamIoFunction) (dbCommon*, format_t*); + +long streamInit(int after); +long streamInitRecord(dbCommon *record, struct link *ioLink, + streamIoFunction readData, streamIoFunction writeData); +long streamReport(int interest); +long streamReadWrite(dbCommon *record); +long streamGetIointInfo(int cmd, dbCommon *record, IOSCANPVT *ppvt); +long streamPrintf(dbCommon *record, format_t *format, ...); +long streamScanSep(dbCommon *record); +long streamScanfN(dbCommon *record, format_t *format, + void*, size_t maxStringSize); + +/* backward compatibility stuff */ +#define devStreamIoFunction streamIoFunction +#define devStreamInit streamInit +#define devStreamInitRecord streamInitRecord +#define devStreamReport streamReport +#define devStreamRead streamReadWrite +#define devStreamWrite streamReadWrite +#define devStreamGetIointInfo streamGetIointInfo +#define devStreamPrintf streamPrintf +#define devStreamPrintSep(record) (0) +#define devStreamScanSep streamScanSep +#define devStreamScanf(record, format, value) \ + streamScanfN(record, format, value, MAX_STRING_SIZE) +#define streamScanf(record, format, value) \ + streamScanfN(record, format, value, MAX_STRING_SIZE) +#define streamRead streamReadWrite +#define streamWrite streamReadWrite +#define streamReport NULL + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/devaaiStream.c b/src/devaaiStream.c new file mode 100644 index 0000000..b2a76cb --- /dev/null +++ b/src/devaaiStream.c @@ -0,0 +1,302 @@ +/*************************************************************** +* StreamDevice record interface for aai records * +* * +* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) * +* (C) 2006 Dirk Zimoch (dirk.zimoch@psi.ch) * +* * +* This is an EPICS record Interface for StreamDevice. * +* Please refer to the HTML files in ../doc/ for a detailed * +* documentation. * +* * +* If you do any changes in this file, you are not allowed to * +* redistribute it any more. If there is a bug or a missing * +* feature, send me an email and/or your patch. If I accept * +* your changes, they will go to the next release. * +* * +* DISCLAIMER: If this software breaks something or harms * +* someone, it's your problem. * +* * +***************************************************************/ + +#include +#include +#include +#include +#include + +static long readData (dbCommon *record, format_t *format) +{ + aaiRecord *aai = (aaiRecord *) record; + double dval; + long lval; + + for (aai->nord = 0; aai->nord < aai->nelm; aai->nord++) + { + switch (format->type) + { + case DBF_DOUBLE: + { + if (streamScanf (record, format, &dval) != OK) + { + return aai->nord ? OK : ERROR; + } + switch (aai->ftvl) + { + case DBF_DOUBLE: + ((double *)aai->bptr)[aai->nord] = dval; + break; + case DBF_FLOAT: + ((float *)aai->bptr)[aai->nord] = (float)dval; + break; + default: + errlogSevPrintf (errlogFatal, + "readData %s: can't convert from double to %s\n", + record->name, pamapdbfType[aai->ftvl].strvalue); + return ERROR; + } + break; + } + case DBF_LONG: + case DBF_ENUM: + { + if (streamScanf (record, format, &lval) != OK) + { + return aai->nord ? OK : ERROR; + } + switch (aai->ftvl) + { + case DBF_DOUBLE: + ((double *)aai->bptr)[aai->nord] = lval; + break; + case DBF_FLOAT: + ((float *)aai->bptr)[aai->nord] = (float)lval; + break; + case DBF_LONG: + case DBF_ULONG: + ((long *)aai->bptr)[aai->nord] = lval; + break; + case DBF_SHORT: + case DBF_USHORT: + case DBF_ENUM: + ((short *)aai->bptr)[aai->nord] = (short)lval; + break; + case DBF_CHAR: + case DBF_UCHAR: + ((char *)aai->bptr)[aai->nord] = (char)lval; + break; + default: + errlogSevPrintf (errlogFatal, + "readData %s: can't convert from long to %s\n", + record->name, pamapdbfType[aai->ftvl].strvalue); + return ERROR; + } + break; + } + case DBF_STRING: + { + switch (aai->ftvl) + { + case DBF_STRING: + if (streamScanfN (record, format, + (char *)aai->bptr + aai->nord * MAX_STRING_SIZE, + MAX_STRING_SIZE) != OK) + { + return aai->nord ? OK : ERROR; + } + break; + case DBF_CHAR: + case DBF_UCHAR: + memset (aai->bptr, 0, aai->nelm); + aai->nord = 0; + if (streamScanfN (record, format, + (char *)aai->bptr, aai->nelm) != OK) + { + return ERROR; + } + ((char*)aai->bptr)[aai->nelm] = 0; + for (lval = aai->nelm; + lval >= 0 && ((char*)aai->bptr)[lval] == 0; + lval--); + aai->nord = lval+1; + return OK; + default: + errlogSevPrintf (errlogFatal, + "readData %s: can't convert from string to %s\n", + record->name, pamapdbfType[aai->ftvl].strvalue); + return ERROR; + } + break; + } + default: + { + errlogSevPrintf (errlogMajor, + "readData %s: can't convert from %s to %s\n", + record->name, pamapdbfType[format->type].strvalue, + pamapdbfType[aai->ftvl].strvalue); + return ERROR; + } + } + } + return OK; +} + +static long writeData (dbCommon *record, format_t *format) +{ + aaiRecord *aai = (aaiRecord *) record; + double dval; + long lval; + unsigned long nowd; + + for (nowd = 0; nowd < aai->nord; nowd++) + { + switch (format->type) + { + case DBF_DOUBLE: + { + switch (aai->ftvl) + { + case DBF_DOUBLE: + dval = ((double *)aai->bptr)[nowd]; + break; + case DBF_FLOAT: + dval = ((float *)aai->bptr)[nowd]; + break; + case DBF_LONG: + dval = ((long *)aai->bptr)[nowd]; + break; + case DBF_ULONG: + dval = ((unsigned long *)aai->bptr)[nowd]; + break; + case DBF_SHORT: + dval = ((short *)aai->bptr)[nowd]; + break; + case DBF_USHORT: + case DBF_ENUM: + dval = ((unsigned short *)aai->bptr)[nowd]; + break; + case DBF_CHAR: + dval = ((char *)aai->bptr)[nowd]; + break; + case DBF_UCHAR: + dval = ((unsigned char *)aai->bptr)[nowd]; + break; + default: + errlogSevPrintf (errlogFatal, + "writeData %s: can't convert from %s to double\n", + record->name, pamapdbfType[aai->ftvl].strvalue); + return ERROR; + } + if (streamPrintf (record, format, dval)) + return ERROR; + break; + } + case DBF_LONG: + case DBF_ENUM: + { + switch (aai->ftvl) + { + case DBF_LONG: + case DBF_ULONG: + lval = ((long *)aai->bptr)[nowd]; + break; + case DBF_SHORT: + lval = ((short *)aai->bptr)[nowd]; + break; + case DBF_USHORT: + case DBF_ENUM: + lval = ((unsigned short *)aai->bptr)[nowd]; + break; + case DBF_CHAR: + lval = ((char *)aai->bptr)[nowd]; + break; + case DBF_UCHAR: + lval = ((unsigned char *)aai->bptr)[nowd]; + break; + default: + errlogSevPrintf (errlogFatal, + "writeData %s: can't convert from %s to long\n", + record->name, pamapdbfType[aai->ftvl].strvalue); + return ERROR; + } + if (streamPrintf (record, format, lval)) + return ERROR; + break; + } + case DBF_STRING: + { + switch (aai->ftvl) + { + case DBF_STRING: + if (streamPrintf (record, format, + ((char *)aai->bptr) + nowd * MAX_STRING_SIZE)) + return ERROR; + break; + case DBF_CHAR: + case DBF_UCHAR: + /* print aai as a null-terminated string */ + if (aai->nord < aai->nelm) + { + ((char *)aai->bptr)[aai->nord] = 0; + } + else + { + ((char *)aai->bptr)[aai->nelm-1] = 0; + } + if (streamPrintf (record, format, + ((char *)aai->bptr))) + return ERROR; + return OK; + default: + errlogSevPrintf (errlogFatal, + "writeData %s: can't convert from %s to string\n", + record->name, pamapdbfType[aai->ftvl].strvalue); + return ERROR; + } + break; + } + default: + { + errlogSevPrintf (errlogFatal, + "writeData %s: can't convert from %s to %s\n", + record->name, pamapdbfType[aai->ftvl].strvalue, + pamapdbfType[format->type].strvalue); + return ERROR; + } + } + } + return OK; +} + +static long initRecord (dbCommon *record) +{ + static const int typesize[] = {MAX_STRING_SIZE,1,1,2,2,4,4,4,8,2}; + aaiRecord *aai = (aaiRecord *) record; + + aai->bptr = calloc(aai->nelm, typesize[aai->ftvl]); + if (aai->bptr == NULL) + { + errlogSevPrintf (errlogFatal, + "initRecord %s: can't allocate memory for data array\n", + record->name); + return ERROR; + } + return streamInitRecord (record, &aai->inp, readData, writeData); +} + +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN read; +} devaaiStream = { + 5, + streamReport, + streamInit, + initRecord, + streamGetIointInfo, + streamRead +}; + +epicsExportAddress(dset,devaaiStream); diff --git a/src/devaaoStream.c b/src/devaaoStream.c new file mode 100644 index 0000000..eba6eff --- /dev/null +++ b/src/devaaoStream.c @@ -0,0 +1,302 @@ +/*************************************************************** +* StreamDevice record interface for aao records * +* * +* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) * +* (C) 2006 Dirk Zimoch (dirk.zimoch@psi.ch) * +* * +* This is an EPICS record Interface for StreamDevice. * +* Please refer to the HTML files in ../doc/ for a detailed * +* documentation. * +* * +* If you do any changes in this file, you are not allowed to * +* redistribute it any more. If there is a bug or a missing * +* feature, send me an email and/or your patch. If I accept * +* your changes, they will go to the next release. * +* * +* DISCLAIMER: If this software breaks something or harms * +* someone, it's your problem. * +* * +***************************************************************/ + +#include +#include +#include +#include +#include + +static long readData (dbCommon *record, format_t *format) +{ + aaoRecord *aao = (aaoRecord *) record; + double dval; + long lval; + + for (aao->nord = 0; aao->nord < aao->nelm; aao->nord++) + { + switch (format->type) + { + case DBF_DOUBLE: + { + if (streamScanf (record, format, &dval) != OK) + { + return aao->nord ? OK : ERROR; + } + switch (aao->ftvl) + { + case DBF_DOUBLE: + ((double *)aao->bptr)[aao->nord] = dval; + break; + case DBF_FLOAT: + ((float *)aao->bptr)[aao->nord] = (float)dval; + break; + default: + errlogSevPrintf (errlogFatal, + "readData %s: can't convert from double to %s\n", + record->name, pamapdbfType[aao->ftvl].strvalue); + return ERROR; + } + break; + } + case DBF_LONG: + case DBF_ENUM: + { + if (streamScanf (record, format, &lval) != OK) + { + return aao->nord ? OK : ERROR; + } + switch (aao->ftvl) + { + case DBF_DOUBLE: + ((double *)aao->bptr)[aao->nord] = lval; + break; + case DBF_FLOAT: + ((float *)aao->bptr)[aao->nord] = (float)lval; + break; + case DBF_LONG: + case DBF_ULONG: + ((long *)aao->bptr)[aao->nord] = lval; + break; + case DBF_SHORT: + case DBF_USHORT: + case DBF_ENUM: + ((short *)aao->bptr)[aao->nord] = (short)lval; + break; + case DBF_CHAR: + case DBF_UCHAR: + ((char *)aao->bptr)[aao->nord] = (char)lval; + break; + default: + errlogSevPrintf (errlogFatal, + "readData %s: can't convert from long to %s\n", + record->name, pamapdbfType[aao->ftvl].strvalue); + return ERROR; + } + break; + } + case DBF_STRING: + { + switch (aao->ftvl) + { + case DBF_STRING: + if (streamScanfN (record, format, + (char *)aao->bptr + aao->nord * MAX_STRING_SIZE, + MAX_STRING_SIZE) != OK) + { + return aao->nord ? OK : ERROR; + } + break; + case DBF_CHAR: + case DBF_UCHAR: + memset (aao->bptr, 0, aao->nelm); + aao->nord = 0; + if (streamScanfN (record, format, + (char *)aao->bptr, aao->nelm) != OK) + { + return ERROR; + } + ((char*)aao->bptr)[aao->nelm] = 0; + for (lval = aao->nelm; + lval >= 0 && ((char*)aao->bptr)[lval] == 0; + lval--); + aao->nord = lval+1; + return OK; + default: + errlogSevPrintf (errlogFatal, + "readData %s: can't convert from string to %s\n", + record->name, pamapdbfType[aao->ftvl].strvalue); + return ERROR; + } + break; + } + default: + { + errlogSevPrintf (errlogMajor, + "readData %s: can't convert from %s to %s\n", + record->name, pamapdbfType[format->type].strvalue, + pamapdbfType[aao->ftvl].strvalue); + return ERROR; + } + } + } + return OK; +} + +static long writeData (dbCommon *record, format_t *format) +{ + aaoRecord *aao = (aaoRecord *) record; + double dval; + long lval; + unsigned long nowd; + + for (nowd = 0; nowd < aao->nord; nowd++) + { + switch (format->type) + { + case DBF_DOUBLE: + { + switch (aao->ftvl) + { + case DBF_DOUBLE: + dval = ((double *)aao->bptr)[nowd]; + break; + case DBF_FLOAT: + dval = ((float *)aao->bptr)[nowd]; + break; + case DBF_LONG: + dval = ((long *)aao->bptr)[nowd]; + break; + case DBF_ULONG: + dval = ((unsigned long *)aao->bptr)[nowd]; + break; + case DBF_SHORT: + dval = ((short *)aao->bptr)[nowd]; + break; + case DBF_USHORT: + case DBF_ENUM: + dval = ((unsigned short *)aao->bptr)[nowd]; + break; + case DBF_CHAR: + dval = ((char *)aao->bptr)[nowd]; + break; + case DBF_UCHAR: + dval = ((unsigned char *)aao->bptr)[nowd]; + break; + default: + errlogSevPrintf (errlogFatal, + "writeData %s: can't convert from %s to double\n", + record->name, pamapdbfType[aao->ftvl].strvalue); + return ERROR; + } + if (streamPrintf (record, format, dval)) + return ERROR; + break; + } + case DBF_LONG: + case DBF_ENUM: + { + switch (aao->ftvl) + { + case DBF_LONG: + case DBF_ULONG: + lval = ((long *)aao->bptr)[nowd]; + break; + case DBF_SHORT: + lval = ((short *)aao->bptr)[nowd]; + break; + case DBF_USHORT: + case DBF_ENUM: + lval = ((unsigned short *)aao->bptr)[nowd]; + break; + case DBF_CHAR: + lval = ((char *)aao->bptr)[nowd]; + break; + case DBF_UCHAR: + lval = ((unsigned char *)aao->bptr)[nowd]; + break; + default: + errlogSevPrintf (errlogFatal, + "writeData %s: can't convert from %s to long\n", + record->name, pamapdbfType[aao->ftvl].strvalue); + return ERROR; + } + if (streamPrintf (record, format, lval)) + return ERROR; + break; + } + case DBF_STRING: + { + switch (aao->ftvl) + { + case DBF_STRING: + if (streamPrintf (record, format, + ((char *)aao->bptr) + nowd * MAX_STRING_SIZE)) + return ERROR; + break; + case DBF_CHAR: + case DBF_UCHAR: + /* print aao as a null-terminated string */ + if (aao->nord < aao->nelm) + { + ((char *)aao->bptr)[aao->nord] = 0; + } + else + { + ((char *)aao->bptr)[aao->nelm-1] = 0; + } + if (streamPrintf (record, format, + ((char *)aao->bptr))) + return ERROR; + return OK; + default: + errlogSevPrintf (errlogFatal, + "writeData %s: can't convert from %s to string\n", + record->name, pamapdbfType[aao->ftvl].strvalue); + return ERROR; + } + break; + } + default: + { + errlogSevPrintf (errlogFatal, + "writeData %s: can't convert from %s to %s\n", + record->name, pamapdbfType[aao->ftvl].strvalue, + pamapdbfType[format->type].strvalue); + return ERROR; + } + } + } + return OK; +} + +static long initRecord (dbCommon *record) +{ + static const int typesize[] = {MAX_STRING_SIZE,1,1,2,2,4,4,4,8,2}; + aaoRecord *aao = (aaoRecord *) record; + + aao->bptr = calloc(aao->nelm, typesize[aao->ftvl]); + if (aao->bptr == NULL) + { + errlogSevPrintf (errlogFatal, + "initRecord %s: can't allocate memory for data array\n", + record->name); + return ERROR; + } + return streamInitRecord (record, &aao->out, readData, writeData); +} + +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN read; +} devaaoStream = { + 5, + streamReport, + streamInit, + initRecord, + streamGetIointInfo, + streamWrite +}; + +epicsExportAddress(dset,devaaoStream); diff --git a/src/devaiStream.c b/src/devaiStream.c new file mode 100644 index 0000000..ebfcb31 --- /dev/null +++ b/src/devaiStream.c @@ -0,0 +1,98 @@ +/*************************************************************** +* StreamDevice record interface for analog input records * +* * +* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) * +* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) * +* * +* This is an EPICS record Interface for StreamDevice. * +* Please refer to the HTML files in ../doc/ for a detailed * +* documentation. * +* * +* If you do any changes in this file, you are not allowed to * +* redistribute it any more. If there is a bug or a missing * +* feature, send me an email and/or your patch. If I accept * +* your changes, they will go to the next release. * +* * +* DISCLAIMER: If this software breaks something or harms * +* someone, it's your problem. * +* * +***************************************************************/ + +#include +#include +#include + +static long readData (dbCommon *record, format_t *format) +{ + aiRecord *ai = (aiRecord *) record; + double val; + + switch (format->type) + { + case DBF_DOUBLE: + { + if (streamScanf (record, format, &val)) return ERROR; + if (ai->aslo != 0.0) val *= ai->aslo; + val += ai->aoff; + if (!INIT_RUN && ai->smoo != 0.0) + { + val = ai->val * ai->smoo + val * (1.0 - ai->smoo); + } + ai->val = val; + return DO_NOT_CONVERT; + } + case DBF_LONG: + { + return streamScanf (record, format, &ai->rval); + } + } + return ERROR; +} + +static long writeData (dbCommon *record, format_t *format) +{ + aiRecord *ai = (aiRecord *) record; + double val; + + switch (format->type) + { + case DBF_DOUBLE: + { + val = ai->val - ai->aoff; + if (ai->aslo != 0) val /= ai->aslo; + return streamPrintf (record, format, val); + } + case DBF_LONG: + { + return streamPrintf (record, format, ai->rval); + } + } + return ERROR; +} + +static long initRecord (dbCommon *record) +{ + aiRecord *ai = (aiRecord *) record; + + return streamInitRecord (record, &ai->inp, readData, writeData); +} + +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN read; + DEVSUPFUN special_linconv; +} devaiStream = { + 6, + streamReport, + streamInit, + initRecord, + streamGetIointInfo, + streamRead, + NULL +}; + +epicsExportAddress(dset,devaiStream); diff --git a/src/devaoStream.c b/src/devaoStream.c new file mode 100644 index 0000000..54ba9f2 --- /dev/null +++ b/src/devaoStream.c @@ -0,0 +1,99 @@ +/*************************************************************** +* Stream Device record interface for analog output records * +* * +* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) * +* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) * +* * +* This is an EPICS record Interface for StreamDevice. * +* Please refer to the HTML files in ../doc/ for a detailed * +* documentation. * +* * +* If you do any changes in this file, you are not allowed to * +* redistribute it any more. If there is a bug or a missing * +* feature, send me an email and/or your patch. If I accept * +* your changes, they will go to the next release. * +* * +* DISCLAIMER: If this software breaks something or harms * +* someone, it's your problem. * +* * +***************************************************************/ + +#include +#include +#include + +static long readData (dbCommon *record, format_t *format) +{ + aoRecord *ao = (aoRecord *) record; + + switch (format->type) + { + case DBF_DOUBLE: + { + double val; + if (streamScanf (record, format, &val)) return ERROR; + if (ao->aslo != 0) val *= ao->aslo; + ao->val = val + ao->aoff; + return DO_NOT_CONVERT; + } + case DBF_LONG: + { + long rval; + if (streamScanf (record, format, &rval)) return ERROR; + ao->rbv = rval; + if (INIT_RUN) ao->rval = rval; + return OK; + } + } + return ERROR; +} + +static long writeData (dbCommon *record, format_t *format) +{ + aoRecord *ao = (aoRecord *) record; + double val; + + switch (format->type) + { + case DBF_DOUBLE: + { + if (INIT_RUN) val = ao->val; + else val = ao->oval; + val -= ao->aoff; + if (ao->aslo != 0) val /= ao->aslo; + return streamPrintf (record, format, val); + } + case DBF_LONG: + { + return streamPrintf (record, format, ao->rval); + } + } + return ERROR; +} + +static long initRecord (dbCommon *record) +{ + aoRecord *ao = (aoRecord *) record; + + return streamInitRecord (record, &ao->out, readData, writeData); +} + +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN write; + DEVSUPFUN special_linconv; +} devaoStream = { + 6, + streamReport, + streamInit, + initRecord, + streamGetIointInfo, + streamWrite, + NULL +}; + +epicsExportAddress(dset,devaoStream); diff --git a/src/devbiStream.c b/src/devbiStream.c new file mode 100644 index 0000000..3e7d973 --- /dev/null +++ b/src/devbiStream.c @@ -0,0 +1,112 @@ +/*************************************************************** +* Stream Device record interface for binary input records * +* * +* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) * +* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) * +* * +* This is an EPICS record Interface for StreamDevice. * +* Please refer to the HTML files in ../doc/ for a detailed * +* documentation. * +* * +* If you do any changes in this file, you are not allowed to * +* redistribute it any more. If there is a bug or a missing * +* feature, send me an email and/or your patch. If I accept * +* your changes, they will go to the next release. * +* * +* DISCLAIMER: If this software breaks something or harms * +* someone, it's your problem. * +* * +***************************************************************/ + +#include +#include +#include +#include + +static long readData (dbCommon *record, format_t *format) +{ + biRecord *bi = (biRecord *) record; + unsigned long val; + + switch (format->type) + { + case DBF_LONG: + { + if (streamScanf (record, format, &val)) return ERROR; + if (bi->mask) val &= bi->mask; + bi->rval = val; + return OK; + } + case DBF_ENUM: + { + if (streamScanf (record, format, &val)) return ERROR; + bi->val = (val != 0); + return DO_NOT_CONVERT; + } + case DBF_STRING: + { + char buffer[sizeof(bi->znam)]; + if (streamScanfN (record, format, buffer, sizeof(buffer))) + return ERROR; + if (strcmp (bi->znam, buffer) == 0) + { + bi->val = 0; + return DO_NOT_CONVERT; + } + if (strcmp (bi->onam, buffer) == 0) + { + bi->val = 1; + return DO_NOT_CONVERT; + } + } + } + return ERROR; +} + +static long writeData (dbCommon *record, format_t *format) +{ + biRecord *bi = (biRecord *) record; + + switch (format->type) + { + case DBF_LONG: + { + return streamPrintf (record, format, bi->rval); + } + case DBF_ENUM: + { + return streamPrintf (record, format, (long) bi->val); + } + case DBF_STRING: + { + return streamPrintf (record, format, + bi->val ? bi->onam : bi->znam); + } + } + return ERROR; +} + +static long initRecord (dbCommon *record) +{ + biRecord *bi = (biRecord *) record; + + return streamInitRecord (record, &bi->inp, readData, writeData); +} + +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN read; +} devbiStream = { + 5, + streamReport, + streamInit, + initRecord, + streamGetIointInfo, + streamRead +}; + +epicsExportAddress(dset,devbiStream); diff --git a/src/devboStream.c b/src/devboStream.c new file mode 100644 index 0000000..3aac446 --- /dev/null +++ b/src/devboStream.c @@ -0,0 +1,113 @@ +/*************************************************************** +* Stream Device record interface for binary output records * +* * +* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) * +* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) * +* * +* This is an EPICS record Interface for StreamDevice. * +* Please refer to the HTML files in ../doc/ for a detailed * +* documentation. * +* * +* If you do any changes in this file, you are not allowed to * +* redistribute it any more. If there is a bug or a missing * +* feature, send me an email and/or your patch. If I accept * +* your changes, they will go to the next release. * +* * +* DISCLAIMER: If this software breaks something or harms * +* someone, it's your problem. * +* * +***************************************************************/ + +#include +#include +#include +#include + +static long readData (dbCommon *record, format_t *format) +{ + boRecord *bo = (boRecord *) record; + unsigned long val; + + switch (format->type) + { + case DBF_LONG: + { + if (streamScanf (record, format, &val)) return ERROR; + if (bo->mask) val &= bo->mask; + bo->rbv = val; + if (INIT_RUN) bo->rval = val; + return OK; + } + case DBF_ENUM: + { + if (streamScanf (record, format, &val)) return ERROR; + bo->val = (val != 0); + return DO_NOT_CONVERT; + } + case DBF_STRING: + { + char buffer[sizeof(bo->znam)]; + if (streamScanfN (record, format, buffer, sizeof(buffer))) + return ERROR; + if (strcmp (bo->znam, buffer) == 0) + { + bo->val = 0; + return DO_NOT_CONVERT; + } + if (strcmp (bo->onam, buffer) == 0) + { + bo->val = 1; + return DO_NOT_CONVERT; + } + } + } + return ERROR; +} + +static long writeData (dbCommon *record, format_t *format) +{ + boRecord *bo = (boRecord *) record; + + switch (format->type) + { + case DBF_LONG: + { + return streamPrintf (record, format, bo->rval); + } + case DBF_ENUM: + { + return streamPrintf (record, format, (long) bo->val); + } + case DBF_STRING: + { + return streamPrintf (record, format, + bo->val ? bo->onam : bo->znam); + } + } + return ERROR; +} + +static long initRecord (dbCommon *record) +{ + boRecord *bo = (boRecord *) record; + + return streamInitRecord (record, &bo->out, readData, writeData); +} + +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN write; +} devboStream = { + 5, + streamReport, + streamInit, + initRecord, + streamGetIointInfo, + streamWrite +}; + +epicsExportAddress(dset,devboStream); diff --git a/src/devcalcoutStream.c b/src/devcalcoutStream.c new file mode 100644 index 0000000..ce698d1 --- /dev/null +++ b/src/devcalcoutStream.c @@ -0,0 +1,92 @@ +/*************************************************************** +* Stream Device record interface for calcout records * +* * +* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) * +* * +* This is an EPICS record Interface for StreamDevice. * +* Please refer to the HTML files in ../doc/ for a detailed * +* documentation. * +* * +* If you do any changes in this file, you are not allowed to * +* redistribute it any more. If there is a bug or a missing * +* feature, send me an email and/or your patch. If I accept * +* your changes, they will go to the next release. * +* * +* DISCLAIMER: If this software breaks something or harms * +* someone, it's your problem. * +* * +***************************************************************/ + +#include +#include +#include +#include + +static long readData (dbCommon *record, format_t *format) +{ + calcoutRecord *co = (calcoutRecord *) record; + + switch (format->type) + { + case DBF_DOUBLE: + { + return streamScanf (record, format, &co->val); + } + case DBF_LONG: + case DBF_ENUM: + { + long lval; + + if (streamScanf (record, format, &lval)) return ERROR; + co->val = lval; + return OK; + } + } + return ERROR; +} + +static long writeData (dbCommon *record, format_t *format) +{ + calcoutRecord *co = (calcoutRecord *) record; + + switch (format->type) + { + case DBF_DOUBLE: + { + return streamPrintf (record, format, co->oval); + } + case DBF_LONG: + case DBF_ENUM: + { + return streamPrintf (record, format, (long)co->oval); + } + } + return ERROR; +} + +static long initRecord (dbCommon *record) +{ + calcoutRecord *co = (calcoutRecord *) record; + + return streamInitRecord (record, &co->out, readData, writeData); +} + +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN write; + DEVSUPFUN special_linconv; +} devcalcoutStream = { + 6, + streamReport, + streamInit, + initRecord, + streamGetIointInfo, + streamWrite, + NULL +}; + +epicsExportAddress(dset,devcalcoutStream); diff --git a/src/devlonginStream.c b/src/devlonginStream.c new file mode 100644 index 0000000..dac7d86 --- /dev/null +++ b/src/devlonginStream.c @@ -0,0 +1,70 @@ +/*************************************************************** +* Stream Device record interface for long input records * +* * +* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) * +* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) * +* * +* This is an EPICS record Interface for StreamDevice. * +* Please refer to the HTML files in ../doc/ for a detailed * +* documentation. * +* * +* If you do any changes in this file, you are not allowed to * +* redistribute it any more. If there is a bug or a missing * +* feature, send me an email and/or your patch. If I accept * +* your changes, they will go to the next release. * +* * +* DISCLAIMER: If this software breaks something or harms * +* someone, it's your problem. * +* * +***************************************************************/ + +#include +#include +#include + +static long readData (dbCommon *record, format_t *format) +{ + longinRecord *li = (longinRecord *) record; + + if (format->type == DBF_LONG || format->type == DBF_ENUM) + { + return streamScanf (record, format, &li->val); + } + return ERROR; +} + +static long writeData (dbCommon *record, format_t *format) +{ + longinRecord *li = (longinRecord *) record; + + if (format->type == DBF_LONG || format->type == DBF_ENUM) + { + return streamPrintf (record, format, li->val); + } + return ERROR; +} + +static long initRecord (dbCommon *record) +{ + longinRecord *li = (longinRecord *) record; + + return streamInitRecord (record, &li->inp, readData, writeData); +} + +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN read; +} devlonginStream = { + 5, + streamReport, + streamInit, + initRecord, + streamGetIointInfo, + streamRead +}; + +epicsExportAddress(dset,devlonginStream); diff --git a/src/devlongoutStream.c b/src/devlongoutStream.c new file mode 100644 index 0000000..84863a3 --- /dev/null +++ b/src/devlongoutStream.c @@ -0,0 +1,71 @@ +/*************************************************************** +* Stream Device record interface for long output records * +* * +* * +* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) * +* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) * +* * +* This is an EPICS record Interface for StreamDevice. * +* Please refer to the HTML files in ../doc/ for a detailed * +* documentation. * +* * +* If you do any changes in this file, you are not allowed to * +* redistribute it any more. If there is a bug or a missing * +* feature, send me an email and/or your patch. If I accept * +* your changes, they will go to the next release. * +* * +* DISCLAIMER: If this software breaks something or harms * +* someone, it's your problem. * +* * +***************************************************************/ + +#include +#include +#include + +static long readData (dbCommon *record, format_t *format) +{ + longoutRecord *lo = (longoutRecord *) record; + + if (format->type == DBF_LONG || format->type == DBF_ENUM) + { + return streamScanf (record, format, &lo->val); + } + return ERROR; +} + +static long writeData (dbCommon *record, format_t *format) +{ + longoutRecord *lo = (longoutRecord *) record; + + if (format->type == DBF_LONG || format->type == DBF_ENUM) + { + return streamPrintf (record, format, lo->val); + } + return ERROR; +} + +static long initRecord (dbCommon *record) +{ + longoutRecord *lo = (longoutRecord *) record; + + return streamInitRecord (record, &lo->out, readData, writeData); +} + +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN write; +} devlongoutStream = { + 5, + streamReport, + streamInit, + initRecord, + streamGetIointInfo, + streamWrite +}; + +epicsExportAddress(dset,devlongoutStream); diff --git a/src/devmbbiDirectStream.c b/src/devmbbiDirectStream.c new file mode 100644 index 0000000..084519b --- /dev/null +++ b/src/devmbbiDirectStream.c @@ -0,0 +1,88 @@ +/*************************************************************** +* StreamDevice record interface for * +* multibit binary input direct records * +* * +* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) * +* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) * +* * +* This is an EPICS record Interface for StreamDevice. * +* Please refer to the HTML files in ../doc/ for a detailed * +* documentation. * +* * +* If you do any changes in this file, you are not allowed to * +* redistribute it any more. If there is a bug or a missing * +* feature, send me an email and/or your patch. If I accept * +* your changes, they will go to the next release. * +* * +* DISCLAIMER: If this software breaks something or harms * +* someone, it's your problem. * +* * +***************************************************************/ + +#include +#include +#include + +static long readData (dbCommon *record, format_t *format) +{ + mbbiDirectRecord *mbbiD = (mbbiDirectRecord *) record; + long val; + + if (format->type == DBF_LONG) + { + if (streamScanf (record, format, &val)) return ERROR; + if (mbbiD->mask) + { + val &= mbbiD->mask; + mbbiD->rval = val; + return OK; + } + else + { + /* No MASK, (NOBT = 0): use VAL field */ + mbbiD->val = (short)val; + return DO_NOT_CONVERT; + } + } + return ERROR; +} + +static long writeData (dbCommon *record, format_t *format) +{ + mbbiDirectRecord *mbbiD = (mbbiDirectRecord *) record; + long val; + + if (format->type == DBF_LONG) + { + if (mbbiD->mask) val = mbbiD->rval & mbbiD->mask; + else val = mbbiD->val; + return streamPrintf (record, format, val); + } + return ERROR; +} + +static long initRecord (dbCommon *record) +{ + mbbiDirectRecord *mbbiD = (mbbiDirectRecord *) record; + + mbbiD->mask <<= mbbiD->shft; + return streamInitRecord (record, &mbbiD->inp, readData, writeData); +} + +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN read; +} devmbbiDirectStream = { + 5, + streamReport, + streamInit, + initRecord, + streamGetIointInfo, + streamRead +}; + +epicsExportAddress(dset,devmbbiDirectStream); diff --git a/src/devmbbiStream.c b/src/devmbbiStream.c new file mode 100644 index 0000000..b8c0f6c --- /dev/null +++ b/src/devmbbiStream.c @@ -0,0 +1,136 @@ +/*************************************************************** +* StreamDevice record interface for * +* multibit binary input records * +* * +* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) * +* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) * +* * +* This is an EPICS record Interface for StreamDevice. * +* Please refer to the HTML files in ../doc/ for a detailed * +* documentation. * +* * +* If you do any changes in this file, you are not allowed to * +* redistribute it any more. If there is a bug or a missing * +* feature, send me an email and/or your patch. If I accept * +* your changes, they will go to the next release. * +* * +* DISCLAIMER: If this software breaks something or harms * +* someone, it's your problem. * +* * +***************************************************************/ + +#include +#include +#include +#include + +static long readData (dbCommon *record, format_t *format) +{ + mbbiRecord *mbbi = (mbbiRecord *) record; + long val; + int i; + + switch (format->type) + { + case DBF_LONG: + { + if (streamScanf (record, format, &val)) return ERROR; + /* read VAL or RBV? Look if any value is defined */ + if (mbbi->sdef) for (i=0; i<16; i++) + { + if ((&mbbi->zrvl)[i]) + { + if (mbbi->mask) val &= mbbi->mask; + mbbi->rval = val; + return OK; + } + } + mbbi->val = (short)val; + return DO_NOT_CONVERT; + } + case DBF_ENUM: + { + if (streamScanf (record, format, &val)) return ERROR; + mbbi->val = (short)val; + return DO_NOT_CONVERT; + } + case DBF_STRING: + { + char buffer[sizeof(mbbi->zrst)]; + if (streamScanfN (record, format, buffer, sizeof(buffer))) + return ERROR; + for (val = 0; val < 16; val++) + { + if (strcmp ((&mbbi->zrst)[val], buffer) == 0) + { + mbbi->val = (short)val; + return DO_NOT_CONVERT; + } + } + } + } + return ERROR; +} + +static long writeData (dbCommon *record, format_t *format) +{ + mbbiRecord *mbbi = (mbbiRecord *) record; + long val; + int i; + + switch (format->type) + { + case DBF_LONG: + { + /* print VAL or RVAL ? Look if any value is defined */ + val = mbbi->val; + if (mbbi->sdef) for (i=0; i<16; i++) + { + if ((&mbbi->zrvl)[i]) + { + val = mbbi->rval; + if (mbbi->mask) val &= mbbi->mask; + break; + } + } + return streamPrintf (record, format, val); + } + case DBF_ENUM: + { + return streamPrintf (record, format, (long) mbbi->val); + } + case DBF_STRING: + { + if (mbbi->val >= 16) return ERROR; + return streamPrintf (record, format, + mbbi->zrst + sizeof(mbbi->zrst) * mbbi->val); + } + } + return ERROR; +} + +static long initRecord (dbCommon *record) +{ + mbbiRecord *mbbi = (mbbiRecord *) record; + + mbbi->mask <<= mbbi->shft; + return streamInitRecord (record, &mbbi->inp, readData, writeData); +} + +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN read; +} devmbbiStream = { + 5, + streamReport, + streamInit, + initRecord, + streamGetIointInfo, + streamRead +}; + +epicsExportAddress(dset,devmbbiStream); diff --git a/src/devmbboDirectStream.c b/src/devmbboDirectStream.c new file mode 100644 index 0000000..436dfc9 --- /dev/null +++ b/src/devmbboDirectStream.c @@ -0,0 +1,89 @@ +/*************************************************************** +* StreamDevice record interface for * +* multibit binary output direct records * +* * +* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) * +* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) * +* * +* This is an EPICS record Interface for StreamDevice. * +* Please refer to the HTML files in ../doc/ for a detailed * +* documentation. * +* * +* If you do any changes in this file, you are not allowed to * +* redistribute it any more. If there is a bug or a missing * +* feature, send me an email and/or your patch. If I accept * +* your changes, they will go to the next release. * +* * +* DISCLAIMER: If this software breaks something or harms * +* someone, it's your problem. * +* * +***************************************************************/ + +#include +#include +#include + +static long readData (dbCommon *record, format_t *format) +{ + mbboDirectRecord *mbboD = (mbboDirectRecord *) record; + long val; + + if (format->type == DBF_LONG) + { + if (streamScanf (record, format, &val)) return ERROR; + if (mbboD->mask) + { + val &= mbboD->mask; + mbboD->rbv = val; + if (INIT_RUN) mbboD->rval = val; + return OK; + } + else + { + /* No MASK, (NOBT = 0): use VAL field */ + mbboD->val = (short)val; + return DO_NOT_CONVERT; + } + } + return ERROR; +} + +static long writeData (dbCommon *record, format_t *format) +{ + mbboDirectRecord *mbboD = (mbboDirectRecord *) record; + long val; + + if (format->type == DBF_LONG) + { + if (mbboD->mask) val = mbboD->rval & mbboD->mask; + else val = mbboD->val; + return streamPrintf (record, format, val); + } + return ERROR; +} + +static long initRecord (dbCommon *record) +{ + mbboDirectRecord *mbboD = (mbboDirectRecord *) record; + + mbboD->mask <<= mbboD->shft; + return streamInitRecord (record, &mbboD->out, readData, writeData); +} + +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN write; +} devmbboDirectStream = { + 5, + streamReport, + streamInit, + initRecord, + streamGetIointInfo, + streamWrite +}; + +epicsExportAddress(dset,devmbboDirectStream); diff --git a/src/devmbboStream.c b/src/devmbboStream.c new file mode 100644 index 0000000..e7b9e73 --- /dev/null +++ b/src/devmbboStream.c @@ -0,0 +1,138 @@ +/*************************************************************** +* StreamDevice record interface for * +* multibit binary output records * +* * +* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) * +* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) * +* * +* This is an EPICS record Interface for StreamDevice. * +* Please refer to the HTML files in ../doc/ for a detailed * +* documentation. * +* * +* If you do any changes in this file, you are not allowed to * +* redistribute it any more. If there is a bug or a missing * +* feature, send me an email and/or your patch. If I accept * +* your changes, they will go to the next release. * +* * +* DISCLAIMER: If this software breaks something or harms * +* someone, it's your problem. * +* * +***************************************************************/ + +#include +#include +#include +#include + +static long readData (dbCommon *record, format_t *format) +{ + mbboRecord *mbbo = (mbboRecord *) record; + long val; + int i; + + switch (format->type) + { + case DBF_LONG: + { + if (streamScanf (record, format, &val)) return ERROR; + /* read VAL or RBV? Look if any value is defined */ + if (mbbo->sdef) for (i=0; i<16; i++) + { + if ((&mbbo->zrvl)[i]) + { + if (mbbo->mask) val &= mbbo->mask; + mbbo->rbv = val; + if (INIT_RUN) mbbo->rval = val; + return OK; + } + } + mbbo->val = (short)val; + return DO_NOT_CONVERT; + } + case DBF_ENUM: + { + if (streamScanf (record, format, &val)) return ERROR; + mbbo->val = (short)val; + return DO_NOT_CONVERT; + } + case DBF_STRING: + { + char buffer[sizeof(mbbo->zrst)]; + if (streamScanfN (record, format, buffer, sizeof(buffer))) + return ERROR; + for (val = 0; val < 16; val++) + { + if (strcmp ((&mbbo->zrst)[val], buffer) == 0) + { + mbbo->val = (short)val; + return DO_NOT_CONVERT; + } + } + } + } + return ERROR; +} + +static long writeData (dbCommon *record, format_t *format) +{ + mbboRecord *mbbo = (mbboRecord *) record; + long val; + int i; + + switch (format->type) + { + case DBF_LONG: + { + /* print VAL or RVAL ? */ + val = mbbo->val; + if (mbbo->sdef) for (i=0; i<16; i++) + { + if ((&mbbo->zrvl)[i]) + { + /* any values defined ? */ + val = mbbo->rval; + if (mbbo->mask) val &= mbbo->mask; + break; + } + } + return streamPrintf (record, format, val); + } + case DBF_ENUM: + { + return streamPrintf (record, format, (long) mbbo->val); + } + case DBF_STRING: + { + if (mbbo->val >= 16) return ERROR; + return streamPrintf (record, format, + mbbo->zrst + sizeof(mbbo->zrst) * mbbo->val); + } + } + return ERROR; +} + +static long initRecord (dbCommon *record) +{ + mbboRecord *mbbo = (mbboRecord *) record; + + mbbo->mask <<= mbbo->shft; + return streamInitRecord (record, &mbbo->out, readData, writeData); +} + +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN write; +} devmbboStream = { + 5, + streamReport, + streamInit, + initRecord, + streamGetIointInfo, + streamWrite +}; + +epicsExportAddress(dset,devmbboStream); diff --git a/src/devstringinStream.c b/src/devstringinStream.c new file mode 100644 index 0000000..0fd6f69 --- /dev/null +++ b/src/devstringinStream.c @@ -0,0 +1,70 @@ +/*************************************************************** +* Stream Device record interface for string input records * +* * +* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) * +* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) * +* * +* This is an EPICS record Interface for StreamDevice. * +* Please refer to the HTML files in ../doc/ for a detailed * +* documentation. * +* * +* If you do any changes in this file, you are not allowed to * +* redistribute it any more. If there is a bug or a missing * +* feature, send me an email and/or your patch. If I accept * +* your changes, they will go to the next release. * +* * +* DISCLAIMER: If this software breaks something or harms * +* someone, it's your problem. * +* * +***************************************************************/ + +#include +#include +#include + +static long readData (dbCommon *record, format_t *format) +{ + stringinRecord *si = (stringinRecord *) record; + + if (format->type == DBF_STRING) + { + return streamScanfN (record, format, si->val, sizeof(si->val)); + } + return ERROR; +} + +static long writeData (dbCommon *record, format_t *format) +{ + stringinRecord *si = (stringinRecord *) record; + + if (format->type == DBF_STRING) + { + return streamPrintf (record, format, si->val); + } + return ERROR; +} + +static long initRecord (dbCommon *record) +{ + stringinRecord *si = (stringinRecord *) record; + + return streamInitRecord (record, &si->inp, readData, writeData); +} + +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN read; +} devstringinStream = { + 5, + streamReport, + streamInit, + initRecord, + streamGetIointInfo, + streamRead +}; + +epicsExportAddress(dset,devstringinStream); diff --git a/src/devstringoutStream.c b/src/devstringoutStream.c new file mode 100644 index 0000000..39a72ec --- /dev/null +++ b/src/devstringoutStream.c @@ -0,0 +1,70 @@ +/*************************************************************** +* Stream Device record interface for string output records * +* * +* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) * +* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) * +* * +* This is an EPICS record Interface for StreamDevice. * +* Please refer to the HTML files in ../doc/ for a detailed * +* documentation. * +* * +* If you do any changes in this file, you are not allowed to * +* redistribute it any more. If there is a bug or a missing * +* feature, send me an email and/or your patch. If I accept * +* your changes, they will go to the next release. * +* * +* DISCLAIMER: If this software breaks something or harms * +* someone, it's your problem. * +* * +***************************************************************/ + +#include +#include +#include + +static long readData (dbCommon *record, format_t *format) +{ + stringoutRecord *so = (stringoutRecord *) record; + + if (format->type == DBF_STRING) + { + return streamScanfN (record, format, so->val, sizeof(so->val)); + } + return ERROR; +} + +static long writeData (dbCommon *record, format_t *format) +{ + stringoutRecord *so = (stringoutRecord *) record; + + if (format->type == DBF_STRING) + { + return streamPrintf (record, format, so->val); + } + return ERROR; +} + +static long initRecord (dbCommon *record) +{ + stringoutRecord *so = (stringoutRecord *) record; + + return streamInitRecord (record, &so->out, readData, writeData); +} + +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN write; +} devstringoutStream = { + 5, + streamReport, + streamInit, + initRecord, + streamGetIointInfo, + streamWrite +}; + +epicsExportAddress(dset,devstringoutStream); diff --git a/src/devwaveformStream.c b/src/devwaveformStream.c new file mode 100644 index 0000000..5fe3c89 --- /dev/null +++ b/src/devwaveformStream.c @@ -0,0 +1,293 @@ +/*************************************************************** +* StreamDevice record interface for waveform records * +* * +* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) * +* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) * +* * +* This is an EPICS record Interface for StreamDevice. * +* Please refer to the HTML files in ../doc/ for a detailed * +* documentation. * +* * +* If you do any changes in this file, you are not allowed to * +* redistribute it any more. If there is a bug or a missing * +* feature, send me an email and/or your patch. If I accept * +* your changes, they will go to the next release. * +* * +* DISCLAIMER: If this software breaks something or harms * +* someone, it's your problem. * +* * +***************************************************************/ + +#include +#include +#include +#include + +static long readData (dbCommon *record, format_t *format) +{ + waveformRecord *wf = (waveformRecord *) record; + double dval; + long lval; + + wf->rarm = 0; + for (wf->nord = 0; wf->nord < wf->nelm; wf->nord++) + { + switch (format->type) + { + case DBF_DOUBLE: + { + if (streamScanf (record, format, &dval) != OK) + { + return wf->nord ? OK : ERROR; + } + switch (wf->ftvl) + { + case DBF_DOUBLE: + ((double *)wf->bptr)[wf->nord] = dval; + break; + case DBF_FLOAT: + ((float *)wf->bptr)[wf->nord] = (float)dval; + break; + default: + errlogSevPrintf (errlogFatal, + "readData %s: can't convert from double to %s\n", + record->name, pamapdbfType[wf->ftvl].strvalue); + return ERROR; + } + break; + } + case DBF_LONG: + case DBF_ENUM: + { + if (streamScanf (record, format, &lval) != OK) + { + return wf->nord ? OK : ERROR; + } + switch (wf->ftvl) + { + case DBF_DOUBLE: + ((double *)wf->bptr)[wf->nord] = (double)lval; + break; + case DBF_FLOAT: + ((float *)wf->bptr)[wf->nord] = (float)lval; + break; + case DBF_LONG: + case DBF_ULONG: + ((long *)wf->bptr)[wf->nord] = lval; + break; + case DBF_SHORT: + case DBF_USHORT: + case DBF_ENUM: + ((short *)wf->bptr)[wf->nord] = (short)lval; + break; + case DBF_CHAR: + case DBF_UCHAR: + ((char *)wf->bptr)[wf->nord] = (char)lval; + break; + default: + errlogSevPrintf (errlogFatal, + "readData %s: can't convert from long to %s\n", + record->name, pamapdbfType[wf->ftvl].strvalue); + return ERROR; + } + break; + } + case DBF_STRING: + { + switch (wf->ftvl) + { + case DBF_STRING: + if (streamScanfN (record, format, + (char *)wf->bptr + wf->nord * MAX_STRING_SIZE, + MAX_STRING_SIZE) != OK) + { + return wf->nord ? OK : ERROR; + } + break; + case DBF_CHAR: + case DBF_UCHAR: + memset (wf->bptr, 0, wf->nelm); + wf->nord = 0; + if (streamScanfN (record, format, + (char *)wf->bptr, wf->nelm) != OK) + { + return ERROR; + } + ((char*)wf->bptr)[wf->nelm] = 0; + for (lval = wf->nelm; + lval >= 0 && ((char*)wf->bptr)[lval] == 0; + lval--); + wf->nord = lval+1; + return OK; + default: + errlogSevPrintf (errlogFatal, + "readData %s: can't convert from string to %s\n", + record->name, pamapdbfType[wf->ftvl].strvalue); + return ERROR; + } + break; + } + default: + { + errlogSevPrintf (errlogMajor, + "readData %s: can't convert from %s to %s\n", + record->name, pamapdbfType[format->type].strvalue, + pamapdbfType[wf->ftvl].strvalue); + return ERROR; + } + } + } + return OK; +} + +static long writeData (dbCommon *record, format_t *format) +{ + waveformRecord *wf = (waveformRecord *) record; + double dval; + long lval; + unsigned long nowd; + + for (nowd = 0; nowd < wf->nord; nowd++) + { + switch (format->type) + { + case DBF_DOUBLE: + { + switch (wf->ftvl) + { + case DBF_DOUBLE: + dval = ((double *)wf->bptr)[nowd]; + break; + case DBF_FLOAT: + dval = ((float *)wf->bptr)[nowd]; + break; + case DBF_LONG: + dval = ((long *)wf->bptr)[nowd]; + break; + case DBF_ULONG: + dval = ((unsigned long *)wf->bptr)[nowd]; + break; + case DBF_SHORT: + dval = ((short *)wf->bptr)[nowd]; + break; + case DBF_USHORT: + case DBF_ENUM: + dval = ((unsigned short *)wf->bptr)[nowd]; + break; + case DBF_CHAR: + dval = ((char *)wf->bptr)[nowd]; + break; + case DBF_UCHAR: + dval = ((unsigned char *)wf->bptr)[nowd]; + break; + default: + errlogSevPrintf (errlogFatal, + "writeData %s: can't convert from %s to double\n", + record->name, pamapdbfType[wf->ftvl].strvalue); + return ERROR; + } + if (streamPrintf (record, format, dval)) + return ERROR; + break; + } + case DBF_LONG: + case DBF_ENUM: + { + switch (wf->ftvl) + { + case DBF_LONG: + case DBF_ULONG: + lval = ((long *)wf->bptr)[nowd]; + break; + case DBF_SHORT: + lval = ((short *)wf->bptr)[nowd]; + break; + case DBF_USHORT: + case DBF_ENUM: + lval = ((unsigned short *)wf->bptr)[nowd]; + break; + case DBF_CHAR: + lval = ((char *)wf->bptr)[nowd]; + break; + case DBF_UCHAR: + lval = ((unsigned char *)wf->bptr)[nowd]; + break; + default: + errlogSevPrintf (errlogFatal, + "writeData %s: can't convert from %s to long\n", + record->name, pamapdbfType[wf->ftvl].strvalue); + return ERROR; + } + if (streamPrintf (record, format, lval)) + return ERROR; + break; + } + case DBF_STRING: + { + switch (wf->ftvl) + { + case DBF_STRING: + if (streamPrintf (record, format, + ((char *)wf->bptr) + nowd * MAX_STRING_SIZE)) + return ERROR; + break; + case DBF_CHAR: + case DBF_UCHAR: + /* print waveform as a null-terminated string */ + if (wf->nord < wf->nelm) + { + ((char *)wf->bptr)[wf->nord] = 0; + } + else + { + ((char *)wf->bptr)[wf->nelm-1] = 0; + } + if (streamPrintf (record, format, + ((char *)wf->bptr))) + return ERROR; + return OK; + default: + errlogSevPrintf (errlogFatal, + "writeData %s: can't convert from %s to string\n", + record->name, pamapdbfType[wf->ftvl].strvalue); + return ERROR; + } + break; + } + default: + { + errlogSevPrintf (errlogFatal, + "writeData %s: can't convert from %s to %s\n", + record->name, pamapdbfType[wf->ftvl].strvalue, + pamapdbfType[format->type].strvalue); + return ERROR; + } + } + } + return OK; +} + +static long initRecord (dbCommon *record) +{ + waveformRecord *wf = (waveformRecord *) record; + + return streamInitRecord (record, &wf->inp, readData, writeData); +} + +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN read; +} devwaveformStream = { + 5, + streamReport, + streamInit, + initRecord, + streamGetIointInfo, + streamRead +}; + +epicsExportAddress(dset,devwaveformStream); diff --git a/src/memguard.cc b/src/memguard.cc new file mode 100644 index 0000000..e8008ec --- /dev/null +++ b/src/memguard.cc @@ -0,0 +1,204 @@ +/*************************************************************** +* memguard * +* * +* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) * +* * +* Include memguard.h and link memguard.o to get some memory * +* guardiance. Allocated memory (using new) is tracked with * +* source file and line of the new operator. Deleted memory is * +* checked for duplicate deletion. * +* Call memguardReport() to see all allocated memory. * +* Calling memguardReport() shows currently allocated memory. * +* * +* DISCLAIMER: If this software breaks something or harms * +* someone, it's your problem. * +* * +***************************************************************/ + +#undef new +#undef delete + +#include +#include +#include + +struct memheader +{ + static memheader* first; + memheader* next; + size_t size; + long line; + const char* file; + long fill1; + long fill2; + unsigned long magic; +}; + +extern "C" void memguardReport(); + +class memguard +{ +public: + memguard() {atexit(memguardReport);} + void report(); +}; + +memheader* memheader::first = NULL; + +static memguard guard; + +extern "C" void memguardReport() +{ + guard.report(); +} + +void* operator new(size_t size, const char* file, long line) throw (std::bad_alloc) +{ + memheader* header; + memheader** prev; + + header = (memheader*) malloc(size + sizeof(memheader)); + if (!header) + { + fprintf (stderr, "out of memory\n"); + if (file) fprintf (stderr, "in %s line %ld\n", file, line); + guard.report(); + abort(); + } + header->magic = 0xA110caed; + for (prev = &memheader::first; *prev; prev = &(*prev)->next); + *prev = header; + header->next = NULL; + header->size = size; + header->line = line; + header->file = file; +// fprintf(stderr, "allocating %p: %d bytes for %s line %ld\n", +// header+1, size, file, line); + return header+1; +} + + +void* operator new[](size_t size, const char* file, long line) throw (std::bad_alloc) +{ + return operator new(size, file, line); +} + +void* operator new(size_t size) throw (std::bad_alloc) +{ + return operator new(size, NULL, 0); +} + +void* operator new[](size_t size) throw (std::bad_alloc) +{ + return operator new(size, NULL, 0); +} + +static const char* deleteFile = NULL; +static long deleteLine = 0; + +int memguardLocation(const char* file, long line) +{ + deleteFile = file; + deleteLine = line; + return 1; +} + +void operator delete (void* memory) throw () +{ + memheader* header; + memheader** prev; + + if (!memory) + { + deleteFile = NULL; + deleteLine = 0; + return; + } + header = ((memheader*)memory)-1; + if (header->magic == 0xDeadBeef) + { + fprintf (stderr, "memory at %p deleted twice\n", memory); + if (header->file) + fprintf (stderr, "allocated by %s line %ld\n", + header->file, header->line); + guard.report(); + abort(); + } + for (prev = &memheader::first; *prev; prev = &(*prev)->next) + { + if (*prev == header) + { + if (header->magic != 0xA110caed) + { + fprintf (stderr, "deleted memory at %p corrupt\n", memory); + if (header->file) + fprintf (stderr, "allocated by %s line %ld\n", + header->file, header->line); + guard.report(); + abort(); + } + *prev = header->next; + header->magic = 0xDeadBeef; +// fprintf(stderr, "deleting %p ", memory); +// if (header->file) fprintf(stderr, "allocated in %s line %ld\n", +// header->file, header->line); +// if (deleteFile) fprintf(stderr, "probably deleted in %s line %ld\n", +// deleteFile, deleteLine); + deleteFile = NULL; + deleteLine = 0; + free(header); +// fprintf(stderr, "done\n"); + return; + } + } + if (deleteFile) + { + fprintf (stderr, "deleted memory at %p was never allocated\n", memory); + fprintf (stderr, "delete was probably called in %s line %ld\n", + deleteFile, deleteLine); + abort(); + } +} + +void operator delete[](void* memory) throw () +{ + operator delete(memory); +} + +void memguard::report() +{ + memheader* header; + unsigned long sum = 0; + unsigned long chunks = 0; + + if (memheader::first) + { + fprintf(stderr, "allocated memory:\n"); + for (header = memheader::first; header; header = header->next) + { + chunks++; + sum += header->size; + if (header->file) + { + fprintf (stderr, "%p: %d bytes by %s line %ld\n", + header+1, header->size, header->file, header->line); + } + else + { + fprintf (stderr, "%p: %d bytes by unknown\n", + header+1, header->size); + } + if (header->magic != 0xA110caed) + { + fprintf (stderr, "memory list is corrupt\n"); + abort(); + } + } + fprintf (stderr, "%lu bytes in %lu chunks\n", sum, chunks); + } + else + { + fprintf(stderr, "no memory allocated\n"); + } +} + diff --git a/src/memguard.h b/src/memguard.h new file mode 100644 index 0000000..5bd030a --- /dev/null +++ b/src/memguard.h @@ -0,0 +1,30 @@ +/*************************************************************** +* memguard * +* * +* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) * +* * +* Include memguard.h and link memguard.o to get some memory * +* guardiance. Allocated memory (using new) is tracked with * +* source file and line of the new operator. Deleted memory is * +* checked for duplicate deletion. * +* Call memguardReport() to see all allocated memory. * +* Calling memguardReport() shows currently allocated memory. * +* * +* DISCLAIMER: If this software breaks something or harms * +* someone, it's your problem. * +* * +***************************************************************/ + +#ifdef __cplusplus +#ifndef memguard_h +#define memguard_h +#include +#define MEMGUARD +void* operator new(size_t, const char*, long) throw (std::bad_alloc); +void* operator new[](size_t, const char*, long) throw (std::bad_alloc); +#define new new(__FILE__,__LINE__) +int memguardLocation(const char*, long); +#define delete if (memguardLocation(__FILE__,__LINE__)) delete +extern "C" void memguardReport(); +#endif +#endif diff --git a/src/munch.pl b/src/munch.pl new file mode 100644 index 0000000..02aaade --- /dev/null +++ b/src/munch.pl @@ -0,0 +1,84 @@ +eval 'exec perl -S $0 ${1+"$@"}' # -*- Mode: perl -*- + if $running_under_some_shell; # makeConfigAppInclude.pl +#************************************************************************* +# Copyright (c) 2002 The University of Chicago, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE Versions 3.13.7 +# and higher are distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + +# Creates a ctdt.c file of c++ static constructors and destructors. +# munch.pl,v 1.10 2002/12/16 20:25:53 jba Exp + +@ctorlist = (); +@dtorlist = (); + +while ($line = ) +{ + chomp; + next if ($line =~ /__?GLOBAL_.F.+/); + next if ($line =~ /__?GLOBAL_.I._GLOBAL_.D.+/); + if ($line =~ /__?GLOBAL_.D.+/) { + ($adr,$type,$name) = split ' ',$line,3; + $name =~ s/^__/_/; + @dtorlist = (@dtorlist,$name); + }; + if ($line =~ /__?GLOBAL_.I.+/) { + ($adr,$type,$name) = split ' ',$line,3; + $name =~ s/^__/_/; + @ctorlist = (@ctorlist,$name); + }; +} + +foreach $ctor (@ctorlist) +{ + if ( $ctor =~ /\./ ) { + printf "void %s() __asm__ (\"_%s\");\n",convertName($ctor),$ctor; + } else { + printf "void %s();\n",$ctor; + } +} + +print "extern void (*_ctors[])();\n"; +print "void (*_ctors[])() = {\n"; +foreach $ctor (@ctorlist) +{ + if ( $ctor =~ /\./ ) { + printf " %s,\n",convertName($ctor); + } else { + printf " %s,\n",$ctor; + } +} +print " 0};\n"; + + +foreach $dtor (@dtorlist) +{ + if ( $dtor =~ /\./ ) { + printf "void %s() __asm__ (\"_%s\");\n",convertName($dtor),$dtor; + } else { + printf "void %s();\n",$dtor; + } +} + +print "extern void (*_ctors[])();\n"; +print "void (*_dtors[])() = {\n"; +foreach $dtor (@dtorlist) +{ + if ( $dtor =~ /\./ ) { + printf " %s,\n",convertName($dtor); + } else { + printf " %s,\n",$dtor; + } +} +print " 0};\n"; + +sub convertName { + my ($name) = @_; + $name =~ s/\./\$/g; + return $name; +} +