Compare commits

...

12 Commits

Author SHA1 Message Date
20dde4c1d8 typo
Some checks failed
StreamDevice / Native Linux with 3.14 (push) Has been cancelled
StreamDevice / Native Linux with 3.15 (push) Has been cancelled
StreamDevice / Native Linux with clang (push) Has been cancelled
StreamDevice / OSX (push) Has been cancelled
StreamDevice / Native Linux (WError) (push) Has been cancelled
StreamDevice / Cross mingw64 DLL (push) Has been cancelled
StreamDevice / RTEMS 4.10 (push) Has been cancelled
StreamDevice / Cross mingw64 static (push) Has been cancelled
StreamDevice / vs2019 DLL (push) Has been cancelled
StreamDevice / vs2019 static (push) Has been cancelled
2025-05-27 17:09:16 +02:00
f61e6404f5 checksums after regsub must check original input 2025-05-27 16:57:58 +02:00
9080d6ca8e typo fixed 2025-05-23 15:28:52 +02:00
13e7f2d3dc Merge pull request #95 from henriquesimoes/scalcout-doc
Document stream-base.dbd usage when sCalcout is not needed
2025-05-22 18:42:22 +02:00
e87e093c84 in recent asyn versions vxi11 is optional 2025-05-06 14:48:59 +02:00
7debc86514 added checksum for Spellman High Voltage Supplies MPS series 2025-01-10 15:47:22 +01:00
668d1d5255 add some per-record debugging 2024-06-06 13:49:15 +02:00
ae5ca0c45b allow to use the StreamDebugClass without file name or line number 2024-06-06 11:29:09 +02:00
b00099973f don't need the wrapper function 2024-06-06 11:29:09 +02:00
5bf5cb9a67 example terminal.tcl uses LF terminator 2024-06-06 11:29:09 +02:00
f6848f0503 Update .ci 2024-03-20 10:17:54 +01:00
2b3e4189c1 Document stream-base.dbd usage when scalcout is not needed
Having StreamDevice built with scalcout support does not require that
the application also depend on calc if it does not use this record
type. But this is only true if we load `stream-base.dbd` instead of
`stream.dbd`. Therefore, this alternative database definition should be
documented in the setup page.
2023-06-26 10:43:59 -03:00
14 changed files with 126 additions and 53 deletions

2
.ci

Submodule .ci updated: 93062ba941...dead44c3cb

View File

@ -485,6 +485,12 @@ than 9.
<p>
This is not a normal "converter", because no user data is converted.
Instead, a checksum is calculated from the input or output.
<span class="new">
Any pre-processing of input, e.g. by the <a href="#regsub">regsub</a> converter
is ignored for the calculation of the checksum.
</span>
</p>
<p>
The <em>width</em> 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
@ -711,10 +717,14 @@ However if an empty string is matched, searching advances by 1 character in orde
avoid matching the same empty string again.</span>
</p>
<p>
In input this converter pre-processes data received from the device before
In input, this converter pre-processes data received from the device before
following converters read it.
Converters preceding this one will read unmodified input.
Thus place this converter before those whose input should be pre-processed.
<span class="new">
However, <a href="#chksum">checksum</a> converters will always use the unmodified
input as sent by the device because the modified input would not match the checksum.
</span>
</p>
<p>
In output it post-processes data already formatted by preceding converters

View File

@ -85,7 +85,7 @@ Make sure that the <em>asyn</em> library can be found by adding the path to the
ASYN=/home/epics/asyn4-30
</pre>
<h4>Support for <em>sCalcout</em> record</h4>
<h4 id="scalcout">Support for <em>sCalcout</em> record</h4>
<p>
The <a
href="https://htmlpreview.github.io/?https://raw.githubusercontent.com/epics-modules/calc/R3-6-1/documentation/sCalcoutRecord.html"
@ -109,7 +109,10 @@ modules. Release R2-8 or newer is recommended.
</p>
<p>
Support for the <em>sCalcout</em> is optional. <em>StreamDevice</em> works
as well without <em>sCalcout</em> or <em>SynApps</em>.
as well without <em>sCalcout</em> or <em>SynApps</em>. If your application does
not need this record support, you may load <kbd>stream-base.dbd</kbd> instead of
<kbd>stream.dbd</kbd>, making it optional to include <em>calc</em> as an
application dependency.
</p>
<h4>Support for regular expression matching</h4>
@ -185,13 +188,14 @@ Regular expressions are optional. If you don't want them, you don't need this.
Go to the <em>StreamDevice</em> directory
and run <code>make</code> (or <code>gmake</code>).
This will create and install the <em>stream</em> library and the
<kbd>stream.dbd</kbd> file and an example IOC application.
<em>StreamDevice</em> database definition files and an example IOC application.
</p>
<p>
To use <em>StreamDevice</em>, your own application must be built with the
<em>stream</em> and <em>asyn</em> (and optionally <em>pcre</em>) libraries
and must load <kbd>asyn.dbd</kbd> and <kbd>stream.dbd</kbd>.
and must load <kbd>asyn.dbd</kbd> and <kbd>stream.dbd</kbd> (or alternatively
<kbd>stream-base.dbd</kbd>; see <a href="#scalcout">Support for sCalcout record</a>).
</p>
<p>
Include the following lines in your application <kbd>Makefile</kbd>:
@ -385,35 +389,45 @@ See the <a href="protocol.html">next chapter</a> for protocol files in depth.
Generation of debug and error messages is controlled with two shell variables,
<code>streamDebug</code> and <code>streamError</code>.
Setting those variables to 1 (actually to any number but 0) enables the
messages.
messages. A few noisy and rarely useful debug messages are only enabled when
setting <code>streamDebug</code> to 2.
Per default debug messages are switched off and error messages are switched on.
Errors occuring while loading protocol files are always shown.
</p>
<p>
Warning: Enabling debug messages can create a lot of output!
At the moment, there is no way to set filters on debug or error messages.
Warning: Enabling debug messages this way can create a lot of output!
Therefore, some limited debugging can be enabled per record, independent of
the <code>streamDebug</code> variable using the <code>.TPRO</code> field of
the record. Currently, setting <code>.TPRO</code> to 1 or 2 enables some
basic information about the processing of a record and its i/o.
</p>
<p>
Debug output can be redirected to a file with the command
<code>streamSetLogfile("<var>filename</var>")</code>.
When called without a filename, debug output is directed back
to the console.
If the file already exists, it will be overwritten, not appended to.
While debug messages are only written to the defined log file, error messages
are still printed to <var>stderr</var> too.
Calling <code>streamSetLogfile</code> without a filename directs debug output
back to <var>stderr</var> and closes the log file.
</p>
<p>
By default the debug/error output is set to be colored if the terminal allows
it but this can be set to always colored or never colored by setting
<code>streamDebugColored</code> to 1 or 0 respectively.
By default, error messages to the console are printed in red color if
<var>stderr</var> is a tty at startup time, using ANSI color codes. Some
terminals may not support this properly.
The variable <code>streamDebugColored</code> can be set to 0 or 1 to
disable or enable colored error messages explicitly.
Error messages written to a log file do not use colors.
</p>
<p>
Error and debug messages are prefixed with a time stamp unless the variable
<code>streamMsgTimeStamped</code> is set to 0.
</p>
<p>
When a device is disconnected StreamDevice can produce many repeated timeout
messages. To reduce this logging you can set <code>streamErrorDeadTime</code>
to an integer number of seconds. When this is set repeated timeout messages
will not be printed in the specified dead time after the last message. The
default dead time is 0, resulting in every message being printed.
when a device is unresponsive, StreamDevice may produce many repeated timeout
messages. To reduce this, you can set <code>streamErrorDeadTime</code>
to an integer number of seconds. In this case, repeated timeout messages
will not be printed during the specified dead time after the last printed
message. The default dead time is 0, resulting in every message being printed.
</p>
<h3>Example (vxWorks):</h3>

View File

@ -616,6 +616,16 @@ static uint32_t hexlrc(const uint8_t* data, size_t len, uint32_t sum)
return sum;
}
// Checksum used by Spellman High Voltage Supplies MPS
static uint32_t hv_mps(const uint8_t* data, size_t len, uint32_t sum)
{
while (len--)
{
sum += *data++;
}
return (~sum & 0x7F) | 0x40;
}
struct checksum
{
const char* name;
@ -665,19 +675,20 @@ static checksum checksumMap[] =
{"bitsum8", bitsum, 0x00, 0x00, 1}, // 0x21
{"bitsum16",bitsum, 0x0000, 0x0000, 2}, // 0x0021
{"bitsum32",bitsum, 0x00000000, 0x00000000, 4}, // 0x00000021
{"hv_mps", hv_mps, 0xFF, 0x00, 1} // 0x63
};
static uint32_t mask[5] = {0, 0xFF, 0xFFFF, 0xFFFFFF, 0xFFFFFFFF};
class ChecksumConverter : public StreamFormatConverter
{
int parse (const StreamFormat&, StreamBuffer&, const char*&, bool);
int parse (const StreamFormat&, StreamBuffer&, const char*&, bool scanFormat);
bool printPseudo(const StreamFormat&, StreamBuffer&);
ssize_t scanPseudo(const StreamFormat&, StreamBuffer&, size_t& cursor);
};
int ChecksumConverter::
parse(const StreamFormat&, StreamBuffer& info, const char*& source, bool)
parse(const StreamFormat&, StreamBuffer& info, const char*& source, bool scanFormat)
{
const char* p = strchr(source, '>');
if (!p)
@ -731,7 +742,7 @@ parse(const StreamFormat&, StreamBuffer& info, const char*& source, bool)
info.append(&xorout, sizeof(xorout));
info.append(fnum);
source = p+1;
return pseudo_format;
return scanFormat ? needs_original_format : pseudo_format;
}
}
@ -836,7 +847,7 @@ scanPseudo(const StreamFormat& format, StreamBuffer& input, size_t& cursor)
else length = 0;
}
debug("ChecksumConverter %s: input to check: \"%s\n",
debug("ChecksumConverter %s: input to check: \"%s\"\n",
checksumMap[fnum].name, input.expand(start,length)());
uint8_t nDigits =
@ -848,8 +859,8 @@ scanPseudo(const StreamFormat& format, StreamBuffer& input, size_t& cursor)
if ((ssize_t)( input.length() - cursor ) < expectedLength)
{
debug("ChecksumConverter %s: Input '%s' too short for checksum\n",
checksumMap[fnum].name, input.expand(cursor)());
debug("ChecksumConverter %s: Input '%s' too short (%zu-%zu<%zu) for checksum\n",
checksumMap[fnum].name, input.expand(cursor)(), input.length(), cursor, expectedLength);
return -1;
}

View File

@ -603,6 +603,7 @@ evalOut()
// flush all unread input
unparsedInput = false;
inputBuffer.clear();
inputLine.clear();
if (!formatOutput())
{
finishProtocol(FormatError);
@ -1011,6 +1012,7 @@ readCallback(StreamIoStatus status,
finishProtocol(Fault);
return 0;
}
inputHook(input, size);
inputBuffer.append(input, size);
debug("StreamCore::readCallback(%s) inputBuffer=\"%s\", size %" Z "u\n",
name(), inputBuffer.expand()(), inputBuffer.length());
@ -1130,7 +1132,7 @@ readCallback(StreamIoStatus status,
inputBuffer.remove(end + termlen);
if (inputBuffer)
{
debug("StreamCore::readCallback(%s) unpared input left: \"%s\"\n",
debug("StreamCore::readCallback(%s) unparsed input left: \"%s\"\n",
name(), inputBuffer.expand()());
unparsedInput = true;
}
@ -1182,6 +1184,7 @@ matchInput()
char command;
const char* fieldName = NULL;
StreamBuffer formatstring;
ssize_t delta = 0;
consumedInput = 0;
@ -1216,7 +1219,7 @@ normal_format:
debug("StreamCore::matchInput(%s): format = \"%%%s\"\n",
name(), formatstring());
if (fmt.flags & skip_flag || fmt.type == pseudo_format)
if (fmt.flags & skip_flag || fmt.type == pseudo_format || fmt.type == needs_original_format)
{
long ldummy;
double ddummy;
@ -1238,9 +1241,20 @@ normal_format:
scanString(fmt, inputLine(consumedInput), NULL, size);
break;
case pseudo_format:
// pass complete input
// pass complete input line for scan and/or re-write
size = inputLine.length();
consumed = StreamFormatConverter::find(fmt.conv)->
scanPseudo(fmt, inputLine, consumedInput);
delta += inputLine.length() - size; // track length changes
debug("after rewrite delta=%zi\n", delta);
break;
case needs_original_format:
// pass original input with adjusted current position
debug("before checksum delta=%zi\n", delta);
consumedInput -= delta; // correct for length changes
consumed = StreamFormatConverter::find(fmt.conv)->
scanPseudo(fmt, inputBuffer, consumedInput);
consumedInput += delta;
break;
default:
error("INTERNAL ERROR (%s): illegal format.type 0x%02x\n",

View File

@ -219,6 +219,7 @@ protected:
// virtual methods
virtual void protocolStartHook() {}
virtual void inputHook(const void* input, size_t size) {};
virtual void protocolFinishHook(ProtocolResult) {}
virtual void startTimer(unsigned long timeout) = 0;
virtual bool formatValue(const StreamFormat&, const void* fieldaddress) = 0;

View File

@ -137,6 +137,7 @@ class Stream : protected StreamCore
#endif
// StreamCore methods
void inputHook(const void* input, size_t size);
void protocolFinishHook(ProtocolResult);
void startTimer(unsigned long timeout);
bool getFieldAddress(const char* fieldname,
@ -934,6 +935,10 @@ process()
debug("Stream::process(%s) start\n", name());
status = NO_ALARM;
convert = OK;
if (record->tpro)
{
StreamDebugClass(record->name).print("start protocol '%s'\n", protocolname());
}
if (!startProtocol(record->proc == 2 ? StreamCore::StartInit : StreamCore::StartNormal))
{
debug("Stream::process(%s): could not start %sprotocol, status=%s (%d)\n",
@ -1028,11 +1033,26 @@ expire(const epicsTime&)
// StreamCore virtual methods ////////////////////////////////////////////
void Stream::
inputHook(const void* input, size_t size)
{
if (record->tpro > 1)
{
StreamDebugClass(record->name).print("received \"%s\"\n",
StreamBuffer(input, size).expand()());
}
}
void Stream::
protocolFinishHook(ProtocolResult result)
{
debug("Stream::protocolFinishHook(%s, %s)\n",
name(), toStr(result));
if (record->tpro)
{
StreamDebugClass(record->name).print("%s. out=\"%s\", in=\"%s\"\n",
toStr(result), outputLine.expand()(), inputLine.expand()());
}
switch (result)
{
case Success:

View File

@ -171,8 +171,6 @@ print(const char* fmt, ...)
{
va_list args;
va_start(args, fmt);
const char* f = strrchr(file, '/');
if (f) f++; else f = file;
FILE* fp = StreamDebugFile ? StreamDebugFile : stderr;
if (streamMsgTimeStamped)
{
@ -184,7 +182,13 @@ print(const char* fmt, ...)
{
fprintf(fp, "%s ", StreamGetThreadNameFunction());
}
fprintf(fp, "%s:%d: ", f, line);
if (file) {
const char* f = strrchr(file, '/');
if (f) f++; else f = file;
fprintf(fp, "%s:", f);
if (line) fprintf(fp, "%d:", line);
fprintf(fp, " ");
}
vfprintf(fp, fmt, args);
fflush(fp);
va_end(args);

View File

@ -56,19 +56,15 @@ class StreamDebugClass
const char* file;
int line;
public:
StreamDebugClass(const char* file, int line) :
StreamDebugClass(const char* file = NULL, int line = 0) :
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
#define debug2 (streamDebug<2)?0:StreamDebugObject(__FILE__,__LINE__).print
#define debug (!streamDebug)?0:StreamDebugClass(__FILE__,__LINE__).print
#define debug2 (streamDebug<2)?0:StreamDebugClass(__FILE__,__LINE__).print
/*
* ANSI escape sequences for terminal output

View File

@ -42,7 +42,8 @@ typedef enum {
enum_format,
double_format,
string_format,
pseudo_format
pseudo_format,
needs_original_format
} StreamFormatType;
extern const char* StreamFormatTypeStr[];

View File

@ -42,10 +42,12 @@ PROD_SRCS_vxWorks = -nil-
PROD_LIBS = stream
ifdef ASYN
# edit asynRegistrars.dbd if necessary
streamApp_DBD += asynRegistrars.dbd
# add asynRecord.dbd if you like
streamApp_DBD += asynRecord.dbd
streamApp_DBD += asyn.dbd
streamApp_DBD += drvAsynIPPort.dbd
streamApp_DBD += drvAsynSerialPort.dbd
# vxi11 support is optional in recent asyn versions
#streamApp_DBD += drvVxi11.dbd
PROD_LIBS += asyn
# cygwin needs separate RPC library for asyn
PROD_SYS_LIBS_cygwin32 += $(CYGWIN_RPC_LIB)

View File

@ -1,9 +0,0 @@
registrar(asynRegister)
registrar(asynInterposeFlushRegister)
registrar(asynInterposeEosRegister)
# asynDriver up to version 4-16 does not support serial port for Windows!
registrar(drvAsynSerialPortRegisterCommands)
registrar(drvAsynIPPortRegisterCommands)
registrar(drvAsynIPServerPortRegisterCommands)
registrar(vxi11RegisterCommands)

View File

@ -19,7 +19,7 @@
# along with StreamDevice. If not, see https://www.gnu.org/licenses/.
#########################################################################/
terminator = CR LF;
terminator = LF;
cmd {
out "%s";

View File

@ -256,6 +256,9 @@ set protocol {
out "bitsum8 %s %#-9.1<bitsum8>"; in "bitsum8 %=s %#-9.1<bitsum8>";
out "bitsum16 %s %#-9.1<bitsum16>"; in "bitsum16 %=s %#-9.1<bitsum16>";
out "bitsum32 %s %#-9.1<bitsum32>"; in "bitsum32 %=s %#-9.1<bitsum32>";
# Check combination of regsub and checksum. Always check what the device sees.
out "crc8 %s%#/[0-9]{2}/&:/ %9.1<crc8>"; in "crc8 %#/[0-9]{2}/&:/%s %9.1<crc8>"; out "%s";
out "DONE";
}
}
@ -740,6 +743,12 @@ assure "bitsum16 123456789 \x32\x31\x30\x30\n"
send "bitsum16 123456789 \x32\x31\x30\x30\n"
assure "bitsum32 123456789 \x32\x31\x30\x30\x30\x30\x30\x30\n"
send "bitsum32 123456789 \x32\x31\x30\x30\x30\x30\x30\x30\n"
# check regsub and checksums
assure "crc8 12:34:56:78:9 \x07\n" ;# modified output string and checksum
send "crc8 123456789 \xF4\n" ;# original input string and checksum
assure "12:34:56:78:9\n" ;# modified input string
assure "DONE\n"
finish