首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何在阻塞读()调用中检测USB电缆断开?

如何在阻塞读()调用中检测USB电缆断开?
EN

Stack Overflow用户
提问于 2021-05-19 05:26:26
回答 1查看 493关注 0票数 0

我有一个智能电能表,它每秒钟发送一次能耗数据。我编写的读取数据的守护进程程序(C++/ call )在USB电缆断开并在阻塞read()调用中无限期停止时不会退出。

如何中断阻塞read()调用(即使用EINTR而不是等待下一个字符)?

我搜索了大量的谷歌,并在这里寻找了这样,并没有找到这个问题的答案。

详细信息:

  • 智能计项目源在Github上
  • 用FT232RL USB到UART桥的红外接口
  • 数据报的固定长度为每秒发送328字节。
  • 读取方法检测开始和结束!数据报的标记
  • 捕捉CTRL+C信号的单动作
  • termios被设置为使用VMIN =1和VTIME = 0进行阻塞read()。

试过:

  • 玩VMIN和VTIME
  • 移除SA_RESTART

可能的解决办法:

  • 使用非阻塞的读取方法,可能与select()和poll()一起使用。
  • 或VMIN >0(数据报大于255个字符,我需要用较小的块读取数据报)
  • 不确定如何处理数据报开始/结束检测和非阻塞读取方法的数据报之间的一秒间隔。

编辑:下面的代码现在将read()调用缓冲为一个中间缓冲区,即255个字节(VMIN = 255,VTIME = 5),这些缓冲区来自于这里。这避免了为每个字符调用read()的小开销。在实践中,与一次读一个字符相比,这并没有什么区别。Read()仍然没有在电缆断开时优雅地退出。需要用kill -s SIGQUIT $PID杀死守护进程。SIGKILL不起作用。

main.cpp:

代码语言:javascript
复制
volatile sig_atomic_t shutdown = false;

void sig_handler(int)
{
  shutdown = true;
}

int main(int argc, char* argv[])
{
  struct sigaction action;
  action.sa_handler = sig_handler;
  sigemptyset(&action.sa_mask);
  action.sa_flags = SA_RESTART;
  sigaction(SIGINT, &action, NULL);
  sigaction(SIGTERM, &action, NULL);

  while (shutdown == false)
  {
      if (!meter->Receive())
      {
        std::cout << meter->GetErrorMessage() << std::endl;
      return EXIT_FAILURE;
      }
  }

Smartmeter.cpp:

代码语言:javascript
复制
bool Smartmeter::Receive(void)
{
  memset(ReceiveBuffer, '\0', Smartmeter::ReceiveBufferSize);  
  if (!Serial->ReadBytes(ReceiveBuffer, Smartmeter::ReceiveBufferSize)) 
  {
    ErrorMessage = Serial->GetErrorMessage();
    return false;
  }
}

SmartMeterSerial.cpp:

代码语言:javascript
复制
#include <cstring>
#include <iostream>
#include <thread>
#include <unistd.h>
#include <termios.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include "SmartmeterSerial.h"

const unsigned char SmartmeterSerial::BufferSize = 255;

SmartmeterSerial::~SmartmeterSerial(void)
{
  if (SerialPort > 0) {
    close(SerialPort);
  }
}

bool SmartmeterSerial::Begin(const std::string &device)
{
  if (device.empty()) {
    ErrorMessage = "Serial device argument empty";
    return false;
  }
  if ((SerialPort = open(device.c_str(), (O_RDONLY | O_NOCTTY))) < 0)
  {
    ErrorMessage = std::string("Error opening serial device: ") 
      + strerror(errno) + " (" + std::to_string(errno) + ")";
    return false;
  }
  if(!isatty(SerialPort))
  {
    ErrorMessage = std::string("Error: Device ") + device + " is not a tty.";
    return false;
  }
  if (flock(SerialPort, LOCK_EX | LOCK_NB) < 0)
  {
    ErrorMessage = std::string("Error locking serial device: ")
      + strerror(errno) + " (" + std::to_string(errno) + ")";
    return false;
  }
  if (ioctl(SerialPort, TIOCEXCL) < 0)
  {
    ErrorMessage = std::string("Error setting exclusive access: ") 
      + strerror(errno) + " (" + std::to_string(errno) + ")";
    return false;
  }

  struct termios serial_port_settings;

  memset(&serial_port_settings, 0, sizeof(serial_port_settings));
  if (tcgetattr(SerialPort, &serial_port_settings))
  {
    ErrorMessage = std::string("Error getting serial port attributes: ")
      + strerror(errno) + " (" + std::to_string(errno) + ")";
    return false;
  }

  cfmakeraw(&serial_port_settings);

  // configure serial port
  // speed: 9600 baud, data bits: 7, stop bits: 1, parity: even
  cfsetispeed(&serial_port_settings, B9600);
  cfsetospeed(&serial_port_settings, B9600);
  serial_port_settings.c_cflag |= (CLOCAL | CREAD);
  serial_port_settings.c_cflag &= ~CSIZE;
  serial_port_settings.c_cflag |= (CS7 | PARENB);
  
  // vmin: read() returns when x byte(s) are available
  // vtime: wait for up to x * 0.1 second between characters
  serial_port_settings.c_cc[VMIN] = SmartmeterSerial::BufferSize;
  serial_port_settings.c_cc[VTIME] = 5;

  if (tcsetattr(SerialPort, TCSANOW, &serial_port_settings))
  {
    ErrorMessage = std::string("Error setting serial port attributes: ") 
      + strerror(errno) + " (" + std::to_string(errno) + ")";
    return false;
  }
  tcflush(SerialPort, TCIOFLUSH);

  return true;
}

char SmartmeterSerial::GetByte(void)
{
  static char buffer[SmartmeterSerial::BufferSize] = {0};
  static char *p = buffer;
  static int count = 0;   

  if ((p - buffer) >= count)
  {
    if ((count = read(SerialPort, buffer, SmartmeterSerial::BufferSize)) < 0)
    {
      // read() never fails with EINTR signal on cable disconnect   
      ErrorMessage = std::string("Read on serial device failed: ")
        + strerror(errno) + " (" + std::to_string(errno) + ")";
      return false;
    }
    p = buffer;
  }
  return *p++;
}

bool SmartmeterSerial::ReadBytes(char *buffer, const int &length)
{
  int bytes_received = 0;
  char *p = buffer;
  bool message_begin = false;
  
  tcflush(SerialPort, TCIOFLUSH);
  
  while (bytes_received < length)
  {
    if ((*p = GetByte()) == '/')
    {
      message_begin = true;
    }
    if (message_begin)
    {
      ++p;
      ++bytes_received;
    }
  }
  if (*(p-3) != '!')
  {
    ErrorMessage = "Serial datagram stream not in sync.";
    return false;
  }
  return true;
}

非常感谢你的帮助。

EN

回答 1

Stack Overflow用户

发布于 2021-05-19 18:54:47

虽然下面的代码不是关于如何中断阻塞read()调用的最初问题的解决方案,但至少对我来说这是一个可实现的解决方案。对于VMIN =0和VTIME =0,现在是一个非阻塞的read():

代码语言:javascript
复制
bool SmartmeterSerial::ReadBytes(char *buffer, const int &length)
{
  int bytes_received = 0;
  char *p = buffer;
  tcflush(SerialPort, TCIOFLUSH);
  bool message_begin = false;
  const int timeout = 10000;
  int count = 0;
  char byte;

  while (bytes_received < length) 
  {
    if ((byte = read(SerialPort, p, 1)) < 0)
    {
      ErrorMessage = std::string("Read on serial device failed: ")
        + strerror(errno) + " (" + std::to_string(errno) + ")";
      return false;
    }
    if (*p == '/')
    {
      message_begin = true;
    }
    if (message_begin && byte)
    {
      ++p;
      bytes_received += byte;
    }
    if (count > timeout)
    {
      ErrorMessage = "Read on serial device failed: Timeout";
      return false;
    }
    ++count;
    std::this_thread::sleep_for(std::chrono::microseconds(100));
  }

  if (*(p-3) != '!')
  {
    ErrorMessage = "Serial datagram stream not in sync.";
    return false;
  }
  return true;
}

但是,我仍然想知道是否真的有可能中断阻塞的“`read()”,因为这个解决方法是不断地轮询串行端口。

我相信一次读取一个字符不是一个问题,因为从UART接收到的字节是由操作系统缓冲的--但是不断地使用read()轮询缓冲区!也许我会在read()之前尝试一个read(),尽管我不知道这是否真的会产生不同的效果。

有什么建议吗?

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/67597395

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档