From d6e14aa943b7393d5f3ee23ab4f5bb15b9e6d34e Mon Sep 17 00:00:00 2001 From: Freddie Akeroyd Date: Fri, 23 Jun 2023 23:50:32 +0100 Subject: [PATCH] Add support for automatically asserting a serial break after every write --- asyn/drvAsynSerial/drvAsynSerialPort.c | 46 ++++++++++++++- asyn/drvAsynSerial/drvAsynSerialPortWin32.c | 64 ++++++++++++++++++++- asyn/miscellaneous/asynInterposeCom.c | 43 ++++++++++++++ docs/source/asynDriver.rst | 19 ++++-- 4 files changed, 163 insertions(+), 9 deletions(-) diff --git a/asyn/drvAsynSerial/drvAsynSerialPort.c b/asyn/drvAsynSerial/drvAsynSerialPort.c index 3818302b6..77a31e085 100644 --- a/asyn/drvAsynSerial/drvAsynSerialPort.c +++ b/asyn/drvAsynSerial/drvAsynSerialPort.c @@ -81,6 +81,8 @@ typedef struct { double writeTimeout; epicsTimerId timer; volatile int timeoutFlag; + unsigned break_len; /* length of serial break to send after a write (ms) when in autobreak mode */ + unsigned break_delay; /* time (ms) to delay after write and before sending a break in autobreak mode */ asynInterface common; asynInterface option; asynInterface octet; @@ -205,6 +207,12 @@ getOption(void *drvPvt, asynUser *pasynUser, /* request serial line break status */ l = epicsSnprintf(val, valSize, "off"); } + else if (epicsStrCaseCmp(key, "autobreak") == 0) { + l = epicsSnprintf(val, valSize, "%u", tty->break_len); + } + else if (epicsStrCaseCmp(key, "autobreak_delay") == 0) { + l = epicsSnprintf(val, valSize, "%u", tty->break_delay); + } #endif #ifdef ASYN_RS485_SUPPORTED else if (epicsStrCaseCmp(key, "rs485_enable") == 0) { @@ -526,6 +534,24 @@ setOption(void *drvPvt, asynUser *pasynUser, const char *key, const char *val) } return asynSuccess; } + else if (epicsStrCaseCmp(key, "autobreak") == 0) { + unsigned break_len; + if(sscanf(val, "%u", &break_len) != 1) { + epicsSnprintf(pasynUser->errorMessage, pasynUser->errorMessageSize, + "Bad number"); + return asynError; + } + tty->break_len = break_len; + } + else if (epicsStrCaseCmp(key, "autobreak_delay") == 0) { + unsigned break_delay; + if(sscanf(val, "%u", &break_delay) != 1) { + epicsSnprintf(pasynUser->errorMessage, pasynUser->errorMessageSize, + "Bad number"); + return asynError; + } + tty->break_delay = break_delay; + } #endif #ifdef ASYN_RS485_SUPPORTED else if (epicsStrCaseCmp(key, "rs485_enable") == 0) { @@ -676,6 +702,8 @@ report(void *drvPvt, FILE *fp, int details) fprintf(fp, " fd: %d\n", tty->fd); fprintf(fp, " Characters written: %lu\n", tty->nWritten); fprintf(fp, " Characters read: %lu\n", tty->nRead); + fprintf(fp, " Autobreak length (ms): %lu\n", tty->break_len); + fprintf(fp, " Autobreak delay (ms): %lu\n", tty->break_delay); } } @@ -840,9 +868,25 @@ static asynStatus writeIt(void *drvPvt, asynUser *pasynUser, } } if (timerStarted) epicsTimerCancel(tty->timer); +#ifndef vxWorks + if (tty->break_len > 0) { + tcdrain(tty->fd); /* ensure all data transmitted prior to break */ + if (tty->break_delay > 0) { + epicsThreadSleep(tty->break_delay / 1000.0); + } + if (tcsendbreak(tty->fd, tty->break_len) < 0) { + epicsSnprintf(pasynUser->errorMessage, pasynUser->errorMessageSize, + "%s tcsendbreak failed: %s", + tty->serialDeviceName, strerror(errno)); + closeConnection(pasynUser,tty); + status = asynError; + } + } +#endif *nbytesTransfered = numchars - nleft; - asynPrint(pasynUser, ASYN_TRACE_FLOW, "wrote %lu to %s, return %s\n", + asynPrint(pasynUser, ASYN_TRACE_FLOW, "wrote %lu %sto %s, return %s\n", (unsigned long)*nbytesTransfered, + (tty->break_len > 0 ? "(with BREAK) " : ""), tty->serialDeviceName, pasynManager->strStatus(status)); return status; diff --git a/asyn/drvAsynSerial/drvAsynSerialPortWin32.c b/asyn/drvAsynSerial/drvAsynSerialPortWin32.c index 20fe15b86..18c2f5704 100644 --- a/asyn/drvAsynSerial/drvAsynSerialPortWin32.c +++ b/asyn/drvAsynSerial/drvAsynSerialPortWin32.c @@ -62,6 +62,8 @@ typedef struct { epicsTimerId timer; volatile int timeoutFlag; unsigned break_active; + unsigned break_len; /* length of serial break to send after a write (ms) when in autobreak mode */ + unsigned break_delay; /* length of sleep (ms) before sending break. */ asynInterface common; asynInterface option; asynInterface octet; @@ -148,6 +150,12 @@ getOption(void *drvPvt, asynUser *pasynUser, /* request serial line break status */ l = epicsSnprintf(val, valSize, "%s", tty->break_active ? "on" : "off"); } + else if (epicsStrCaseCmp(key, "autobreak") == 0) { + l = epicsSnprintf(val, valSize, "%u", tty->break_len); + } + else if (epicsStrCaseCmp(key, "autobreak_delay") == 0) { + l = epicsSnprintf(val, valSize, "%u", tty->break_delay); + } else { epicsSnprintf(pasynUser->errorMessage,pasynUser->errorMessageSize, "Unsupported key \"%s\"", key); @@ -320,8 +328,9 @@ setOption(void *drvPvt, asynUser *pasynUser, const char *key, const char *val) if (on) { FlushFileBuffers(tty->commHandle); /* ensure all data transmitted prior to break */ if (SetCommBreak(tty->commHandle) == 0) { + error = GetLastError(); epicsSnprintf(pasynUser->errorMessage, pasynUser->errorMessageSize, - "Bad number"); + "%s error calling SetCommBreak %d", tty->serialDeviceName, error); return asynError; } tty->break_active = 1; @@ -329,14 +338,34 @@ setOption(void *drvPvt, asynUser *pasynUser, const char *key, const char *val) if (len) Sleep(break_len > 0 ? break_len : 250); /* wait while break is being asserted */ if (off) { if (ClearCommBreak(tty->commHandle) == 0) { + error = GetLastError(); epicsSnprintf(pasynUser->errorMessage, pasynUser->errorMessageSize, - "Bad number"); + "%s error calling ClearCommBreak %d", tty->serialDeviceName, error); return asynError; } tty->break_active = 0; } return asynSuccess; } + else if (epicsStrCaseCmp(key, "autobreak") == 0) { + unsigned break_len; + if(sscanf(val, "%u", &break_len) != 1) { + epicsSnprintf(pasynUser->errorMessage, pasynUser->errorMessageSize, + "Bad number"); + return asynError; + } + tty->break_len = break_len; + } + else if (epicsStrCaseCmp(key, "autobreak_delay") == 0) { + unsigned break_delay; + if(sscanf(val, "%u", &break_delay) != 1) { + epicsSnprintf(pasynUser->errorMessage, pasynUser->errorMessageSize, + "Bad number"); + return asynError; + } + tty->break_delay = break_delay; + return asynSuccess; + } else if (epicsStrCaseCmp(key, "") != 0) { epicsSnprintf(pasynUser->errorMessage,pasynUser->errorMessageSize, "Unsupported key \"%s\"", key); @@ -398,6 +427,8 @@ report(void *drvPvt, FILE *fp, int details) fprintf(fp, " commHandle: %p\n", tty->commHandle); fprintf(fp, " Characters written: %lu\n", tty->nWritten); fprintf(fp, " Characters read: %lu\n", tty->nRead); + fprintf(fp, " autobreak len (ms): %lu\n", tty->break_len); + fprintf(fp, " autobreak delay (ms): %lu\n", tty->break_delay); } } @@ -537,9 +568,36 @@ static asynStatus writeIt(void *drvPvt, asynUser *pasynUser, } } if (timerStarted) epicsTimerCancel(tty->timer); + /* raise a serial break if requested */ + if (!tty->break_active && tty->break_len > 0) { + FlushFileBuffers(tty->commHandle); /* ensure all data transmitted prior to break */ + /* Sleep after sending bytes if requested */ + if (tty->break_delay > 0) { + Sleep(tty->break_delay); + } + if ( (ret = SetCommBreak(tty->commHandle)) == 0 ) { + error = GetLastError(); + epicsSnprintf(pasynUser->errorMessage,pasynUser->errorMessageSize, + "%s SetCommBreak error: %d", + tty->serialDeviceName, error); + closeConnection(pasynUser,tty); + status = asynError; + } else { + Sleep(tty->break_len); /* wait while break is being asserted */ + if ( (ret = ClearCommBreak(tty->commHandle)) == 0 ) { + error = GetLastError(); + epicsSnprintf(pasynUser->errorMessage,pasynUser->errorMessageSize, + "%s ClearCommBreak error: %d", + tty->serialDeviceName, error); + closeConnection(pasynUser,tty); + status = asynError; + } + } + } *nbytesTransfered = numchars - nleft; - asynPrint(pasynUser, ASYN_TRACE_FLOW, "wrote %lu to %s, return %s\n", + asynPrint(pasynUser, ASYN_TRACE_FLOW, "wrote %lu %sto %s, return %s\n", (unsigned long)*nbytesTransfered, + (tty->break_len > 0 ? "(with BREAK) " : ""), tty->serialDeviceName, pasynManager->strStatus(status)); return status; diff --git a/asyn/miscellaneous/asynInterposeCom.c b/asyn/miscellaneous/asynInterposeCom.c index 24c715079..80fbef72e 100644 --- a/asyn/miscellaneous/asynInterposeCom.c +++ b/asyn/miscellaneous/asynInterposeCom.c @@ -80,6 +80,8 @@ typedef struct interposePvt { int stop; int flow; int break_active; + unsigned break_len; /* length of break (ms) in autobreak mode */ + unsigned break_delay; /* break delay (ms) in autobreak mode */ char *xBuf; /* Buffer for transmit IAC stuffing */ size_t xBufCapacity; @@ -124,6 +126,9 @@ expectChar(interposePvt *pinterposePvt, asynUser *pasynUser, int expect) return 1; } +static asynStatus +sbComPortOption(interposePvt *pinterposePvt, asynUser *pasynUser, const char *xBuf, int xLen, char *rBuf); + /* * asynOctet methods */ @@ -141,6 +146,7 @@ writeIt(void *ppvt, asynUser *pasynUser, const char *iac; char *dst = pinterposePvt->xBuf; size_t nIAC = 0; + char xBuf[5], rBuf[4]; asynStatus status; if ((iac = memchr(data, C_IAC, numchars)) != NULL) { @@ -189,6 +195,19 @@ writeIt(void *ppvt, asynUser *pasynUser, } status = pinterposePvt->pasynOctetDrv->write(pinterposePvt->drvOctetPvt, pasynUser, data, numchars, nbytesTransfered); + if (!pinterposePvt->break_active && pinterposePvt->break_len > 0) { + if (pinterposePvt->break_delay > 0) { + epicsThreadSleep(pinterposePvt->break_delay / 1000.0); + } + xBuf[0] = CPO_SET_CONTROL; + xBuf[1] = CPO_CONTROL_BREAK_ON; + status = sbComPortOption(pinterposePvt, pasynUser, xBuf, 2, rBuf); + if (status != asynSuccess) return status; + epicsThreadSleep(((double)pinterposePvt->break_len) / 1000.); + xBuf[1] = CPO_CONTROL_BREAK_OFF; + status = sbComPortOption(pinterposePvt, pasynUser, xBuf, 2, rBuf); + if (status != asynSuccess) return status; + } if (*nbytesTransfered == numchars) *nbytesTransfered -= nIAC; return status; @@ -641,6 +660,24 @@ setOption(void *ppvt, asynUser *pasynUser, const char *key, const char *val) } return asynSuccess; } + else if (epicsStrCaseCmp(key, "autobreak") == 0) { + unsigned break_len; + if(sscanf(val, "%u", &break_len) != 1) { + epicsSnprintf(pasynUser->errorMessage, pasynUser->errorMessageSize, + "Bad number"); + return asynError; + } + pinterposePvt->break_len = break_len; + } + else if (epicsStrCaseCmp(key, "autobreak_delay") == 0) { + unsigned break_delay; + if(sscanf(val, "%u", &break_delay) != 1) { + epicsSnprintf(pasynUser->errorMessage, pasynUser->errorMessageSize, + "Bad number"); + return asynError; + } + pinterposePvt->break_delay = break_delay; + } else { if (pinterposePvt->pasynOptionDrv) { /* Call the setOption function in the underlying driver */ @@ -706,6 +743,12 @@ getOption(void *ppvt, asynUser *pasynUser, const char *key, char *val, int valSi l = epicsSnprintf(val, valSize, "%s", pinterposePvt->break_active ? "on" : "off"); } + else if (epicsStrCaseCmp(key, "autobreak") == 0) { + l = epicsSnprintf(val, valSize, "%u", pinterposePvt->break_len); + } + else if (epicsStrCaseCmp(key, "autobreak_delay") == 0) { + l = epicsSnprintf(val, valSize, "%u", pinterposePvt->break_delay); + } else { if (pinterposePvt->pasynOptionDrv) { /* Call the getOption function in the underlying driver */ diff --git a/docs/source/asynDriver.rst b/docs/source/asynDriver.rst index 35cfe5fec..fc9b1a80e 100644 --- a/docs/source/asynDriver.rst +++ b/docs/source/asynDriver.rst @@ -3143,7 +3143,7 @@ This provides the ability to configure serial ports on terminal servers using th RFC 2117 protocol. It is not configured from the iocsh directly, but is rather configured automatically by the drvAsynIPPort driver if the COM protocol is specified. It supports the same options as drvAsynSerialPort, i.e. "baud", "bits", "parity", "stop", "crtscts", -"ixon" and "break". +"ixon", "break" and "autobreak". asynInterposeDelay ~~~~~~~~~~~~~~~~~~ @@ -4228,7 +4228,11 @@ values. When a serial port connects the current values are fetched. * - rs485_delay_rts_after_send - msec_delay * - break - off on + - off on + * - autobreak + - + * - autobreak_delay + - On some systems (e.g. Windows, Darwin) the driver accepts any numeric value for the baud rate, which must, of course be supported by the system hardware. On Linux @@ -4265,9 +4269,14 @@ on hardware ports that support RS-485. The delay option units are integer millis The break option should send a serial break state on supported systems (Linux, Windows work, vxWorks does not). A numeric value should send a break for the -specified time, which is device depended (deci seconds, milli seconds, ...). -A zero value means a default time. A value "on" should set the break state on -for a unlimited time and "off" should clear the break state. +specified time, which is in units of milliseconds on Windows and Linux but +implementation defined elsewhere (see tcsendbreak()).A zero value means a default time. A value "on" +should set the break state on for a unlimited time and "off" should clear the break state. + +For devices that use a serial break as a command terminator the autobreak option can +be used. This will wait autobreak_delay (ms) before asserting a serial break for +the specified length (see break above for time units) after every write. Use 0 to +disable autobreak or autobreak_delay vxWorks IOC serial ports may need to be set up using hardware-specific commands. Once this is done, the standard drvAsynSerialPortConfigure and asynSetOption commands