Skip to content

Commit 8a87d25

Browse files
authored
Merge pull request #79 from epics-motor/issue72
Fix for binary communication error false positives. Significantly improved binary error checking so that messages that happen to have "?" and "\r" in the same locations of the reply as error messages are no longer incorrectly interpreted as error messages with invalid error numbers. Human-readable error strings are now printed along with the error number when real errors occur. Fixes #72
2 parents 6f5b27e + 18069db commit 8a87d25

3 files changed

Lines changed: 157 additions & 32 deletions

File tree

acsMotionApp/src/SPiiPlusCommDriver.cpp

Lines changed: 138 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11

22
#include <string.h>
33
#include <cstdlib>
4+
#include <cstdint>
45
#include <sstream>
56

67
#include <iocsh.h>
@@ -260,7 +261,7 @@ asynStatus SPiiPlusComm::writeReadErrorMessage(char* errNoReply)
260261
std::stringstream val_convert;
261262
std::stringstream local_cmd;
262263
char inString[MAX_CONTROLLER_STRING_SIZE];
263-
int errNo;
264+
int errNo = 0;
264265

265266
/* errNoReply is of the form ?#### */
266267

@@ -298,6 +299,41 @@ asynStatus SPiiPlusComm::writeReadErrorMessage(char* errNoReply)
298299
return status;
299300
}
300301

302+
// A separate method to read error messages from binary comm methods is needed to avoid deadlocks
303+
asynStatus SPiiPlusComm::writeReadBinaryErrorMessage(int errNo)
304+
{
305+
static const char *functionName = "writeReadBinaryErrorMessage";
306+
std::stringstream local_cmd;
307+
char inString[MAX_CONTROLLER_STRING_SIZE];
308+
309+
std::fill(inString, inString + 256, '\0');
310+
311+
// The command to query the error message is ??####
312+
local_cmd << "??" << errNo;
313+
314+
asynPrint(pasynUserSelf, ASYN_TRACEIO_DRIVER, "%s:%s: output = %s\n", driverName, functionName, local_cmd.str().c_str());
315+
316+
size_t response;
317+
// The function calling binaryErrorCheck already has the lock, so no locking is needed here
318+
asynStatus status = writeReadController(local_cmd.str().c_str(), inString, 256, &response, -1);
319+
320+
asynPrint(pasynUserSelf, ASYN_TRACEIO_DRIVER, "%s:%s: input = %s\n", driverName, functionName, inString);
321+
asynPrint(pasynUserSelf, ASYN_TRACEIO_DRIVER, "%s:%s: status = %i\n", driverName, functionName, status);
322+
323+
if (inString[0] != '?')
324+
{
325+
asynPrint(pasynUserSelf, ASYN_TRACE_ERROR, "%s:%s: ERROR #%i: %s\n", driverName, functionName, errNo, inString);
326+
}
327+
else {
328+
// We should never get here unless a controller returns an error for which it doesn't have an error message defined
329+
asynPrint(pasynUserSelf, ASYN_TRACE_ERROR, "%s:%s: ERROR #%i\n", driverName, functionName, errNo);
330+
331+
status = asynError;
332+
}
333+
334+
return status;
335+
}
336+
301337
// NOTE: readBytes the number of data bytes that were read, excluding the command header and suffix
302338
// NOTE: there is no error checking on inBytes and outBytes
303339
// FYI: motor/motorApp/MotorSrc/asynMotorController.h:#define MAX_CONTROLLER_STRING_SIZE 256
@@ -307,6 +343,7 @@ asynStatus SPiiPlusComm::writeReadBinary(char *output, int outBytes, char *input
307343
char* packetBuffer;
308344
size_t nwrite, nread;
309345
int eomReason;
346+
int errNo = 0;
310347
asynStatus status;
311348
static const char *functionName = "writeReadBinary";
312349

@@ -333,17 +370,18 @@ asynStatus SPiiPlusComm::writeReadBinary(char *output, int outBytes, char *input
333370
// The reply from the controller has a 4-byte header and a 1-byte suffix
334371
status = pasynOctetSyncIO->read(pasynUserComm_, packetBuffer, inBytes, SPIIPLUS_ARRAY_TIMEOUT, &nread, &eomReason);
335372

336-
asynPrint(pasynUserSelf, ASYN_TRACEIO_DRIVER, "%s:%s: input bytes = %i\n", driverName, functionName, inBytes);
373+
asynPrint(pasynUserSelf, ASYN_TRACEIO_DRIVER, "%s:%s: input bytes = %i\n", driverName, functionName, inBytes);
337374
asynPrint(pasynUserSelf, ASYN_TRACEIO_DRIVER, "%s:%s: status = %i\n", driverName, functionName, status);
338375

339376
if (status == asynSuccess)
340377
{
341378
// Check for an error reply
342-
status = binaryErrorCheck(packetBuffer);
343-
if (status == asynError)
379+
errNo = binaryErrorCheck(packetBuffer, nread);
380+
if (errNo != 0)
344381
{
345382
*sliceAvailable = false;
346383
asynPrint(pasynUserSelf, ASYN_TRACE_ERROR, "%s:%s: Binary read failed (controller)\n", driverName, functionName);
384+
status = asynError;
347385
}
348386
else
349387
{
@@ -371,6 +409,16 @@ asynStatus SPiiPlusComm::writeReadBinary(char *output, int outBytes, char *input
371409
{
372410
*sliceAvailable = false;
373411
asynPrint(pasynUserSelf, ASYN_TRACE_ERROR, "%s:%s: Binary read failed (asyn): status=%i, nread=%li\n", driverName, functionName, status, nread);
412+
413+
// If fewer bytes were read and there was an asyn timeout, there might still be an error message
414+
if (nread > 0)
415+
{
416+
errNo = binaryErrorCheck(packetBuffer, nread);
417+
if (errNo != 0)
418+
{
419+
status = asynError;
420+
}
421+
}
374422
}
375423

376424
// Restore the EOS characters
@@ -382,37 +430,93 @@ asynStatus SPiiPlusComm::writeReadBinary(char *output, int outBytes, char *input
382430

383431
unlock();
384432

433+
if (errNo != 0)
434+
{
435+
// Print the human-readable error string; humans don't like searching pdfs for error descriptions
436+
writeReadBinaryErrorMessage(errNo);
437+
}
438+
385439
asynPrint(pasynUserSelf, ASYN_TRACE_FLOW, "%s:%s: end\n", driverName, functionName);
386440

387441
return status;
388442
}
389443

390-
asynStatus SPiiPlusComm::binaryErrorCheck(char *buffer)
444+
// Return value is 0 if there is no error and the error number if an error is detected
445+
int SPiiPlusComm::binaryErrorCheck(char *buffer, int readBytes)
391446
{
392-
asynStatus status=asynSuccess;
393447
std::stringstream val_convert;
394-
int errNo;
448+
int errNo = 0;
449+
int idx;
450+
uint8_t replyStart, replyEnd, cmdId, bodyLenLsb, bodyLenMsb, bodyStart, bodyEnd;
451+
int bodyLength;
452+
uint8_t errorStr[5] = {0, 0, 0, 0, 0};
453+
bool errNoIsValid = true;
395454
static const char *functionName = "binaryErrorCheck";
396455

397-
// If the first character of the data is a question mark, the error number follows it
398-
if ((buffer[4] == 0x3f) && (buffer[9] == 0x0d))
456+
/*
457+
* This was the original expected error response (11 bytes), but I can't find it anywhere in the documentation now.
458+
* Error response: [E3][XX][06][00]?####[0D][E6]
459+
*
460+
* This is documented error response (10 bytes) that is present in many verions of the low level host communication user guide.
461+
* Error response: [E3][XX]6?####[0D][E6]
462+
*/
463+
464+
if (readBytes == 11)
399465
{
400-
/*
401-
* Error response: [E3][XX][06][00]?####[0D][E6]
402-
*/
403-
404-
// replace the carriage return with a null byte
405-
buffer[9] = 0;
466+
replyStart = buffer[0];
467+
cmdId = buffer[1];
468+
bodyLenLsb = buffer[2];
469+
bodyLenMsb = buffer[3];
470+
// Only the least two significant bits of the most signficant body-length byte are the most significant bits of the body length
471+
bodyLength = ((int)bodyLenMsb << 8) | (int)bodyLenLsb;
472+
bodyStart = buffer[4];
473+
bodyEnd = buffer[9];
474+
replyEnd = buffer[10];
406475

407-
// convert the error number bytes into an int
408-
val_convert << buffer+5;
409-
val_convert >> errNo;
410-
411-
asynPrint(pasynUserSelf, ASYN_TRACE_ERROR, "%s:%s: Binary command error #%i\n", driverName, functionName, errNo);
412-
status = asynError;
476+
if ((replyStart == 0xe3) && (bodyLength == 6) && (replyEnd == 0xe6))
477+
{
478+
// '?' is 0x3f
479+
if ((bodyStart == 0x3f) && (bodyEnd == 0x0d))
480+
{
481+
for (idx=0; idx<4; idx++)
482+
{
483+
/*
484+
* The error number starts at index = 4 in the error reply
485+
* Confirm the error number has valid characters (digits 0-9)
486+
* '0' is 48; '9' is 57
487+
*/
488+
if ((buffer[5+idx] < 48) && (buffer[5+idx] > 57))
489+
{
490+
errNoIsValid = false;
491+
break;
492+
}
493+
else
494+
{
495+
errorStr[idx] = buffer[5+idx];
496+
}
497+
}
498+
499+
if (errNoIsValid)
500+
{
501+
// The error string is valid and can be converted into an int and reported on the IOC's shell
502+
val_convert << errorStr;
503+
val_convert >> errNo;
504+
505+
asynPrint(pasynUserSelf, ASYN_TRACE_ERROR, "%s:%s: Binary command error %i for command id %x\n", driverName, functionName, errNo, cmdId);
506+
}
507+
}
508+
else
509+
{
510+
asynPrint(pasynUserSelf, ASYN_TRACE_ERROR, "%s:%s: Incorrect error body start/end: bodyStart = %x, bodyEnd = %x\n", driverName, functionName, bodyStart, bodyEnd);
511+
}
512+
}
513+
else
514+
{
515+
asynPrint(pasynUserSelf, ASYN_TRACE_ERROR, "%s:%s: Incorrect error reply prefix/suffix: replyStart = %x, bodyLength = %i, replyEnd = %x\n", driverName, functionName, replyStart, bodyLength, replyEnd);
516+
}
413517
}
414518

415-
return status;
519+
return errNo;
416520
}
417521

418522
asynStatus SPiiPlusComm::isVariableDefined(bool *isDefined, const char *var)
@@ -453,6 +557,7 @@ asynStatus SPiiPlusComm::writeReadAckBinary(char *output, int outBytes, char *in
453557
size_t nwrite, nread, extraRead;
454558
int eomReason;
455559
int commandID;
560+
int errNo = 0;
456561
asynStatus status;
457562
static const char *functionName = "writeReadAckBinary";
458563

@@ -492,11 +597,12 @@ asynStatus SPiiPlusComm::writeReadAckBinary(char *output, int outBytes, char *in
492597

493598
asynPrint(pasynUserSelf, ASYN_TRACEIO_DRIVER, "%s:%s: status = %i; extraRead = %li, eomReason = %i\n", driverName, functionName, status, extraRead, eomReason);
494599

495-
// Check for an error reply -- this overwrites the buffer if an error occurs
496-
status = binaryErrorCheck(input);
497-
if (status == asynError)
600+
// Check for an error reply
601+
errNo = binaryErrorCheck(input, nread+extraRead);
602+
if (errNo != 0)
498603
{
499604
asynPrint(pasynUserSelf, ASYN_TRACE_ERROR, "%s:%s: Binary read failed (controller)\n", driverName, functionName);
605+
status = asynError;
500606
}
501607

502608
}
@@ -509,6 +615,7 @@ asynStatus SPiiPlusComm::writeReadAckBinary(char *output, int outBytes, char *in
509615
else
510616
{
511617
asynPrint(pasynUserSelf, ASYN_TRACE_ERROR, "%s:%s: Command ID mismatch: write ID = %i, read ID = %i\n", driverName, functionName, commandID, input[1]);
618+
// Should status be set to asynError here?
512619
}
513620
}
514621
}
@@ -523,6 +630,12 @@ asynStatus SPiiPlusComm::writeReadAckBinary(char *output, int outBytes, char *in
523630

524631
unlock();
525632

633+
if (errNo > 0)
634+
{
635+
// Print the human-readable error string; humans don't like searching pdfs for error descriptions
636+
writeReadBinaryErrorMessage(errNo);
637+
}
638+
526639
asynPrint(pasynUserSelf, ASYN_TRACE_FLOW, "%s:%s: end\n", driverName, functionName);
527640

528641
return status;

acsMotionApp/src/SPiiPlusCommDriver.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,13 @@ class epicsShareClass SPiiPlusComm : public asynPortDriver {
2121
asynStatus writeReadStr(std::stringstream& cmd, char* val);
2222
asynStatus writeReadAck(std::stringstream& cmd);
2323
asynStatus writeReadErrorMessage(char* errNoReply);
24+
asynStatus writeReadBinaryErrorMessage(int errNo);
2425
asynStatus getIntegerArray(char *output, const char *var, int idx1start, int idx1end, int idx2start, int idx2end);
2526
asynStatus getDoubleArray(char *output, const char *var, int idx1start, int idx1end, int idx2start, int idx2end);
2627
asynStatus putDoubleArray(double *data, const char *var, int idx1start, int idx1end, int idx2start, int idx2end);
2728
asynStatus writeReadBinary(char *output, int outBytes, char *input, int inBytes, size_t *dataBytes, bool* sliceAvailable);
2829
asynStatus writeReadAckBinary(char *output, int outBytes, char *input, int inBytes);
29-
asynStatus binaryErrorCheck(char *buffer);
30+
int binaryErrorCheck(char *buffer, int readBytes);
3031
asynStatus isVariableDefined(bool *isDefined, const char *var);
3132
asynStatus globalVarCheck(const char *var, int idx1start, int idx1end, int idx2start, int idx2end, int *dimensions, int *numElements, int *errNo);
3233
asynStatus createGlobalRealVar(const char *var, int idx1start, int idx1end, int idx2start, int idx2end);

acsMotionApp/src/SPiiPlusDriver.cpp

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3005,30 +3005,40 @@ asynStatus SPiiPlusController::test()
30053005
{
30063006
asynStatus status;
30073007
char* buffer=NULL;
3008-
double* data=NULL;
3009-
long maxDoubles;
3010-
long dataSize;
3011-
int i;
3008+
//double* data=NULL;
3009+
//long maxDoubles;
3010+
//long dataSize;
3011+
//int i;
30123012
static const char *functionName = "test";
30133013

30143014
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, "%s:%s: calling test function\n", driverName, functionName);
30153015

30163016
buffer = (char *)calloc(MAX_BINARY_READ_LEN, sizeof(char));
30173017

30183018
// MAX_BINARY_READ_LEN is in bytes so we need to calculate how many doubles that will hold
3019+
/*
30193020
maxDoubles = floorl(MAX_BINARY_READ_LEN/sizeof(double));
30203021
data = (double *)calloc(maxDoubles, sizeof(double));
3022+
*/
30213023

30223024
//status = pComm_->getDoubleArray(buffer, "DC_DATA_1", 0, 2, 0, (maxProfilePoints_-1));
30233025

3026+
// Generate three binary read errors by attempting to read variables that don't exist
3027+
status = pComm_->getDoubleArray(buffer, "FAKE_VAR_1", 0, 2, 0, 0);
3028+
status = pComm_->getDoubleArray(buffer, "FAKE_VAR_2", 0, 2, 0, 0);
3029+
status = pComm_->getDoubleArray(buffer, "FAKE_VAR_3", 0, 2, 0, 0);
3030+
// Try to read too many points
3031+
//status = pComm_->getDoubleArray(buffer, "testVar", 0, 2, 0, maxProfilePoints_);
3032+
30243033
// Create test data
30253034
//dataSize = 101;
3035+
/*
30263036
dataSize = 2000;
30273037
for (i=0; i<dataSize; i++)
30283038
{
30293039
data[i] = i * 1.0;
30303040
}
3031-
3041+
*/
30323042
/*
30333043
// create larger test data
30343044
dataSize = 100000;
@@ -3043,7 +3053,8 @@ asynStatus SPiiPlusController::test()
30433053
*/
30443054

30453055
// Note: it is assumed that data has enough values to fill the specified array
3046-
status = pComm_->putDoubleArray(data, "testVar", 0, dataSize-1, 0, 0);
3056+
//status = pComm_->putDoubleArray(data, "testVar", 0, dataSize-1, 0, 0);
3057+
30473058
/*
30483059
// 2D testing
30493060
// This results in a real(500)(4) array and the data from the 11th packet doesn't get appended properly

0 commit comments

Comments
 (0)