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

wirenboard / wb-mqtt-serial / 690

26 Aug 2025 01:28PM UTC coverage: 73.031%. Remained the same
690

push

github

web-flow
Add protocol parameter to device/Probe RPC (#988)

  * Add protocol to Modbus TCP port information in ports/Load RPC
  * Add protocol parameter to device/Probe RPC
  * Set TCP_NODELAY for TCP ports
  * Handle TCP port closing by remote

6605 of 9406 branches covered (70.22%)

28 of 59 new or added lines in 11 files covered. (47.46%)

136 existing lines in 8 files now uncovered.

12554 of 17190 relevant lines covered (73.03%)

372.2 hits per line

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

15.94
/src/tcp_port.cpp
1
#include "tcp_port.h"
2
#include "serial_exc.h"
3

4
#include <cmath>
5
#include <iostream>
6
#include <sstream>
7
#include <stdio.h>
8
#include <stdlib.h>
9
#include <string.h>
10
#include <unistd.h>
11

12
#include <fcntl.h>
13
#include <netdb.h>
14
#include <netinet/in.h>
15
#include <netinet/tcp.h>
16
#include <sys/socket.h>
17
#include <sys/types.h>
18

19
#include "log.h"
20

21
#define LOG(logger) ::logger.Log() << "[tcp port] "
22

23
using namespace std;
24

25
namespace
26
{
27
    const int CONNECTION_TIMEOUT_S = 5;
28

29
    // Additional timeout for reading from tcp port. It is caused by intermediate hardware and internal Linux processing
30
    // Values are taken from old default timeouts
31
    const std::chrono::microseconds ResponseTCPLag = std::chrono::microseconds(500000);
32
    const std::chrono::microseconds FrameTCPLag = std::chrono::microseconds(150000);
33
}
34

35
TTcpPort::TTcpPort(const TTcpPortSettings& settings): Settings(settings)
6✔
36
{}
6✔
37

UNCOV
38
void TTcpPort::Open()
×
39
{
40
    if (IsOpen()) {
×
UNCOV
41
        throw TSerialDeviceException("port is already open");
×
42
    }
43

UNCOV
44
    struct hostent* server = gethostbyname(Settings.Address.c_str());
×
UNCOV
45
    if (!server) {
×
46
        throw TSerialDeviceException("no such host: " + Settings.Address);
×
47
    }
48

UNCOV
49
    Fd = socket(AF_INET, SOCK_STREAM, 0);
×
UNCOV
50
    if (Fd < 0) {
×
51
        throw TSerialDeviceErrnoException("cannot open tcp port: ", errno);
×
52
    }
53

54
    struct sockaddr_in serv_addr;
UNCOV
55
    memset((char*)&serv_addr, 0, sizeof(serv_addr));
×
UNCOV
56
    serv_addr.sin_family = AF_INET;
×
57
    memmove((char*)&serv_addr.sin_addr.s_addr, (char*)server->h_addr, server->h_length);
×
58
    serv_addr.sin_port = htons(Settings.Port);
×
59

60
    // set socket to non-blocking state
UNCOV
61
    auto arg = fcntl(Fd, F_GETFL, NULL);
×
UNCOV
62
    arg |= O_NONBLOCK;
×
63
    fcntl(Fd, F_SETFL, arg);
×
64

65
    // Send packets immediately without waiting for the buffer to fill
66
    // This is useful for protocols that require immediate response
67
    // and do not tolerate delays, such as Modbus RTU over TCP.
NEW
68
    int one = 1;
×
NEW
69
    if (setsockopt(Fd, SOL_TCP, TCP_NODELAY, &one, sizeof(one)) < 0) {
×
NEW
70
        LOG(Debug) << "Can't enable TCP_NODELAY for " << GetDescription() << ": " << FormatErrno(errno);
×
71
    }
72

73
    try {
UNCOV
74
        if (connect(Fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
×
UNCOV
75
            if (errno != EINPROGRESS) {
×
UNCOV
76
                throw std::runtime_error("connect error: " + FormatErrno(errno));
×
77
            }
78
            timeval tv = {CONNECTION_TIMEOUT_S, 0};
×
79
            fd_set myset;
80
            FD_ZERO(&myset);
×
UNCOV
81
            FD_SET(Fd, &myset);
×
UNCOV
82
            auto res = select(Fd + 1, NULL, &myset, NULL, &tv);
×
UNCOV
83
            if (res > 0) {
×
84
                socklen_t lon = sizeof(int);
×
85
                int valopt;
86
                getsockopt(Fd, SOL_SOCKET, SO_ERROR, (void*)(&valopt), &lon);
×
UNCOV
87
                if (valopt) {
×
88
                    throw std::runtime_error("connect error: " + FormatErrno(valopt));
×
89
                }
90
            } else if (res < 0 && errno != EINTR) {
×
91
                throw std::runtime_error("connect error: " + FormatErrno(errno));
×
92
            } else {
93
                throw std::runtime_error("connect error: timeout");
×
94
            }
95
        }
96
    } catch (const std::runtime_error& e) {
×
97
        close(Fd);
×
98
        Fd = -1;
×
UNCOV
99
        throw TSerialDeviceException(GetDescription() + " " + e.what());
×
100
    }
101

102
    // set socket back to blocking state
103
    arg = fcntl(Fd, F_GETFL, NULL);
×
UNCOV
104
    arg &= (~O_NONBLOCK);
×
UNCOV
105
    fcntl(Fd, F_SETFL, arg);
×
106

107
    LastInteraction = std::chrono::steady_clock::now();
×
108
}
109

UNCOV
110
void TTcpPort::OnReadyEmptyFd()
×
111
{
UNCOV
112
    Close();
×
113
    throw TSerialDeviceTransientErrorException("socket closed");
×
114
}
115

UNCOV
116
void TTcpPort::WriteBytes(const uint8_t* buf, int count)
×
117
{
UNCOV
118
    if (IsOpen()) {
×
UNCOV
119
        Base::WriteBytes(buf, count);
×
120
    } else {
UNCOV
121
        LOG(Debug) << "Attempt to write to not open port";
×
122
    }
123
}
124

UNCOV
125
uint8_t TTcpPort::ReadByte(const std::chrono::microseconds& timeout)
×
126
{
UNCOV
127
    return Base::ReadByte(CalcResponseTimeout(timeout) + ResponseTCPLag);
×
128
}
129

UNCOV
130
TReadFrameResult TTcpPort::ReadFrame(uint8_t* buf,
×
131
                                     size_t count,
132
                                     const std::chrono::microseconds& responseTimeout,
133
                                     const std::chrono::microseconds& frameTimeout,
134
                                     TFrameCompletePred frame_complete)
135
{
UNCOV
136
    if (IsOpen()) {
×
137
        return Base::ReadFrame(buf,
138
                               count,
UNCOV
139
                               CalcResponseTimeout(responseTimeout) + ResponseTCPLag,
×
140
                               frameTimeout + FrameTCPLag,
×
UNCOV
141
                               frame_complete);
×
142
    }
UNCOV
143
    LOG(Debug) << "Attempt to read from not open port";
×
UNCOV
144
    return TReadFrameResult();
×
145
}
146

147
std::string TTcpPort::GetDescription(bool verbose) const
6✔
148
{
149
    if (verbose) {
6✔
150
        return Settings.ToString();
2✔
151
    }
152
    return Settings.Address + ":" + std::to_string(Settings.Port);
8✔
153
}
154

155
std::chrono::microseconds TTcpPort::GetSendTimeBytes(double bytesNumber) const
3✔
156
{
157
    // TCP ports are mostly used to communicate with gateways.
158
    // Devices behind gateways usually use serial protocols.
159
    // Assume that the default speed is 9600 bps.
160
    // and calculate the time for sending bytes as if they were sent in serial mode.
161
    // This is a simplification, but it works for most cases.
162
    // 1 byte = 11 bits (1 start bit, 8 data bits, 2 stop bit)
163
    return GetSendTimeBits(std::ceil(bytesNumber * 11));
3✔
164
}
165

166
std::chrono::microseconds TTcpPort::GetSendTimeBits(size_t bitsNumber) const
6✔
167
{
168
    // TCP ports are mostly used to communicate with gateways.
169
    // Devices behind gateways usually use serial protocols.
170
    // Assume that the default speed is 9600 bps.
171
    // and calculate the time for sending bits as if they were sent in serial mode.
172
    // This is a simplification, but it works for most cases.
173
    auto us = std::ceil(bitsNumber * 1000000.0 / 9600.0);
6✔
174
    return std::chrono::microseconds(static_cast<std::chrono::microseconds::rep>(us));
6✔
175
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc