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

wirenboard / wb-mqtt-serial / 666

08 Jul 2025 11:10AM UTC coverage: 73.854% (+0.1%) from 73.706%
666

push

github

web-flow
Port handling refactoring (#960)

Pass port by reference when needed instead of storing it in every device

6444 of 9057 branches covered (71.15%)

526 of 700 new or added lines in 56 files covered. (75.14%)

6 existing lines in 5 files now uncovered.

12341 of 16710 relevant lines covered (73.85%)

305.53 hits per line

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

71.54
/src/iec_common.cpp
1
#include "iec_common.h"
2

3
#include <iomanip>
4
#include <sstream>
5
#include <string.h>
6

7
#include "log.h"
8
#include "serial_exc.h"
9
#include "serial_port.h"
10

11
namespace IEC
12
{
13
    const size_t RESPONSE_BUF_LEN = 1000;
14

15
    TPort::TFrameCompletePred GetCRLFPacketPred()
10✔
16
    {
17
        return [](uint8_t* b, size_t s) { return s >= 2 && b[s - 1] == '\n' && b[s - 2] == '\r'; };
110✔
18
    }
19

20
    // replies are either single-byte ACK, NACK or ends with ETX followed by CRC byte
21
    TPort::TFrameCompletePred GetProgModePacketPred(uint8_t startByte)
18✔
22
    {
23
        return [=](uint8_t* b, size_t s) {
320✔
24
            return (s == 1 && b[s - 1] == IEC::ACK) ||                   // single-byte ACK
320✔
25
                   (s == 1 && b[s - 1] == IEC::NAK) ||                   // single-byte NAK
914✔
26
                   (s > 3 && b[0] == startByte && b[s - 2] == IEC::ETX); // <STX> ... <ETX>[CRC]
594✔
27
        };
18✔
28
    }
29

30
    void DumpASCIIChar(std::stringstream& ss, char c)
×
31
    {
32
        switch (c) {
×
33
            case SOH:
×
34
                ss << "<SOH>";
×
35
                break;
×
36
            case STX:
×
37
                ss << "<STX>";
×
38
                break;
×
39
            case ETX:
×
40
                ss << "<ETX>";
×
41
                break;
×
42
            case EOT:
×
43
                ss << "<EOT>";
×
44
                break;
×
45
            case ACK:
×
46
                ss << "<ACK>";
×
47
                break;
×
48
            case NAK:
×
49
                ss << "<NAK>";
×
50
                break;
×
51
            case '\r':
×
52
                ss << "<CR>";
×
53
                break;
×
54
            case '\n':
×
55
                ss << "<LF>";
×
56
                break;
×
57
            default: {
×
58
                if (isprint(c)) {
×
59
                    ss << c;
×
60
                } else {
61
                    ss << "0x" << std::hex << std::uppercase << std::setfill('0') << std::setw(2) << (0xFF & c);
×
62
                }
63
            }
64
        }
65
    }
66

67
    std::string ToString(const uint8_t* buf, size_t nread)
×
68
    {
69
        std::stringstream ss;
×
70
        for (size_t i = 0; i < nread; ++i) {
×
71
            DumpASCIIChar(ss, buf[i]);
×
72
        }
73
        return ss.str();
×
74
    }
75

76
    std::vector<uint8_t>& operator<<(std::vector<uint8_t>& v, uint8_t value)
73✔
77
    {
78
        v.push_back(value);
73✔
79
        return v;
73✔
80
    }
81

82
    std::vector<uint8_t>& operator<<(std::vector<uint8_t>& v, const std::string& value)
33✔
83
    {
84
        std::copy(value.begin(), value.end(), std::back_inserter<std::vector<uint8_t>>(v));
33✔
85
        return v;
33✔
86
    }
87

88
    std::vector<uint8_t> MakeRequest(const std::string& command, const std::string& commandData, TCrcFn crcFn)
13✔
89
    {
90
        std::vector<uint8_t> res;
13✔
91
        res << SOH << command << STX << commandData << ETX << crcFn(&res[1], res.size() - 1);
13✔
92
        return res;
13✔
93
    }
94

95
    std::vector<uint8_t> MakeRequest(const std::string& command, TCrcFn crcFn)
7✔
96
    {
97
        std::vector<uint8_t> res;
7✔
98
        res << SOH << command << ETX << crcFn(&res[1], res.size() - 1);
7✔
99
        return res;
7✔
100
    }
101

102
    size_t ReadFrame(TPort& port,
29✔
103
                     uint8_t* buf,
104
                     size_t count,
105
                     const std::chrono::microseconds& responseTimeout,
106
                     const std::chrono::microseconds& frameTimeout,
107
                     TPort::TFrameCompletePred frame_complete,
108
                     const std::string& logPrefix)
109
    {
110
        size_t nread = port.ReadFrame(buf, count, responseTimeout, frameTimeout, frame_complete).Count;
34✔
111
        if (Debug.IsEnabled()) {
24✔
112
            Debug.Log() << logPrefix << "ReadFrame: " << ToString(buf, nread);
×
113
        }
114
        return nread;
24✔
115
    }
116

117
    void WriteBytes(TPort& port, const uint8_t* buf, size_t count, const std::string& logPrefix)
36✔
118
    {
119
        if (Debug.IsEnabled()) {
36✔
120
            Debug.Log() << logPrefix << "Write: " << ToString(buf, count);
×
121
        }
122
        port.WriteBytes(buf, count);
36✔
123
    }
36✔
124

125
    void WriteBytes(TPort& port, const std::string& str, const std::string& logPrefix)
10✔
126
    {
127
        WriteBytes(port, (const uint8_t*)str.data(), str.size(), logPrefix);
10✔
128
    }
10✔
129

130
    uint8_t Calc7BitCrc(const uint8_t* data, size_t size)
12✔
131
    {
132
        uint8_t crc = 0;
12✔
133
        for (size_t i = 0; i < size; ++i) {
274✔
134
            crc = (crc + data[i]) & 0x7F;
262✔
135
        }
136
        return crc;
12✔
137
    }
138

139
    uint8_t CalcXorCRC(const uint8_t* data, size_t size)
24✔
140
    {
141
        uint8_t crc = 0;
24✔
142
        for (size_t i = 0; i < size; ++i) {
327✔
143
            crc ^= data[i];
303✔
144
        }
145
        return crc & 0x7F;
24✔
146
    }
147
}
148

149
TIEC61107Device::TIEC61107Device(PDeviceConfig device_config, PProtocol protocol)
7✔
150
    : TSerialDevice(device_config, protocol),
151
      SlaveId(device_config->SlaveId)
7✔
152
{
153
    if (SlaveId.size() > 32) {
7✔
154
        throw TSerialDeviceException("SlaveId shall be 32 characters maximum");
×
155
    }
156

157
    if (SlaveId.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890 ") !=
7✔
158
        std::string::npos)
159
    {
160
        throw TSerialDeviceException("The characters in SlaveId can only be a...z, A...Z, 0...9 and space");
×
161
    }
162
}
7✔
163

164
TIEC61107Protocol::TIEC61107Protocol(const std::string& name, const TRegisterTypes& reg_types)
572✔
165
    : IProtocol(name, reg_types)
572✔
166
{}
572✔
167

168
bool TIEC61107Protocol::IsSameSlaveId(const std::string& id1, const std::string& id2) const
×
169
{
170
    // Can be only one device with broadcast address
171
    if (id1.empty() || id2.empty()) {
×
172
        return true;
×
173
    }
174
    return id1 == id2;
×
175
}
176

177
bool TIEC61107Protocol::SupportsBroadcast() const
×
178
{
179
    return true;
×
180
}
181

182
TIEC61107ModeCDevice::TIEC61107ModeCDevice(PDeviceConfig device_config,
6✔
183
                                           PProtocol protocol,
184
                                           const std::string& logPrefix,
185
                                           IEC::TCrcFn crcFn)
6✔
186
    : TIEC61107Device(device_config, protocol),
187
      CrcFn(crcFn),
188
      LogPrefix(logPrefix),
189
      ReadCommand(IEC::UnformattedReadCommand),
190
      DefaultBaudRate(9600),
191
      CurrentBaudRate(9600)
6✔
192
{
193
    SetDesiredBaudRate(9600);
6✔
194
}
6✔
195

196
void TIEC61107ModeCDevice::PrepareImpl(TPort& port)
6✔
197
{
198
    TIEC61107Device::PrepareImpl(port);
6✔
199
    bool sessionIsOpen = false;
6✔
200
    try {
201
        StartSession(port);
6✔
202
        sessionIsOpen = true;
5✔
203
        SwitchToProgMode(port);
5✔
204
        SendPassword(port);
4✔
205
        SetTransferResult(true);
3✔
206
    } catch (const TSerialDeviceTransientErrorException& e) {
6✔
207
        Debug.Log() << LogPrefix << "Session start error: " << e.what() << " [slave_id is " << ToString() + "]";
3✔
208
        if (sessionIsOpen) {
3✔
209
            SendEndSession(port);
2✔
210
        }
211
        throw;
3✔
212
    }
213
}
3✔
214

215
void TIEC61107ModeCDevice::StartSession(TPort& port)
6✔
216
{
217
    if (DefaultBaudRate != DesiredBaudRate) {
6✔
218
        CurrentBaudRate = DefaultBaudRate;
×
219
    }
220
    TSerialPortConnectionSettings bf(CurrentBaudRate, 'E', 7, 1);
6✔
221
    port.ApplySerialPortSettings(bf);
6✔
222
    if (Probe(port)) {
6✔
223
        return;
5✔
224
    }
225
    // Try desired baudrate, the device can remember it
226
    if (DefaultBaudRate != DesiredBaudRate) {
1✔
227
        bf.BaudRate = DesiredBaudRate;
×
NEW
228
        port.ApplySerialPortSettings(bf);
×
NEW
229
        if (Probe(port)) {
×
230
            CurrentBaudRate = bf.BaudRate;
×
231
            return;
×
232
        }
233
    }
234
    throw TSerialDeviceTransientErrorException("Sign on failed");
1✔
235
}
236

237
bool TIEC61107ModeCDevice::Probe(TPort& port)
6✔
238
{
239
    uint8_t buf[IEC::RESPONSE_BUF_LEN] = {};
6✔
240
    size_t retryCount = 5;
6✔
241
    while (true) {
242
        try {
243
            port.SkipNoise();
10✔
244
            // Send session start request
245
            WriteBytes(port, "/?" + SlaveId + "!\r\n");
10✔
246
            // Pass identification response
247
            IEC::ReadFrame(port,
10✔
248
                           buf,
249
                           sizeof(buf),
250
                           GetResponseTimeout(port),
15✔
251
                           GetFrameTimeout(port),
15✔
252
                           IEC::GetCRLFPacketPred(),
25✔
253
                           LogPrefix);
40✔
254
            return true;
5✔
255
        } catch (const TSerialDeviceTransientErrorException& e) {
10✔
256
            --retryCount;
5✔
257
            SendEndSession(port);
5✔
258
            if (retryCount == 0) {
5✔
259
                return false;
1✔
260
            }
261
        }
262
    }
4✔
263
}
264

NEW
265
void TIEC61107ModeCDevice::EndSession(TPort& port)
×
266
{
NEW
267
    SendEndSession(port);
×
NEW
268
    port.ResetSerialPortSettings(); // Return old port settings
×
NEW
269
    TIEC61107Device::EndSession(port);
×
270
}
271

272
void TIEC61107ModeCDevice::InvalidateReadCache()
9✔
273
{
274
    CmdResultCache.clear();
9✔
275
    TSerialDevice::InvalidateReadCache();
9✔
276
}
9✔
277

278
TRegisterValue TIEC61107ModeCDevice::ReadRegisterImpl(TPort& port, const TRegisterConfig& reg)
13✔
279
{
280
    port.CheckPortOpen();
13✔
281
    port.SkipNoise();
13✔
282
    return GetRegisterValue(reg, GetCachedResponse(port, GetParameterRequest(reg)));
25✔
283
}
284

285
std::string TIEC61107ModeCDevice::GetCachedResponse(TPort& port, const std::string& paramRequest)
13✔
286
{
287
    auto it = CmdResultCache.find(paramRequest);
13✔
288
    if (it != CmdResultCache.end()) {
13✔
289
        return it->second;
4✔
290
    }
291

292
    WriteBytes(port, IEC::MakeRequest(ReadCommand, paramRequest, CrcFn));
9✔
293

294
    uint8_t resp[IEC::RESPONSE_BUF_LEN] = {};
9✔
295
    auto len = ReadFrameProgMode(port, resp, sizeof(resp), IEC::STX);
9✔
296
    // Proper response (inc. error) must start with STX, and end with ETX
297
    if ((resp[0] != IEC::STX) || (resp[len - 2] != IEC::ETX)) {
9✔
298
        throw TSerialDeviceTransientErrorException("malformed response");
×
299
    }
300

301
    // strip STX and ETX
302
    resp[len - 2] = '\000';
9✔
303
    char* presp = (char*)resp + 1;
9✔
304

305
    // parameter name is the a part of a request before '('
306
    std::string paramName(paramRequest.substr(0, paramRequest.find('(')));
18✔
307

308
    // Check that response starts from requested parameter name
309
    if (memcmp(presp, paramName.data(), paramName.size()) == 0) {
9✔
310
        std::string data(presp + paramName.size(), len - 3 - paramName.size());
16✔
311
        CmdResultCache.insert({paramRequest, data});
8✔
312
        return data;
8✔
313
    }
314
    if (presp[0] != '(') {
1✔
315
        throw TSerialDeviceTransientErrorException("response parameter address doesn't match request");
×
316
    }
317
    // It is probably a error response. It lacks parameter name part
318
    presp += 1;
1✔
319
    if (presp[strlen(presp) - 1] == ')') {
1✔
320
        presp[strlen(presp) - 1] = '\000';
1✔
321
    }
322
    throw TSerialDeviceTransientErrorException(presp);
1✔
323
}
324

325
void TIEC61107ModeCDevice::SwitchToProgMode(TPort& port)
5✔
326
{
327
    uint8_t buf[IEC::RESPONSE_BUF_LEN] = {};
5✔
328

329
    WriteBytes(port, ToProgModeCommand);
5✔
330
    if (CurrentBaudRate != DesiredBaudRate) {
5✔
331
        // Time before response must be more than 20ms according to standard
332
        // Wait some time to transmit request
NEW
333
        port.SleepSinceLastInteraction(std::chrono::milliseconds(10));
×
334
        TSerialPortConnectionSettings bf(DesiredBaudRate, 'E', 7, 1);
×
NEW
335
        port.ApplySerialPortSettings(bf);
×
336
        CurrentBaudRate = DesiredBaudRate;
×
337
    }
338
    ReadFrameProgMode(port, buf, sizeof(buf), IEC::SOH);
5✔
339

340
    // <SOH>P0<STX>(IDENTIFIER)<ETX>CRC
341
    if (buf[1] != 'P' || buf[2] != '0' || buf[3] != IEC::STX) {
4✔
342
        throw TSerialDeviceTransientErrorException("cannot switch to prog mode: invalid response");
×
343
    }
344
}
4✔
345

346
void TIEC61107ModeCDevice::SendPassword(TPort& port)
4✔
347
{
348
    if (DeviceConfig()->Password.empty()) {
4✔
349
        return;
×
350
    }
351
    uint8_t buf[IEC::RESPONSE_BUF_LEN] = {};
4✔
352
    std::stringstream ss;
8✔
353
    ss << "(";
4✔
354
    for (auto p: DeviceConfig()->Password) {
19✔
355
        ss << std::hex << std::uppercase << std::setfill('0') << std::setw(2) << int(p);
15✔
356
    }
357
    ss << ")";
4✔
358
    WriteBytes(port, IEC::MakeRequest("P1", ss.str(), CrcFn));
4✔
359
    auto nread = ReadFrameProgMode(port, buf, sizeof(buf), IEC::STX);
4✔
360

361
    if ((nread != 1) || (buf[0] != IEC::ACK)) {
3✔
362
        throw TSerialDeviceTransientErrorException("cannot authenticate with password");
×
363
    }
364
}
365

366
void TIEC61107ModeCDevice::SendEndSession(TPort& port)
7✔
367
{
368
    // We need to terminate the session so meter won't respond to the data meant for other devices
369
    WriteBytes(port, IEC::MakeRequest("B0", CrcFn));
7✔
370

371
    // A device needs some time to process the command
372
    std::this_thread::sleep_for(GetFrameTimeout(port));
7✔
373
}
7✔
374

375
size_t TIEC61107ModeCDevice::ReadFrameProgMode(TPort& port, uint8_t* buf, size_t size, uint8_t startByte)
18✔
376
{
377
    auto len = IEC::ReadFrame(port,
36✔
378
                              buf,
379
                              size,
380
                              GetResponseTimeout(port),
18✔
381
                              GetFrameTimeout(port),
18✔
382
                              IEC::GetProgModePacketPred(startByte),
36✔
383
                              LogPrefix);
54✔
384

385
    if ((len == 1) && (buf[0] == IEC::ACK || buf[0] == IEC::NAK)) {
18✔
386
        return len;
3✔
387
    }
388
    if (len < 2) {
15✔
389
        throw TSerialDeviceTransientErrorException("empty response");
1✔
390
    }
391
    uint8_t checksum = CrcFn(buf + 1, len - 2);
14✔
392
    if (buf[len - 1] != checksum) {
14✔
393
        throw TSerialDeviceTransientErrorException("invalid response checksum (" + std::to_string(buf[len - 1]) +
3✔
394
                                                   " != " + std::to_string(checksum) + ")");
4✔
395
    }
396
    // replace crc with null byte to make it C string
397
    buf[len - 1] = '\000';
13✔
398
    return len;
13✔
399
}
400

401
void TIEC61107ModeCDevice::WriteBytes(TPort& port, const std::vector<uint8_t>& data)
25✔
402
{
403
    port.SleepSinceLastInteraction(GetFrameTimeout(port));
25✔
404
    IEC::WriteBytes(port, data.data(), data.size(), LogPrefix);
25✔
405
}
25✔
406

407
void TIEC61107ModeCDevice::WriteBytes(TPort& port, const std::string& data)
10✔
408
{
409
    port.SleepSinceLastInteraction(GetFrameTimeout(port));
10✔
410
    IEC::WriteBytes(port, data, LogPrefix);
10✔
411
}
10✔
412

413
void TIEC61107ModeCDevice::SetReadCommand(const std::string& command)
×
414
{
415
    ReadCommand = command;
×
416
}
417

418
void TIEC61107ModeCDevice::SetDesiredBaudRate(int baudRate)
6✔
419
{
420
    const std::unordered_map<int, uint8_t> supportedBaudRates = {{300, '0'},
421
                                                                 {2400, '3'},
422
                                                                 {4800, '4'},
423
                                                                 {9600, '5'},
424
                                                                 {19200, '6'}};
6✔
425
    auto baudRateCodeIt = supportedBaudRates.find(baudRate);
6✔
426
    if (baudRateCodeIt == supportedBaudRates.end()) {
6✔
427
        throw TSerialDeviceException("Unsupported IEC61107 baud rate: " + std::to_string(baudRate));
×
428
    }
429
    DesiredBaudRate = baudRate;
6✔
430
    ToProgModeCommand = {IEC::ACK, '0', baudRateCodeIt->second, '1', '\r', '\n'};
6✔
431
}
6✔
432

433
void TIEC61107ModeCDevice::SetDefaultBaudRate(int baudRate)
×
434
{
435
    DefaultBaudRate = baudRate;
×
436
    CurrentBaudRate = baudRate;
×
437
}
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