I am using Termios in an (ressource constrained) embedded Linux platform (in C) to send commands and receive data from various tty peripherals (CP2102 USB-UART). Apparently there are various ways to do that and many are improper. I tried few with mitigated success, I came across something that works but i am not sure it is optimal or even correct :
#define BAUDRATE 115200
#define DEVICE "/dev/ttyUSB0"
#define DATA "BLK\r"
int handler(void){
char buf[255];
struct pollfd fds[1];
int fd, ret, res, retry = 0;
connect:
fd = open(DEVICE, O_RDWR | O_NOCTTY | O_NONBLOCK);
if (fd == 0){
perror(DEVICE);
printf("Failed to open %s\n",DEVICE);
sleepms(2000);
if(retry++<5) goto connect;
//exit(-1);
}
set_interface_attribs (fd, BAUDRATE, 0); // 8n1 no parity
set_blocking (fd, 0); // not blocking
fds[0].fd = fd; // streams
fds[0].events = POLLRDNORM;
for (;;)
{
int count = write(fd, DATA, strlen(DATA));
ret = poll(fds, 1, 1000);
if (ret > 0){
if (fds[0].revents & POLLHUP){ printf("Hangup\n"); close(fd); goto connect;}
if (fds[0].revents & POLLRDNORM){
res = read(fd,buf,255);
if(!res){close(fd); goto connect;}
buf[res-2]=0;
printf("Received %d bytes : %s",res,buf);
}
}
}
}
Basically the command is sent, then its polling until some data come or timeout occurs. This worked for over 24h and shown no issue communication wise however there is one problem : if the peripheral is disconnected then i get the "hangout" notification but it never reconnects, i was expecting it would since i close the file and retry connection to interface.
Also, is poll is the best approach here? i dont want to spend CPU time in pure lost by polling (unless polling has some mecanism to release the CPU time to other threads) but i still want the call to be blocking until the timeout occurs or response arrive (i.e. i dont want to send command and get the response later with a callback). The response come from peripheral under few millisecond.
n.b. i know some eyes are bleeding because of the goto statement, no worries, goto has been used here to test quickly the "reconnect" approach (which didn't worked) but in the end if the connection has to be restarted it will be in a separate function, goto will never be used in the final implementation.
EDIT:
After rewrite. It works but there are still problems, mainly, when i disconnect the peripheral, Linux will randomly keep the port name or change it. Sometimes it will keep the port name for multiple consecutive disconnect, sometime it will rename it and stick with the new name for a while. So i have to find a way to identify the peripheral from USB and get current port name whatever it is.
On disconnect it throws error 9 from tcgetattr (coming from set_interface_attribs or set_blocking i think) but once it is reconnected and if Linux does not change the port name then it reconnects right away and restart to send and receive as it should, however, when Linux rename the port then it fail.
int fd=-1,retry=0;
struct pollfd fds[1];
int connect(void){
if(fd==-1) fd = open(MODEMDEVICE, O_RDWR | O_NOCTTY | O_NONBLOCK);
else return 0; // already opened
printf("Connecting to fd #%d\n",fd);
if (fd==0){
perror(MODEMDEVICE);
printf("Failed to open %s\n",MODEMDEVICE);
retry++;
sleepms(2000);
return -1;
}
set_interface_attribs (fd, BAUDRATE, 0); // 8n1 no parity
set_blocking (fd, 0); // not blocking
fds[0].fd = fd; // streams
fds[0].events = POLLERR|POLLHUP|POLLRDNORM;
return 0;
}
int handlePoll(){
int ret=0,res=0;
char buf[255];
ret = poll(fds, 1, 1000); // 1000ms
if (ret > 0){
if (fds[0].revents & POLLERR){ close(fd); fd=-1; return -1; } // IO error
if (fds[0].revents & POLLHUP){ close(fd); fd=-1; return -2; } // interface closed
if (fds[0].revents & POLLRDNORM){
res = read(fd,buf,255);
if(!res){ close(fd); return -3; } // data receive error
buf[res-2]=0;
printf("Received %d bytes : %s\n",res,buf);
return 0;
}
return -4; // unknown error
}
return -5; // timeout
}
int sendCMD(char* cmd){
int res=1;
retry=0;
while(res && retry<5){ res=connect(); }
if(res) return -7;
if(retry>5) return -8;
int len = strlen(cmd);
int count = write(fd, cmd, len);
if(count<len){ return -6;}
return handlePoll();
}
int main(void){
while(1){
switch(sendCMD(DATA)){
case -1: printf("IO error\n"); break;
case -2: printf("Interface closed error\n"); break;
case -3: printf("data receive error\n"); break;
case -4: printf("Unknown error\n"); break;
case -5: printf("Timeout\n"); break;
case -6: printf("Command send error\n"); sleepms(200); break;
case -7: printf("Interface open error\n"); break;
case -8: printf("Cannot open interface after 5 try\n"); break;
default: break;
}
}
}
I think there should be a better way to deal with disconnect (Detecting if a character device has disconnected in Linux in with termios api (c++))