Here is the bug fix.
Dirk
On 01.04.2015 14:03, Dirk Zimoch wrote:
On 01.04.2015 12:07, Isabella Rey wrote:
Hi Ralph,
Yes, but with %s the device receives something like:
load_curve 675892112117108115101461161201160 3870\r\n
You are right. And I think that is a bug.
Dirk
The null character corresponds to the 0 at the end of the waveform (I've
checked it with small strings)
Isabella
On 1 April 2015 at 10:44, Ralph Lange <[email protected]
<mailto:[email protected]>> wrote:
Have you tried using "%s" for the string output?
~Ralph
On 01/04/2015 10:44, Isabella Rey wrote:
Hi All,
I have a device to which I need to send a file path followed by
a number:
example:
load_curve C:\pulse.txt 3870
The file path is stored in the database as a waveform of chars.
When I run the protocol stream device sends (what I think is) a
null terminator after the file path:
device receives:
load_curve C:\pulse.txt\x00 3870
Is there any way of getting rid of the \x00 terminator?
Below I copy my protocol and the database records.
Cheers,
Isabella
I've defined the protocol in stream device as:
load_curve
{
out "load_curve %(\$1)c %(\$2)i";
in "%(\$1:\$2)b";
}
and in the database I have:
record(bo, "DEV:LOAD_CURVE") {
field(DTYP, "stream")
field(OUT, "@my.proto load_curve(DEV:CURVE_FILE, DEV:VPI)
$(PORT)")
}
record(waveform, "DEV:CURVE_FILE") {
field(DESC, "File path")
field(DTYP, "Soft Channel")
field(NELM, "256")
field(FTVL, "CHAR")
}
/***************************************************************
* StreamDevice Support *
* *
* (C) 1999 Dirk Zimoch ([email protected]) *
* (C) 2005 Dirk Zimoch ([email protected]) *
* *
* 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. *
* *
***************************************************************/
#define epicsExportSharedSymbols
#include "devStream.h"
#undef epicsExportSharedSymbols
#include "StreamCore.h"
#include "StreamError.h"
#include <epicsVersion.h>
#ifdef BASE_VERSION
#define EPICS_3_13
#endif
#ifdef EPICS_3_13
extern "C" {
#endif
#define epicsAlarmGLOBAL
#include <alarm.h>
#undef epicsAlarmGLOBAL
#include <ctype.h>
#include <stdarg.h>
#include <stdlib.h>
#include <dbStaticLib.h>
#include <drvSup.h>
#include <recSup.h>
#include <recGbl.h>
#include <devLib.h>
#include <callback.h>
#ifdef EPICS_3_13
#include <semLib.h>
#include <wdLib.h>
#include <taskLib.h>
extern DBBASE *pdbbase;
} // extern "C"
#else
#include <epicsTimer.h>
#include <epicsMutex.h>
#include <epicsEvent.h>
#include <epicsTime.h>
#include <epicsThread.h>
#include <registryFunction.h>
#include <iocsh.h>
#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 <epicsExport.h>
#endif
#if defined(__vxworks) || defined(vxWorks)
#include <symLib.h>
#include <sysSymTbl.h>
#endif
enum MoreFlags {
// 0x00FFFFFF used by StreamCore
InDestructor = 0x0100000,
ValueReceived = 0x0200000,
Aborted = 0x0400000
};
extern "C" void streamExecuteCommand(CALLBACK *pcallback);
extern "C" void streamRecordProcessCallback(CALLBACK *pcallback);
extern "C" long streamReload(char* recordname);
class Stream : protected StreamCore
#ifndef EPICS_3_13
, epicsTimerNotify
#endif
{
dbCommon* record;
const struct link *ioLink;
streamIoFunction readData;
streamIoFunction writeData;
#ifdef EPICS_3_13
WDOG_ID timer;
CALLBACK timeoutCallback;
SEM_ID mutex;
SEM_ID initDone;
#else
epicsTimerQueueActive* timerQueue;
epicsTimer* timer;
epicsMutex mutex;
epicsEvent initDone;
#endif
StreamBuffer fieldBuffer;
int status;
int convert;
long currentValueLength;
IOSCANPVT ioscanpvt;
CALLBACK commandCallback;
CALLBACK processCallback;
#ifdef EPICS_3_13
static void expire(CALLBACK *pcallback);
#else
// epicsTimerNotify method
expireStatus expire(const epicsTime&);
#endif
// StreamCore methods
void protocolStartHook();
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);
friend void streamRecordProcessCallback(CALLBACK *pcallback);
// Stream Epics methods
Stream(dbCommon* record, const struct link *ioLink,
streamIoFunction readData, streamIoFunction writeData);
~Stream();
long parseLink(const struct link *ioLink, char* filename, char* protocol,
char* busname, int* addr, char* busparam);
long initRecord(const char* filename, const char* protocol,
const char* busname, int addr, const char* busparam);
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, const 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 streamReload(char* recordname);
public:
long priority() { return record->prio; };
static long report(int interest);
static long drvInit();
};
// shell functions ///////////////////////////////////////////////////////
#ifndef EPICS_3_13
extern "C" {
epicsExportAddress(int, streamDebug);
}
#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;
}
#ifndef EPICS_3_13
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 ()
{
iocshRegister(&reloadDef, streamReloadFunc);
// make streamReload available for subroutine records
registryFunctionAdd("streamReload",
(REGISTRYFUNCTION)streamReloadSub);
registryFunctionAdd("streamReloadSub",
(REGISTRYFUNCTION)streamReloadSub);
}
extern "C" {
epicsExportRegistrar(streamRegistrar);
}
#endif
// driver support ////////////////////////////////////////////////////////
struct stream_drvsup {
long number;
long (*report)(int);
long (*init)();
} stream = {
2,
Stream::report,
Stream::drvInit
};
#ifndef EPICS_3_13
extern "C" {
epicsExportAddress(drvet, stream);
}
#endif
#ifdef EPICS_3_13
void streamEpicsPrintTimestamp(char* buffer, int size)
{
int tlen;
char* c;
TS_STAMP tm;
tsLocalTime (&tm);
tsStampToText(&tm, TS_TEXT_MMDDYY, buffer);
c = strchr(buffer,'.');
if (c) {
c[4] = 0;
}
tlen = strlen(buffer);
sprintf(buffer+tlen, " %.*s", size-tlen-2, taskName(0));
}
#else
void streamEpicsPrintTimestamp(char* buffer, int size)
{
int tlen;
epicsTime tm = epicsTime::getCurrent();
tlen = tm.strftime(buffer, size, "%Y/%m/%d %H:%M:%S.%06f");
sprintf(buffer+tlen, " %.*s", size-tlen-2, epicsThreadGetNameSelf());
}
#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<Stream*>(first); pstream;
pstream = static_cast<Stream*>(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);
if (interest == 3)
{
StreamBuffer buffer;
pstream->printStatus(buffer);
printf(" %s\n", buffer());
}
}
}
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, const struct link *ioLink,
streamIoFunction readData, streamIoFunction writeData)
{
long status;
char filename[80];
char protocol[80];
char busname[80];
int addr = -1;
char busparam[80];
memset(busparam, 0 ,sizeof(busparam));
debug("streamInitRecord(%s): SEVR=%d\n", record->name, record->sevr);
Stream* pstream = (Stream*)record->dpvt;
if (!pstream)
{
// initialize the first time
debug("streamInitRecord(%s): create new Stream object\n",
record->name);
pstream = new Stream(record, ioLink, readData, writeData);
record->dpvt = pstream;
} else {
// stop any running protocol
debug("streamInitRecord(%s): stop running protocol\n",
record->name);
pstream->finishProtocol(Stream::Abort);
}
// scan the i/o link
debug("streamInitRecord(%s): parse link \"%s\"\n",
record->name, ioLink->value.instio.string);
status = pstream->parseLink(ioLink, filename, protocol,
busname, &addr, busparam);
// (re)initialize bus and protocol
debug("streamInitRecord(%s): calling initRecord\n",
record->name);
if (status == 0)
status = pstream->initRecord(filename, protocol,
busname, addr, busparam);
if (status != OK && status != DO_NOT_CONVERT)
{
error("%s: Record initialization failed\n", record->name);
}
else if (!pstream->ioscanpvt)
{
scanIoInit(&pstream->ioscanpvt);
}
debug("streamInitRecord(%s) done status=%#lx\n", record->name, status);
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 streamScanfN(dbCommon* record, format_t *format,
void* value, size_t maxStringSize)
{
debug("streamScanfN(%s,format=%%%c,maxStringSize=%ld)\n",
record->name, format->priv->conv, (long)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, const struct link *ioLink,
streamIoFunction readData, streamIoFunction writeData)
:record(_record), ioLink(ioLink), readData(readData), writeData(writeData)
{
streamname = record->name;
#ifdef EPICS_3_13
timer = wdCreate();
mutex = semMCreate(SEM_INVERSION_SAFE | SEM_Q_PRIORITY);
initDone = semBCreate(SEM_Q_FIFO, SEM_EMPTY);
callbackSetCallback(expire, &timeoutCallback);
callbackSetUser(this, &timeoutCallback);
#else
timerQueue = &epicsTimerQueueActive::allocate(true);
timer = &timerQueue->createTimer();
#endif
callbackSetCallback(streamExecuteCommand, &commandCallback);
callbackSetUser(this, &commandCallback);
callbackSetCallback(streamRecordProcessCallback, &processCallback);
callbackSetUser(this, &processCallback);
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_13
wdDelete(timer);
debug("~Stream(%s): watchdog destroyed\n", name());
#else
timer->destroy();
debug("~Stream(%s): timer destroyed\n", name());
timerQueue->release();
debug("~Stream(%s): timer queue released\n", name());
#endif
releaseMutex();
}
long Stream::
parseLink(const struct link *ioLink, char* filename,
char* protocol, char* busname, int* addr, char* busparam)
{
// parse link parameters: filename protocol busname addr busparam
int n;
if (ioLink->type != INST_IO)
{
error("%s: Wrong I/O link type %s\n", name(),
pamaplinkType[ioLink->type].strvalue);
return S_dev_badInitRet;
}
int items = sscanf(ioLink->value.instio.string, "%s%s%s%n%i%n",
filename, protocol, busname, &n, addr, &n);
if (items <= 0)
{
error("%s: Empty I/O link. "
"Forgot the leading '@' or confused INP with OUT ?\n",
name());
return S_dev_badInitRet;
}
if (items < 3)
{
error("%s: Wrong I/O link format\n"
" expect \"@file protocol bus addr params\"\n"
" in \"@%s\"\n", name(),
ioLink->value.instio.string);
return S_dev_badInitRet;
}
while (isspace((unsigned char)ioLink->value.instio.string[n])) n++;
strcpy (busparam, ioLink->value.constantStr+n);
return OK;
}
long Stream::
initRecord(const char* filename, const char* protocol,
const char* busname, int addr, const char* busparam)
{
// It is safe to call this function again with different arguments
// attach to bus interface
debug("Stream::initRecord %s: attachBus(%s, %d, \"%s\")\n",
name(), busname, addr, busparam);
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
debug("Stream::initRecord %s: parse(%s, %s)\n",
name(), filename, protocol);
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_13
semTake(initDone, WAIT_FOREVER);
#else
initDone.wait();
#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=%s (%d)\n",
name(),
status >= 0 && status < ALARM_NSTATUS ?
epicsAlarmConditionStrings[status] : "ERROR",
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);
record->pact = false;
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_13
void Stream::
expire(CALLBACK *pcallback)
{
Stream* pstream = static_cast<Stream*>(pcallback->user);
pstream->timerCallback();
}
#else
epicsTimerNotify::expireStatus Stream::
expire(const epicsTime&)
{
timerCallback();
return noRestart;
}
#endif
// StreamCore virtual methods ////////////////////////////////////////////
void Stream::
protocolStartHook()
{
flags &= ~Aborted;
}
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:
flags |= Aborted;
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_13
semGive(initDone);
#else
initDone.signal();
#endif
return;
}
if (result != Abort && record->scan == SCAN_IO_EVENT)
{
// re-enable early input
flags |= AcceptInput;
}
if (record->pact || record->scan == SCAN_IO_EVENT)
{
// process record in callback thread to break possible recursion
callbackSetPriority(priority(), &processCallback);
callbackRequest(&processCallback);
}
}
void streamRecordProcessCallback(CALLBACK *pcallback)
{
Stream* pstream = static_cast<Stream*>(pcallback->user);
dbCommon* record = pstream->record;
// process record
// This will call streamReadWrite.
debug("streamRecordProcessCallback(%s) processing record\n",
pstream->name());
dbScanLock(record);
((DEVSUPFUN)record->rset->process)(record);
dbScanUnlock(record);
debug("streamRecordProcessCallback(%s) processing record done\n",
pstream->name());
if (record->scan == SCAN_IO_EVENT && !(pstream->flags & Aborted))
{
// restart protocol for next turn
debug("streamRecordProcessCallback(%s) restart async protocol\n",
pstream->name());
if (!pstream->startProtocol(Stream::StartAsync))
{
error("%s: Can't restart \"I/O Intr\" protocol\n",
pstream->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_13
callbackSetPriority(priority(), &timeoutCallback);
wdStart(timer, (timeout+1)*sysClkRateGet()/1000-1,
reinterpret_cast<FUNCPTR>(callbackRequest),
reinterpret_cast<int>(&timeoutCallback));
#else
timer->start(*this, timeout * 0.001);
#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 or VAL in other record
char fullname[PVNAME_SZ + 1];
sprintf(fullname, "%s.%s", name(), fieldname);
if (dbNameToAddr(fullname, &dbaddr) != OK)
{
// VAL in other record
sprintf(fullname, "%s.VAL", 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;
/* Handle time stamps special. %T converter takes double. */
if (strcmp(((dbFldDes*)pdbaddr->pfldDes)->name, "TIME") == 0)
{
double time;
if (format.type != double_format)
{
error ("%s: can only read double values from TIME field\n",
name());
return false;
}
if (pdbaddr->precord == record)
{
/* if getting time from own record, update timestamp first */
recGblGetTimeStamp(record);
}
/* convert EPICS epoch (1990) to unix epoch (1970) */
/* we are losing about 3 digits precision here */
time = pdbaddr->precord->time.secPastEpoch +
631152000u + pdbaddr->precord->time.nsec * 1e-9;
debug("Stream::formatValue(%s): read %f from TIME field\n",
name(), time);
return printValue(format, time);
}
/* convert type to LONG, ENUM, DOUBLE, or STRING */
int type = dbfMapping[format.type];
long nelem = pdbaddr->no_elements;
size_t size = nelem * typeSize[format.type];
/* print (U)CHAR arrays as string */
if (format.type == string_format &&
(pdbaddr->field_type == DBF_CHAR || pdbaddr->field_type == DBF_UCHAR))
{
debug("Stream::formatValue(%s): format %s.%s array[%ld] size %d of %s as string\n",
name(),
pdbaddr->precord->name,
((dbFldDes*)pdbaddr->pfldDes)->name,
nelem,
pdbaddr->field_size,
pamapdbfType[pdbaddr->field_type].strvalue);
type = DBF_CHAR;
size = nelem;
}
char* buffer = fieldBuffer.clear().reserve(size);
if (dbGet(pdbaddr, 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;
}
debug("Stream::formatValue(%s): got %ld elements\n",
name(),nelem);
/* terminate CHAR array as string */
if (type == DBF_CHAR)
{
if (nelem >= pdbaddr->no_elements) nelem = pdbaddr->no_elements-1;
buffer[nelem] = 0;
nelem = 1; /* array is only 1 string */
}
long i;
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());
return false;
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
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++)
{
debug("Stream::matchValue(%s): buffer before: %s\n",
name(), fieldBuffer.expand()());
switch (format.type)
{
case long_format:
{
consumed = scanValue(format, lval);
if (consumed >= 0) ((epicsInt32*)buffer)[nord] = lval;
debug("Stream::matchValue(%s): %s[%li] = %li\n",
name(), pdbaddr->precord->name, nord, lval);
break;
}
case enum_format:
{
consumed = scanValue(format, lval);
if (consumed >= 0)
((epicsUInt16*)buffer)[nord] = (epicsUInt16)lval;
debug("Stream::matchValue(%s): %s[%li] = %li\n",
name(), pdbaddr->precord->name, nord, lval);
break;
}
case double_format:
{
consumed = scanValue(format, dval);
// Direct assignment to buffer fails with
// gcc 3.4.3 for xscale_be
// Optimization bug?
epicsFloat64 f64=dval;
if (consumed >= 0)
memcpy(((epicsFloat64*)buffer)+nord,
&f64, sizeof(f64));
debug("Stream::matchValue(%s): %s[%li] = %#g %#g\n",
name(), pdbaddr->precord->name, nord, dval,
((epicsFloat64*)buffer)[nord]);
break;
}
case string_format:
{
consumed = scanValue(format,
buffer+MAX_STRING_SIZE*nord, MAX_STRING_SIZE);
debug("Stream::matchValue(%s): %s[%li] = \"%.*s\"\n",
name(), pdbaddr->precord->name, nord,
MAX_STRING_SIZE, buffer+MAX_STRING_SIZE*nord);
break;
}
default:
error("INTERNAL ERROR: Stream::matchValue %s: "
"Illegal format type\n", name());
return false;
}
debug("Stream::matchValue(%s): buffer after: %s\n",
name(), fieldBuffer.expand()());
if (consumed < 0) break;
consumedInput += consumed;
}
if (!nord)
{
// scan error: set other record to alarm status
if (pdbaddr->precord != record)
{
(void) recGblSetSevr(pdbaddr->precord,
CALC_ALARM, INVALID_ALARM);
if (!INIT_RUN)
{
// process other record to send alarm monitor
dbProcess(pdbaddr->precord);
}
}
return false;
}
if (strcmp(((dbFldDes*)pdbaddr->pfldDes)->name, "TIME") == 0)
{
#ifdef epicsTimeEventDeviceTime
if (format.type != double_format)
{
error ("%s: can only write double values to TIME field\n",
name());
return false;
}
dval = dval-631152000u;
pdbaddr->precord->time.secPastEpoch = (long)dval;
// rouding: we don't have 9 digits precision
// in a double of today's number of seconds
pdbaddr->precord->time.nsec = (long)((dval-(long)dval)*1e6)*1000;
debug("Stream::matchValue(%s): writing %i.%i to TIME field\n",
name(),
pdbaddr->precord->time.secPastEpoch,
pdbaddr->precord->time.nsec);
pdbaddr->precord->tse = epicsTimeEventDeviceTime;
return true;
#else
error ("%s: writing TIME field is not supported "
"in this EPICS version\n", name());
return false;
#endif
}
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,%s)\n",
name(),
pdbaddr->precord->name,
((dbFldDes*)pdbaddr->pfldDes)->name,
fieldBuffer.expand()());
putfunc = "dbPut";
status = dbPut(pdbaddr, dbfMapping[format.type], buffer, 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,%s)\n",
name(),
pdbaddr->precord->name,
((dbFldDes*)pdbaddr->pfldDes)->name,
fieldBuffer.expand()());
putfunc = "dbPutField";
status = dbPutField(pdbaddr, dbfMapping[format.type],
buffer, 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_13
// Pass command to vxWorks shell
extern "C" int execute(const char *cmd);
void streamExecuteCommand(CALLBACK *pcallback)
{
Stream* pstream = static_cast<Stream*>(pcallback->user);
if (execute(pstream->outputLine()) != OK)
{
pstream->execCallback(StreamIoFault);
}
else
{
pstream->execCallback(StreamIoSuccess);
}
}
#else
// Pass command to iocsh
void streamExecuteCommand(CALLBACK *pcallback)
{
Stream* pstream = static_cast<Stream*>(pcallback->user);
if (iocshCmd(pstream->outputLine()) != OK)
{
pstream->execCallback(StreamIoFault);
}
else
{
pstream->execCallback(StreamIoSuccess);
}
}
#endif
bool Stream::
execute()
{
callbackSetPriority(priority(), &commandCallback);
callbackRequest(&commandCallback);
return true;
}
void Stream::
lockMutex()
{
#ifdef EPICS_3_13
semTake(mutex, WAIT_FOREVER);
#else
mutex.lock();
#endif
}
void Stream::
releaseMutex()
{
#ifdef EPICS_3_13
semGive(mutex);
#else
mutex.unlock();
#endif
}
- Replies:
- Re: Stream device: how to send waveform of chars without null terminator? Isabella Rey
- References:
- Stream device: how to send waveform of chars without null terminator? Isabella Rey
- Re: Stream device: how to send waveform of chars without null terminator? Ralph Lange
- Re: Stream device: how to send waveform of chars without null terminator? Isabella Rey
- Re: Stream device: how to send waveform of chars without null terminator? Dirk Zimoch
- Navigate by Date:
- Prev:
RE: data of modbus write function didn't updated automatically Mark Rivers
- Next:
Remember to Register for the Spring 2015 EPICS Collaboration Meeting Spring 2015 EPICS Collaboration Meeting
- Index:
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
<2015>
2016
2017
2018
2019
2020
2021
2022
2023
2024
- Navigate by Thread:
- Prev:
Re: Stream device: how to send waveform of chars without null terminator? Dirk Zimoch
- Next:
Re: Stream device: how to send waveform of chars without null terminator? Isabella Rey
- Index:
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
<2015>
2016
2017
2018
2019
2020
2021
2022
2023
2024
|