• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

wirenboard / wb-mqtt-serial / 680

05 Aug 2025 11:42AM UTC coverage: 73.001% (-0.002%) from 73.003%
680

push

github

web-flow
Use response_timeout_ms from port settings for RPC requests (#978)

6493 of 9259 branches covered (70.13%)

16 of 26 new or added lines in 6 files covered. (61.54%)

12408 of 16997 relevant lines covered (73.0%)

373.17 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

51.81
/src/serial_port.cpp
1
#include "serial_port.h"
2
#include "iec_common.h"
3
#include "log.h"
4
#include "serial_exc.h"
5

6
#include <cmath>
7
#include <fcntl.h>
8
#include <filesystem>
9
#include <fstream>
10
#include <iomanip>
11
#include <iostream>
12
#include <string.h>
13
#include <unistd.h>
14
#include <utility>
15

16
#include <linux/serial.h>
17
#include <sys/ioctl.h>
18

19
#define LOG(logger) ::logger.Log() << "[serial port] "
20

21
using namespace WBMQTT;
22

23
namespace
24
{
25
    int ConvertBaudRate(int rate)
2✔
26
    {
27
        switch (rate) {
2✔
28
            case 110:
×
29
                return B110;
×
30
            case 300:
×
31
                return B300;
×
32
            case 600:
×
33
                return B600;
×
34
            case 1200:
×
35
                return B1200;
×
36
            case 2400:
×
37
                return B2400;
×
38
            case 4800:
×
39
                return B4800;
×
40
            case 9600:
2✔
41
                return B9600;
2✔
42
            case 19200:
×
43
                return B19200;
×
44
            case 38400:
×
45
                return B38400;
×
46
            case 57600:
×
47
                return B57600;
×
48
            case 115200:
×
49
                return B115200;
×
50
            default:
×
51
                LOG(Warn) << "unsupported baud rate " << rate << " defaulting to 9600";
×
52
                return B9600;
×
53
        }
54
    }
55

56
    int ConvertDataBits(int data_bits)
2✔
57
    {
58
        switch (data_bits) {
2✔
59
            case 5:
×
60
                return CS5;
×
61
            case 6:
×
62
                return CS6;
×
63
            case 7:
×
64
                return CS7;
×
65
            case 8:
2✔
66
                return CS8;
2✔
67
            default:
×
68
                LOG(Warn) << "unsupported data bits count " << data_bits << " defaulting to 8";
×
69
                return CS8;
×
70
        }
71
    }
72

73
    std::chrono::milliseconds GetLinuxLag(int baudRate)
1✔
74
    {
75
        return std::chrono::milliseconds(baudRate < 9600 ? 34 : 24);
1✔
76
    }
77

78
    size_t GetRxTrigBytes(const std::string& path)
19✔
79
    {
80
        std::filesystem::path dev(path);
38✔
81
        while (std::filesystem::is_symlink(dev)) {
19✔
82
            dev = std::filesystem::read_symlink(dev);
×
83
        }
84
        auto rxTrigBytesPath = "/sys/class/tty" / dev.filename() / "rx_trig_bytes";
57✔
85
        std::ofstream f(rxTrigBytesPath);
38✔
86
        if (f.is_open()) {
19✔
87
            try {
88
                f << 1;
×
89
                if (f.good()) {
×
90
                    LOG(Debug) << rxTrigBytesPath << " = 1";
×
91
                    return 1;
×
92
                }
93
            } catch (const std::exception& e) {
×
94
                LOG(Warn) << rxTrigBytesPath << " write failed: " << e.what();
×
95
            }
96
        }
97
        return 1;
19✔
98
    }
99

100
    void MakeTermios(const TSerialPortConnectionSettings& settings, termios& dev)
2✔
101
    {
102
        memset(&dev, 0, sizeof(termios));
2✔
103
        auto baud_rate = ConvertBaudRate(settings.BaudRate);
2✔
104
        if (cfsetospeed(&dev, baud_rate) != 0 || cfsetispeed(&dev, baud_rate) != 0) {
2✔
105
            throw std::runtime_error("can't set baud rate " + std::to_string(settings.BaudRate) + " " +
×
106
                                     FormatErrno(errno));
×
107
        }
108

109
        if (settings.StopBits == 1) {
2✔
110
            dev.c_cflag &= ~CSTOPB;
2✔
111
        } else {
112
            dev.c_cflag |= CSTOPB;
×
113
        }
114

115
        switch (settings.Parity) {
2✔
116
            case 'N':
2✔
117
                dev.c_cflag &= ~PARENB;
2✔
118
                dev.c_iflag &= ~INPCK;
2✔
119
                break;
2✔
120
            case 'E':
×
121
                dev.c_cflag |= PARENB;
×
122
                dev.c_cflag &= ~PARODD;
×
123
                dev.c_iflag |= INPCK;
×
124
                break;
×
125
            case 'O':
×
126
                dev.c_cflag |= PARENB;
×
127
                dev.c_cflag |= PARODD;
×
128
                dev.c_iflag |= INPCK;
×
129
                break;
×
130
            default:
×
131
                std::stringstream ss;
×
132
                ss << "invalid parity value: ";
×
133
                if (isprint(settings.Parity)) {
×
134
                    ss << "'" << settings.Parity << "'";
×
135
                } else {
136
                    ss << "0x" << std::hex << std::uppercase << std::setfill('0') << std::setw(2)
×
137
                       << int(settings.Parity);
×
138
                }
139
                throw std::runtime_error(ss.str());
×
140
        }
141

142
        dev.c_cflag = (dev.c_cflag & ~CSIZE) | ConvertDataBits(settings.DataBits) | CREAD | CLOCAL;
2✔
143
        dev.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
2✔
144
        dev.c_iflag &= ~(IXON | IXOFF | IXANY);
2✔
145
        dev.c_oflag &= ~OPOST;
2✔
146
        dev.c_cc[VMIN] = 0;
2✔
147
        dev.c_cc[VTIME] = 0;
2✔
148
    }
2✔
149
}
150

151
TSerialPort::TSerialPort(const TSerialPortSettings& settings)
19✔
152
    : Settings(settings),
153
      InitialSettings(settings),
154
      RxTrigBytes(GetRxTrigBytes(Settings.Device))
19✔
155
{
156
    memset(&OldTermios, 0, sizeof(termios));
19✔
157
}
19✔
158

159
void TSerialPort::Open()
2✔
160
{
161
    try {
162
        if (IsOpen())
2✔
163
            throw std::runtime_error("port is already open");
×
164

165
        Fd = open(Settings.Device.c_str(), O_RDWR | O_NOCTTY | O_EXCL | O_NDELAY);
2✔
166
        if (Fd < 0)
2✔
167
            throw std::runtime_error("can't open serial port");
×
168

169
        if (tcgetattr(Fd, &OldTermios) != 0) {
2✔
170
            throw std::runtime_error("can't get termios attributes " + FormatErrno(errno));
×
171
        }
172

173
        ApplySerialPortSettings(Settings);
2✔
174

175
    } catch (const std::runtime_error& e) {
×
176
        if (Fd >= 0) {
×
177
            close(Fd);
×
178
            Fd = -1;
×
179
        }
180
        throw TSerialDeviceException(Settings.Device + ", " + e.what());
×
181
    }
182
    LastInteraction = std::chrono::steady_clock::now();
2✔
183
    SkipNoise(); // flush data from previous instance if any
2✔
184
}
2✔
185

186
void TSerialPort::Close()
2✔
187
{
188
    if (Base::IsOpen()) {
2✔
189
        tcsetattr(Fd, TCSANOW, &OldTermios);
2✔
190
    }
191
    Base::Close();
2✔
192
}
2✔
193

194
void TSerialPort::ApplySerialPortSettings(const TSerialPortConnectionSettings& settings)
2✔
195
{
196
    termios dev;
197
    MakeTermios(settings, dev);
2✔
198

199
    if (tcsetattr(Fd, TCSANOW, &dev) != 0) {
2✔
200
        throw std::runtime_error("can't set termios attributes" + FormatErrno(errno));
×
201
    }
202

203
    if (tcflush(Fd, TCIOFLUSH) != 0) {
2✔
204
        throw std::runtime_error("can't flush port" + FormatErrno(errno));
×
205
    }
206

207
    Settings.Set(settings);
2✔
208
    LOG(Debug) << "Setup " << Settings.Device << " port: " << settings.BaudRate << " " << settings.DataBits << " "
4✔
209
               << settings.Parity << " " << settings.StopBits;
2✔
210
    ;
211
}
2✔
212

213
void TSerialPort::ResetSerialPortSettings()
×
214
{
215
    ApplySerialPortSettings(InitialSettings);
×
216
}
217

218
std::chrono::microseconds TSerialPort::GetSendTimeBytes(double bytesNumber) const
3✔
219
{
220
    size_t bitsPerByte = 1 + Settings.DataBits + Settings.StopBits;
3✔
221
    if (Settings.Parity != 'N') {
3✔
222
        ++bitsPerByte;
×
223
    }
224
    return GetSendTimeBits(std::ceil(bitsPerByte * bytesNumber));
3✔
225
}
226

227
std::chrono::microseconds TSerialPort::GetSendTimeBits(size_t bitsNumber) const
3✔
228
{
229
    auto us = std::ceil(bitsNumber * 1000000.0 / double(Settings.BaudRate));
3✔
230
    return std::chrono::microseconds(static_cast<std::chrono::microseconds::rep>(us));
3✔
231
}
232

233
uint8_t TSerialPort::ReadByte(const std::chrono::microseconds& timeout)
1✔
234
{
235
    return Base::ReadByte(CalcResponseTimeout(timeout) + GetLinuxLag(Settings.BaudRate) + GetSendTimeBytes(1));
1✔
236
}
237

238
TReadFrameResult TSerialPort::ReadFrame(uint8_t* buf,
×
239
                                        size_t count,
240
                                        const std::chrono::microseconds& responseTimeout,
241
                                        const std::chrono::microseconds& frameTimeout,
242
                                        TFrameCompletePred frameComplete)
243
{
244
    return Base::ReadFrame(buf,
245
                           count,
NEW
246
                           CalcResponseTimeout(responseTimeout) + GetLinuxLag(Settings.BaudRate) +
×
NEW
247
                               GetSendTimeBytes(RxTrigBytes),
×
248
                           frameTimeout + std::chrono::milliseconds(15) + GetSendTimeBytes(RxTrigBytes),
×
249
                           frameComplete);
×
250
}
251

252
void TSerialPort::WriteBytes(const uint8_t* buf, int count)
2✔
253
{
254
    Base::WriteBytes(buf, count);
2✔
255
    SleepSinceLastInteraction(GetSendTimeBytes(count));
2✔
256
    LastInteraction = std::chrono::steady_clock::now();
2✔
257
}
2✔
258

259
std::string TSerialPort::GetDescription(bool verbose) const
27✔
260
{
261
    if (verbose) {
27✔
262
        return Settings.ToString();
10✔
263
    }
264
    return Settings.Device;
17✔
265
}
266

267
const TSerialPortSettings& TSerialPort::GetSettings() const
×
268
{
269
    return Settings;
×
270
}
271

272
TSerialPortSettingsGuard::TSerialPortSettingsGuard(PPort port, const TSerialPortConnectionSettings& settings)
3✔
273
    : Port(port)
3✔
274
{
275
    Port->ApplySerialPortSettings(settings);
3✔
276
}
3✔
277

278
TSerialPortSettingsGuard::~TSerialPortSettingsGuard()
3✔
279
{
280
    Port->ResetSerialPortSettings();
3✔
281
}
3✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc