/*--------------------------------------------------------------------- R S 2 3 2 C o n t r o l l e r A general object which represents a controller connected to the network via a terminal server. This bypasses David Maden's SerPortServer software. Basic facilities are provided for writinga nd reading to and from the device. For more information see the rs232controller.tex file. copyright: see copyright.h Mark Koennecke, October 2001 -----------------------------------------------------------------------*/ #include #include #include #include #include "fortify.h" #include "sics.h" #include "splitter.h" #include "nread.h" #include "rs232controller.h" /*--------------------------------------------------------------------*/ static char *MakeID(prs232 self){ static char id[132]; snprintf(id,sizeof(id),"%s:%d",self->pHost, self->iPort); return id; } /*--------------------------------------------------------------------*/ void setRS232SendTerminator(prs232 self, char *term) { assert(self); if (self->sendTerminator != NULL) { free(self->sendTerminator); } if (term != NULL) { self->sendTerminator = strdup(term); } else { self->sendTerminator = NULL; } } /*--------------------------------------------------------------------*/ void setRS232ReplyTerminator(prs232 self, char *term) { assert(self); if (self->replyTerminator != NULL) { free(self->replyTerminator); } if (term != NULL) { self->replyTerminator = strdup(term); } else { self->replyTerminator = NULL; } } /*---------------------------------------------------------------------*/ void setRS232Timeout(prs232 self, int timeout) { assert(self); self->timeout = timeout; } /*---------------------------------------------------------------------*/ void setRS232Debug(prs232 self, int deb) { assert(self); self->debug = deb; } /*--------------------------------------------------------------------*/ int writeRS232(prs232 self, void *data, int dataLen) { char *pPtr = NULL; int iRet; assert(self); /* catch an unconnected socket */ if (!self->pSock) { return NOTCONNECTED; } /* do the terminator processing, if required. If new data had to be allocated in order to add the terminator, pPtr is not NULL. I any other case it is. */ if (self->sendTerminator != NULL) { pPtr = (char *) data; if (strstr(pPtr, self->sendTerminator) == NULL) { /* the terminator is missing. add it. */ pPtr = (char *) malloc((dataLen + strlen(self->sendTerminator) + 2) * sizeof(char)); if (!pPtr) { return BADMEMORY; } strcpy(pPtr, (char *) data); strcat(pPtr, self->sendTerminator); dataLen += strlen(self->sendTerminator); data = pPtr; } else { pPtr = NULL; } } /* send */ iRet = NETWrite(self->pSock, data, dataLen); if (self->debug > 0) { printf("RS232 OUT : %s", (char *) data); if (strchr((char *) data, '\n') == NULL) { puts(""); } fflush(stdout); } if(strlen((char *)data) <= dataLen){ traceIO(MakeID(self),"out:%s",(char *)data); } else { traceIO(MakeID(self),"out:%s","Binary output"); } if (pPtr != NULL) free(pPtr); if (iRet != 1) { return BADSEND; } return iRet; } /*----------------------------------------------------------------------*/ int readRS232(prs232 self, void *data, int *dataLen) { long lRead; int iRet; size_t rLength; assert(self); /* catch an unconnected socket */ if (!self->pSock) { return NOTCONNECTED; } /* clean our space in order to prevent corrupted stuff */ memset(data, 0, *dataLen); iRet = NETAvailable(self->pSock, self->timeout); if (iRet < 0) { return iRet; } else if (iRet == 0) { *dataLen = 0; return TIMEOUT; } else { rLength = *dataLen; lRead = recv(self->pSock->sockid, data, rLength, 0); if (lRead >= 0) { if (self->debug > 0) { printf("RS232 IN : %s", (char *) data); if (strchr((char *) data, '\n') == NULL) { puts(""); } fflush(stdout); } if(strlen((char *)data) <= *dataLen){ traceIO(MakeID(self),"in:%s",(char *)data); } else { traceIO(MakeID(self),"in:%s","Binary Input"); } *dataLen = lRead; return 1; } else { return (int) lRead; } } /* not reached */ return 0; } /*--------------------------------------------------------------------*/ int readRS232TillTerm(prs232 self, void *data, int *datalen) { int iRet, replylen; assert(self); /* catch an unconnected socket */ if (!self->pSock) { return NOTCONNECTED; } memset(data, 0, *datalen); replylen = *datalen; iRet = NETReadTillTerm(self->pSock, self->timeout, self->replyTerminator, (char *) data, replylen); if (self->debug > 0 && iRet > 0) { printf("RS232 IN/TERM : %s", (char *) data); if (strchr((char *) data, '\n') == NULL) { puts(""); } fflush(stdout); } if (iRet == 0) { if (self->debug > 0) { printf("RS232 IN/TERM : TIMEOUT:%s\n", (char *) data); } traceIO(MakeID(self),"in:%s","TIMEOUT"); return TIMEOUT; } else if (iRet == -1) { traceIO(MakeID(self),"in:%s %s","Incomplete read", (char *)data); printf("Incomplete read: %s, errno = %d\n", (char *) data, errno); return INCOMPLETE; } else if (iRet < 0) { traceIO(MakeID(self),"in:Error %d",errno); return iRet; } if (*self->replyTerminator != 0) { *datalen = strlen((char *) data); } else { *datalen = iRet; } traceIO(MakeID(self),"in:%s",(char *)data); return 1; } /*----------------------------------------------------------------------*/ int readRS232UntilWord(prs232 self, char *buffer, int buflen, char *word) { time_t endTime; int status; int bytesRead = 0; endTime = time(NULL) + self->timeout; memset(buffer, 0, buflen); while (time(NULL) < endTime) { if (availableRS232(self)) { bytesRead = recv(self->pSock->sockid, buffer + bytesRead, buflen - bytesRead - 1, 0); if (bytesRead < 0) { return BADREAD; } if (strstr(buffer, word) != NULL) { return 1; } } else { SicsWait(1); } } return 0; } /*-----------------------------------------------------------------------*/ int availableRS232(prs232 self) { assert(self); /* catch an unconnected socket */ if (!self->pSock) { return NOTCONNECTED; } return NETAvailable(self->pSock, 0); } /*-----------------------------------------------------------------------*/ int availableNetRS232(prs232 self) { int status; assert(self); /* catch an unconnected socket */ if (!self->pSock) { return NOTCONNECTED; } if (!self->registered) { if (pServ->pReader != NULL) { NetReadRegisterUserSocket(pServ->pReader, self->pSock->sockid); self->registered = 1; return 0; } } status = NetReadReadable(pServ->pReader, self->pSock->sockid); NetReadResetUser(pServ->pReader, self->pSock->sockid); return status; } /*------------------------------------------------------------------------*/ int transactRS232(prs232 self, void *send, int sendLen, void *reply, int replyLen) { int iRet, len; assert(self); /* catch an unconnected socket */ if (!self->pSock) { return NOTCONNECTED; } /* if there is still data on the socket: clear it while(availableRS232(self)){ len = replyLen; readRS232(self,reply,&len); } */ /* write data */ iRet = writeRS232(self, send, sendLen); if (iRet <= 0) { return BADSEND; } /* read */ memset(reply, 0, replyLen); iRet = NETReadTillTerm(self->pSock, self->timeout, self->replyTerminator, reply, replyLen); if (self->debug > 0) { if (iRet > 0) { printf("RS232 IN/TRANS: %s", (char *) reply); if (strchr((char *) reply, '\n') == NULL) { puts(""); } fflush(stdout); } else if (iRet == 0) { printf("RS232 IN/TRANS: TIMEOUT\n"); } else { printf("RS232 IN/TRANS/INCOMPLETE: %s, errno = %d", (char *) reply, errno); } } if (iRet == 0) { traceIO(MakeID(self),"in:%s", "TIMEOUT"); return TIMEOUT; } else if (iRet == -1) { traceIO(MakeID(self),"in:%s:%d", "Incomplete read", errno); return INCOMPLETE; } else { traceIO(MakeID(self),"in:%s", (char *)reply); return iRet; } } /*------------------------------------------------------------------------*/ void getRS232Error(int iCode, char *errorBuffer, int errorBufferLen) { /* the error code is either one of our errors, or an errno code from the system */ switch (iCode) { case BADMEMORY: strlcpy(errorBuffer, "Out of memory for appending terminators", errorBufferLen); break; case NOTCONNECTED: strlcpy(errorBuffer, "Not connected!", errorBufferLen); break; case TIMEOUT: strlcpy(errorBuffer, "Timeout or network error when reading data", errorBufferLen); break; case FAILEDCONNECT: strlcpy(errorBuffer, "Failed to connect to terminal server", errorBufferLen); break; case INCOMPLETE: strlcpy(errorBuffer, "Did not find terminator in read buffer", errorBufferLen); break; case BADSEND: strlcpy(errorBuffer, "Network problem: failed to send", errorBufferLen); break; default: strlcpy(errorBuffer, strerror(errno), errorBufferLen); break; } } /*--------------------------------------------------------------------*/ int fixRS232Error(prs232 self, int iCode) { int i, status, read; char buffer[8192]; switch (iCode) { case BADMEMORY: case FAILEDCONNECT: return 0; break; case INCOMPLETE: case TIMEOUT: /* * try to clear possibly pending stuff */ for (i = 0; i < 3; i++) { if (availableRS232(self)) { read = 8192; readRS232(self, buffer, &read); } } return 1; break; case NOTCONNECTED: case BADSEND: closeRS232(self); status = initRS232(self); if (status > 0) { return 1; } else { return 0; } break; } return 0; } /*--------------------------------------------------------------------*/ int getRS232Timeout(prs232 self) { return self->timeout; } /*--------------------------------------------------------------------*/ int initRS232WithFlags(prs232 self, int flags) { int iRet; assert(self); traceIO(MakeID(self),"connect"); if (self->pSock != NULL) { if (pServ->pReader != NULL) { NetReadRemoveUserSocket(pServ->pReader, self->pSock->sockid); } NETClosePort(self->pSock); free(self->pSock); self->pSock = NULL; } self->pSock = NETConnectWithFlags(self->pHost, self->iPort, flags); if (!self->pSock) { return FAILEDCONNECT; } else { if (pServ->pReader != NULL) { NetReadRegisterUserSocket(pServ->pReader, self->pSock->sockid); self->registered = 1; } return 1; } } /*--------------------------------------------------------------------*/ int initRS232(prs232 self) { return initRS232WithFlags(self, 0); } /*--------------------------------------------------------------------*/ int initRS232Finished(prs232 self) { int iret; iret = NETConnectFinished(self->pSock); if (iret < 0) { return FAILEDCONNECT; } else { return iret; } } /*--------------------------------------------------------------------*/ void closeRS232(prs232 self) { assert(self); if (self->pSock != NULL) { if (pServ->pReader != NULL) { NetReadRemoveUserSocket(pServ->pReader, self->pSock->sockid); } NETClosePort(self->pSock); free(self->pSock); self->pSock = NULL; } } /*------------------------------------------------------------------*/ prs232 createRS232(char *host, int iPort) { prs232 pNew = NULL; assert(iPort > 0); /* create data structure */ pNew = (prs232) malloc(sizeof(rs232)); if (!pNew) { return NULL; } memset(pNew, 0, sizeof(rs232)); pNew->pHost = strdup(host); pNew->iPort = iPort; pNew->sendTerminator = strdup("\r"); pNew->replyTerminator = strdup("\n"); pNew->timeout = 1000; pNew->debug = 0; pNew->pDes = CreateDescriptor("RS232 Controller"); if (!pNew->pDes || !pNew->pHost || !pNew->replyTerminator || !pNew->sendTerminator) { return NULL; } return pNew; } /*-------------------------------------------------------------------*/ void KillRS232(void *pData) { prs232 self = (prs232) pData; if (!self) { return; } if (self->pDes) { DeleteDescriptor(self->pDes); } if (self->sendTerminator != NULL) { free(self->sendTerminator); } if (self->replyTerminator != NULL) { free(self->replyTerminator); } if (self->pSock) { if (pServ->pReader != NULL) { NetReadRemoveUserSocket(pServ->pReader, self->pSock->sockid); } NETClosePort(self->pSock); free(self->pSock); } if (self->pHost) { free(self->pHost); } } /*-------------------------------------------------------------------*/ static void KillAndFreeRS232(void *pData) { KillRS232(pData); free(pData); } /*-------------------------------------------------------------------*/ static int checkSet(SConnection * pCon, int argc, int rights) { if (argc < 3) { return 0; } else { if (SCMatchRights(pCon, rights)) { return 1; } else { return 0; } } /* not reached */ return 0; } /*--------------------------------------------------------------------*/ static void encodeTerminator(char *result, char *terminator) { int i, len; char pBuffer[10]; if (terminator == NULL) { result[0] = '\0'; } len = strlen(terminator); snprintf(pBuffer,sizeof(pBuffer)-1, "0x%x", (int) terminator[0]); strcpy(result, pBuffer); for (i = 1; i < len; i++) { snprintf(pBuffer,sizeof(pBuffer)-1, "0x%x", (int) terminator[i]); strcat(result, pBuffer); } } extern char *stptok(char *pPtr, char *pToken, int tokenLen, char *term); /*--------------------------------------------------------------------*/ char *decodeTerminator(char *code) { int count = 0, icode; char *pResult; char *pPtr, pToken[10]; /* max 10 terminators! */ pResult = (char *) malloc(10 * sizeof(char)); if (!pResult) { return NULL; } memset(pResult, 0, 10); pToken[0] = '0'; /* I seem to get an empty token on the first call to stptok, this is why I do 2 stptoks. Strange and wonderful. */ pPtr = stptok(code, pToken + 1, 9, "0"); pPtr = stptok(pPtr, pToken + 1, 9, "0"); while (pPtr != NULL) { sscanf(pToken, "%x", &icode); pResult[count] = (char) icode; count++; pPtr = stptok(pPtr, pToken + 1, 9, "0"); } return pResult; } /*--------------------------------------------------------------------*/ int RS232Action(SConnection * pCon, SicsInterp * pSics, void *pData, int argc, char *argv[]) { prs232 self = NULL; char pError[256]; char pBuffer[8192], pReply[8192]; char pTerm[10]; char *pPtr = NULL; int iRet, iRead = 8191, count, i; self = (prs232) pData; assert(self); assert(pCon); /* check for arguments */ if (argc < 2) { snprintf(pError,sizeof(pError)-1, "ERROR: insufficient no of arguments to %s", argv[0]); SCWrite(pCon, pError, eError); return 0; } strtolower(argv[1]); if (strcmp(argv[1], "sendterminator") == 0) { if (checkSet(pCon, argc, usMugger)) { pPtr = decodeTerminator(argv[2]); setRS232SendTerminator(self, pPtr); if (pPtr) free(pPtr); SCSendOK(pCon); return 1; } else { encodeTerminator(pBuffer, self->sendTerminator); snprintf(pError,sizeof(pError)-1, "%s.sendTerminator = \"%s\"", argv[0], pBuffer); SCWrite(pCon, pError, eValue); return 1; } } else if (strcmp(argv[1], "debug") == 0) { self->debug = atoi(argv[2]); SCSendOK(pCon); return 1; } else if (strcmp(argv[1], "timeout") == 0) { if (checkSet(pCon, argc, usUser)) { setRS232Timeout(self, atoi(argv[2])); SCSendOK(pCon); return 1; } else { snprintf(pError,sizeof(pError)-1, "%s.Timeout = %d", argv[0], self->timeout); SCWrite(pCon, pError, eValue); return 1; } } else if (strcmp(argv[1], "replyterminator") == 0) { if (checkSet(pCon, argc, usMugger)) { memset(pTerm, 0, 10); for (i = 2; i < argc; i++) { pPtr = decodeTerminator(argv[i]); pTerm[i - 2] = pPtr[0]; if (pPtr) free(pPtr); } setRS232ReplyTerminator(self, pTerm); SCSendOK(pCon); return 1; } else { encodeTerminator(pBuffer, self->replyTerminator); snprintf(pError,sizeof(pError)-1, "%s.replyTerminator = \"%s\"", argv[0], pBuffer); SCWrite(pCon, pError, eValue); return 1; } } else if (strcmp(argv[1], "write") == 0) { Arg2Text(argc - 2, argv + 2, pBuffer, 8191); iRet = writeRS232(self, pBuffer, strlen(pBuffer)); if (iRet < 0) { getRS232Error(iRet, pError, 255); SCWrite(pCon, pError, eError); return 0; } SCSendOK(pCon); return 1; } else if (strcmp(argv[1], "read") == 0) { if (!availableRS232(self)) { SCWrite(pCon, "Nothing to read!", eError); return 1; } iRet = readRS232(self, pBuffer, &iRead); if (iRet < 0) { getRS232Error(iRet, pError, 255); SCWrite(pCon, pError, eError); return 0; } SCWrite(pCon, pBuffer, eValue); return 1; } else if (strcmp(argv[1], "readchar") == 0) { if (argc < 3) { SCWrite(pCon, "ERROR: need number of chars to read", eError); return 0; } iRet = Tcl_GetInt(pSics->pTcl, argv[2], &iRead); if (iRet != TCL_OK) { SCWrite(pCon, "ERROR: failed to convert argument to number", eError); return 0; } if (!availableRS232(self)) { SCWrite(pCon, "Nothing to read!", eError); return 1; } iRet = readRS232(self, pBuffer, &iRead); if (iRet < 0) { getRS232Error(iRet, pError, 255); SCWrite(pCon, pError, eError); return 0; } SCWrite(pCon, pBuffer, eValue); return 1; } else if (strcmp(argv[1], "available") == 0) { iRet = availableRS232(self); if (iRet < 0) { getRS232Error(iRet, pError, 255); SCWrite(pCon, pError, eError); return 0; } else if (iRet == 0) { SCWrite(pCon, "No data pending", eValue); return 1; } else { SCWrite(pCon, "Data available", eValue); return 1; } } else if (strcmp(argv[1], "send") == 0) { Arg2Text(argc - 2, argv + 2, pBuffer, 8191); iRet = transactRS232(self, pBuffer, strlen(pBuffer), pReply, iRead); if (iRet < 0) { getRS232Error(iRet, pError, 255); SCWrite(pCon, pError, eError); return 0; } SCWrite(pCon, pReply, eValue); return 1; } else if (strcmp(argv[1], "binwrite") == 0) { count = argc - 2; for (i = 0; i < count; i++) { pBuffer[i] = (char) atoi(argv[i + 2]); } if (self->pSock) { iRet = NETWrite(self->pSock, pBuffer, count); } else { iRet = NOTCONNECTED; } if (iRet < 0) { getRS232Error(iRet, pError, 255); SCWrite(pCon, pError, eError); return 0; } SCSendOK(pCon); return 1; } else if (strcmp(argv[1], "init") == 0) { iRet = initRS232(self); if (iRet != 1) { snprintf(pError,sizeof(pError)-1, "ERROR: reinitializing connection to %s at %d failed", self->pHost, self->iPort); SCWrite(pCon, pError, eError); return 0; } else { SCSendOK(pCon); return 1; } } else { snprintf(pError,sizeof(pError)-1, "ERROR: %s does not understand %s", argv[0], argv[1]); SCWrite(pCon, pError, eError); return 0; } return 1; } /*-------------------------------------------------------------------*/ int RS232Factory(SConnection * pCon, SicsInterp * pSics, void *pData, int argc, char *argv[]) { prs232 pNew = NULL; int iRet, status; char pError[256]; if (argc < 4) { SCWrite(pCon, "ERROR: insufficient no of arguments to RS232Factory", eError); return 0; } /* create data structure and open port */ pNew = createRS232(argv[2], atoi(argv[3])); if (!pNew) { SCWrite(pCon, "ERROR: out of memory in RS232Factory", eError); return 0; } status = initRS232(pNew); if (status != 1) { snprintf(pError,sizeof(pError)-1, "ERROR: failed to connect to %s at port %d", pNew->pHost, pNew->iPort); SCWrite(pCon, pError, eError); } /* create the command */ iRet = AddCommand(pSics, argv[1], RS232Action, KillAndFreeRS232, pNew); if (!iRet) { snprintf(pError,sizeof(pError)-1, "ERROR: duplicate command %s not created", argv[1]); SCWrite(pCon, pError, eError); KillAndFreeRS232(pNew); return 0; } return 1; }