У меня есть умный счетчик энергии, который отправляет данные о потреблении энергии каждую секунду. Программа-демон, которую я написал (C++/C Arch Linux) для чтения данных, не завершает работу при отключении USB-кабеля и останавливается на неопределенное время в блокирующем вызове read().
Как прервать блокирующий вызов read() (т.е. сбой с EINTR вместо ожидания следующего символа)?
Я тщательно искал Google и смотрел здесь, в SO, и не смог найти ответ на эту проблему.
Подробности:
- Источник проекта Smartmeter на Github
- ИК-ключ с мостом FT232RL USB-UART
- Дейтаграммы имеют фиксированную длину 328 байт, отправляемых каждую секунду.
- Метод чтения обнаруживает начало \ и конец! маркеры дейтаграммы
- sigaction для перехвата сигналов CTRL+C SIGINT и SIGTERM
- termios настроен на блокировку read() с VMIN = 1 и VTIME = 0.
Пытался:
- Игра с VMIN и VTIME
- Удалено SA_RESTART
Возможное решение:
- Используйте неблокирующий метод чтения, возможно, с помощью select() и poll()
- Или VMIN › 0 (датаграмма длиннее 255 символов, и мне нужно будет прочитать дейтаграмму более мелкими фрагментами)
- Не знаете, как обрабатывать обнаружение начала/конца дейтаграммы и интервал в одну секунду между дейтаграммами для неблокирующего метода чтения.
EDIT: приведенный ниже код теперь буферизует вызов read() в промежуточный буфер размером 255 байт (VMIN = 255 и VTIME = 5), адаптированный из здесь. Это позволяет избежать небольших накладных расходов на вызов read() для каждого символа. На практике это не имеет значения по сравнению с чтением по одному символу за раз. Read() по-прежнему не завершается корректно при отключении кабеля. Демон должен быть убит с помощью kill -s SIGQUIT $PID
. SIGKILL не имеет никакого эффекта.
основной.cpp:
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:
bool Smartmeter::Receive(void)
{
memset(ReceiveBuffer, '\0', Smartmeter::ReceiveBufferSize);
if (!Serial->ReadBytes(ReceiveBuffer, Smartmeter::ReceiveBufferSize))
{
ErrorMessage = Serial->GetErrorMessage();
return false;
}
}
SmartMeterSerial.cpp:
#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;
}
Большое спасибо за вашу помощь.
kill -s SIGQUIT $PID
, что раздражает. - person apohl   schedule 20.05.2021