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

wirenboard / wb-mqtt-serial / 2

29 Dec 2025 12:28PM UTC coverage: 76.817% (+4.0%) from 72.836%
2

Pull #1045

github

54aa0c
pgasheev
up changelog
Pull Request #1045: Fix firmware version in WB-M1W2 template

6873 of 9161 branches covered (75.02%)

12966 of 16879 relevant lines covered (76.82%)

1651.61 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 "port/serial_port.h"
9
#include "serial_exc.h"
10

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

15
    TPort::TFrameCompletePred GetCRLFPacketPred()
20✔
16
    {
17
        return [](uint8_t* b, size_t s) { return s >= 2 && b[s - 1] == '\n' && b[s - 2] == '\r'; };
220✔
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)
36✔
22
    {
23
        return [=](uint8_t* b, size_t s) {
640✔
24
            return (s == 1 && b[s - 1] == IEC::ACK) ||                   // single-byte ACK
640✔
25
                   (s == 1 && b[s - 1] == IEC::NAK) ||                   // single-byte NAK
1,828✔
26
                   (s > 3 && b[0] == startByte && b[s - 2] == IEC::ETX); // <STX> ... <ETX>[CRC]
1,188✔
27
        };
36✔
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)
146✔
77
    {
78
        v.push_back(value);
146✔
79
        return v;
146✔
80
    }
81

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

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

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

102
    size_t ReadFrame(TPort& port,
58✔
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;
68✔
111
        if (Debug.IsEnabled()) {
48✔
112
            Debug.Log() << logPrefix << "ReadFrame: " << ToString(buf, nread);
×
113
        }
114
        return nread;
48✔
115
    }
116

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

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

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

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

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

157
    if (SlaveId.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890 ") !=
14✔
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
}
14✔
163

164
TIEC61107Protocol::TIEC61107Protocol(const std::string& name, const TRegisterTypes& reg_types)
1,152✔
165
    : IProtocol(name, reg_types)
1,152✔
166
{}
1,152✔
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,
12✔
183
                                           PProtocol protocol,
184
                                           const std::string& logPrefix,
185
                                           IEC::TCrcFn crcFn)
12✔
186
    : TIEC61107Device(device_config, protocol),
187
      CrcFn(crcFn),
188
      LogPrefix(logPrefix),
189
      ReadCommand(IEC::UnformattedReadCommand),
190
      DefaultBaudRate(9600),
191
      CurrentBaudRate(9600)
12✔
192
{
193
    SetDesiredBaudRate(9600);
12✔
194
}
12✔
195

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

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

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

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

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

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

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

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

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

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

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

308
    // Check that response starts from requested parameter name
309
    if (memcmp(presp, paramName.data(), paramName.size()) == 0) {
18✔
310
        std::string data(presp + paramName.size(), len - 3 - paramName.size());
32✔
311
        CmdResultCache.insert({paramRequest, data});
16✔
312
        return data;
16✔
313
    }
314
    if (presp[0] != '(') {
2✔
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;
2✔
319
    if (presp[strlen(presp) - 1] == ')') {
2✔
320
        presp[strlen(presp) - 1] = '\000';
2✔
321
    }
322
    throw TSerialDeviceTransientErrorException(presp);
2✔
323
}
324

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

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

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

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

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

366
void TIEC61107ModeCDevice::SendEndSession(TPort& port)
14✔
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));
14✔
370

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

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

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

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

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

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

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