commit 236a4ad497cff07b208d4c36d8e35909b73fc3d5 Author: anicic Date: Wed Sep 22 13:44:37 2010 +0000 initial version of s7plcFW driver diff --git a/MODULE b/MODULE new file mode 100644 index 0000000..3eae6ed --- /dev/null +++ b/MODULE @@ -0,0 +1,6 @@ +# Please change the following email with yours. +Email: Damir.Anicic@psi.ch +Module-Name: s7plcFW +Description: s7plcFW - the same as Dirk's s7plc, but uses FETCH/WRITE instead of SEND/RECEIVE +Project-Name: + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..390bea0 --- /dev/null +++ b/Makefile @@ -0,0 +1,10 @@ +include /ioc/tools/driver.makefile + +#BUILDCLASSES+=Linux + +DBDS += s7plcFWBase.dbd +DBDS_3.14 += s7plcFWCalcout.dbd +DBDS_3.14 += s7plcFWReg.dbd + +EPICS_VERSIONS = +EPICS_VERSIONS += 3.14.8 diff --git a/compat3_13.h b/compat3_13.h new file mode 100644 index 0000000..114a84d --- /dev/null +++ b/compat3_13.h @@ -0,0 +1,144 @@ +/* EPICS R3.13 compatibility header +* +* Including this header allows (some) EPICS R3.14 code to be compiled +* with EPICS R3.13 for a vxWorks system using a GNU compiler. +* +* It replaces R3.14 headers +* epicsTypes.h +* epicsAssert.h +* epicsString.h +* epicsExport.h +* epicsThread.h +* epicsTimer.h +* epicsMutex.h +* epicsEvent.h +* cantProceed.h +* +* It is not complete and only simulates the C interfaces (not C++). +* It uses some gcc extensions and might not work with other compilers. +* It requires vxWorks. +* It might be buggy or produce slightly different behaviour than the +* original R3.14 functions. +* +* Since all functions are simulated as macros, you might not be able to +* take the address of a function. Be careful with arguments as they +* might be evaluated multiple times. +* This won't work: fptr = &epicsTimerStartDelay; +* Don't do this (changing arg): epicsTimerStartDelay(*ptimer++, delay); +* +* You can take the address of +* epicsMutexUnlock +* epicsMutexDestroy +* epicsEventSignal +* epicsEventDestroy +* +* You have been warned. +* Dirk Zimoch, March 2005 +*/ + +#ifndef compat3_13_h +#define compat3_13_h + +#include +#include +#include + +/* epicsAssert */ +#include + +/* epicsString */ +#define epicsStrDup(s) strcpy(malloc(strlen(s)+1),(s)) + +/* epicsExport */ +#define epicsExportAddress(a,b) extern void avoid_compiler_warning + +/* epicsThread */ +#include +#define epicsThreadId void* +#define EPICSTHREADFUNC FUNCPTR +#define epicsThreadPriorityLow 10 +#define epicsThreadPriorityMedium 50 +#define epicsThreadPriorityHigh 90 + +#define epicsThreadCreate(name, pri, stack, func, args) \ + (void*)taskSpawn((name), 255-(pri)*255/99, VX_FP_TASK, (stack), (FUNCPTR)(func), (int)(args), \ + 0,0,0,0,0,0,0,0,0) + +#define epicsThreadSleep(sec) taskDelay((sec)*sysClkRateGet()) +#define epicsThreadIsSuspended(t) (taskIdVerify((int)(t)) || taskIsSuspended((int)(t))) + +/* epicsTimer */ +#include +#define epicsTimerQueueId char* +#define epicsTimerQueueAllocate(a,b) NULL +typedef void (*epicsTimerCallback) (void* pPrivate); +typedef struct epicsTimerForC {WDOG_ID wd; FUNCPTR cb; int p;} *epicsTimerId; + +#define epicsTimerQueueCreateTimer(q, callback, param) __extension__ \ +({ \ + epicsTimerId ti = calloc(1,sizeof(struct epicsTimerForC)); \ + ti->wd = wdCreate(); \ + ti->cb = (FUNCPTR)(callback); \ + ti->p = (int)(param); \ + ti; \ +}) + +#define epicsTimerStartDelay(ti,delay) \ + (void) wdStart((ti)->wd, (delay)*sysClkRateGet(), (ti)->cb, (ti)->p) + +/* cantProceed */ +#define callocMustSucceed(n,s,m) __extension__ \ +({ \ + void *mem = calloc((n),(s)); \ + if (!mem) { printErr("FATAL: callocMustSucceed failed in %s line %d %s: \n", \ + __FILE__, __LINE__, (m), strerror(errno)); taskSuspend(0); } \ + mem; \ +}) + +/* epicsMutex */ +#include +#define epicsMutexId SEM_ID + +#define epicsMutexMustCreate() __extension__ \ +({ \ + SEM_ID m = semMCreate(SEM_Q_FIFO); \ + if (!m) { printErr("FATAL: epicsMutexMustCreate failed in %s line %d: %s\n", \ + __FILE__, __LINE__, strerror(errno)); taskSuspend(0); } \ + m; \ +}) + +#define epicsMutexMustLock(m) \ + (semTake(m, WAIT_FOREVER) && \ + (printErr("FATAL: epicsMutexMustLock failed in %s line %d: %s\n", \ + __FILE__, __LINE__, strerror(errno)), \ + taskSuspend(0), ERROR)) + +#define epicsMutexUnlock semGive +#define epicsMutexDestroy semDelete + +/* epicsEvent */ +#define epicsEventId SEM_ID +#define epicsEventEmpty SEM_EMPTY +#define epicsEventFull SEM_FULL + +#define epicsEventMustCreate(state) __extension__ \ +({ \ + SEM_ID e = semBCreate(SEM_Q_FIFO, state); \ + if (!e) { printErr("FATAL: epicsEventMustCreate failed in %s line %d: %s\n", \ + __FILE__, __LINE__, strerror(errno)); taskSuspend(0); } \ + e; \ +}) + +#define epicsEventMustWait(e) \ + (semTake(e, WAIT_FOREVER) && \ + (printErr("FATAL: epicsEventMustWait failed in %s line %d: %s\n", \ + __FILE__, __LINE__, strerror(errno)), \ + taskSuspend(0), ERROR)) + +#define epicsEventSignal semGive +#define epicsEventWaitWithTimeout(e,t) semTake((e),(t)*sysClkRateGet()) +#define epicsEventWait(e) semTake((e),WAIT_FOREVER) +#define epicsEventTryWait(e) semTake((e),NO_WAIT) +#define epicsEventDestroy semDelete + +#endif diff --git a/devS7plcFW.c b/devS7plcFW.c new file mode 100644 index 0000000..900e1c7 --- /dev/null +++ b/devS7plcFW.c @@ -0,0 +1,2516 @@ +/* $Author: anicic $ */ +/* $Date: 2010/09/22 13:44:37 $ */ +/* $Id: devS7plcFW.c,v 1.1 2010/09/22 13:44:37 anicic Exp $ */ +/* $Name: $ */ +/* $Revision: 1.1 $ */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if ((EPICS_VERSION==3 && EPICS_REVISION>=14) || EPICS_VERSION>3) +/* R3.14 */ +#include +#include +#include +#include +#else +/* R3.13 */ +#include "compat3_13.h" +#endif + +/* suppress compiler warning concerning long long with __extension__ */ +#ifndef __GNUC__ +#define __extension__ +#endif + +#define S7MEM_TIME 100 + +typedef struct { /* Private structure to save IO arguments */ + s7plcFWStation *station; /* Card id */ + unsigned short offs; /* Offset (in bytes) within memory block */ + unsigned short bit; /* Bit number (0-15) for bi/bo */ + unsigned short dtype; /* Data type */ + unsigned short dlen; /* Data length (in bytes) */ + epicsInt32 hwLow; /* Hardware Low limit */ + epicsInt32 hwHigh; /* Hardware High limit */ +} S7memPrivate_t; + +static char cvsid_devS7plcFW[] = + "$Id: devS7plcFW.c,v 1.1 2010/09/22 13:44:37 anicic Exp $"; + +STATIC long s7plcFWReport(); + +STATIC int s7plcFWIoParse(char* recordName, char *parameters, S7memPrivate_t *); +STATIC long s7plcFWGetInIntInfo(int cmd, dbCommon *record, IOSCANPVT *ppvt); +STATIC long s7plcFWGetOutIntInfo(int cmd, dbCommon *record, IOSCANPVT *ppvt); + +struct devsup { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN io; +}; + +/* bi for status bit ************************************************/ + +STATIC long s7plcFWInitRecordStat(biRecord *); +STATIC long s7plcFWReadStat(biRecord *); +STATIC long s7plcFWReadStat2(biRecord *); + +struct devsup s7plcFWStat = +{ + 5, + NULL, + NULL, + s7plcFWInitRecordStat, + s7plcFWGetInIntInfo, + s7plcFWReadStat +}; +epicsExportAddress(dset, s7plcFWStat); + +struct devsup s7plcFWStat2 = +{ + 5, + NULL, + NULL, + s7plcFWInitRecordStat, + s7plcFWGetInIntInfo, + s7plcFWReadStat2 +}; +epicsExportAddress(dset, s7plcFWStat2); + + +/* bi ***************************************************************/ + +STATIC long s7plcFWInitRecordBi(biRecord *); +STATIC long s7plcFWReadBi(biRecord *); + +struct devsup s7plcFWBi = +{ + 5, + NULL, + NULL, + s7plcFWInitRecordBi, + s7plcFWGetInIntInfo, + s7plcFWReadBi +}; + +epicsExportAddress(dset, s7plcFWBi); + +/* bo ***************************************************************/ + +STATIC long s7plcFWInitRecordBo(boRecord *); +STATIC long s7plcFWWriteBo(boRecord *); + +struct devsup s7plcFWBo = +{ + 5, + NULL, + NULL, + s7plcFWInitRecordBo, + s7plcFWGetOutIntInfo, + s7plcFWWriteBo +}; + +epicsExportAddress(dset, s7plcFWBo); + +/* mbbi *************************************************************/ + +STATIC long s7plcFWInitRecordMbbi(mbbiRecord *); +STATIC long s7plcFWReadMbbi(mbbiRecord *); + +struct devsup s7plcFWMbbi = +{ + 5, + NULL, + NULL, + s7plcFWInitRecordMbbi, + s7plcFWGetInIntInfo, + s7plcFWReadMbbi +}; + +epicsExportAddress(dset, s7plcFWMbbi); + +/* mbbo *************************************************************/ + +STATIC long s7plcFWInitRecordMbbo(mbboRecord *); +STATIC long s7plcFWWriteMbbo(mbboRecord *); + +struct devsup s7plcFWMbbo = +{ + 5, + NULL, + NULL, + s7plcFWInitRecordMbbo, + s7plcFWGetOutIntInfo, + s7plcFWWriteMbbo +}; + +epicsExportAddress(dset, s7plcFWMbbo); + +/* mbbiDirect *******************************************************/ + +STATIC long s7plcFWInitRecordMbbiDirect(mbbiDirectRecord *); +STATIC long s7plcFWReadMbbiDirect(mbbiDirectRecord *); + +struct devsup s7plcFWMbbiDirect = +{ + 5, + s7plcFWReport, + NULL, + s7plcFWInitRecordMbbiDirect, + s7plcFWGetInIntInfo, + s7plcFWReadMbbiDirect +}; + +epicsExportAddress(dset, s7plcFWMbbiDirect); + +/* mbboDirect *******************************************************/ + +STATIC long s7plcFWInitRecordMbboDirect(mbboDirectRecord *); +STATIC long s7plcFWWriteMbboDirect(mbboDirectRecord *); + +struct devsup s7plcFWMbboDirect = +{ + 5, + NULL, + NULL, + s7plcFWInitRecordMbboDirect, + s7plcFWGetOutIntInfo, + s7plcFWWriteMbboDirect +}; + +epicsExportAddress(dset, s7plcFWMbboDirect); + +/* longin ***********************************************************/ + +STATIC long s7plcFWInitRecordLongin(longinRecord *); +STATIC long s7plcFWReadLongin(longinRecord *); + +struct devsup s7plcFWLongin = +{ + 5, + NULL, + NULL, + s7plcFWInitRecordLongin, + s7plcFWGetInIntInfo, + s7plcFWReadLongin +}; + +epicsExportAddress(dset, s7plcFWLongin); + +/* longout **********************************************************/ + +STATIC long s7plcFWInitRecordLongout(longoutRecord *); +STATIC long s7plcFWWriteLongout(longoutRecord *); + +struct devsup s7plcFWLongout = +{ + 5, + NULL, + NULL, + s7plcFWInitRecordLongout, + s7plcFWGetOutIntInfo, + s7plcFWWriteLongout +}; + +epicsExportAddress(dset, s7plcFWLongout); + +/* ai ***************************************************************/ + +STATIC long s7plcFWInitRecordAi(aiRecord *); +STATIC long s7plcFWReadAi(aiRecord *); +STATIC long s7plcFWSpecialLinconvAi(aiRecord *, int after); + +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN read; + DEVSUPFUN special_linconv; +} s7plcFWAi = +{ + 6, + NULL, + NULL, + s7plcFWInitRecordAi, + s7plcFWGetInIntInfo, + s7plcFWReadAi, + s7plcFWSpecialLinconvAi +}; + +epicsExportAddress(dset, s7plcFWAi); + +/* ao ***************************************************************/ + +STATIC long s7plcFWInitRecordAo(aoRecord *); +STATIC long s7plcFWWriteAo(aoRecord *); +STATIC long s7plcFWSpecialLinconvAo(aoRecord *, int after); + +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN write; + DEVSUPFUN special_linconv; +} s7plcFWAo = +{ + 6, + NULL, + NULL, + s7plcFWInitRecordAo, + s7plcFWGetOutIntInfo, + s7plcFWWriteAo, + s7plcFWSpecialLinconvAo +}; + +epicsExportAddress(dset, s7plcFWAo); + +/* stringin *********************************************************/ + +STATIC long s7plcFWInitRecordStringin(stringinRecord *); +STATIC long s7plcFWReadStringin(stringinRecord *); + +struct devsup s7plcFWStringin = +{ + 5, + NULL, + NULL, + s7plcFWInitRecordStringin, + s7plcFWGetInIntInfo, + s7plcFWReadStringin +}; + +epicsExportAddress(dset, s7plcFWStringin); + +/* stringout ********************************************************/ + +STATIC long s7plcFWInitRecordStringout(stringoutRecord *); +STATIC long s7plcFWWriteStringout(stringoutRecord *); + +struct devsup s7plcFWStringout = +{ + 5, + NULL, + NULL, + s7plcFWInitRecordStringout, + s7plcFWGetOutIntInfo, + s7plcFWWriteStringout +}; + +epicsExportAddress(dset, s7plcFWStringout); + +/* waveform *********************************************************/ + +STATIC long s7plcFWInitRecordWaveform(waveformRecord *); +STATIC long s7plcFWReadWaveform(waveformRecord *); + +struct devsup s7plcFWWaveform = +{ + 5, + NULL, + NULL, + s7plcFWInitRecordWaveform, + s7plcFWGetInIntInfo, + s7plcFWReadWaveform +}; + +epicsExportAddress(dset, s7plcFWWaveform); + +/* calcout **********************************************************/ +#if ((EPICS_VERSION==3 && EPICS_REVISION>=14) || EPICS_VERSION>3) + +STATIC long s7plcFWInitRecordCalcout(calcoutRecord *); +STATIC long s7plcFWWriteCalcout(calcoutRecord *); + +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN write; + DEVSUPFUN special_linconv; +} s7plcFWCalcout = { + 6, + NULL, + NULL, + s7plcFWInitRecordCalcout, + s7plcFWGetOutIntInfo, + s7plcFWWriteCalcout, + NULL +}; + +epicsExportAddress(dset, s7plcFWCalcout); +#endif + +/********* Report routine ********************************************/ + +STATIC long s7plcFWReport() +{ + printf("devS7mem version: %s\n", cvsid_devS7plcFW); + return 0; +} + +/********* Support for "I/O Intr" for input records ******************/ + +STATIC long s7plcFWGetInIntInfo(int cmd, dbCommon *record, IOSCANPVT *ppvt) +{ + S7memPrivate_t* p = record->dpvt; + if (p == NULL) + { + recGblRecordError(S_db_badField, record, + "s7plcFWGetInIntInfo: uninitialized record"); + return -1; + } + *ppvt = s7plcFWGetFetchScanPvt(p->station); + return 0; +} + +/********* Support for "I/O Intr" for output records ****************/ + +STATIC long s7plcFWGetOutIntInfo(int cmd, dbCommon *record, IOSCANPVT *ppvt) +{ + S7memPrivate_t* p = record->dpvt; + if (p == NULL) + { + recGblRecordError(S_db_badField, record, + "s7plcFWGetInIntInfo: uninitialized record"); + return -1; + } + *ppvt = s7plcFWGetWriteScanPvt(p->station); + return 0; +} + +/*********************************************************************** + * Routine to parse IO arguments + * IO address line format: + * + * /[+] [T=] [B=] [L=] [H=] + * + * where: - symbolic device name + * - address (byte number) within memory block + * - parameters to be passed to a particular + * devSup parsering routine + * - INT8, INT16, INT32, + * UINT16 (or UNSIGN16), UINT32 (or UNSIGN32), + * REAL32 (or FLOAT), REAL64 (or DOUBLE), + * STRING,TIME + * - least significant bit is 0 + * - raw value that mapps to EGUL + * - raw value that mapps to EGUF + **********************************************************************/ + +STATIC int s7plcFWIoParse(char* recordName, char *par, S7memPrivate_t *priv) +{ + char devName[255]; + char *p = par, separator; + int nchar, i; + int status = 0; + + struct {char* name; int dlen; epicsType type;} datatypes [] = + { + { "INT8", 1, epicsInt8T }, + + { "UINT8", 1, epicsUInt8T }, + { "UNSIGN8", 1, epicsUInt8T }, + { "BYTE", 1, epicsUInt8T }, + { "CHAR", 1, epicsUInt8T }, + + { "INT16", 2, epicsInt16T }, + { "SHORT", 2, epicsInt16T }, + + { "UINT16", 2, epicsUInt16T }, + { "UNSIGN16", 2, epicsUInt16T }, + { "WORD", 2, epicsUInt16T }, + + { "INT32", 4, epicsInt32T }, + { "LONG", 4, epicsInt32T }, + + { "UINT32", 4, epicsUInt32T }, + { "UNSIGN32", 4, epicsUInt32T }, + { "DWORD", 4, epicsUInt32T }, + + { "REAL32", 4, epicsFloat32T }, + { "FLOAT32", 4, epicsFloat32T }, + { "FLOAT", 4, epicsFloat32T }, + + { "REAL64", 8, epicsFloat64T }, + { "FLOAT64", 8, epicsFloat64T }, + { "DOUBLE", 8, epicsFloat64T }, + + { "TIME", 1, S7MEM_TIME }, + { "BCD", 1, S7MEM_TIME } + }; + + /* Get rid of leading whitespace and non-alphanumeric chars */ + while (!isalnum((unsigned char)*p)) + if (*p++ == '\0') return S_drv_badParam; + + /* Get device name */ + nchar = strcspn(p, "/"); + strncpy(devName, p, nchar); + devName[nchar] = '\0'; + p += nchar; + separator = *p++; + s7plcFWDebugLog(1, "s7plcFWIoParse %s: station=%s\n", recordName, devName); + + priv->station = s7plcFWOpen(devName); + if (!priv->station) + { + errlogSevPrintf(errlogFatal, "s7plcFWIoParse %s: device not found\n", + recordName); + return S_drv_noDevice; + } + + /* Check station offset */ + if (separator == '/') + { + priv->offs = strtol(p, &p, 0); + separator = *p++; + /* Handle any number of optional +o additions to the offs */ + while (separator == '+') + { + priv->offs += strtol(p, &p, 0); + separator = *p++; + } + } + else + { + priv->offs = 0; + } + + s7plcFWDebugLog(1, + "s7plcFWIoParse %s: offs=%d\n", recordName, priv->offs); + + /* set default values for parameters */ + if (!priv->dtype && !priv->dlen) + { + priv->dtype = epicsInt16T; + priv->dlen = 2; + } + priv->bit = 0; + priv->hwLow = 0; + priv->hwHigh = 0; + + /* allow whitespaces before parameter for device support */ + while ((separator == '\t') || (separator == ' ')) + separator = *p++; + + /* handle parameter for device support if present */ + nchar = 0; + if (separator != '\'') p--; /* quote is optional*/ + + /* parse parameters */ + while (p && *p) + { + switch (*p) + { + case ' ': + case '\t': + p++; + break; + case 'T': /* T= */ + p+=2; + + if (strncmp(p,"STRING",6) == 0) + { + priv->dtype = epicsStringT; + p += 6; + } + else + { + static int maxtype = + sizeof(datatypes)/sizeof(*datatypes); + for (i = 0; i < maxtype; i++) + { + nchar = strlen(datatypes[i].name); + if (strncmp(p, datatypes[i].name, nchar) == 0) + { + priv->dtype = datatypes[i].type; + priv->dlen = datatypes[i].dlen; + p += nchar; + break; + } + } + if (i == maxtype) + { + errlogSevPrintf(errlogFatal, + "s7plcFWIoParse %s: invalid datatype %s\n", + recordName, p); + return S_drv_badParam; + } + } + break; + case 'B': /* B= */ + p += 2; + priv->bit = strtol(p,&p,0); + break; + case 'L': /* L= (converts to EGUL)*/ + p += 2; + priv->hwLow = strtol(p,&p,0); + break; + case 'H': /* L= (converts to EGUF)*/ + p += 2; + priv->hwHigh = strtol(p,&p,0); + break; + case '\'': + if (separator == '\'') + { + p = 0; + break; + } + default: + errlogSevPrintf(errlogFatal, + "s7plcFWIoParse %s: unknown parameter '%c'\n", + recordName, *p); + return S_drv_badParam; + } + } + + /* for T=STRING L=... means length, not low */ + if (priv->dtype == epicsStringT && priv->hwLow) + { + priv->dlen = priv->hwLow; + priv->hwLow = 0; + } + + /* check if bit number is in range */ + if (priv->bit && priv->bit >= priv->dlen*8) + { + errlogSevPrintf(errlogFatal, + "s7plcFWIoParse %s: invalid bit number %d (>%d)\n", + recordName, priv->bit, priv->dlen*8-1); + return S_drv_badParam; + } + + /* get default values for L and H if user did'n define them */ + switch (priv->dtype) + { + case epicsUInt8T: + if (priv->hwHigh > 0xFF) status = S_drv_badParam; + if (!priv->hwHigh) priv->hwLow = 0x00; + if (!priv->hwHigh) priv->hwHigh = 0xFF; + break; + case epicsUInt16T: + if (priv->hwHigh > 0xFFFF) status = S_drv_badParam; + if (!priv->hwHigh) priv->hwLow = 0x0000; + if (!priv->hwHigh) priv->hwHigh = 0xFFFF; + break; + case epicsUInt32T: + if (!priv->hwHigh) priv->hwLow = 0x00000000; + if (!priv->hwHigh) priv->hwHigh = 0xFFFFFFFF; + break; + case epicsInt8T: + if (priv->hwHigh > 0x7F) status = S_drv_badParam; + if (!priv->hwHigh) priv->hwLow = 0xFFFFFF81; + if (!priv->hwHigh) priv->hwHigh = 0x0000007F; + break; + case epicsInt16T: + if (priv->hwHigh > 0x7FFF) status = S_drv_badParam; + if (!priv->hwHigh) priv->hwLow = 0xFFFF8001; + if (!priv->hwHigh) priv->hwHigh = 0x00007FFF; + break; + case epicsInt32T: + if (!priv->hwHigh) priv->hwLow = 0x80000001; + if (!priv->hwHigh) priv->hwHigh = 0x7FFFFFFF; + break; + default: + if (priv->hwHigh || priv->hwLow) { + errlogSevPrintf(errlogMinor, + "s7plcFWIoParse %s: L or H makes" + " no sense with this data type\n", + recordName); + } + break; + } + s7plcFWDebugLog(1, "s7plcFWIoParse %s: dlen=%d\n",recordName, priv->dlen); + s7plcFWDebugLog(1, "s7plcFWIoParse %s: B=%d\n", recordName, priv->bit); + s7plcFWDebugLog(1, "s7plcFWIoParse %s: L=%#x\n", recordName, priv->hwLow); + s7plcFWDebugLog(1, "s7plcFWIoParse %s: H=%#x\n", recordName, priv->hwHigh); + + if (status) + { + errlogSevPrintf(errlogMinor, + "s7plcFWIoParse %s: L or H out of range for this data type\n", + recordName); + return status; + } + + return 0; +} + +/* bi for status bit ************************************************/ + +STATIC long s7plcFWInitRecordStat(biRecord *record) +{ + S7memPrivate_t *priv; + int status; + + if (record->inp.type != INST_IO) + { + recGblRecordError(S_db_badField, record, + "s7plcFWInitRecordStat: illegal INP field type"); + return S_db_badField; + } + priv = (S7memPrivate_t *)callocMustSucceed(1, sizeof(S7memPrivate_t), + "s7plcFWInitRecordStat"); + status = s7plcFWIoParse(record->name, + record->inp.value.instio.string, priv); + if (status) + { + recGblRecordError(S_db_badField, record, + "s7plcFWInitRecordStat: bad INP field"); + return S_db_badField; + } + assert(priv->station); + record->dpvt = priv; + return 0; +} + + +STATIC long s7plcFWReadStat(biRecord *record) +{ + int status; + S7memPrivate_t *priv = (S7memPrivate_t *)record->dpvt; + + if (!priv) + { + recGblSetSevr(record, UDF_ALARM, INVALID_ALARM); + errlogSevPrintf(errlogFatal, "%s: not initialized\n", record->name); + return -1; + } + assert(priv->station); + /* psudo-read (0 bytes, 0 data len, which 1=fetch-status 2 =write-status) just to get the connection status */ + status = s7plcFWReadArray(priv->station, 1, 0, 0, NULL); + if (status == S_drv_noConn) + { + /* errlogSevPrintf(errlogFatal, "%s: read error - no connection\n", record->name); */ + record->rval = 0; + return 0; + } + if (status) + { + recGblSetSevr(record, READ_ALARM, INVALID_ALARM); + return status; + } + record->rval = 1; + return 0; +} +STATIC long s7plcFWReadStat2(biRecord *record) +{ + int status; + S7memPrivate_t *priv = (S7memPrivate_t *)record->dpvt; + + if (!priv) + { + recGblSetSevr(record, UDF_ALARM, INVALID_ALARM); + errlogSevPrintf(errlogFatal, "%s: not initialized\n", record->name); + return -1; + } + assert(priv->station); + /* psudo-read (0 bytes, 0 data len, which 0=fetch-status 1 =write-status) just to get the connection status */ + status = s7plcFWReadArray(priv->station, 2, 0, 0, NULL); + if (status == S_drv_noConn) + { + /* errlogSevPrintf(errlogFatal, "%s: read error - no connection\n", record->name); */ + record->rval = 0; + return 0; + } + if (status) + { + recGblSetSevr(record, READ_ALARM, INVALID_ALARM); + return status; + } + record->rval = 1; + return 0; +} + +/* bi ***************************************************************/ + +STATIC long s7plcFWInitRecordBi(biRecord *record) +{ + S7memPrivate_t *priv; + int status; + + if (record->inp.type != INST_IO) + { + recGblRecordError(S_db_badField, record, + "s7plcFWInitRecordBi: illegal INP field type"); + return S_db_badField; + } + priv = (S7memPrivate_t *)callocMustSucceed(1, + sizeof(S7memPrivate_t), "s7plcFWInitRecordBi"); + status = s7plcFWIoParse(record->name, + record->inp.value.instio.string, priv); + if (status) + { + recGblRecordError(S_db_badField, record, + "s7plcFWInitRecordBi: bad INP field"); + return S_db_badField; + } + assert(priv->station); + switch (priv->dtype) + { + case epicsInt8T: + case epicsUInt8T: + case epicsInt16T: + case epicsUInt16T: + case epicsInt32T: + case epicsUInt32T: + break; + default: + errlogSevPrintf(errlogFatal, + "s7plcFWInitRecordBi %s: illegal data type\n", + record->name); + return S_db_badField; + } + record->mask = 1 << priv->bit; + record->dpvt = priv; + return 0; +} + +STATIC long s7plcFWReadBi(biRecord *record) +{ + int status; + S7memPrivate_t *priv = (S7memPrivate_t *)record->dpvt; + epicsUInt8 rval8; + epicsUInt16 rval16; + epicsUInt32 rval32; + + if (!priv) + { + recGblSetSevr(record, UDF_ALARM, INVALID_ALARM); + errlogSevPrintf(errlogFatal, + "%s: not initialized\n", record->name); + return -1; + } + assert(priv->station); + switch (priv->dtype) + { + case epicsInt8T: + case epicsUInt8T: + status = s7plcFWRead(priv->station, priv->offs, + 1, &rval8); + s7plcFWDebugLog(3, "bi %s: read 8bit %02x\n", + record->name, rval8); + rval32 = rval8; + break; + case epicsInt16T: + case epicsUInt16T: + status = s7plcFWRead(priv->station, priv->offs, + 2, &rval16); + s7plcFWDebugLog(3, "bi %s: read 16bit %04x\n", + record->name, rval16); + rval32 = rval16; + break; + case epicsInt32T: + case epicsUInt32T: + status = s7plcFWRead(priv->station, priv->offs, + 4, &rval32); + s7plcFWDebugLog(3, "bi %s: read 32bit %04x\n", + record->name, rval32); + break; + default: + recGblSetSevr(record, COMM_ALARM, INVALID_ALARM); + errlogSevPrintf(errlogFatal, + "%s: unexpected data type requested\n", + record->name); + return -1; + } + record->rval = rval32 & record->mask; + if (status == S_drv_noConn) + { + recGblSetSevr(record, COMM_ALARM, INVALID_ALARM); + return 0; + } + if (status) + { + errlogSevPrintf(errlogFatal, + "%s: read error\n", record->name); + recGblSetSevr(record, READ_ALARM, INVALID_ALARM); + } + return status; +} + +/* bo ***************************************************************/ + +STATIC long s7plcFWInitRecordBo(boRecord *record) +{ + S7memPrivate_t *priv; + int status; + + if (record->out.type != INST_IO) + { + recGblRecordError(S_db_badField, record, + "s7plcFWInitRecordBo: illegal OUT field"); + return S_db_badField; + } + priv = (S7memPrivate_t *)callocMustSucceed(1, + sizeof(S7memPrivate_t), "s7plcFWInitRecordBo"); + status = s7plcFWIoParse(record->name, + record->out.value.instio.string, priv); + if (status) + { + recGblRecordError(S_db_badField, record, + "s7plcFWInitRecordBo: bad OUT field"); + return S_db_badField; + } + assert(priv->station); + switch (priv->dtype) + { + case epicsInt8T: + case epicsUInt8T: + case epicsInt16T: + case epicsUInt16T: + case epicsInt32T: + case epicsUInt32T: + break; + default: + errlogSevPrintf(errlogFatal, + "s7plcFWInitRecordBo %s: illegal data type\n", + record->name); + return S_db_badField; + } + record->mask = 1 << priv->bit; + record->dpvt = priv; + return 2; /* preserve whatever is in the VAL field */ +} + +STATIC long s7plcFWWriteBo(boRecord *record) +{ + int status; + S7memPrivate_t *priv = (S7memPrivate_t *)record->dpvt; + epicsUInt8 rval8, mask8; + epicsUInt16 rval16, mask16; + epicsUInt32 rval32, mask32; + + if (!priv) + { + recGblSetSevr(record, UDF_ALARM, INVALID_ALARM); + errlogSevPrintf(errlogFatal, + "%s: not initialized\n", record->name); + return -1; + } + assert(priv->station); + switch (priv->dtype) + { + case epicsInt8T: + case epicsUInt8T: + rval8 = record->rval; + mask8 = record->mask; + s7plcFWDebugLog(2, "bo %s: write 8bit %02x mask %02x\n", + record->name, rval8, mask8); + status = s7plcFWWriteMasked(priv->station, priv->offs, + 1, &rval8, &mask8); + break; + case epicsInt16T: + case epicsUInt16T: + rval16 = record->rval; + mask16 = record->mask; + s7plcFWDebugLog(2, "bo %s: write 16bit %04x mask %04x\n", + record->name, rval16, mask16); + status = s7plcFWWriteMasked(priv->station, priv->offs, + 2, &rval16, &mask16); + break; + case epicsInt32T: + case epicsUInt32T: + rval32 = record->rval; + mask32 = record->mask; + s7plcFWDebugLog(2, "bo %s: write 32bit %08x mask %08x\n", + record->name, rval32, mask32); + status = s7plcFWWriteMasked(priv->station, priv->offs, + 4, &rval32, &mask32); + break; + default: + recGblSetSevr(record, COMM_ALARM, INVALID_ALARM); + errlogSevPrintf(errlogFatal, + "%s: unexpected data type requested\n", + record->name); + return -1; + } + if (status == S_drv_noConn) + { + recGblSetSevr(record, COMM_ALARM, INVALID_ALARM); + return 0; + } + if (status) + { + recGblSetSevr(record, WRITE_ALARM, INVALID_ALARM); + } + return status; +} + +/* mbbi *************************************************************/ + +STATIC long s7plcFWInitRecordMbbi(mbbiRecord *record) +{ + S7memPrivate_t *priv; + int status; + + if (record->inp.type != INST_IO) + { + recGblRecordError(S_db_badField, record, + "s7plcFWInitRecordMbbi: illegal INP field type"); + return S_db_badField; + } + priv = (S7memPrivate_t *)callocMustSucceed(1, + sizeof(S7memPrivate_t), "s7plcFWInitRecordMbbi"); + status = s7plcFWIoParse(record->name, + record->inp.value.instio.string, priv); + if (status) + { + recGblRecordError(S_db_badField, record, + "s7plcFWInitRecordMbbi: bad INP field"); + return S_db_badField; + } + assert(priv->station); + switch (priv->dtype) + { + case epicsInt8T: + case epicsUInt8T: + case epicsInt16T: + case epicsUInt16T: + case epicsInt32T: + case epicsUInt32T: + break; + default: + errlogSevPrintf(errlogFatal, + "s7plcFWInitRecordMbbi %s: illegal data type\n", + record->name); + return S_db_badField; + } + if (record->shft > 0) record->mask <<= record->shft; + record->dpvt = priv; + return 0; +} + +STATIC long s7plcFWReadMbbi(mbbiRecord *record) +{ + int status; + S7memPrivate_t *priv = (S7memPrivate_t *)record->dpvt; + epicsUInt8 rval8; + epicsUInt16 rval16; + epicsUInt32 rval32; + + if (!priv) + { + recGblSetSevr(record, UDF_ALARM, INVALID_ALARM); + errlogSevPrintf(errlogFatal, + "%s: not initialized\n", record->name); + return -1; + } + assert(priv->station); + switch (priv->dtype) + { + case epicsInt8T: + case epicsUInt8T: + status = s7plcFWRead(priv->station, priv->offs, + 1, &rval8); + s7plcFWDebugLog(3, "mbbi %s: read 8bit %02x\n", + record->name, rval8); + rval32 = rval8; + break; + case epicsInt16T: + case epicsUInt16T: + status = s7plcFWRead(priv->station, priv->offs, + 2, &rval16); + s7plcFWDebugLog(3, "mbbi %s: read 16bit %04x\n", + record->name, rval16); + rval32 = rval16; + break; + case epicsInt32T: + case epicsUInt32T: + status = s7plcFWRead(priv->station, priv->offs, + 4, &rval32); + s7plcFWDebugLog(3, "mbbi %s: read 32bit %04x\n", + record->name, rval32); + break; + default: + recGblSetSevr(record, COMM_ALARM, INVALID_ALARM); + errlogSevPrintf(errlogFatal, + "%s: unexpected data type requested\n", + record->name); + return -1; + } + record->rval = rval32 & record->mask; + if (status == S_drv_noConn) + { + recGblSetSevr(record, COMM_ALARM, INVALID_ALARM); + return 0; + } + if (status) + { + errlogSevPrintf(errlogFatal, + "%s: read error\n", record->name); + recGblSetSevr(record, READ_ALARM, INVALID_ALARM); + } + return status; +} + +/* mbbo *************************************************************/ + +STATIC long s7plcFWInitRecordMbbo(mbboRecord *record) +{ + S7memPrivate_t *priv; + int status; + + if (record->out.type != INST_IO) { + recGblRecordError(S_db_badField, record, + "s7plcFWInitRecordMbbo: illegal OUT field"); + return S_db_badField; + } + priv = (S7memPrivate_t *)callocMustSucceed(1, + sizeof(S7memPrivate_t), "s7plcFWInitRecordMbbo"); + status = s7plcFWIoParse(record->name, + record->out.value.instio.string, priv); + if (status) + { + recGblRecordError(S_db_badField, record, + "s7plcFWInitRecordMbbo: bad OUT field"); + return S_db_badField; + } + assert(priv->station); + if (record->shft > 0) record->mask <<= record->shft; + switch (priv->dtype) + { + case epicsInt8T: + case epicsUInt8T: + case epicsInt16T: + case epicsUInt16T: + case epicsInt32T: + case epicsUInt32T: + break; + default: + errlogSevPrintf(errlogFatal, + "s7plcFWInitRecordMbbo %s: illegal data type\n", + record->name); + return S_db_badField; + } + record->dpvt = priv; + return 2; /* preserve whatever is in the VAL field */ +} + +STATIC long s7plcFWWriteMbbo(mbboRecord *record) +{ + int status; + S7memPrivate_t *priv = (S7memPrivate_t *)record->dpvt; + epicsUInt8 rval8, mask8; + epicsUInt16 rval16, mask16; + epicsUInt32 rval32, mask32; + + if (!priv) + { + recGblSetSevr(record, UDF_ALARM, INVALID_ALARM); + errlogSevPrintf(errlogFatal, + "%s: not initialized\n", record->name); + return -1; + } + assert(priv->station); + switch (priv->dtype) + { + case epicsInt8T: + case epicsUInt8T: + rval8 = record->rval; + mask8 = record->mask; + s7plcFWDebugLog(2, "mbbo %s: write 8bit %02x mask %02x\n", + record->name, rval8, mask8); + status = s7plcFWWriteMasked(priv->station, priv->offs, + 1, &rval8, &mask8); + break; + case epicsInt16T: + case epicsUInt16T: + rval16 = record->rval; + mask16 = record->mask; + s7plcFWDebugLog(2, "mbbo %s: write 16bit %04x mask %04x\n", + record->name, rval16, mask16); + status = s7plcFWWriteMasked(priv->station, priv->offs, + 2, &rval16, &mask16); + break; + case epicsInt32T: + case epicsUInt32T: + rval32 = record->rval; + mask32 = record->mask; + s7plcFWDebugLog(2, "mbbo %s: write 32bit %08x mask %08x\n", + record->name, rval32, mask32); + status = s7plcFWWriteMasked(priv->station, priv->offs, + 4, &rval32, &mask32); + break; + default: + recGblSetSevr(record, COMM_ALARM, INVALID_ALARM); + errlogSevPrintf(errlogFatal, + "%s: unexpected data type requested\n", + record->name); + return -1; + } + if (status == S_drv_noConn) + { + recGblSetSevr(record, COMM_ALARM, INVALID_ALARM); + return 0; + } + if (status) + { + recGblSetSevr(record, WRITE_ALARM, INVALID_ALARM); + } + return status; +} + +/* mbbiDirect *******************************************************/ + +STATIC long s7plcFWInitRecordMbbiDirect(mbbiDirectRecord *record) +{ + S7memPrivate_t *priv; + int status; + + if (record->inp.type != INST_IO) + { + recGblRecordError(S_db_badField, record, + "s7plcFWInitRecordMbbiDirect: illegal INP field type"); + return S_db_badField; + } + priv = (S7memPrivate_t *)callocMustSucceed(1, + sizeof(S7memPrivate_t), "s7plcFWInitRecordMbbiDirect"); + status = s7plcFWIoParse(record->name, + record->inp.value.instio.string, priv); + if (status) + { + recGblRecordError(S_db_badField, record, + "s7plcFWInitRecordMbbiDirect: bad INP field"); + return S_db_badField; + } + assert(priv->station); + if (record->shft > 0) record->mask <<= record->shft; + switch (priv->dtype) + { + case epicsInt8T: + case epicsUInt8T: + case epicsInt16T: + case epicsUInt16T: + case epicsInt32T: + case epicsUInt32T: + break; + default: + errlogSevPrintf(errlogFatal, + "s7plcFWInitRecordMbbiDirect %s: illegal data type\n", + record->name); + return S_db_badField; + } + record->dpvt = priv; + return 0; +} + +STATIC long s7plcFWReadMbbiDirect(mbbiDirectRecord *record) +{ + int status; + S7memPrivate_t *priv = (S7memPrivate_t *)record->dpvt; + epicsUInt8 rval8; + epicsUInt16 rval16; + epicsUInt32 rval32; + + if (!priv) + { + recGblSetSevr(record, UDF_ALARM, INVALID_ALARM); + errlogSevPrintf(errlogFatal, + "%s: not initialized\n", record->name); + return -1; + } + assert(priv->station); + switch (priv->dtype) + { + case epicsInt8T: + case epicsUInt8T: + status = s7plcFWRead(priv->station, priv->offs, + 1, &rval8); + s7plcFWDebugLog(3, "mbbiDirect %s: read 8bit %02x\n", + record->name, rval8); + rval32 = rval8; + break; + case epicsInt16T: + case epicsUInt16T: + status = s7plcFWRead(priv->station, priv->offs, + 2, &rval16); + s7plcFWDebugLog(3, "mbbiDirect %s: read 16bit %04x\n", + record->name, rval16); + rval32 = rval16; + break; + case epicsInt32T: + case epicsUInt32T: + status = s7plcFWRead(priv->station, priv->offs, + 4, &rval32); + s7plcFWDebugLog(3, "mbbiDirect %s: read 32bit %08x\n", + record->name, rval32); + break; + default: + recGblSetSevr(record, COMM_ALARM, INVALID_ALARM); + errlogSevPrintf(errlogFatal, + "%s: unexpected data type requested\n", + record->name); + return -1; + } + record->rval = rval32 & record->mask; + if (status == S_drv_noConn) + { + recGblSetSevr(record, COMM_ALARM, INVALID_ALARM); + return 0; + } + if (status) + { + errlogSevPrintf(errlogFatal, + "%s: read error\n", record->name); + recGblSetSevr(record, READ_ALARM, INVALID_ALARM); + } + return status; +} + +/* mbboDirect *******************************************************/ + +STATIC long s7plcFWInitRecordMbboDirect(mbboDirectRecord *record) +{ + S7memPrivate_t *priv; + int status; + + if (record->out.type != INST_IO) { + recGblRecordError(S_db_badField, record, + "s7plcFWInitRecordMbboDirect: illegal OUT field"); + return S_db_badField; + } + priv = (S7memPrivate_t *)callocMustSucceed(1, + sizeof(S7memPrivate_t), "s7plcFWInitRecordMbboDirect"); + status = s7plcFWIoParse(record->name, + record->out.value.instio.string, priv); + if (status) + { + recGblRecordError(S_db_badField, record, + "s7plcFWInitRecordMbboDirect: bad OUT field"); + return S_db_badField; + } + assert(priv->station); + if (record->shft > 0) record->mask <<= record->shft; + switch (priv->dtype) + { + case epicsInt8T: + case epicsUInt8T: + case epicsInt16T: + case epicsUInt16T: + case epicsInt32T: + case epicsUInt32T: + break; + default: + errlogSevPrintf(errlogFatal, + "s7plcFWInitRecordMbboDirect %s: illegal data type\n", + record->name); + return S_db_badField; + } + record->dpvt = priv; + return 2; /* preserve whatever is in the VAL field */ +} + +STATIC long s7plcFWWriteMbboDirect(mbboDirectRecord *record) +{ + int status; + S7memPrivate_t *priv = (S7memPrivate_t *)record->dpvt; + epicsUInt8 rval8, mask8; + epicsUInt16 rval16, mask16; + epicsUInt32 rval32, mask32; + + if (!priv) + { + recGblSetSevr(record, UDF_ALARM, INVALID_ALARM); + errlogSevPrintf(errlogFatal, + "%s: not initialized\n", record->name); + return -1; + } + assert(priv->station); + switch (priv->dtype) + { + case epicsInt8T: + case epicsUInt8T: + rval8 = record->rval; + mask8 = record->mask; + s7plcFWDebugLog(2, "mbboDirect %s: write 8bit %02x mask %02x\n", + record->name, rval8, mask8); + status = s7plcFWWriteMasked(priv->station, priv->offs, + 1, &rval8, &mask8); + break; + case epicsInt16T: + case epicsUInt16T: + rval16 = record->rval; + mask16 = record->mask; + s7plcFWDebugLog(2, "mbboDirect %s: write 16bit %04x mask %04x\n", + record->name, rval16, mask16); + status = s7plcFWWriteMasked(priv->station, priv->offs, + 2, &rval16, &mask16); + break; + case epicsInt32T: + case epicsUInt32T: + rval32 = record->rval; + mask32 = record->mask; + s7plcFWDebugLog(2, "mbboDirect %s: write 32bit %08x mask %08x\n", + record->name, rval32, mask32); + status = s7plcFWWriteMasked(priv->station, priv->offs, + 4, &rval32, &mask32); + break; + default: + recGblSetSevr(record, COMM_ALARM, INVALID_ALARM); + errlogSevPrintf(errlogFatal, + "%s: unexpected data type requested\n", record->name); + return -1; + } + if (status == S_drv_noConn) + { + recGblSetSevr(record, COMM_ALARM, INVALID_ALARM); + return 0; + } + if (status) + { + recGblSetSevr(record, WRITE_ALARM, INVALID_ALARM); + } + return status; +} + +/* longin ***********************************************************/ + +STATIC long s7plcFWInitRecordLongin(longinRecord *record) +{ + S7memPrivate_t *priv; + int status; + + if (record->inp.type != INST_IO) + { + recGblRecordError(S_db_badField, record, + "s7plcFWInitRecordLongin: illegal INP field type"); + return S_db_badField; + } + priv = (S7memPrivate_t *)callocMustSucceed(1, + sizeof(S7memPrivate_t), "s7plcFWInitRecordLongin"); + status = s7plcFWIoParse(record->name, + record->inp.value.instio.string, priv); + if (status) + { + recGblRecordError(S_db_badField, record, + "s7plcFWInitRecordLongin: bad INP field"); + return S_db_badField; + } + assert(priv->station); + switch (priv->dtype) + { + case epicsInt8T: + case epicsUInt8T: + case epicsInt16T: + case epicsUInt16T: + case epicsInt32T: + case epicsUInt32T: + break; + default: + errlogSevPrintf(errlogFatal, + "s7plcFWInitRecordLongin %s: illegal data type\n", + record->name); + return S_db_badField; + } + record->dpvt = priv; + return 0; +} + +STATIC long s7plcFWReadLongin(longinRecord *record) +{ + int status; + S7memPrivate_t *priv = (S7memPrivate_t *)record->dpvt; + signed char sval8; + epicsUInt8 uval8; + epicsInt16 sval16; + epicsUInt16 uval16; + epicsInt32 sval32; + epicsUInt32 uval32; + + if (!priv) + { + recGblSetSevr(record, UDF_ALARM, INVALID_ALARM); + errlogSevPrintf(errlogFatal, + "%s: not initialized\n", record->name); + return -1; + } + assert(priv->station); + switch (priv->dtype) + { + case epicsInt8T: + status = s7plcFWRead(priv->station, priv->offs, + 1, &sval8); + s7plcFWDebugLog(3, "longin %s: read 8bit %02x\n", + record->name, sval8); + record->val = sval8; + break; + case epicsUInt8T: + status = s7plcFWRead(priv->station, priv->offs, + 1, &uval8); + s7plcFWDebugLog(3, "longin %s: read 8bit %02x\n", + record->name, uval8); + record->val = uval8; + break; + case epicsInt16T: + status = s7plcFWRead(priv->station, priv->offs, + 2, &sval16); + s7plcFWDebugLog(3, "longin %s: read 16bit %04x\n", + record->name, sval16); + record->val = sval16; + break; + case epicsUInt16T: + status = s7plcFWRead(priv->station, priv->offs, + 2, &uval16); + s7plcFWDebugLog(3, "longin %s: read 16bit %04x\n", + record->name, sval16); + record->val = uval16; + break; + case epicsInt32T: + status = s7plcFWRead(priv->station, priv->offs, + 4, &sval32); + s7plcFWDebugLog(3, "longin %s: read 32bit %04x\n", + record->name, sval32); + record->val = sval32; + break; + case epicsUInt32T: + status = s7plcFWRead(priv->station, priv->offs, + 4, &uval32); + s7plcFWDebugLog(3, "longin %s: read 32bit %04x\n", + record->name, uval32); + record->val = uval32; + break; + default: + recGblSetSevr(record, COMM_ALARM, INVALID_ALARM); + errlogSevPrintf(errlogFatal, + "%s: unexpected data type requested\n", + record->name); + return -1; + } + if (status == S_drv_noConn) + { + recGblSetSevr(record, COMM_ALARM, INVALID_ALARM); + return 0; + } + if (status) + { + errlogSevPrintf(errlogFatal, + "%s: read error\n", record->name); + recGblSetSevr(record, READ_ALARM, INVALID_ALARM); + } + return status; +} + +/* longout **********************************************************/ + +STATIC long s7plcFWInitRecordLongout(longoutRecord *record) +{ + S7memPrivate_t *priv; + int status; + + if (record->out.type != INST_IO) { + recGblRecordError(S_db_badField, record, + "s7plcFWInitRecordLongout: illegal OUT field"); + return S_db_badField; + } + priv = (S7memPrivate_t *)callocMustSucceed(1, + sizeof(S7memPrivate_t), "s7plcFWInitRecordLongout"); + status = s7plcFWIoParse(record->name, + record->out.value.instio.string, priv); + if (status) + { + recGblRecordError(S_db_badField, record, + "s7plcFWInitRecordLongout: bad OUT field"); + return S_db_badField; + } + assert(priv->station); + switch (priv->dtype) + { + case epicsInt8T: + case epicsUInt8T: + case epicsInt16T: + case epicsUInt16T: + case epicsInt32T: + case epicsUInt32T: + break; + default: + errlogSevPrintf(errlogFatal, + "s7plcFWInitRecordLongout %s: illegal data type\n", + record->name); + return S_db_badField; + } + record->dpvt = priv; + return 0; +} + +STATIC long s7plcFWWriteLongout(longoutRecord *record) +{ + int status; + S7memPrivate_t *priv = (S7memPrivate_t *)record->dpvt; + epicsUInt8 rval8; + epicsUInt16 rval16; + epicsUInt32 rval32; + + if (!priv) + { + recGblSetSevr(record, UDF_ALARM, INVALID_ALARM); + errlogSevPrintf(errlogFatal, + "%s: not initialized\n", record->name); + return -1; + } + assert(priv->station); + switch (priv->dtype) + { + case epicsInt8T: + case epicsUInt8T: + rval8 = record->val; + s7plcFWDebugLog(2, "longout %s: write 8bit %02x\n", + record->name, rval8); + status = s7plcFWWrite(priv->station, priv->offs, + 1, &rval8); + break; + case epicsInt16T: + case epicsUInt16T: + rval16 = record->val; + s7plcFWDebugLog(2, "longout %s: write 16bit %04x\n", + record->name, rval16); + status = s7plcFWWrite(priv->station, priv->offs, + 2, &rval16); + break; + case epicsInt32T: + case epicsUInt32T: + rval32 = record->val; + s7plcFWDebugLog(2, "longout %s: write 32bit %08x\n", + record->name, rval32); + status = s7plcFWWrite(priv->station, priv->offs, + 4, &rval32); + break; + default: + recGblSetSevr(record, COMM_ALARM, INVALID_ALARM); + errlogSevPrintf(errlogFatal, + "%s: unexpected data type requested\n", + record->name); + return -1; + } + if (status == S_drv_noConn) + { + recGblSetSevr(record, COMM_ALARM, INVALID_ALARM); + return 0; + } + if (status) + { + recGblSetSevr(record, WRITE_ALARM, INVALID_ALARM); + } + return status; +} + +/* ai ***************************************************************/ + +STATIC long s7plcFWInitRecordAi(aiRecord *record) +{ + S7memPrivate_t *priv; + int status; + + if (record->inp.type != INST_IO) + { + recGblRecordError(S_db_badField, record, + "s7plcFWInitRecordAi: illegal INP field type"); + return S_db_badField; + } + priv = (S7memPrivate_t *)callocMustSucceed(1, sizeof(S7memPrivate_t), + "s7plcFWInitRecordAi"); + status = s7plcFWIoParse(record->name, record->inp.value.instio.string, priv); + if (status) + { + recGblRecordError(S_db_badField, record, + "s7plcFWInitRecordAi: bad INP field"); + return S_db_badField; + } + assert(priv->station); + switch (priv->dtype) + { + case epicsInt8T: + case epicsUInt8T: + case epicsInt16T: + case epicsUInt16T: + case epicsInt32T: + case epicsUInt32T: + case epicsFloat32T: + case epicsFloat64T: + break; + default: + errlogSevPrintf(errlogFatal, + "s7plcFWInitRecordAi %s: illegal data type\n", + record->name); + return S_db_badField; + } + record->dpvt = priv; + s7plcFWSpecialLinconvAi(record, TRUE); + return 0; +} + +STATIC long s7plcFWReadAi(aiRecord *record) +{ + int status, floatval = FALSE; + S7memPrivate_t *priv = (S7memPrivate_t *)record->dpvt; + signed char sval8; + unsigned char uval8; + epicsInt16 sval16; + epicsUInt16 uval16; + epicsInt32 sval32; + epicsUInt32 uval32; + epicsFloat32 val32; + epicsFloat64 val64; + + if (!priv) + { + recGblSetSevr(record, UDF_ALARM, INVALID_ALARM); + errlogSevPrintf(errlogFatal, "%s: not initialized\n", record->name); + return -1; + } + assert(priv->station); + switch (priv->dtype) + { + case epicsInt8T: + status = s7plcFWRead(priv->station, priv->offs, + 1, &sval8); + s7plcFWDebugLog(3, "ai %s: read 8bit %02x\n", + record->name, sval8); + record->rval = sval8; + break; + case epicsUInt8T: + status = s7plcFWRead(priv->station, priv->offs, + 1, &uval8); + s7plcFWDebugLog(3, "ai %s: read 8bit %02x\n", + record->name, uval8); + record->rval = uval8; + break; + case epicsInt16T: + status = s7plcFWRead(priv->station, priv->offs, + 2, &sval16); + s7plcFWDebugLog(3, "ai %s: read 16bit %04x\n", + record->name, sval16); + record->rval = sval16; + break; + case epicsUInt16T: + status = s7plcFWRead(priv->station, priv->offs, + 2, &uval16); + s7plcFWDebugLog(3, "ai %s: read 16bit %04x\n", + record->name, uval16); + record->rval = uval16; + break; + case epicsInt32T: + status = s7plcFWRead(priv->station, priv->offs, + 4, &sval32); + s7plcFWDebugLog(3, "ai %s: read 32bit %04x\n", + record->name, sval32); + record->rval = sval32; + break; + case epicsUInt32T: + status = s7plcFWRead(priv->station, priv->offs, + 4, &uval32); + s7plcFWDebugLog(3, "ai %s: read 32bit %04x\n", + record->name, uval32); + record->rval = uval32; + break; + case epicsFloat32T: + status = s7plcFWRead(priv->station, priv->offs, + 4, &val32); + s7plcFWDebugLog(3, "ai %s: read 32bit %04x = %g\n", + record->name, *(unsigned int*) &val32, val32); + val64 = val32; + floatval = TRUE; + break; + case epicsFloat64T: + status = s7plcFWRead(priv->station, priv->offs, + 8, &val64); + __extension__ s7plcFWDebugLog(3, "ai %s: read 64bit %08Lx = %g\n", + record->name, *(long long*) &val64, val64); + floatval = TRUE; + break; + default: + recGblSetSevr(record, COMM_ALARM, INVALID_ALARM); + errlogSevPrintf(errlogFatal, + "%s: unexpected data type requested\n", + record->name); + return -1; + } + if (status == S_drv_noConn) + { + recGblSetSevr(record, COMM_ALARM, INVALID_ALARM); + return 0; + } + if (status) + { + errlogSevPrintf(errlogFatal, + "%s: read error\n", record->name); + recGblSetSevr(record, READ_ALARM, INVALID_ALARM); + return status; + } + if (floatval) + { + /* emulate scaling */ + if (record->aslo != 0.0) val64 *= record->aslo; + val64 += record->aoff; + if (record->udf) + record->val = val64; + else + /* emulate smoothing */ + record->val = record->val * record->smoo + + val64 * (1.0 - record->smoo); + record->udf = FALSE; + return 2; + } + return 0; +} + +STATIC long s7plcFWSpecialLinconvAi(aiRecord *record, int after) +{ + epicsUInt32 hwSpan; + S7memPrivate_t *priv = (S7memPrivate_t *)record->dpvt; + + if (after) { + hwSpan = priv->hwHigh - priv->hwLow; + record->eslo = (record->eguf - record->egul) / hwSpan; + record->eoff = + (priv->hwHigh*record->egul - priv->hwLow*record->eguf) + / hwSpan; + } + return 0; +} + +/* ao ***************************************************************/ + +STATIC long s7plcFWInitRecordAo(aoRecord *record) +{ + S7memPrivate_t *priv; + int status; + + if (record->out.type != INST_IO) { + recGblRecordError(S_db_badField, record, + "s7plcFWInitRecordAo: illegal OUT field"); + return S_db_badField; + } + priv = (S7memPrivate_t *)callocMustSucceed(1, + sizeof(S7memPrivate_t), "s7plcFWInitRecordAo"); + status = s7plcFWIoParse(record->name, + record->out.value.instio.string, priv); + if (status) + { + recGblRecordError(S_db_badField, record, + "s7plcFWInitRecordAo: bad OUT field"); + return S_db_badField; + } + assert(priv->station); + switch (priv->dtype) + { + case epicsInt8T: + case epicsUInt8T: + case epicsInt16T: + case epicsUInt16T: + case epicsInt32T: + case epicsUInt32T: + case epicsFloat32T: + case epicsFloat64T: + break; + default: + errlogSevPrintf(errlogFatal, + "s7plcFWInitRecordAo %s: illegal data type\n", + record->name); + return S_db_badField; + } + record->dpvt = priv; + s7plcFWSpecialLinconvAo(record, TRUE); + return 2; /* preserve whatever is in the VAL field */ +} + +STATIC long s7plcFWWriteAo(aoRecord *record) +{ + int status; + S7memPrivate_t *priv = (S7memPrivate_t *)record->dpvt; + epicsUInt8 rval8; + epicsUInt16 rval16; + epicsUInt32 rval32; + epicsFloat32 val32; + epicsFloat64 val64; + + if (!priv) + { + recGblSetSevr(record, UDF_ALARM, INVALID_ALARM); + errlogSevPrintf(errlogFatal, + "%s: not initialized\n", record->name); + return -1; + } + assert(priv->station); + rval32 = record->rval; + switch (priv->dtype) + { + case epicsInt8T: + if (record->rval > priv->hwHigh) rval32 = priv->hwHigh; + if (record->rval < priv->hwLow) rval32 = priv->hwLow; + rval8 = rval32; + s7plcFWDebugLog(2, "ao %s: write 8bit %02x\n", + record->name, rval8 & 0xff); + status = s7plcFWWrite(priv->station, priv->offs, + 1, &rval8); + break; + case epicsUInt8T: + if (rval32 > (epicsUInt32)priv->hwHigh) rval32 = priv->hwHigh; + if (rval32 < (epicsUInt32)priv->hwLow) rval32 = priv->hwLow; + rval8 = rval32; + s7plcFWDebugLog(2, "ao %s: write 8bit %02x\n", + record->name, rval8 & 0xff); + status = s7plcFWWrite(priv->station, priv->offs, + 1, &rval8); + break; + case epicsInt16T: + if (record->rval > priv->hwHigh) rval32 = priv->hwHigh; + if (record->rval < priv->hwLow) rval32 = priv->hwLow; + rval16 = rval32; + s7plcFWDebugLog(2, "ao %s: write 16bit %04x\n", + record->name, rval16 & 0xffff); + status = s7plcFWWrite(priv->station, priv->offs, + 2, &rval16); + break; + case epicsUInt16T: + if (rval32 > (epicsUInt32)priv->hwHigh) rval32 = priv->hwHigh; + if (rval32 < (epicsUInt32)priv->hwLow) rval32 = priv->hwLow; + rval16 = rval32; + s7plcFWDebugLog(2, "ao %s: write 16bit %04x\n", + record->name, rval16 & 0xffff); + status = s7plcFWWrite(priv->station, priv->offs, + 2, &rval16); + break; + case epicsInt32T: + if (record->rval > priv->hwHigh) rval32 = priv->hwHigh; + if (record->rval < priv->hwLow) rval32 = priv->hwLow; + s7plcFWDebugLog(2, "ao %s: write 32bit %08x\n", + record->name, rval32); + status = s7plcFWWrite(priv->station, priv->offs, + 4, &rval32); + break; + case epicsUInt32T: + if (rval32 > (epicsUInt32)priv->hwHigh) rval32 = priv->hwHigh; + if (rval32 < (epicsUInt32)priv->hwLow) rval32 = priv->hwLow; + s7plcFWDebugLog(2, "ao %s: write 32bit %08x\n", + record->name, rval32); + status = s7plcFWWrite(priv->station, priv->offs, + 4, &rval32); + break; + case epicsFloat32T: + /* emulate scaling */ + val32 = record->oval - record->aoff; + if (record->aslo != 0) val32 /= record->aslo; + + s7plcFWDebugLog(2, "ao %s: write 32bit %08x\n", + record->name, *(epicsInt32*)&val32); + status = s7plcFWWrite(priv->station, priv->offs, + 4, &val32); + break; + case epicsFloat64T: + /* emulate scaling */ + val64 = record->oval - record->aoff; + if (record->aslo != 0) val64 /= record->aslo; + + __extension__ s7plcFWDebugLog(2, "ao %s: write 64bit %016Lx\n", + record->name, *(long long*)&val64); + status = s7plcFWWrite(priv->station, priv->offs, + 8, &val64); + break; + default: + recGblSetSevr(record, COMM_ALARM, INVALID_ALARM); + errlogSevPrintf(errlogFatal, + "%s: unexpected data type requested\n", + record->name); + return -1; + } + if (status == S_drv_noConn) + { + recGblSetSevr(record, COMM_ALARM, INVALID_ALARM); + return 0; + } + if (status) + { + recGblSetSevr(record, WRITE_ALARM, INVALID_ALARM); + } + return status; +} + +STATIC long s7plcFWSpecialLinconvAo(aoRecord *record, int after) +{ + epicsUInt32 hwSpan; + S7memPrivate_t *priv = (S7memPrivate_t *) record->dpvt; + + if (after) { + hwSpan = priv->hwHigh - priv->hwLow; + record->eslo = (record->eguf - record->egul) / hwSpan; + record->eoff = + (priv->hwHigh*record->egul -priv->hwLow*record->eguf) + / hwSpan; + } + return 0; +} + +/* stringin *********************************************************/ + +STATIC long s7plcFWInitRecordStringin(stringinRecord *record) +{ + S7memPrivate_t *priv; + int status; + + if (record->inp.type != INST_IO) + { + recGblRecordError(S_db_badField, record, + "s7plcFWInitRecordStringin: illegal INP field type"); + return S_db_badField; + } + priv = (S7memPrivate_t *)callocMustSucceed(1, + sizeof(S7memPrivate_t), "s7plcFWInitRecordStringin"); + priv->dtype = epicsStringT; + priv->dlen = sizeof(record->val); + status = s7plcFWIoParse(record->name, + record->inp.value.instio.string, priv); + if (status) + { + recGblRecordError(S_db_badField, record, + "s7plcFWInitRecordLongin: bad INP field"); + return S_db_badField; + } + assert(priv->station); + if (priv->dtype != epicsStringT) + { + errlogSevPrintf(errlogFatal, + "s7plcFWInitRecordStringin %s: illegal data type\n", + record->name); + return S_db_badField; + } + if (priv->dlen > sizeof(record->val)) + { + errlogSevPrintf(errlogMinor, + "%s: string size reduced from %d to %d\n", + record->name, priv->dlen, sizeof(record->val)); + priv->dlen = sizeof(record->val); + } + record->dpvt = priv; + return 0; +} + +STATIC long s7plcFWReadStringin(stringinRecord *record) +{ + int status; + S7memPrivate_t *priv = (S7memPrivate_t *)record->dpvt; + + if (!priv) + { + recGblSetSevr(record, UDF_ALARM, INVALID_ALARM); + errlogSevPrintf(errlogFatal, + "%s: not initialized\n", record->name); + return -1; + } + assert(priv->station); + memset(record->val, 0, priv->dlen); + status = s7plcFWReadArray(priv->station, priv->offs, + 1, priv->dlen, record->val); + s7plcFWDebugLog(3, "stringin %s: read array of %d 8bit values\n", + record->name, priv->dlen); + if (record->val[priv->dlen] && !memchr(record->val, 0, priv->dlen)) + { + /* truncate oversize string */ + record->val[priv->dlen] = 0; + } + if (status == S_drv_noConn) + { + recGblSetSevr(record, COMM_ALARM, INVALID_ALARM); + return 0; + } + if (status) + { + errlogSevPrintf(errlogFatal, + "%s: read error\n", record->name); + recGblSetSevr(record, READ_ALARM, INVALID_ALARM); + } + return status; +} + +/* stringout ********************************************************/ + +STATIC long s7plcFWInitRecordStringout(stringoutRecord *record) +{ + S7memPrivate_t *priv; + int status; + + if (record->out.type != INST_IO) { + recGblRecordError(S_db_badField, record, + "s7plcFWInitRecordStringout: illegal OUT field"); + return S_db_badField; + } + priv = (S7memPrivate_t *)callocMustSucceed(1, + sizeof(S7memPrivate_t), "s7plcFWInitRecordStringout"); + priv->dtype = epicsStringT; + priv->dlen = sizeof(record->val); + status = s7plcFWIoParse(record->name, + record->out.value.instio.string, priv); + if (status) + { + recGblRecordError(S_db_badField, record, + "s7plcFWInitRecordStringout: bad OUT field"); + return S_db_badField; + } + assert(priv->station); + if (priv->dtype != epicsStringT) + { + errlogSevPrintf(errlogFatal, + "s7plcFWInitRecordStringout %s: illegal data type\n", + record->name); + return S_db_badField; + } + if (priv->dlen > sizeof(record->val)) + { + errlogSevPrintf(errlogMinor, + "%s: string size reduced from %d to %d\n", + record->name, priv->dlen, sizeof(record->val)); + priv->dlen = sizeof(record->val); + } + record->dpvt = priv; + return 0; +} + +STATIC long s7plcFWWriteStringout(stringoutRecord *record) +{ + int status; + S7memPrivate_t *priv = (S7memPrivate_t *)record->dpvt; + + if (!priv) + { + recGblSetSevr(record, UDF_ALARM, INVALID_ALARM); + errlogSevPrintf(errlogFatal, + "%s: not initialized\n", record->name); + return -1; + } + assert(priv->station); + s7plcFWDebugLog(2, "stringout %s: write %d 8bit values: \"%.*s\"\n", + record->name, priv->dlen, priv->dlen, record->val); + status = s7plcFWWriteArray(priv->station, priv->offs, + 1, priv->dlen, record->val); + if (status == S_drv_noConn) + { + recGblSetSevr(record, COMM_ALARM, INVALID_ALARM); + return 0; + } + if (status) + { + recGblSetSevr(record, WRITE_ALARM, INVALID_ALARM); + } + return status; +} + +/* waveform *********************************************************/ + +STATIC long s7plcFWInitRecordWaveform(waveformRecord *record) +{ + S7memPrivate_t *priv; + int status; + + if (record->inp.type != INST_IO) { + recGblRecordError(S_db_badField, record, + "s7plcFWInitRecordWaveform: illegal INP field"); + return S_db_badField; + } + priv = (S7memPrivate_t *)callocMustSucceed(1, + sizeof(S7memPrivate_t), "s7plcFWInitRecordWaveform"); + switch (record->ftvl) + { + case DBF_CHAR: + priv->dtype = epicsInt8T; + priv->dlen = 1; + break; + case DBF_UCHAR: + priv->dtype = epicsUInt8T; + priv->dlen = 1; + break; + case DBF_SHORT: + priv->dtype = epicsInt16T; + priv->dlen = 2; + break; + case DBF_USHORT: + priv->dtype = epicsUInt16T; + priv->dlen = 2; + break; + case DBF_LONG: + priv->dtype = epicsInt32T; + priv->dlen = 4; + break; + case DBF_ULONG: + priv->dtype = epicsUInt32T; + priv->dlen = 4; + break; + case DBF_FLOAT: + priv->dtype = epicsFloat32T; + priv->dlen = 4; + break; + case DBF_DOUBLE: + priv->dtype = epicsFloat64T; + priv->dlen = 8; + break; + default: + errlogSevPrintf(errlogFatal, + "s7plcFWInitRecordWaveform %s: illegal FTVL value\n", + record->name); + return S_db_badField; + } + status = s7plcFWIoParse(record->name, + record->inp.value.instio.string, priv); + if (status) + { + recGblRecordError(S_db_badField, record, + "s7plcFWInitRecordWaveform: bad INP field"); + return S_db_badField; + } + assert(priv->station); + record->nord = record->nelm; + switch (priv->dtype) + { + case S7MEM_TIME: + if ((record->ftvl != DBF_CHAR) && (record->ftvl != DBF_UCHAR)) + { + status = S_db_badField; + } + break; + case epicsFloat64T: + if (record->ftvl != DBF_DOUBLE) + { + status = S_db_badField; + } + break; + case epicsFloat32T: + if (record->ftvl != DBF_FLOAT) + { + status = S_db_badField; + } + break; + case epicsStringT: + if ((record->ftvl == DBF_CHAR) || (record->ftvl == DBF_UCHAR)) + { + if (!priv->dlen) priv->dlen = record->nelm; + if (priv->dlen > record->nelm) priv->dlen = record->nelm; + break; + } + break; + case epicsInt8T: + case epicsUInt8T: + if ((record->ftvl != DBF_CHAR) && (record->ftvl == DBF_UCHAR)) + { + status = S_db_badField; + } + break; + case epicsInt16T: + case epicsUInt16T: + if ((record->ftvl != DBF_SHORT) && (record->ftvl == DBF_USHORT)) + { + status = S_db_badField; + } + break; + case epicsInt32T: + case epicsUInt32T: + if ((record->ftvl != DBF_LONG) && (record->ftvl == DBF_ULONG)) + { + status = S_db_badField; + } + break; + default: + errlogSevPrintf(errlogFatal, + "s7plcFWInitRecordWaveform %s: illegal data type\n", + record->name); + return S_db_badField; + } + if (status) + { + errlogSevPrintf(errlogFatal, + "s7plcFWInitRecordWaveform %s: " + "wrong FTVL field for this data type", + record->name); + return status; + } + record->dpvt = priv; + return 0; +} + +/* + * bcd2d routine to convert byte from BCD to decimal format. + */ +static unsigned char bcd2d(unsigned char bcd) +{ + unsigned char tmp; + + tmp = bcd & 0xF; + tmp += ((bcd >> 4) & 0xF)*10; + + return tmp; +} + +STATIC long s7plcFWReadWaveform(waveformRecord *record) +{ + int status; + S7memPrivate_t *priv = (S7memPrivate_t *)record->dpvt; + char Time[8]; + int i; + char *p; + + if (!priv) + { + recGblSetSevr(record, UDF_ALARM, INVALID_ALARM); + errlogSevPrintf(errlogFatal, + "%s: not initialized\n", record->name); + return -1; + } + assert(priv->station); + switch (priv->dtype) + { + case epicsInt8T: + case epicsUInt8T: + case epicsStringT: + status = s7plcFWReadArray(priv->station, priv->offs, + 1, record->nelm, record->bptr); + s7plcFWDebugLog(3, + "waveform %s: read %ld values of 8bit to %p\n", + record->name, (long int) record->nelm, record->bptr); + break; + case epicsInt16T: + case epicsUInt16T: + status = s7plcFWReadArray(priv->station, priv->offs, + 2, record->nelm, record->bptr); + s7plcFWDebugLog(3, + "waveform %s: read %ld values of 16bit to %p\n", + record->name, (long int) record->nelm, record->bptr); + break; + case epicsInt32T: + case epicsUInt32T: + case epicsFloat32T: + status = s7plcFWReadArray(priv->station, priv->offs, + 4, record->nelm, record->bptr); + s7plcFWDebugLog(3, + "waveform %s: read %ld values of 32bit to %p\n", + record->name, (long int) record->nelm, record->bptr); + break; + case epicsFloat64T: + status = s7plcFWReadArray(priv->station, priv->offs, + 8, record->nelm, record->bptr); + s7plcFWDebugLog(3, + "waveform %s: read %ld values of 64bit to %p\n", + record->name, (long int) record->nelm, record->bptr); + break; + case S7MEM_TIME: + status = s7plcFWReadArray(priv->station, priv->offs, + 1, 8, Time); + s7plcFWDebugLog(3, + "waveform %s: read 8 values of 8bit to %p\n", + record->name, record->bptr); + if (status) break; + for (i = 0, p = record->bptr; i < record->nelm; i++) + *p++ = (i == 7)? Time[i] : bcd2d(Time[i]); + break; + default: + recGblSetSevr(record, COMM_ALARM, INVALID_ALARM); + errlogSevPrintf(errlogFatal, + "%s: unexpected data type requested\n", + record->name); + return -1; + } + record->nord = record->nelm; + if (status == S_drv_noConn) + { + recGblSetSevr(record, COMM_ALARM, INVALID_ALARM); + return 0; + } + if (status) + { + errlogSevPrintf(errlogFatal, + "%s: read error\n", record->name); + recGblSetSevr(record, READ_ALARM, INVALID_ALARM); + } + return status; +} + +#if (EPICS_REVISION>=14) +/* calcout **********************************************************/ + +STATIC long s7plcFWInitRecordCalcout(calcoutRecord *record) +{ + S7memPrivate_t *priv; + int status; + + if (record->out.type != INST_IO) { + recGblRecordError(S_db_badField, record, + "s7plcFWInitRecordCalcout: illegal OUT field"); + return S_db_badField; + } + priv = (S7memPrivate_t *)callocMustSucceed(1, + sizeof(S7memPrivate_t), "s7plcFWInitRecordCalcout"); + status = s7plcFWIoParse(record->name, + record->out.value.instio.string, priv); + if (status) + { + recGblRecordError(S_db_badField, record, + "s7plcFWInitRecordCalcout: bad OUT field"); + return S_db_badField; + } + assert(priv->station); + switch (priv->dtype) + { + case epicsInt8T: + case epicsUInt8T: + case epicsInt16T: + case epicsUInt16T: + case epicsInt32T: + case epicsUInt32T: + case epicsFloat32T: + case epicsFloat64T: + break; + default: + errlogSevPrintf(errlogFatal, + "s7plcFWInitRecordCalcout %s: illegal data type\n", + record->name); + return S_db_badField; + } + record->dpvt = priv; + return 2; /* preserve whatever is in the VAL field */ +} + +STATIC long s7plcFWWriteCalcout(calcoutRecord *record) +{ + int status; + S7memPrivate_t *priv = (S7memPrivate_t *)record->dpvt; + epicsUInt8 uval8; + epicsUInt16 uval16; + epicsUInt32 uval32; + epicsInt8 sval8; + epicsInt16 sval16; + epicsInt32 sval32; + epicsFloat32 val32; + epicsFloat64 val64; + + if (!priv) + { + recGblSetSevr(record, UDF_ALARM, INVALID_ALARM); + errlogSevPrintf(errlogFatal, + "%s: not initialized\n", record->name); + return -1; + } + assert(priv->station); + val64 = record->oval; + switch (priv->dtype) + { + case epicsInt8T: + sval8 = val64; + if (val64 > priv->hwHigh) sval8 = priv->hwHigh; + if (val64 < priv->hwLow) sval8 = priv->hwLow; + s7plcFWDebugLog(2, "calcout %s: write 8bit %02x\n", + record->name, sval8 & 0xff); + status = s7plcFWWrite(priv->station, priv->offs, + 1, &sval8); + break; + case epicsUInt8T: + uval8 = val64; + if (val64 > priv->hwHigh) uval8 = priv->hwHigh; + if (val64 < priv->hwLow) uval8 = priv->hwLow; + s7plcFWDebugLog(2, "calcout %s: write 8bit %02x\n", + record->name, uval8 & 0xff); + status = s7plcFWWrite(priv->station, priv->offs, + 1, &uval8); + break; + case epicsInt16T: + sval16 = val64; + if (val64 > priv->hwHigh) sval16 = priv->hwHigh; + if (val64 < priv->hwLow) sval16 = priv->hwLow; + s7plcFWDebugLog(2, "calcout %s: write 16bit %04x\n", + record->name, sval16 & 0xffff); + status = s7plcFWWrite(priv->station, priv->offs, + 2, &sval16); + break; + case epicsUInt16T: + uval16 = val64; + if (val64 > priv->hwHigh) uval16 = priv->hwHigh; + if (val64 < priv->hwLow) uval16 = priv->hwLow; + s7plcFWDebugLog(2, "calcout %s: write 16bit %04x\n", + record->name, uval16 & 0xffff); + status = s7plcFWWrite(priv->station, priv->offs, + 2, &uval16); + break; + case epicsInt32T: + sval32 = val64; + if (val64 > priv->hwHigh) sval32 = priv->hwHigh; + if (val64 < priv->hwLow) sval32 = priv->hwLow; + s7plcFWDebugLog(2, "calcout %s: write 32bit %08x\n", + record->name, sval32); + status = s7plcFWWrite(priv->station, priv->offs, + 4, &sval32); + break; + case epicsUInt32T: + uval32 = val64; + if (val64 > priv->hwHigh) uval32 = priv->hwHigh; + if (val64 < priv->hwLow) uval32 = priv->hwLow; + s7plcFWDebugLog(2, "calcout %s: write 32bit %08x\n", + record->name, uval32); + status = s7plcFWWrite(priv->station, priv->offs, + 4, &uval32); + break; + case epicsFloat32T: + val32 = val64; + s7plcFWDebugLog(2, "calcout %s: write 32bit %08x\n", + record->name, *(epicsInt32*)&val32); + status = s7plcFWWrite(priv->station, priv->offs, + 4, &val32); + break; + case epicsFloat64T: + __extension__ s7plcFWDebugLog(2, "calcout %s: write 64bit %016Lx\n", + record->name, *(long long*)&val64); + status = s7plcFWWrite(priv->station, priv->offs, + 8, &val64); + break; + default: + recGblSetSevr(record, COMM_ALARM, INVALID_ALARM); + errlogSevPrintf(errlogFatal, + "%s: unexpected data type requested\n", + record->name); + return -1; + } + if (status == S_drv_noConn) + { + recGblSetSevr(record, COMM_ALARM, INVALID_ALARM); + return 0; + } + if (status) + { + recGblSetSevr(record, WRITE_ALARM, INVALID_ALARM); + } + return status; +} + +#endif diff --git a/drvS7plcFW.c b/drvS7plcFW.c new file mode 100644 index 0000000..119c781 --- /dev/null +++ b/drvS7plcFW.c @@ -0,0 +1,1055 @@ +/* $Date: 2010/09/22 13:44:37 $ */ +/* $Id: drvS7plcFW.c,v 1.1 2010/09/22 13:44:37 anicic Exp $ */ +/* $Name: $ */ +/* $Revision: 1.1 $ */ + +/* + * NOTE: s7plcFWwriteThread -is not used for writting (we write direct), + * s7plcFWIOintThread - is used instead, just to trigger output records configured for "I/O intr" +*/ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __vxworks +#include +#include +#include +#else +#include +#endif + +#include +#include +#include +#include + +#include "drvS7plcFW.h" + +#if ((EPICS_VERSION==3 && EPICS_REVISION>=14) || EPICS_VERSION>3) +/* R3.14 */ +#include +#include +#include +#include +#include +#include +#include +#include +#else +/* R3.13 */ +#include "compat3_13.h" +#endif + +#ifdef __vxworks +#define __BYTE_ORDER _BYTE_ORDER +#define __LITTLE_ENDIAN _LITTLE_ENDIAN +#define __BIG_ENDIAN _BIG_ENDIAN +#else +#include +#endif + +#define STACK_SIZE 20000 /* io thread stack size */ +#define CONNECT_TIMEOUT 5.0 /* connect timeout [s] */ +#define RECONNECT_DELAY 10.0 /* delay before reconnect [s] */ + +static char cvsid[] __attribute__((unused)) = +"$Id: drvS7plcFW.c,v 1.1 2010/09/22 13:44:37 anicic Exp $"; + +STATIC long s7plcFWIoReport(int level); +STATIC long s7plcFWInit(); +STATIC void s7plcFWMain (); +STATIC void s7plcFWIOintThread(s7plcFWStation* station); +STATIC void s7plcFWFetchThread(s7plcFWStation* station); +STATIC int s7plcFWWaitForInput(s7plcFWStation* station, int which, double timeout); +STATIC int s7plcFWEstablishConnection(s7plcFWStation* station, int which); +STATIC void s7plcFWCloseConnection(s7plcFWStation* station, int which); +STATIC void s7plcFWSignal(void* event); +STATIC int s7plcFWdoFetch(s7plcFWStation *station, int org, int db, int offs, int size, char *recvBuf); +STATIC int s7plcFWdoWrite(s7plcFWStation *station, char *data, int offs, int len); + +s7plcFWStation* s7plcFWStationList = NULL; +static epicsTimerQueueId timerqueue = NULL; + + +struct { + long number; + long (*report)(); + long (*init)(); +} s7plcFW = { + 2, + s7plcFWIoReport, + s7plcFWInit +}; +epicsExportAddress(drvet, s7plcFW); + +int s7plcFWDebug = 0; +epicsExportAddress(int, s7plcFWDebug); + + +#define FOR_FETCH 1 +#define FOR_WRITE 2 + +struct s7plcFWStation { + struct s7plcFWStation *next; + + char name[32]; + + char serverIP[20]; + + unsigned short int fetchPort, writePort; + unsigned char fetchOrg, writeOrg; + unsigned char fetchDb, writeDb; + unsigned int fetchOffs, writeOffs; + unsigned int fetchSize, writeSize; + + volatile int fetchConnStatus, writeConnStatus; + volatile int fetchSocket, writeSocket; + + int fetchWriteDb; /* we will fetch write db once, on driver startup */ + + epicsMutexId fetchMutex, writeMutex; + epicsMutexId fetchIo, writeIo; + + IOSCANPVT fetchScanPvt, writeScanPvt; + + epicsThreadId iointThread, fetchThread; + + epicsTimerId timer; + epicsEventId outTrigger; + + char *fetchBuffer, *writeBuffer; + int swapBytes; + float recvTimeout, recvDelay, outIOintDelay; +}; + +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +void s7plcFWDebugLog(int level, const char *fmt, ...) +{ + va_list args; + + if (level > s7plcFWDebug) return; + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); +} + +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +STATIC long s7plcFWIoReport(int level) +{ + s7plcFWStation *station; + + printf("%s\n", cvsid); + if (level == 1) + { + printf("S7plcFW stations:\n"); + for (station = s7plcFWStationList; station; station=station->next) { + printf(" ** Station %s\n", station->name); + printf(" PLC: IP-address=%s\n", station->serverIP); + + printf(" FETCH: Port=%5d Org=%3d Db=%3d Offs=%5d Size=%5d buffer@%p (%5d bytes)", + station->fetchPort, station->fetchOrg, station->fetchDb, station->fetchOffs, station->fetchSize, station->fetchBuffer, station->fetchSize); + if (station->fetchConnStatus) { + printf(" CONNECTED (fd=%d)\n", station->fetchSocket); + } + else { + printf(" NOT CONNECTED\n"); + } + + printf(" WRITE: Port=%5d Org=%3d Db=%3d Offs=%5d Size=%5d buffer@%p (%5d bytes)", + station->writePort, station->writeOrg, station->writeDb, station->writeOffs, station->writeSize, station->writeBuffer, station->writeSize); + if (station->writeConnStatus) { + printf(" CONNECTED (fd=%d)\n", station->writeSocket); + } + else { + printf(" NOT CONNECTED\n"); + } + + printf(" swap bytes %s\n", station->swapBytes ? +#if (__BYTE_ORDER == __LITTLE_ENDIAN) + "ioc:intel <-> plc:motorola" : "no (both intel)" +#else + "ioc:motorola <-> plc:intel" : "no (both motorola)" +#endif + ); + printf(" receive timeout %g sec\n", station->recvTimeout); + printf(" receive delay %g sec\n", station->recvDelay); + printf(" outIOint delay %g sec\n", station->outIOintDelay); + } + } + return 0; +} + +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +STATIC long s7plcFWInit() +{ + if (!s7plcFWStationList) { + errlogSevPrintf(errlogInfo, "s7plcFWInit: no stations configured\n"); + return 0; + } + s7plcFWDebugLog(1, "s7plcFWInit: starting main thread\n"); + epicsThreadCreate("s7plcFWMain", epicsThreadPriorityMedium, STACK_SIZE, (EPICSTHREADFUNC) s7plcFWMain, NULL); + return 0; +} + +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +STATIC void s7plcFWSignal(void* event) +{ + epicsEventSignal((epicsEventId)event); +} + +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +STATIC void extractPGDOS(char *info, unsigned short int *port, unsigned char *org, unsigned char *db, unsigned int *offs, unsigned int *size) +{ + int status; + unsigned int p, g, d, o, s; + + *port = 0; + *org = 0; + *db = 0; + *offs = 0; + *size = 0; + + if (info == NULL) return; + status = sscanf(info, "%u,%u,%u,%u,%u", &p, &g, &d, &o, &s); + if (status == 5) { + *port = p; + *org = g; + *db = d; + *offs = o; + *size = s; + } +} + +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +/* strings fetchInfo & writeInfo: "Port,Org,Db,Offs,Size" */ +int s7plcFWConfigure(char *name, char* IPaddr, char *fetchInfo, char *writeInfo, int bigEndian, int recvTimeout, int recvDelay, int outIOintDelay) +{ + s7plcFWStation *station; + s7plcFWStation **pstation; + unsigned long ip; + unsigned short int fetchPort, writePort; + unsigned char fetchOrg, writeOrg; + unsigned char fetchDb, writeDb; + unsigned int fetchOffs, writeOffs; + unsigned int fetchSize, writeSize; + + if (!name) { + errlogSevPrintf(errlogFatal, "s7plcFWConfigure: missing name\n"); + return -1; + } + if (*name == '\0') { + errlogSevPrintf(errlogFatal, "s7plcFWConfigure: missing name\n"); + return -1; + } + if (!IPaddr) { + errlogSevPrintf(errlogFatal, "s7plcFWConfigure: missing IP address\n"); + return -1; + } + if (!fetchInfo) { + errlogSevPrintf(errlogFatal, "s7plcFWConfigure: missing FETCH info\n"); + return -1; + } + + extractPGDOS(fetchInfo, &fetchPort, &fetchOrg, &fetchDb, &fetchOffs, &fetchSize); + extractPGDOS(writeInfo, &writePort, &writeOrg, &writeDb, &writeOffs, &writeSize); + + if ((fetchPort == 0) || (fetchOrg == 0) || (fetchDb == 0) || ((fetchOffs%2) != 0) || (fetchSize == 0) || ((fetchSize%2) != 0)) { /* size & offs: only even numbers */ + fetchPort = fetchOrg = fetchDb = fetchOffs = fetchSize = 0; + } + if ((writePort == 0) || (writeOrg == 0) || (writeDb == 0) || ((writeOffs%2) != 0) || (writeSize == 0) || ((writeSize%2) != 0)) { /* size & offs: only even numbers */ + writePort = writeOrg = writeDb = writeOffs = writeSize = 0; + } + + ip = inet_addr(IPaddr); + if (ip == INADDR_NONE) { + errlogSevPrintf(errlogFatal, "s7plcFWConfigure: invalid IP address %s\n", IPaddr); + return -1; + } + ip = ntohl(ip); + + if (fetchPort == 0) { + errlogSevPrintf(errlogFatal, "s7plcFWConfigure: invalid FETCH info\n"); + return -1; + } + if (writePort == 0) { + errlogSevPrintf(errlogFatal, "s7plcFWConfigure: WRITE will not be supported\n"); + /* return -1; */ + } + + /* find last station in list */ + for (pstation = &s7plcFWStationList; *pstation; pstation = &(*pstation)->next); + + station = callocMustSucceed(1, sizeof(s7plcFWStation), "s7plcFWConfigure"); + + station->next = NULL; + sprintf(station->serverIP, "%ld.%ld.%ld.%ld", (ip>>24)&0xff, (ip>>16)&0xff, (ip>>8)&0xff, ip&0xff); + station->fetchPort = fetchPort; + station->writePort = writePort; + station->fetchOrg = fetchOrg; + station->writeOrg = writeOrg; + station->fetchDb = fetchDb; + station->writeDb = writeDb; + station->fetchOffs = fetchOffs; + station->writeOffs = writeOffs; + station->fetchSize = fetchSize; + station->writeSize = writeSize; + + if (fetchSize > 0) + station->fetchBuffer = callocMustSucceed(1, fetchSize, "s7plcFWConfigure"); + else + station->fetchBuffer = NULL; + + if (writeSize > 0) + station->writeBuffer = callocMustSucceed(1, writeSize, "s7plcFWConfigure"); + else + station->writeBuffer = NULL; + + sprintf(station->name, "%.31s", name); + +#if (__BYTE_ORDER == __LITTLE_ENDIAN) + station->swapBytes = bigEndian; +#elif (__BYTE_ORDER == __BIG_ENDIAN) + station->swapBytes = !bigEndian; +#else +#error Strange byte order on this machine. +#endif + station->fetchConnStatus = 0; + station->writeConnStatus = 0; + station->fetchSocket = -1; + station->writeSocket = -1; + + station->fetchWriteDb = 1; /* fetch it (next time when both fetch and write sockets are connected) */ + + station->fetchMutex = epicsMutexMustCreate(); + station->writeMutex = epicsMutexMustCreate(); + station->fetchIo = epicsMutexMustCreate(); + station->writeIo = epicsMutexMustCreate(); + if (station->writeSize) + { + station->outTrigger = epicsEventMustCreate(epicsEventEmpty); + if (!timerqueue) + { + timerqueue = epicsTimerQueueAllocate(1, epicsThreadPriorityHigh); + } + station->timer = epicsTimerQueueCreateTimer(timerqueue, s7plcFWSignal, station->outTrigger); + } + scanIoInit(&station->fetchScanPvt); + scanIoInit(&station->writeScanPvt); + station->fetchThread = NULL; + station->iointThread = NULL; + station->recvTimeout = recvTimeout > 0 ? recvTimeout/1000.0 : 2.0; + station->recvDelay = recvDelay > 0 ? recvDelay/1000.0 : 1.0; + station->outIOintDelay = outIOintDelay > 0 ? outIOintDelay/1000.0 : 1.0; + + + /* append station to list */ + *pstation = station; + + return 0; +} + +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +#if (EPICS_REVISION>=14) +static const iocshArg s7plcFWConfigureArg0 = { "name", iocshArgString }; +static const iocshArg s7plcFWConfigureArg1 = { "IPaddr", iocshArgString }; +static const iocshArg s7plcFWConfigureArg2 = { "fetchInfo", iocshArgString }; +static const iocshArg s7plcFWConfigureArg3 = { "writeInfo", iocshArgString }; +static const iocshArg s7plcFWConfigureArg4 = { "bigEndian", iocshArgInt }; +static const iocshArg s7plcFWConfigureArg5 = { "recvTimeout", iocshArgInt }; +static const iocshArg s7plcFWConfigureArg6 = { "recvDelay", iocshArgInt }; +static const iocshArg s7plcFWConfigureArg7 = { "outIOintDelay", iocshArgInt }; +static const iocshArg * const s7plcFWConfigureArgs[] = { + &s7plcFWConfigureArg0, + &s7plcFWConfigureArg1, + &s7plcFWConfigureArg2, + &s7plcFWConfigureArg3, + &s7plcFWConfigureArg4, + &s7plcFWConfigureArg5, + &s7plcFWConfigureArg6, + &s7plcFWConfigureArg7 +}; + + +static const iocshFuncDef s7plcFWConfigureDef = { "s7plcFWConfigure", 8, s7plcFWConfigureArgs }; +static void s7plcFWConfigureFunc (const iocshArgBuf *args) +{ + int status = s7plcFWConfigure( + args[0].sval, args[1].sval, args[2].sval, args[3].sval, + args[4].ival, args[5].ival, args[6].ival, args[7].ival); + + if (status) exit(1); +} + +static void s7plcFWRegister () +{ + iocshRegister(&s7plcFWConfigureDef, s7plcFWConfigureFunc); +} + +epicsExportRegistrar(s7plcFWRegister); +#endif + + + +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +s7plcFWStation *s7plcFWOpen(char *name) +{ + s7plcFWStation *station; + + for (station = s7plcFWStationList; station; station = station->next) { + if (strcmp(name, station->name) == 0) { + return station; + } + } + errlogSevPrintf(errlogFatal, "s7plcFWOpen: station %s not found\n", name); + return NULL; +} + +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +IOSCANPVT s7plcFWGetFetchScanPvt(s7plcFWStation *station) +{ + return station->fetchScanPvt; +} + +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +IOSCANPVT s7plcFWGetWriteScanPvt(s7plcFWStation *station) +{ + return station->writeScanPvt; +} + +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +STATIC void s7plcFWMain () +{ + s7plcFWStation* station; + char threadname[20]; + + s7plcFWDebugLog(1, "s7plcFWMain: main thread started\n"); + + while (1) { /* watch loop to restart dead threads and reopen sockets*/ + for (station = s7plcFWStationList; station; station=station->next) { + + /* establish connection with server - for fetch */ + epicsMutexMustLock(station->fetchIo); + if (station->fetchSocket == -1) { + /* create station socket */ + if ((station->fetchSocket = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + fprintf(stderr, "s7plcFWMain %s: FATAL ERROR! fetch socket(AF_INET, SOCK_STREAM, 0) failed: %s\n", station->name, strerror(errno)); + abort(); + } + s7plcFWDebugLog(1, "s7plcFWMain %s: Connect to %s:%d on fetch socket %d\n", station->name, station->serverIP, station->fetchPort, station->fetchSocket); + if (s7plcFWEstablishConnection(station, FOR_FETCH) < 0) { + s7plcFWDebugLog(1, "s7plcFWMain %s: connect(%d, %s:%d) failed for fetch: %s. Retry in %g seconds\n", + station->name, station->fetchSocket, station->serverIP, station->fetchPort, strerror(errno), (double)RECONNECT_DELAY); + if (close(station->fetchSocket) && errno != ENOTCONN) { + s7plcFWDebugLog(1, "s7plcFWMain %s: close(%d) failed for fetch (ignored): %s\n", station->name, station->fetchSocket, strerror(errno)); + } + station->fetchSocket=-1; + } + else { + station->fetchConnStatus = 1; + } + } + epicsMutexUnlock(station->fetchIo); + + /* establish connection with server - for write */ + if (station->writeSize != 0) { + epicsMutexMustLock(station->writeIo); + if (station->writeSocket == -1) { + /* create station socket */ + if ((station->writeSocket = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + fprintf(stderr, "s7plcFWMain %s: FATAL ERROR! write socket(AF_INET, SOCK_STREAM, 0) failed: %s\n", station->name, strerror(errno)); + abort(); + } + s7plcFWDebugLog(1, "s7plcFWMain %s: Connect to %s:%d on write socket %d\n", station->name, station->serverIP, station->writePort, station->writeSocket); + if (s7plcFWEstablishConnection(station, FOR_WRITE) < 0) { + s7plcFWDebugLog(1, "s7plcFWMain %s: connect(%d, %s:%d) failed for write: %s. Retry in %g seconds\n", + station->name, station->writeSocket, station->serverIP, station->writePort, strerror(errno), (double)RECONNECT_DELAY); + if (close(station->writeSocket) && errno != ENOTCONN) { + s7plcFWDebugLog(1, "s7plcFWMain %s: close(%d) failed for write (ignored): %s\n", station->name, station->writeSocket, strerror(errno)); + } + station->writeSocket=-1; + } + else { + station->writeConnStatus = 1; + } + } + epicsMutexUnlock(station->writeIo); + } + + /* printf("s7plcFWMain: fetchConnStatus=%d fetchSocket=%d writeConnStatus=%d writeSocket=%d\n", + station->fetchConnStatus, station->fetchSocket, station->writeConnStatus, station->writeSocket); */ + + if ((station->fetchSocket >= 0) && (station->writeSocket >= 0) && (station->fetchWriteDb == 1)) { + int status; + epicsMutexMustLock(station->writeMutex); + status = s7plcFWdoFetch(station, station->writeOrg, station->writeDb, station->writeOffs, station->writeSize, station->writeBuffer); + epicsMutexUnlock(station->writeMutex); + if (status != 0) { + printf("s7plcFWMain: %s: FETCH writeDb %d size %d FAILED - will use zeroed writeDb\n", station->name, station->writeDb, station->writeSize); + bzero(station->writeBuffer, station->writeSize); + } +/* + else { + int i; + unsigned short int *ptr = (unsigned short int *) station->writeBuffer; + printf("s7plcFWMain: %s: FETCH writeDb %d size %d OK\n", station->name, station->writeDb, station->writeSize); + for (i = 0; i < (station->writeSize / 2); i++) { + printf(" writeDb[%3d] = 0x%04X\n", i, ptr[i] & 0xFFFF); + } + } +*/ + station->fetchWriteDb = 0; /* we declare it here fetched (even if it failed to fetch) */ + } + + + /* check whether station threads are running */ + + /* thread for former write - now out I/O intr notification (periodic action) */ + sprintf (threadname, "%.15sS", station->name); + if (station->iointThread && epicsThreadIsSuspended(station->iointThread)) { /* if suspended delete it */ + s7plcFWDebugLog(0, "s7plcFWMain %s: send thread %s %p is dead\n", station->name, threadname, station->iointThread); + station->iointThread = 0; + } + if (!station->iointThread && station->writeSize) { + s7plcFWDebugLog(1, "s7plcFWMain %s: starting send thread %s\n", station->name, threadname); + station->iointThread = epicsThreadCreate(threadname, epicsThreadPriorityMedium, STACK_SIZE, (EPICSTHREADFUNC) s7plcFWIOintThread, station); + if (!station->iointThread) { + fprintf(stderr, "s7plcFWMain %s: FATAL ERROR! could not start send thread %s\n", station->name, threadname); + abort(); + } + } + + /* thread for fetch */ + sprintf (threadname, "%.15sR", station->name); + if (station->fetchThread && epicsThreadIsSuspended(station->fetchThread)) { /* if suspended delete it */ + s7plcFWDebugLog(0, "s7plcFWMain %s: recv thread %s %p is dead\n", station->name, threadname, station->fetchThread); + /* maybe we should cleanup the semaphores ? */ + s7plcFWCloseConnection(station, FOR_FETCH); + station->fetchThread = 0; + } + if (!station->fetchThread && station->fetchSize) { + s7plcFWDebugLog(1, "s7plcFWMain %s: starting recv thread %s\n", station->name, threadname); + station->fetchThread = epicsThreadCreate(threadname, epicsThreadPriorityMedium, STACK_SIZE, (EPICSTHREADFUNC) s7plcFWFetchThread, station); + if (!station->fetchThread) { + fprintf(stderr, "s7plcFWMain %s: FATAL ERROR! could not start recv thread %s\n", station->name, threadname); + abort(); + } + } + + } + epicsThreadSleep(RECONNECT_DELAY); + } +} + +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +int s7plcFWReadArray(s7plcFWStation *station, unsigned int offset, unsigned int dlen, unsigned int nelem, void* data) +{ + unsigned int elem, i; + unsigned char byte; + epicsUInt16 connStatus; + + + /* special case: nelem = 0 & dlen = 0 - we actualy read only connections status */ + if ((nelem == 0) && (dlen == 0)) { + if (offset == 1) + connStatus = station->fetchConnStatus; + else + connStatus = station->writeConnStatus; + if (!connStatus) + return S_drv_noConn; + return S_drv_OK; + } + + + if (offset+dlen > station->fetchSize) + { + errlogSevPrintf(errlogMajor, "s7plcFWRead %s/%u: offset out of range\n", station->name, offset); + return S_drv_badParam; + } + if (offset+nelem*dlen > station->fetchSize) { + errlogSevPrintf(errlogMajor, "s7plcFWRead %s/%u: too many elements (%u)\n", station->name, offset, nelem); + return S_drv_badParam; + } + s7plcFWDebugLog(4, "s7plcFWReadArray (station=%p, offset=%u, dlen=%u, nelem=%u)\n", station, offset, dlen, nelem); + epicsMutexMustLock(station->fetchMutex); + connStatus = station->fetchConnStatus; + for (elem = 0; elem < nelem; elem++) + { + s7plcFWDebugLog(5, "data in:"); + for (i = 0; i < dlen; i++) { + if (station->swapBytes) + byte = station->fetchBuffer[offset + elem*dlen + dlen - 1 - i]; + else + byte = station->fetchBuffer[offset + elem*dlen + i]; + ((char*)data)[elem*dlen+i] = byte; + s7plcFWDebugLog(5, " %02x", byte); + } + s7plcFWDebugLog(5, "\n"); + } + epicsMutexUnlock(station->fetchMutex); + if (!connStatus) return S_drv_noConn; + return S_drv_OK; +} + + + +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +STATIC int s7plcFWdoFetch(s7plcFWStation *station, int org, int db, int offs, int size, char *recvBuf) +{ + int s7addr = offs / 2; + int s7len = size / 2; + int status; + unsigned char req[16] = {'S','5',16,1,3,5,3,8,0,0,0,0,0,0,0xff,2}; + unsigned char ack[16]; + int input; + int received; + + + if ((station->fetchConnStatus == 0) || (station->fetchSocket < 0)) return (-1); /* this should actually never happen */ + + req[8] = org; + req[9] = db; + req[0xa] = s7addr / 0x100; + req[0xb] = s7addr % 0x100; + req[0xc] = s7len / 0x100; + req[0xd] = s7len % 0x100; + + epicsMutexMustLock(station->fetchIo); + status = write(station->fetchSocket, req, 16); + epicsMutexUnlock(station->fetchIo); + if (status != 16) { + s7plcFWDebugLog(3, "s7plcFWdoFetch: write 16 byte header failed, returned status = %d, errno = %d\n", status, errno); + return (-1); + } + + status = s7plcFWWaitForInput(station, FOR_FETCH, station->recvTimeout); + if (status <= 0) { + s7plcFWDebugLog(3, "s7plcFWdoFetch: s7plcFWWaitForInput timed-out, returned status = %d\n", status); + return(-1); + } + + epicsMutexMustLock(station->fetchIo); + status = read(station->fetchSocket, ack, 16); + epicsMutexUnlock(station->fetchIo); + if(status < 16) { + s7plcFWDebugLog(3, "s7plcFWdoFetch: Got too few bytes (%d) ACK from PLC!\n", status); + return(-1); + } + + if(ack[8] != 0) { + s7plcFWDebugLog(3, "s7plcFWdoFetch:Got error %d from PLC!\n", ack[8]); + return(ack[8]); + } + + /* now receive data */ + input = 0; + while (input < size) { + + s7plcFWDebugLog(3, "s7plcFWdoFetch: %s: waiting for input for %g seconds\n", station->name, station->recvTimeout); + status = s7plcFWWaitForInput(station, FOR_FETCH, station->recvTimeout); + if (status <= 0) { + s7plcFWDebugLog(0, "s7plcFWdoFetch: %s: s7plcFWWaitForInput(%d, ..., %d, 0) failed\n", station->name, station->fetchSocket, size - input); + return (-1); + } + /* data available; receive data from server plc */ + epicsMutexMustLock(station->fetchIo); + received = recv(station->fetchSocket, recvBuf+input, size-input, 0); + epicsMutexUnlock(station->fetchIo); + + s7plcFWDebugLog(3, "s7plcFWdoFetch: %s: received %d bytes\n", station->name, received); + if (received <= 0) { + s7plcFWDebugLog(0, "s7plcFWdoFetch: %s: recv(%d, ..., %d, 0) failed\n", station->name, station->fetchSocket, size - input); + return (-1); + } + input += received; + + if (input > size) { /* input complete, check for excess bytes */ + s7plcFWDebugLog(0, "s7plcFWdoFetch: %s: %d bytes excess data received\n", station->name, input - size); + return (-1); + } + + } + + return 0; /* OK */ +} + +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +STATIC int s7plcFWdoWrite(s7plcFWStation *station, char *data, int offs, int len) +{ + int s7addr = station->writeOffs / 2 + offs / 2; + int s7len = len / 2; + int status, dstatus, sstatus; + unsigned char req[16] = {'S','5',16,1,3,3,3,8,0,0,0,0,0,0,0xff,2}; + unsigned char ack[16]; + + + /* printf("AD84: s7plcFWdoWrite: REQUEST to write %d bytes at offset %d\n", len, offs); */ + + req[8] = station->writeOrg; + req[9] = station->writeDb; + req[0xa] = s7addr / 0x100; + req[0xb] = s7addr % 0x100; + req[0xc] = s7len / 0x100; + req[0xd] = s7len % 0x100; + + epicsMutexMustLock(station->writeIo); + status = write(station->writeSocket, req, 16); + dstatus = write(station->writeSocket, data, len); + epicsMutexUnlock(station->writeIo); + + if (status != 16) { + s7plcFWDebugLog(3, "s7plcFWdoWrite: Sent too few header bytes (%d), errno =%d\n", status, errno); + return (-1); + } + if (dstatus != len) { + s7plcFWDebugLog(3, "s7plcFWdoWrite: Sent too few data bytes (%d), errno =%d\n", dstatus, errno); + return (-1); + } + + /* printf("AD84: s7plcFWdoWrite: OK, sent 16 header bytes and %d data bytes\n", len); */ + + sstatus = s7plcFWWaitForInput(station, FOR_WRITE, station->recvTimeout); + /* printf("AD84: s7plcFWdoWrite: s7plcFWWaitForInput returned ssatus=%d\n", sstatus); */ + if (sstatus <= 0) { + s7plcFWDebugLog(3, "s7plcFWdoWrite: s7plcFWWaitForInput timed-out, returned status = %d\n", sstatus); + return(-1); + } + + epicsMutexMustLock(station->writeIo); + if((status = read(station->writeSocket, ack, 16)) < 16) { + s7plcFWDebugLog(3, "Got too few bytes (%d) ACK from PLC!\n", status); + /* printf("AD84: s7plcFWdoWrite: read too few bytes (%d) ACK from PLC!\n", status); */ + epicsMutexUnlock(station->writeIo); + return(-1); + } + epicsMutexUnlock(station->writeIo); + + /* printf("AD84: s7plcFWdoWrite: OK, received 16 header bytes confirmation\n"); */ + + if(ack[8] != 0) { + s7plcFWDebugLog(3, "Got error %d from PLC!\n", ack[8]); + return(ack[8]); + } + + /* printf("AD84: s7plcFWWrite: OK, write %d data bytes succeeded\n", len); */ + + return 0; /* OK */ +} +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +int s7plcFWWriteMaskedArray(s7plcFWStation *station, unsigned int offset, unsigned int dlen, unsigned int nelem, void* data, void* mask) +{ + unsigned int elem, i; + unsigned char byte; + epicsUInt16 connStatus; + int wstatus, woffs, wlen; + + + if (station->writeSize == 0) { + errlogSevPrintf(errlogMajor, "s7plcFWWriteMaskedArray %s: write buffer size = 0, no writes allowed\n", station->name); + return -1; + } + if (offset+dlen > station->writeSize) { + errlogSevPrintf(errlogMajor, "s7plcFWWriteMaskedArray %s/%d: offset out of range\n", station->name, offset); + return -1; + } + if (offset+nelem*dlen > station->writeSize) { + errlogSevPrintf(errlogMajor, "s7plcFWWriteMaskedArray %s/%d: too many elements (%u)\n", station->name, offset, nelem); + return -1; + } + s7plcFWDebugLog(4, "s7plcFWWriteMaskedArray (station=%p, offset=%u, dlen=%u, nelem=%u)\n", station, offset, dlen, nelem); + epicsMutexMustLock(station->writeMutex); + connStatus = station->writeConnStatus; + + for (elem = 0; elem < nelem; elem++) { + s7plcFWDebugLog(5, "data out:"); + for (i = 0; i < dlen; i++) { + byte = ((unsigned char*)data)[elem*dlen+i]; + if (mask) { + s7plcFWDebugLog(5, "(%02x & %02x)", byte, ((unsigned char*)mask)[i]); + byte &= ((unsigned char*)mask)[i]; + } + if (station->swapBytes) { + if (mask) { + s7plcFWDebugLog(5, " | (%02x & %02x) =>", station->writeBuffer[offset + elem*dlen + dlen - 1 - i], ~((unsigned char*)mask)[i]); + byte |= + station->writeBuffer[offset + elem*dlen + dlen - 1 - i] + & ~((unsigned char*)mask)[i]; + } + s7plcFWDebugLog(5, " %02x", byte); + station->writeBuffer[offset + elem*dlen + dlen - 1 - i] = byte; + s7plcFWDebugLog(3, "s7plcFWWriteMaskedArray: writeBuffer[%3d] <- 0x%02X\n", offset + elem*dlen + dlen - 1 - i, byte & 0xFF); + } + else { + if (mask) { + s7plcFWDebugLog(5, " | (%02x & %02x) =>", station->writeBuffer[offset + elem*dlen + i], ~((unsigned char*)mask)[i]); + byte |= + station->writeBuffer[offset + elem*dlen + i] + & ~((unsigned char*)mask)[i]; + } + s7plcFWDebugLog(5, " %02x", byte); + station->writeBuffer[offset + elem*dlen + i] = byte; + s7plcFWDebugLog(3, "s7plcFWWriteMaskedArray: writeBuffer[%3d] <- 0x%02X\n", offset + elem*dlen + i, byte & 0xFF); + } + } + s7plcFWDebugLog(5, "\n"); + + /* we do write immediately here - not in a writethread (as original s7plc driver does) */ + /* warning: we allways have to write even number of bytes - so it can happen that we have to write 1 or 2 bytes more than requested */ + woffs = offset & 0xFFFFFFFE; /* we need even byte offset */ + wlen = 0; if (woffs != offset) wlen += 1; wlen += nelem*dlen; if ((wlen % 2) != 0) wlen += 1; + wstatus = s7plcFWdoWrite(station, &station->writeBuffer[woffs], woffs, wlen); + if (wstatus != 0) { + s7plcFWDebugLog(0, "s7plcFWWriteMaskedArray %s: s7plcFWdoWrite(%d, ...) failed\n", station->name, station->writeSocket); + s7plcFWCloseConnection(station, FOR_WRITE); + break; + } + + } + + epicsMutexUnlock(station->writeMutex); + if (!connStatus) return S_drv_noConn; + return S_drv_OK; +} + +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +/* this mimics (partly) I/O int for output records as used in S7plc driver */ +STATIC void s7plcFWIOintThread (s7plcFWStation* station) +{ + s7plcFWDebugLog(1, "s7plcFWIOintThread %s: started\n", station->name); + + while (1) { + epicsTimerStartDelay(station->timer, station->outIOintDelay); + + if (interruptAccept) { + scanIoRequest(station->writeScanPvt); + /* printf("AD84: out I/O interrupt\n"); */ + } + + epicsEventMustWait(station->outTrigger); + } +} + +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +STATIC void s7plcFWFetchThread (s7plcFWStation *station) +{ + char* recvBuf = callocMustSucceed(2, station->fetchSize, "s7plcFWFetchThread"); + + s7plcFWDebugLog(1, "s7plcFWFetchThread %s: started\n", station->name); + + while (1) + { + int status; + + epicsThreadSleep(station->recvDelay); + + /* check (with timeout) for data arrival from server */ + if ((station->fetchConnStatus != 0) && (station->fetchSocket != -1)) { + + s7plcFWDebugLog(3, "s7plcFWFetchThread %s: will initiate FETCH\n", station->name); + status = s7plcFWdoFetch(station, station->fetchOrg, station->fetchDb, station->fetchOffs, station->fetchSize, recvBuf); + if (status != 0) { + s7plcFWDebugLog(0, "s7plcFWFetchThread %s: s7plcFWdoFetch(%d, 0, %d, ...) failed\n", station->name, station->fetchSocket, station->fetchSize); + s7plcFWCloseConnection(station, FOR_FETCH); + continue; /* was break before, but it would exit thread - no need to end thread */ + } + + /* got it */ + epicsMutexMustLock(station->fetchMutex); + memcpy(station->fetchBuffer, recvBuf, station->fetchSize); + epicsMutexUnlock(station->fetchMutex); +#if 0 + { + int i; + unsigned short int *ptr = (unsigned short int *) station->fetchBuffer; + + printf("received data (before swap):\n"); + for (i = 0; i < (station->fetchSize/2); i++) { + printf(" [%32d] 0x%04X\n", i, ptr[i] & 0xFFFF); + } + } +#endif + /* notify all "I/O Intr" input records */ + s7plcFWDebugLog(3, "s7plcFWFetchThread %s: receive successful, notify all input records\n", station->name); + scanIoRequest(station->fetchScanPvt); + } + + } +} + +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +STATIC int s7plcFWWaitForInput(s7plcFWStation* station, int which, double timeout) +{ + struct timeval to; /* AD84: was static - why? */ + int socket; + int iSelect; + fd_set socklist; + + if (which != FOR_WRITE) + socket = station->fetchSocket; + else + socket = station->writeSocket; + + FD_ZERO(&socklist); + FD_SET(socket, &socklist); + to.tv_sec=(int)timeout; + to.tv_usec=(int)((timeout-to.tv_sec)*1000000); + /* select returns when either the socket has data or the timeout elapsed */ + errno = 0; + while ((iSelect=select(socket+1, &socklist, 0, 0, &to)) < 0) { + if (errno != EINTR) { + s7plcFWDebugLog(0, "s7plcFWWaitForInput %s: select(%d, %f sec) failed: %s\n", station->name, socket, timeout, strerror(errno)); + return -1; + } + } + if (iSelect==0 && timeout > 0) { /* timed out */ + s7plcFWDebugLog(0, "s7plcFWWaitForInput %s: select(%d, %f sec) timed out\n", station->name, socket, timeout); + errno = ETIMEDOUT; + } + return iSelect; +} + +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +STATIC int s7plcFWEstablishConnection(s7plcFWStation* station, int which) +{ + struct sockaddr_in serverAddr; /* server socket address */ + struct timeval to; + int socket; + unsigned short int port; +#ifndef __vxworks + long opt; +#endif + + if (which != FOR_WRITE) { + socket = station->fetchSocket; + port = station->fetchPort; + } + else { + socket = station->writeSocket; + port = station->writePort; + } + + s7plcFWDebugLog(1, "s7plcFWEstablishConnection %s: fd=%d, IP=%s port=%d\n", station->name, socket, station->serverIP, port); + + /* build server socket address */ + bzero((char *) &serverAddr, sizeof (serverAddr)); + serverAddr.sin_family = AF_INET; + serverAddr.sin_port = htons(port); + serverAddr.sin_addr.s_addr = inet_addr(station->serverIP); + + /* connect to server */ + to.tv_sec=(int)(CONNECT_TIMEOUT); + to.tv_usec=(int)(CONNECT_TIMEOUT-to.tv_sec)*1000000; +#ifdef __vxworks + if (connectWithTimeout(socket, (struct sockaddr *) &serverAddr, sizeof (serverAddr), &to) < 0) { + s7plcFWDebugLog(0, "s7plcFWEstablishConnection %s: connectWithTimeout(%d,...,%g sec) failed: %s\n", station->name, socket, CONNECT_TIMEOUT, strerror(errno)); + return -1; + } +#else + /* connect in non-blocking mode */ + if((opt = fcntl(socket, F_GETFL, NULL)) < 0) { + s7plcFWDebugLog(0, "s7plcFWEstablishConnection %s: fcntl(%d, F_GETFL, NULL) failed: %s\n", station->name, socket, strerror(errno)); + return -1; + } + opt |= O_NONBLOCK; + if(fcntl(socket, F_SETFL, opt) < 0) { + s7plcFWDebugLog(0, "s7plcFWEstablishConnection %s: fcntl(%d, F_SETFL, O_NONBLOCK) failed: %s\n", station->name, socket, strerror(errno)); + return -1; + } + if (connect(socket, (struct sockaddr *) &serverAddr, sizeof(serverAddr)) < 0) { + if (errno == EINPROGRESS) { + /* start timeout */ + long status; + socklen_t lon = sizeof(status); + fd_set fdset; + + FD_ZERO(&fdset); + FD_SET(socket, &fdset); + /* wait for connection */ + while ((status = select(socket+1, NULL, &fdset, NULL, &to)) < 0) { + if (errno != EINTR) { + s7plcFWDebugLog(0, "s7plcFWEstablishConnection %s: select(%d, %f sec) failed: %s\n", station->name, socket, CONNECT_TIMEOUT, strerror(errno)); + return -1; + } + } + if (status == 0) { + s7plcFWDebugLog(0, "s7plcFWEstablishConnection %s: select(%d, %f sec) timed out\n", station->name, socket, CONNECT_TIMEOUT); + errno = ETIMEDOUT; + return -1; + } + /* get background error status */ + if (getsockopt(socket, SOL_SOCKET, SO_ERROR, &status, &lon) < 0) { + s7plcFWDebugLog(0, "s7plcFWEstablishConnection %s: getsockopt(%d,...) failed: %s\n", station->name, socket, strerror(errno)); + return -1; + } + if (status) { + errno = status; + s7plcFWDebugLog(0, "s7plcFWEstablishConnection %s: background connect(%d,...) failed: %s\n", station->name, socket, strerror(errno)); + return -1; + } + } + else { + s7plcFWDebugLog(0, "s7plcFWEstablishConnection %s: connect(%d,...) failed: %s\n", station->name, socket, strerror(errno)); + return -1; + } + } + /* connected */ + opt &= ~O_NONBLOCK; + if(fcntl(socket, F_SETFL, opt) < 0) { + s7plcFWDebugLog(0, "s7plcFWEstablishConnection %s: fcntl(%d, F_SETFL, ~O_NONBLOCK) failed: %s\n", station->name, socket, strerror(errno)); + return -1; + } +#endif + + s7plcFWDebugLog(1, "s7plcFWEstablishConnection %s: fd=%d, IP=%s port=%d - ESTABLISHED\n", station->name, socket, station->serverIP, port); + + return 0; +} + +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +STATIC void s7plcFWCloseConnection(s7plcFWStation* station, int which) +{ + int socket; + unsigned short int port; + + if (which != FOR_WRITE) { + socket = station->fetchSocket; + port = station->fetchPort; + station->fetchConnStatus = 0; + } + else { + socket = station->writeSocket; + port = station->writePort; + station->writeConnStatus = 0; + } + + if (socket > 0) { + if (shutdown(socket, 2) < 0) { + s7plcFWDebugLog(0, "s7plcFWCloseConnection %s: shutdown(%d, 2) failed (ignored): %s\n", station->name, socket, strerror(errno)); + } + if (close(socket) && errno != ENOTCONN) { + s7plcFWDebugLog(0, "s7plcFWCloseConnection %s: close(%d) failed (ignored): %s\n", station->name, socket, strerror(errno)); + } + if (which != FOR_WRITE) { + station->fetchSocket = -1; + } + else { + station->writeSocket = -1; + } + } + /* notify all "I/O Intr" input records */ + scanIoRequest(station->fetchScanPvt); + + s7plcFWDebugLog(1, "s7plcFWCloseConnection %s: fd=%d, IP=%s port=%d - CLOSED\n", station->name, socket, station->serverIP, port); + +} + +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + diff --git a/drvS7plcFW.h b/drvS7plcFW.h new file mode 100644 index 0000000..ac66330 --- /dev/null +++ b/drvS7plcFW.h @@ -0,0 +1,78 @@ +/* $Author: anicic $ */ +/* $Date: 2010/09/22 13:44:37 $ */ +/* $Id: drvS7plcFW.h,v 1.1 2010/09/22 13:44:37 anicic Exp $ */ +/* $Name: $ */ +/* $Revision: 1.1 $ */ + +#ifndef drvS7plcFW_h +#define drvS7plcFW_h + +#include + +#ifndef __GNUC__ +#define __attribute__(a) +#endif + +#ifndef DEBUG +#define STATIC static +#else +#define STATIC +#endif + +/* driver initialisation define */ + +typedef struct s7plcFWStation s7plcFWStation; + +extern int s7plcFWDebug; + +void s7plcFWDebugLog(int level, const char *fmt, ...) +__attribute__((format(printf, 2, 3))); + +s7plcFWStation *s7plcFWOpen(char *name); +IOSCANPVT s7plcFWGetFetchScanPvt(s7plcFWStation *station); +IOSCANPVT s7plcFWGetWriteScanPvt(s7plcFWStation *station); + +int s7plcFWReadArray( + s7plcFWStation *station, + unsigned int offset, + unsigned int dlen, + unsigned int nelem, + void* pdata +); + +int s7plcFWWriteMaskedArray( + s7plcFWStation *station, + unsigned int offset, + unsigned int dlen, + unsigned int nelem, + void* pdata, + void* pmask +); + +#define s7plcFWWriteArray(station, offset, dlen, nelem, pdata) \ + s7plcFWWriteMaskedArray((station), (offset), (dlen), (nelem), (pdata), NULL) + +#define s7plcFWWriteMasked(station, offset, dlen, pdata, mask) \ + s7plcFWWriteMaskedArray((station), (offset), (dlen), 1, (pdata), (mask)) + +#define s7plcFWWrite(station, offset, dlen, pdata) \ + s7plcFWWriteMaskedArray((station), (offset), (dlen), 1, (pdata), NULL) + +#define s7plcFWRead(station, offset, dlen, pdata) \ + s7plcFWReadArray((station), (offset), (dlen), 1, (pdata)) + +/************************************************************************/ +/* * DRV driver error codes */ +#define M_drvLib (1003<<16U) +#define drvError(CODE) (M_drvLib | (CODE)) + +#define S_drv_OK 0 /* success */ +#define S_drv_badParam drvError(1) /*driver: bad parameter*/ +#define S_drv_noMemory drvError(2) /*driver: no memory*/ +#define S_drv_noDevice drvError(3) /*driver: device not configured*/ +#define S_drv_invSigMode drvError(4)/*driver: signal mode conflicts with device config*/ +#define S_drv_cbackChg drvError(5) /*driver: specified callback differs from previous config*/ +#define S_drv_alreadyQd drvError(6)/*driver: a read request is already queued for the channel*/ +#define S_drv_noConn drvError(7) /*driver: connection to plc lost*/ + +#endif /* drvS7plcFW_h */ diff --git a/s7plcFW.html b/s7plcFW.html new file mode 100644 index 0000000..49f8748 --- /dev/null +++ b/s7plcFW.html @@ -0,0 +1,1050 @@ + + + +s7plcFW EPICS driver + + + + + +

s7plcFW EPICS driver documentation

+

Contents

+
    +
  1. Driver Configuration
  2. +
  3. Device Support +
      +
    1. Connection Status
    2. +
    3. Analog Input
    4. +
    5. Analog Output
    6. +
    7. Binary Input
    8. +
    9. Binary Output
    10. +
    11. Multibit Binary Input
    12. +
    13. Multibit Binary Output
    14. +
    15. Multibit Binary Input Direct
    16. +
    17. Multibit Binary Output Direct
    18. +
    19. Long Input
    20. +
    21. Long Output
    22. +
    23. String Input
    24. +
    25. String Output
    26. +
    27. Waveform Input
    28. +
    29. Calculation Output
    30. +
  4. +
  5. Driver Functions
  6. +
+

s7plcFW Intro

+

+This is a documentation for s7plcFW driver, which is a copy of s7plc driver +adjusted to use FETCH/WRITE communication with PLC, instead of SEND/RECEIVE. +
+Besides the +

    +
  1. new communication protocol (FETCH/WRITE)
  2. +
  3. the modified s7plcFWConfigure function
  4. +
  5. additional support for write-connection-status (see "s7plcFW stat2" )
  6. +
+there should be no other differences. +

+The following description is a copy of s7plc documentation. +Only change is regarding the three above mentioned differences. +
+

+
+ +NOTE:
+    FETCH/WRITE mechanism can do transfers only with even number of bytes.
+    Any record writting even number of bytes, will force the other byte of the same 16bit WORD to be written, too,
+    at the beginning and at the end of the block beeing written. +
+

+

+ +

Intro

+

+This driver is intended to connect a Siemens S7 PLC (programmable +logic controller) via TCP/IP to an EPICS IOC. However, it can be used for +any device sending and receiving blocks of process variables (PVs) in the +same way. I highly recommend to connect to the PLC on a separate physical +network using a second network interface to avoid connection problems. +

+

+The driver was originally developped for SLS (Swiss Light Source) in 2000. +Later is has been modified by DESY (Deutsches Elektronen Synchrotron). The +current version has been completely rewritten for PPT (Puls-Plasmatechnik GmbH) +to run on a R3.14.6 PC based system, but it can also run on R3.13 vxWorks +system. Author of the current version is +Damir +Anicic (SLS) <damir.anicic@psi.ch>. +

+

+In this document, it is assumed that the reader is familiar with EPICS, the +record concept and meanings of the fields of the standard records. +Recommended documentation: EPICS Record Refecrence Manual. +

+ + +

1 Driver Configuration

+

+In the IOC startup script, the s7plcFW driver needs to be configured: +

+

+ +s7plcFWConfigure (PLCname, IPaddr, fetchInfo, writeInfo, +bigEndian, recvTimeout, recvDelay, outIOintDelay) + +

+

+PLCname is an arbitrary symbolic name for the PLC running +a pair of server TCP sockets on IPaddr:fetchPort and IPaddr:writePort. +The records reference the PLC with this name in their INP or +OUT link. PLCname must not contain the +slash character (/). +

+

+IPaddr is TCP/IP address of the PLC (in dotted notation, like: 192.168.1.10) +

+

+fetchInfo and writeInfo are the string parameters, +containing the +"fetchPort,fetchOrg,fetchDb,fetchOffsetInDb,fetchSizeOfDb" +and +"writePort,writeOrg,writeDb,writeOffsetInDb,writeSizeOfDb". +
+fetchPort and writePort come in pairs. Usualy starting at 2000,2001. +
+Offsets and Sizes are in bytes and must be even numbers. Offset will usualy be zero, but one does not neccessarilly have to start at the beginning of Db. The size is number of bytes to fetch or write starting from given offset - it does not have to go to the end of Db and it should never exceed the size of Db. +
+
+If any of Port, Org, Db or Size are zero or if Offset or Size are not even numbers, the info is declared invalid. +fetchInfo should not be invalid, but invalid writeInfo is allowed, but the writting will be disabled. +

+ +

+bigEndian defines the Byte order of the PLC. If +this is 1, the IOC expects the PLC to send and receive any +multibyte PV (word, float, etc) most significant byte first. If it is +0, the data is least significant byte first. This is independent +of the byte order of the IOC. +

+

+recvTimeout is the time in milliseconds, that IOC will wait for data. If exceeded the IOC will close the connection and +try to reopen it after a few seconds. recvTimeoutshould be big enough, not to get frequent disconnects. +
+One, two, or even more seconds is OK. It is anyhow important only in cases when something goes wrong. +

+

+recvDelay is the IOC polling delay for getting new data (fetch), in milliseconds. +The frequency of getting the data will be smaller than 1 / recvDelay, +because data receiving time is not 0. So, the real data reading frequency would be 1 / (recvDelay + dataTransferTime) +

+

+The outIOintDelay is replacement for sendInterval of the s7plc driver. +s7plcFW driver sends written data immediately. The outIOintDelay is added to provide +interrupt based processing of output records (SCAN="I/O intr") as used in s7plc driver. +
+

+ +

Example:

+

+ +s7plcFWConfigure("VakuumPLC-10", "192.168.1.10", "2000,1,50,0,1000", "2001,1,40,0,50", 0, 1000, 200, 1000) + +

+

+In the vxWorks target shell, PLCname, +IPaddr, fetchInfo and writeInfo must be quoted. In the iocsh, quotes are +optional. +

+

+The variable s7plcFWDebug can be set in the statup script or +at any time on the command line to change the amount or debug output. +The following levels are supported: +

+

+-1:  fatal errors only
+ 0:  errors only
+ 1:  startup messages
+ 2:+ output record processing
+ 3:+ inputput record processing
+ 4:+ driver calls
+ 5:+ io printout
+

+

+Be careful using level>1 since many messages can introduce considerable +delays which may result in connection losses. Default level is 0. +

+

+On vxWorks, s7plcFWDebug can be set with +s7plcFWDebug=level +

+

+In the iocsh use +var s7plcFWDebug level +

+ + +

2 Device Support

+

+The driver supports the standard record types ai, +ao, bi, bo, +mbbi, mbbo, +mbbiDirect, mbboDirect, +longin, longout, +stringin, stringout, +and waveform. With EPICS R3.14, +calcout is supported, too. +The DTYP is "s7plcFW". If the record processes when +the PLC is not connected (off, down, unreachable), the record raises an +alarm with SEVR="INVALID" and STAT="CONN". +

+

+There are also two connection statuses supported for bi. The +DTYP are "s7plcFW stat" for fetch and "s7plcFW stat2" for write. This records do not +raise an alarm when the PLC is disconnected. It just changes to +0 state in that case. +

+

+SCAN="I/O Intr" is supported. Whenever input data is received +from a PLC, all "I/O Intr" input records connected to this PLC +are processed. In each output cyle, all "I/O Intr" output +records are processed. +

+

+The general form of the INP or OUT link is +

+

+ + "@PLCname/offset T=type L=low H=high B=bit" + +

+

+Not all parameters T, L, H, and +B are required for each record type and parameters equal to the +default value may be omitted. The default values depend on the record type. +

+

+PLCname is the PLC name as defined by +s7plcFWConfigure in the startup script. +

+

+offset is the byte offset of the PV relative to the +beginning of the input or output data block for this PLC. It must be an +integer number or a sum of integer numbers like 20+3+2. +

+

+T=type defines the data type for transmitting the PV +from or to the PLC. It is not case sensitive and has several aliases (see +table below). +The default is T=INT16 for most record types. +L=low and H=high are used in analog +input and output records if LINR is set to "LINEAR" +to convert analog values to integer values and back. They define the raw +values which correspond to EGUL and EGUF, +respectively. +Analog output records will never write integer values lower than +L or higher than H. If necessary, the raw output +value is truncated to the nearest limit. The default values for +L and H depend on T. +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
T=Data TypeDefault L=Default H=
INT8 + 8 bit (1 byte) signed integer number-0x7F
-127
0x7F
127
UINT8
UNSIGN8
BYTE
CHAR
+
8 bit (1 byte) unsigned integer number0x00
0
0xFF
255
INT16
SHORT
16 bit (2 bytes) signed integer number-0x7FFF
-32767
0x7FFF
32767
UINT16
UNSIGN16
WORD
16 bit (2 bytes) unsigned integer number0x0000
0
0xFFFF
65535
INT32
LONG
32 bit (4 bytes) signed integer number-0x7FFFFFFF
-2147483647
0x7FFFFFFF
2147483647
UINT32
UNSIGN32
DWORD
32 bit (4 bytes) unsigned integer number0x00000000
0
0xFFFFFFFF
4294967295
REAL32
FLOAT32
FLOAT
32 bit (4 bytes) floating point numberN/AN/A
REAL64
FLOAT64
DOUBLE
64 bit (8 bytes) floating point numberN/AN/A
STRINGcharacter array40N/A
+
+ +

+If T=STRING, L means length, not low. +The default value is the length of the VAL field. +In the case of the stringin and stringout records, this is 40 (including +the terminating null byte). +

+

+B=bit is only used for bi and bo records to define the +bit number within the data byte, word, or doubleword (depending on +T). Bit number 0 is the least significant bit. +Note that in big endian byte order (also known as motorola format) bit 0 is +in the last byte, while in little endian byte order (intel format) bit 0 is +in the first byte. If in doubt, use T=BYTE to avoid all +byte order problems when handling single bits. +

+

+Note that the output buffer is initialised with null bytes at startup and +any output record that has not been processed after reboot will send null +values to the PLC. The driver does not send anything before the global +variable interruptAccept has been set TRUE at the +end of iocInit. All records with PINI set to +"YES" have already been processed by that time. The driver +does not change the VAL field of any output record at +initialisation. Thus, auto save and restore can be used in combination with +PINI="YES". +

+ + + +

2.1 Connection Status

+
+record (bi, "$(RECORDNAME):ConnStatusFetch") {
+        field (DTYP, "S7plcFW stat")
+        field (INP,  "@$(PLCNAME)")
+        field (ZNAM, "Disconnected")
+        field (ONAM, "Connected")
+        field (SCAN, "I/O Intr")
+}
+record (bi, "$(RECORDNAME):ConnStatusWrite") {
+        field (DTYP, "S7plcFW stat2")
+        field (INP,  "@$(PLCNAME)")
+        field (ZNAM, "Disconnected")
+        field (ONAM, "Connected")
+        field (SCAN, "I/O Intr")
+}
+
+

+The record value is 1 if a connection to the PLC is established and 0 if not. +Disconnect does not raise an alarm. +

+ + +

2.2 Analog Input

+
+ record (ai, "$(RECORDNAME)") {
+  field (DTYP, "s7plcFW")
+  field (INP,  "@$(PLCNAME)/$(OFFSET) T=$(T) L=$(L) H=$(H)")
+  field (SCAN, "I/O Intr")
+  field (LINR, "Linear")
+  field (EGUL, "$(MINVAL)")
+  field (EGUF, "$(MAXVAL)")
+ }
+
+ record (ai, "$(RECORDNAME)") {
+  field (DTYP, "s7plcFW")
+  field (INP,  "@$(PLCNAME)/$(OFFSET) T=$(T)")
+  field (SCAN, "I/O Intr")
+ }
+
+

+Default type is T=INT16. +Defaults for L and H depend +on T (see table above). +

+

+If T is an integer type, the PV is read into +RVAL. If LINR is set to "LINEAR", +then the record support converts RVAL to VAL +so that RVAL=L converts to VAL=EGUL and +RVAL=H converts to VAL=EGUF. +

+

+VALtemp=(RVAL-L)*(EGUF-EGUL)/(H-L)+EGUL +

+

+After this conversion, VALtemp is still +subject to scaling and smoothing. +

+

+VAL=(VALtemp*ASLO+AOFF)*(1-SMOO)+VALold*SMOO. +

+

+If T=FLOAT or T=DOUBLE, +the PV is read directly into VAL and L, +H, EGUL and EGUF are ignored. +The device support emulates scaling and smoothing which is otherwise done +by the record support during conversion. +

+

+VAL=(PV*ASLO+AOFF)*(1-SMOO)+VALold*SMOO +

+

+T=STRING is not valid for ai records. +

+ + +

2.3 Analog Output

+
+ record (ao, "$(RECORDNAME)") {
+  field (DTYP, "s7plcFW")
+  field (OUT,  "@$(PLCNAME)/$(OFFSET) T=$(T) L=$(L) H=$(H)")
+  field (LINR, "Linear")
+  field (PINI, "YES")
+  field (EGUL, "$(MINVAL)")
+  field (EGUF, "$(MAXVAL)")
+ }
+
+ record (ao, "$(RECORDNAME)") {
+  field (DTYP, "s7plcFW")
+  field (OUT,  "@$(PLCNAME)/$(OFFSET) T=$(T)")
+  field (PINI, "YES")
+ }
+
+

+Default type is T=INT16. +Defaults for L and H depend +on T (see table above). +

+

+If T is an integer type, RVAL is +written to the PV. If LINR is set to "LINEAR", +then the record support first scales OVAL. +

+

+OVALtemp=(OVAL-AOFF)/ASLO +

+

+After that, the value is converted to RVAL so that +OVALtemp=EGUL converts to RVAL=L and +OVALtemp=EGUF converts to RVAL=H. +

+

+RVAL=(OVALtemp-EGUL)*(H-L)/(EGUF-EGUL)+L +

+

+If RVAL is higher than H or lower than +L, the value is truncated to the nearest limit. +

+

+If T=FLOAT or T=DOUBLE, +OVAL is written directly to the PV. L, +H, EGUL and EGUF are ignored. +The device support emulates scaling which is otherwise done by the +record support during conversion. +

+

+PV=(OVAL-AOFF)/ASLO +

+

+T=STRING is not valid for ao records. +

+ + +

2.4 Binary Input

+
+ record(bi, "$(NAME)") {
+  field (DTYP, "s7plcFW")
+  field (INP,  "@$(PLCNAME)/$(OFFSET) T=$(T) B=$(B)")
+  field (SCAN, "I/O Intr")
+ }
+
+

+Default type is T=INT16. Default bit is B=0. +

+

+Depending on T, B can vary from 0 to 7, 15, or 31. +Bit 0 is the least significant bit. In little endian byte order, bit 0 is in +the first byte, in big endian byte order it is in the last byte of the PV. +If in doubt, use T=BYTE to avoid all byte order problems when +handling single bits. +

+

+The PV is read to RVAL and masked with +2B. +VAL is 1 if RVAL is not 0. +

+

+RVAL=PV&(1<<B); VAL=(RVAL!=0)?1:0 +

+

+T=STRING, T=FLOAT or T=DOUBLE are not +valid for bo records. Signed and unsigned types are equivalent. +

+ + +

2.5 Binary Output

+
+ record(bo, "$(NAME)") {
+  field (DTYP, "s7plcFW")
+  field (OUT,  "@$(PLCNAME)/$(OFFSET) T=$(T) B=bit")
+  field (PINI, "YES")
+ }
+
+

+Default type is T=INT16. Default bit is B=0. +

+

+Depending on T, B can vary from 0 to 7, 15, or 31. +Bit 0 is the least significant bit. In little endian byte order, bit 0 is in +the first byte, in big endian byte order it is in the last byte of the PV. +If in doubt, use T=BYTE to avoid all byte order problems when +handling single bits. +

+

+If VAL is not 0, then RVAL is set to +2B, else RVAL is set to 0. +Only the referenced bit of the PV is changed while all other bits remain +untouched. Thus, other output records can write to different bits of the +same PV. +

+RVAL=(VAL!=0)?(1<<bit):0; +PV=(PVold&~(1<<bit))|RVAL +

+

+T=STRING, T=FLOAT or T=DOUBLE are not +valid for bo records. Signed and unsigned types are equivalent. +

+ + +

2.6 Multibit Binary Input

+
+ record(mbbi, "$(NAME)") {
+  field (DTYP, "s7plcFW")
+  field (INP,  "@$(PLCNAME)/$(OFFSET) T=$(T)")
+  field (SCAN, "I/O Intr")
+  field (NOBT, "$(NUMBER_OF_BITS)")
+  field (SHFT, "$(RIGHT_SHIFT)")
+ }
+
+

+Default type is T=INT16. +

+

+The PV is read to RVAL, shifted right by SHFT bits +and masked with NOBT bits. Valid values for NOBT +and SHFT depend on T: +NOBT+SHFT must not exceed the number of bits of +the type. +

+

+Bit 0 is the least significant bit. In little endian byte order, bit 0 is in +the first byte, in big endian byte order it is in the last byte of the PV. +

+

+Example: Use bits 4 to 9 out of 16. +T=INT16, NOBT=6, SHFT=4 +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PV1514131211109876543210
RVAL          987654
+

+T=STRING, T=FLOAT or T=DOUBLE are not +valid for mbbi records. Signed and unsigned types are equivalent. +

+ + +

2.7 Multibit Binary Output

+
+ record(mbbo, "$(NAME)") {
+  field (DTYP, "s7plcFW")
+  field (OUT,  "@$(PLCNAME)/$(OFFSET) T=$(T)")
+  field (PINI, "YES")
+  field (NOBT, "$(NUMBER_OF_BITS)")
+  field (SHFT, "$(LEFT_SHIFT)")
+ }
+
+

+Default type is T=INT16. +

+

+RVAL is masked with NOBT bits, shifted left by +SHFT bits and written to the PV. Valid values for +NOBT and SHFT depend on T: +NOBT+SHFT must not exceed the number of bits of +the type. +

+

+Bit 0 is the least significant bit. In little endian byte order, bit 0 is in +the first byte, in big endian byte order it is in the last byte of the PV. +

+

+Only the referenced NOBT bits of the PV are changed. All other +bits remain untouched. Thus, other output records can write to different bits +of the same PV. +

+

+Example: Use bits 5 to 8 out of 16. +T=INT16, NOBT=4, SHFT=5 +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
RVAL            8765
PV1514131211109876543210
+

+T=STRING, T=FLOAT or T=DOUBLE are not +valid for mbbo records. Signed and unsigned types are equivalent. +

+ + +

2.8 Multibit Binary Input Direct

+
+ record(mbbiDirect, "$(NAME)") {
+  field (DTYP, "s7plcFW")
+  field (INP,  "@$(PLCNAME)/$(OFFSET) T=$(T)")
+  field (SCAN, "I/O Intr")
+  field (NOBT, "$(NUMBER_OF_BITS)")
+  field (SHFT, "$(RIGHT_SHIFT)")
+ }
+
+

+Default type is T=INT16. +

+

+The PV is read to VAL, shifted right by SHFT +bits and masked with NOBT bits (see mbbi). +Valid values for NOBT and SHFT depend +on T: NOBT+SHFT must +not exceed the number of bits of the type. +

+

+Bit 0 is the least significant bit. In little endian byte order, bit 0 is in +the first byte, in big endian byte order it is in the last byte of the PV. +

+

+T=STRING, T=FLOAT or T=DOUBLE are not +valid for mbbiDirect records. Signed and unsigned types are equivalent. +

+ + +

2.9 Multibit Binary Output Direct

+
+ record(mbboDirect, "$(NAME)") {
+  field (DTYP, "s7plcFW")
+  field (OUT,  "@$(PLCNAME)/$(OFFSET) T=$(T)")
+  field (PINI, "YES")
+  field (NOBT, "$(NUMBER_OF_BITS)")
+  field (SHFT, "$(LEFT_SHIFT)")
+ }
+
+

+Default type is T=INT16. +

+

+VAL is masked with NOBT bits, shifted left by SHFT +bits and written to the PV (see mbbo). Valid values for +NOBT and SHFT depend on T: +NOBT+SHFT must not exceed the number of bits of +the type. +

+

+Bit 0 is the least significant bit. In little endian byte order, bit 0 is in +the first byte, in big endian byte order it is in the last byte of the PV. +

+

+Only the referenced NOBT bits of the PV are changed. All other +bits remain untouched. Thus, other output records can write to different bits +of the same PV. +

+

+T=STRING, T=FLOAT or T=DOUBLE are not +valid for mbboDirect records. Signed and unsigned types are equivalent. +

+ + +

2.10 Long Input

+
+ record(longin, "$(NAME)") {
+  field (DTYP, "s7plcFW")
+  field (INP,  "@$(PLCNAME)/$(OFFSET) T=$(T)")
+  field (SCAN, "I/O Intr")
+ }
+
+

+Default type is T=INT16. +

+

+The PV is read to VAL. If the type has less than 32 bits, the +value is zero extended or sign extended depending on the signedness of +the type. +

+

+T=STRING, T=FLOAT or T=DOUBLE are not +valid for longin records. +

+ + +

2.11 Long Output

+
+ record(longout, "$(NAME)") {
+  field (DTYP, "s7plcFW")
+  field (OUT,  "@$(PLCNAME)/$(OFFSET) T=$(T)")
+  field (PINI, "YES")
+ }
+
+

+Default type is T=INT16. +

+

+Depending on T, the least significant 8, 16, or 32 bytes +of VAL are written to the PV. +

+

+T=STRING, T=FLOAT or T=DOUBLE are not +valid for longout records. +

+ + +

2.12 String Input

+
+ record(stringin, "$(NAME)") {
+  field (DTYP, "s7plcFW")
+  field (INP,  "@$(PLCNAME)/$(OFFSET) L=$(LENGTH)")
+  field (SCAN, "I/O Intr")
+ }
+
+

+Default and only valid type is T=STRING. +Default length is L=40. +

+

+L bytes are read from the PV to VAL and null +terminated. Thus, the effective string length is maximal +L-1 bytes. +

+ + +

2.13 String Output

+
+ record(stringout, "$(NAME)") {
+  field (DTYP, "s7plcFW")
+  field (OUT,  "@$(PLCNAME)/$(OFFSET) L=$(LENGTH)")
+  field (PINI, "YES")
+ }
+
+

+Default and only valid type is T=STRING. +Default length is L=40. +

+

+L bytes are written from VAL to the PV. +If the actual string length of VAL is shorter than +L, the remaining space is filled with null bytes. If +it is longer than L, the string is truncated and not +null terminated +

+ + +

2.14 Waveform Input

+
+ record(waveform, "$(NAME)") {
+  field (DTYP, "s7plcFW")
+  field (INP,  "@$(PLCNAME)/$(OFFSET)")
+  field (SCAN, "I/O Intr")
+  field (NELM, "$(NUMBER_OF_ELEMENTS)")
+  field (FTVL, "$(DATATYPE)")
+ }
+
+

+NELM elements are read from the PV to VAL. +

+

+The default type depends on FTVL. For example +FTVL=LONG results in T=INT32. +T and FTVL must match but can differ +in signedness. In most cases, better just specify FTVL and +leave T to the default. +

+

+If T=STRING, FTVL must be "CHAR" or +"UCHAR". L=length can be specified but +defaults to and must not exceed NELM. +If L is less than NELM, the +remaining elements are left untouched. +

+

+FTVL="STRING" is not supported. +

+

+The special type T=TIME is supported for +waveforms records only. FTVL must be +"CHAR" or "UCHAR" and NELM should be +"8". The input bytes are converted from BCD (binary coded decimal) +to integer values in the range from 0 to 99 each. This type is intended +to transfer BCD coded real time clock timestamps. +

+

+The Siemens "STEP 7" manual defines the 8 byte PLC timetamp as follows: +

+ + + + + + + + + + + + + + + + + + + + + +
01234567
yearmonthdayhourminutesecondmsec(hi)msec(lo)*10+day of week
+

+Years 90 to 99 mean 1990 to 1999, years 0 to 89 mean 2000 to 2089. Months +and days start with 1. Hour is 0 to 23, minute and second 0 to 59. Msec are +milliseconds in the range 0 to 999. The first two digits (0-99 hundredth of +a second) are in msec(hi). The last digit (0-9 thousandth of a second) +is multiplyed by 10 and added to the day of week (Sunday=1 to Saturday=7). +If you want to have the unconverted BCD bytes, do not use +T=TIME. +

+ + +

2.15 Calculation Output

+
+ record(calcout, "$(NAME)") {
+  field (DTYP, "s7plcFW")
+  field (OUT,  "@$(PLCNAME)/$(OFFSET) T=$(T) L=$(L) H=$(H)")
+  field (PINI, "YES")
+ }
+
+

+Default type is T=INT16. +Defaults for L and H depend +on T (see table above). +

+

+OVAL (the result of CALC or OCAL, +depending on DOPT) is written to the PV. If +T is an integer type, the value is truncated to an +integer and compared to L and H. +If OVAL is lower than L or higher than +H, it is truncated to the nearest limit. +

+

+If T=FLOAT or T=DOUBLE, +OVAL is written to the PV directly without any conversion. +

+

+T=STRING is not valid for calcout records. +

+

+To use this device support with calcout records, you need EPICS R3.14. +

+ + +

3 Driver Functions

+

+Device support for other record types can be written with calls to the +following driver functions: +

+

+ +s7plcFWStation* s7plcFWOpen (char* PLCname); + +

+

+ +int s7plcFWRead (s7plcFWStation* station, +unsigned int offset, unsigned int dlen, +void* pdata); + +

+

+ +int s7plcFWReadArray (s7plcFWStation* station, +unsigned int offset, unsigned int dlen, +unsigned int nelem, void* pdata); + +

+

+ +int s7plcFWWrite (s7plcFWStation* station, +unsigned int offset, unsigned int dlen, +void* pdata); + +

+

+ +int s7plcFWWriteMasked (s7plcFWStation* station, +unsigned int offset, unsigned int dlen, +void* pdata, void* pmask); + +

+

+ +int s7plcFWWriteArray (s7plcFWStation* station, +unsigned int offset, unsigned int dlen, +unsigned int nelem, void* pdata); + +

+

+ +int s7plcFWWriteMaskedArray (s7plcFWStation* station, +unsigned int offset, unsigned int dlen, +unsigned int nelem, void* pdata, void* pmask); + +

+

+The functions s7plcFWRead(), s7plcFWWrite(), +s7plcFWWriteMasked(), and s7plcFWWriteArray() +are actually macros for +s7plcFWReadArray() and s7plcFWWriteMaskedArray() with +nelem=1 and/or mask=NULL. +

+

+station is a handle previously obtained by a call to +s7plcFWOpen(). +

+

+offset is the byte offset of the PV relative +to the beginning to the data block. +

+

+dlen is the length of the PV in bytes (one element in case of +arrays). If the endianess of the PLC differs from the IOC, the byte order of +the dlen bytes is swapped by the driver. +

+

+nelem is the number of elements in an array. +

+

+pdata is a pointer to a buffer of +nelem*dlen bytes. +PVs are read to or written from this buffer. +

+

+mask is a pointer to a bitmask of dlen bytes. +Only those bits are changed where the mask contains 1 bits. All other bits +remain untouched. +

+

+For strings, use array functions with dlen=1 and +nelem=buffersize. +

+
+Damir Anicic, September 2010 + + diff --git a/s7plcFWBase.dbd b/s7plcFWBase.dbd new file mode 100644 index 0000000..56471ff --- /dev/null +++ b/s7plcFWBase.dbd @@ -0,0 +1,17 @@ +device(mbboDirect, INST_IO, s7plcFWMbboDirect, "S7plcFW") +device(mbbiDirect, INST_IO, s7plcFWMbbiDirect, "S7plcFW") +device(bi, INST_IO, s7plcFWBi, "S7plcFW") +device(bo, INST_IO, s7plcFWBo, "S7plcFW") +device(longin, INST_IO, s7plcFWLongin, "S7plcFW") +device(longout, INST_IO, s7plcFWLongout, "S7plcFW") +device(mbbo, INST_IO, s7plcFWMbbo, "S7plcFW") +device(mbbi, INST_IO, s7plcFWMbbi, "S7plcFW") +device(ao, INST_IO, s7plcFWAo, "S7plcFW") +device(ai, INST_IO, s7plcFWAi, "S7plcFW") +device(stringout, INST_IO, s7plcFWStringout, "S7plcFW") +device(stringin, INST_IO, s7plcFWStringin, "S7plcFW") +device(waveform, INST_IO, s7plcFWWaveform, "S7plcFW") +device(bi, INST_IO, s7plcFWStat, "S7plcFW stat") +device(bi, INST_IO, s7plcFWStat2,"S7plcFW stat2") +driver(s7plcFW) + diff --git a/s7plcFWCalcout.dbd b/s7plcFWCalcout.dbd new file mode 100644 index 0000000..d6294e3 --- /dev/null +++ b/s7plcFWCalcout.dbd @@ -0,0 +1 @@ +device(calcout, INST_IO, s7plcFWCalcout, "S7plcFW") diff --git a/s7plcFWReg.dbd b/s7plcFWReg.dbd new file mode 100644 index 0000000..c128641 --- /dev/null +++ b/s7plcFWReg.dbd @@ -0,0 +1,3 @@ +registrar(s7plcFWRegister) +variable(s7plcFWDebug, int) +