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

wirenboard / wb-mqtt-serial / 1

20 Oct 2026 04:57AM UTC coverage: 86.817% (+14.0%) from 72.836%
1

Pull #1281

github

fab311
KraPete
Fix device model fallback in device/LoadConfig RPC

The short model register was never read because the extended register
error was converted to TRPCException. The RPC error now says that
the device model could not be read.
Pull Request #1281: Fix device model fallback in device/LoadConfig RPC

31180 of 80922 branches covered (38.53%)

74 of 76 new or added lines in 2 files covered. (97.37%)

1379 existing lines in 84 files now uncovered.

22464 of 25875 relevant lines covered (86.82%)

5205.91 hits per line

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

87.24
/src/serial_device.cpp
1
#include "serial_device.h"
2

3
#include "log.h"
4
#include <iostream>
5
#include <string.h>
6
#include <unistd.h>
7

8
#define LOG(logger) logger.Log() << "[serial device] "
9

10
IProtocol::IProtocol(const std::string& name, const TRegisterTypes& reg_types)
17,032 ✔
11
    : Name(name),
17,032 ✔
12
      RegTypes(std::make_shared<TRegisterTypeMap>(reg_types))
17,032 !
13
{}
17,032 ✔
14

15
const std::string& IProtocol::GetName() const
21,021 ✔
16
{
17
    return Name;
21,021 ✔
18
}
19

20
PRegisterTypeMap IProtocol::GetRegTypes() const
1,636 ✔
21
{
22
    return RegTypes;
1,636 ✔
23
}
24

25
bool IProtocol::IsModbus() const
×
26
{
27
    return false;
×
28
}
29

30
bool IProtocol::SupportsBroadcast() const
642 ✔
31
{
32
    return false;
642 ✔
33
}
34

35
TDeviceSetupItem::TDeviceSetupItem(PDeviceSetupItemConfig config, PSerialDevice device)
720 ✔
36
    : Name(config->GetName()),
720 ✔
37
      RawValue(config->GetRawValue()),
720 !
38
      HumanReadableValue(config->GetValue()),
720 !
39
      RegisterConfig(config->GetRegisterConfig()),
720 !
40
      Device(device),
720 ✔
41
      Order(device ? device->GetSetupItems().size() : 0)
720 ✔
42
{}
720 ✔
43

44
std::string TDeviceSetupItem::ToString()
362 ✔
45
{
46
    std::stringstream stream;
362 !
47
    auto reg = "<" + (Device ? Device->ToString() : "unknown device") + ":" + RegisterConfig->ToString() + ">";
362 !
48
    stream << "Init setup register \"" << Name << "\": " << reg << "<-- " << HumanReadableValue;
362 !
49
    if (RawValue.GetType() == TRegisterValue::ValueType::String) {
362 !
50
        stream << " (\"" << RawValue << "\")";
×
51
    } else {
52
        stream << " (0x" << std::hex << RawValue << ")";
362 !
53
    }
54
    return stream.str();
724 !
55
}
362 ✔
56

57
bool TDeviceSetupItemComparePredicate::operator()(const PDeviceSetupItem& a, const PDeviceSetupItem& b) const
1,078 ✔
58
{
59
    if (a->Device && a->Device->DeviceConfig()->PreserveSetupOrder) {
1,078 ✔
60
        return a->Order < b->Order;
14 ✔
61
    }
62
    if (a->RegisterConfig->WriteType != b->RegisterConfig->WriteType) {
1,064 ✔
63
        return a->RegisterConfig->WriteType < b->RegisterConfig->WriteType;
18 ✔
64
    }
65
    auto compare = a->RegisterConfig->GetWriteAddress().Compare(b->RegisterConfig->GetWriteAddress());
1,046 ✔
66
    if (compare == 0) {
1,046 ✔
67
        return a->RegisterConfig->GetDataOffset() < b->RegisterConfig->GetDataOffset();
12 ✔
68
    }
69
    return compare < 0;
1,034 ✔
70
}
71

72
TUint32SlaveIdProtocol::TUint32SlaveIdProtocol(const std::string& name,
11,204 ✔
73
                                               const TRegisterTypes& reg_types,
74
                                               bool allowBroadcast)
11,204 ✔
75
    : IProtocol(name, reg_types),
76
      AllowBroadcast(allowBroadcast)
11,204 ✔
77
{}
11,204 ✔
78

79
bool TUint32SlaveIdProtocol::IsSameSlaveId(const std::string& id1, const std::string& id2) const
382 ✔
80
{
81
    return (TUInt32SlaveId(id1, AllowBroadcast) == TUInt32SlaveId(id2, AllowBroadcast));
382 !
82
}
83

84
bool TUint32SlaveIdProtocol::SupportsBroadcast() const
58 ✔
85
{
86
    return AllowBroadcast;
58 ✔
87
}
88

89
TSerialDevice::TSerialDevice(PDeviceConfig config, PProtocol protocol)
1,538 ✔
90
    : _DeviceConfig(config),
1,538 ✔
91
      _Protocol(protocol),
1,538 ✔
92
      LastSuccessfulCycle(),
1,538 ✔
93
      ConnectionState(TDeviceConnectionState::UNKNOWN),
1,538 ✔
94
      RemainingFailCycles(0),
1,538 ✔
95
      SupportsHoles(true),
1,538 ✔
96
      SporadicOnly(true),
1,538 ✔
97
      WbDevice(false),
1,538 ✔
98
      TimeSyncUnsupported(false)
3,076 ✔
99
{}
1,538 ✔
100

101
std::string TSerialDevice::ToString() const
3,050 ✔
102
{
103
    return Protocol()->GetName() + ":" + DeviceConfig()->SlaveId;
3,050 !
104
}
105

106
PRegisterRange TSerialDevice::CreateRegisterRange() const
220 ✔
107
{
108
    return PRegisterRange(new TSameAddressRegisterRange());
440 !
109
}
110

111
void TSerialDevice::Prepare(TPort& port, TDevicePrepareMode prepareMode)
928 ✔
112
{
113
    bool deviceWasDisconnected = (ConnectionState != TDeviceConnectionState::CONNECTED);
928 ✔
114
    if (deviceWasDisconnected) {
928 ✔
115
        LastTimeSync = std::chrono::system_clock::time_point();
603 ✔
116
        TimeSyncUnsupported = false;
603 ✔
117
    }
118
    try {
119
        PrepareImpl(port);
928 ✔
120
        if (prepareMode == TDevicePrepareMode::WITH_SETUP_IF_WAS_DISCONNECTED && deviceWasDisconnected) {
922 ✔
121
            WriteSetupRegisters(port, GetSetupItems());
485 ✔
122
        }
123
    } catch (const TSerialDeviceException& ex) {
53 !
124
        SetTransferResult(false);
53 !
125
        throw;
53 ✔
126
    }
53 ✔
127
}
875 ✔
128

129
void TSerialDevice::PrepareImpl(TPort& port)
926 ✔
130
{
131
    port.SleepSinceLastInteraction(GetFrameTimeout(port));
926 !
132
}
926 ✔
133

134
void TSerialDevice::EndSession(TPort& port)
436 ✔
135
{}
436 ✔
136

137
void TSerialDevice::InvalidateReadCache()
598 ✔
138
{}
598 ✔
139

140
void TSerialDevice::WriteRegister(TPort& port, PRegister reg, const TRegisterValue& value)
290 ✔
141
{
142
    try {
143
        WriteRegisterImpl(port, *reg->GetConfig(), value);
322 !
144
        SetTransferResult(true);
258 !
145
    } catch (const TSerialDevicePermanentRegisterException& e) {
32 !
146
        SetTransferResult(true);
6 !
147
        throw;
6 ✔
148
    } catch (const TSerialDeviceException& ex) {
32 ✔
149
        SetTransferResult(false);
26 !
150
        throw;
26 ✔
151
    }
26 ✔
152
}
258 ✔
153

154
void TSerialDevice::WriteRegister(TPort& port, PRegister reg, uint64_t value)
36 ✔
155
{
156
    WriteRegister(port, reg, TRegisterValue{value});
42 !
157
}
30 ✔
158

UNCOV
159
TRegisterValue TSerialDevice::ReadRegisterImpl(TPort& port, const TRegisterConfig& reg)
×
160
{
UNCOV
161
    throw TSerialDeviceException("single register reading is not supported");
×
162
}
163

UNCOV
164
void TSerialDevice::WriteRegisterImpl(TPort& port, const TRegisterConfig& reg, const TRegisterValue& value)
×
165
{
UNCOV
166
    throw TSerialDeviceException(ToString() + ": register writing is not supported");
×
167
}
168

UNCOV
169
void TSerialDevice::WriteTimeImpl(TPort& port, time_t deviceTime)
×
UNCOV
170
{}
×
171

172
void TSerialDevice::SyncTime(TPort& port, std::chrono::system_clock::time_point now)
9,613 ✔
173
{
174
    if (DeviceConfig()->TimeSyncInterval <= TimeSyncDisabled || TimeSyncUnsupported) {
9,613 !
175
        return;
9,597 ✔
176
    }
177
    if (now - LastTimeSync < DeviceConfig()->TimeSyncInterval) {
16 !
178
        return;
4 ✔
179
    }
180
    try {
181
        const auto nowTimeT = std::chrono::system_clock::to_time_t(now);
12 ✔
182
        std::tm localTm{};
12 ✔
183
        localtime_r(&nowTimeT, &localTm);
12 ✔
184
        WriteTimeImpl(port, timegm(&localTm));
12 ✔
185
        LastTimeSync = now;
8 ✔
186
        LOG(Debug) << "Time sync [slave_id is " << DeviceConfig()->SlaveId + "]";
8 !
187
    } catch (const TSerialDevicePermanentRegisterException& e) {
4 !
188
        TimeSyncUnsupported = true;
2 ✔
189
        LOG(Debug) << "Device doesn't support time sync, no more attempts until reconnect [slave_id is "
2 !
190
                   << DeviceConfig()->SlaveId + "]" << e.what();
2 !
191
    } catch (const std::exception& e) {
4 ✔
192
        LOG(Debug) << "Time sync failed [slave_id is " << DeviceConfig()->SlaveId + "]" << e.what();
2 !
193
    }
2 ✔
194
}
195

196
void TSerialDevice::ReadRegisterRange(TPort& port, PRegisterRange range, bool breakOnError)
566 ✔
197
{
198
    for (auto& reg: range->RegisterList()) {
1,594 !
199
        try {
200
            if (reg->GetAvailable() != TRegisterAvailability::UNAVAILABLE) {
1,028 !
201
                port.SleepSinceLastInteraction(DeviceConfig()->RequestDelay);
1,028 !
202
                auto value = ReadRegisterImpl(port, *reg->GetConfig());
1,128 !
203
                SetTransferResult(true);
928 !
204
                reg->SetValue(value);
928 !
205
            }
928 ✔
206
        } catch (const TSerialDeviceInternalErrorException& e) {
100 !
UNCOV
207
            reg->SetError(TRegister::TError::ReadError);
×
UNCOV
208
            LOG(Warn) << "TSerialDevice::ReadRegister(): " << e.what() << " [slave_id is "
×
UNCOV
209
                      << reg->Device()->ToString() + "]";
×
UNCOV
210
            SetTransferResult(true);
×
UNCOV
211
            if (breakOnError) {
×
UNCOV
212
                throw;
×
213
            }
UNCOV
214
        } catch (const TSerialDevicePermanentRegisterException& e) {
×
UNCOV
215
            reg->SetAvailable(TRegisterAvailability::UNAVAILABLE);
×
UNCOV
216
            reg->SetError(TRegister::TError::ReadError);
×
UNCOV
217
            LOG(Warn) << "TSerialDevice::ReadRegister(): " << e.what() << " [slave_id is "
×
UNCOV
218
                      << reg->Device()->ToString() + "] Register " << reg->ToString()
×
UNCOV
219
                      << " is now marked as unsupported";
×
UNCOV
220
            SetTransferResult(true);
×
UNCOV
221
            if (breakOnError) {
×
UNCOV
222
                throw;
×
223
            }
224
        } catch (const TSerialDeviceException& e) {
100 !
225
            reg->SetError(TRegister::TError::ReadError);
100 !
226
            auto& logger = (ConnectionState == TDeviceConnectionState::DISCONNECTED) ? Debug : Warn;
100 ✔
227
            LOG(logger) << "TSerialDevice::ReadRegister(): " << e.what() << " [slave_id is "
100 !
228
                        << reg->Device()->ToString() + "]";
100 !
229
            SetTransferResult(false);
100 !
230
            if (breakOnError) {
100 !
UNCOV
231
                throw;
×
232
            }
233
        }
100 ✔
234
    }
235
    InvalidateReadCache();
566 ✔
236
}
566 ✔
237

238
void TSerialDevice::SetTransferResult(bool ok)
10,974 ✔
239
{
240
    // disable reconnect functionality option
241
    if (_DeviceConfig->DeviceTimeout.count() < 0 || _DeviceConfig->DeviceMaxFailCycles < 0) {
10,974 !
UNCOV
242
        return;
×
243
    }
244

245
    if (ok) {
10,974 ✔
246
        LastSuccessfulCycle = std::chrono::steady_clock::now();
10,257 ✔
247
        SetConnectionState(TDeviceConnectionState::CONNECTED);
10,257 ✔
248
        RemainingFailCycles = _DeviceConfig->DeviceMaxFailCycles;
10,257 ✔
249
    } else {
250

251
        if (RemainingFailCycles > 0) {
717 ✔
252
            --RemainingFailCycles;
102 ✔
253
        }
254

255
        if (RemainingFailCycles == 0 && (ConnectionState != TDeviceConnectionState::DISCONNECTED) &&
1,268 ✔
256
            (std::chrono::steady_clock::now() - LastSuccessfulCycle > _DeviceConfig->DeviceTimeout))
1,268 !
257
        {
258
            SetDisconnected();
137 ✔
259
        }
260
    }
261
}
262

263
TDeviceConnectionState TSerialDevice::GetConnectionState() const
44,524 ✔
264
{
265
    return ConnectionState;
44,524 ✔
266
}
267

268
void TSerialDevice::WriteSetupRegisters(TPort& port, const TDeviceSetupItems& setupItems, bool breakOnError)
154 ✔
269
{
270
    for (const auto& item: setupItems) {
226 ✔
271
        WriteRegisterImpl(port, *item->RegisterConfig, item->RawValue);
88 ✔
272
        LOG(Info) << item->ToString();
72 !
273
    }
274
    if (!setupItems.empty()) {
138 ✔
275
        SetTransferResult(true);
32 ✔
276
    }
277
}
138 ✔
278

279
PDeviceConfig TSerialDevice::DeviceConfig() const
77,837 ✔
280
{
281
    return _DeviceConfig;
77,837 ✔
282
}
283

284
PProtocol TSerialDevice::Protocol() const
6,003 ✔
285
{
286
    return _Protocol;
6,003 ✔
287
}
288

289
bool TSerialDevice::GetSupportsHoles() const
940 ✔
290
{
291
    return SupportsHoles;
940 ✔
292
}
293

294
void TSerialDevice::SetSupportsHoles(bool supportsHoles)
153 ✔
295
{
296
    SupportsHoles = supportsHoles;
153 ✔
297
}
153 ✔
298

299
bool TSerialDevice::IsSporadicOnly() const
5,976 ✔
300
{
301
    return SporadicOnly;
5,976 ✔
302
}
303

304
void TSerialDevice::SetSporadicOnly(bool sporadicOnly)
1,262 ✔
305
{
306
    SporadicOnly = sporadicOnly;
1,262 ✔
307
}
1,262 ✔
308

309
bool TSerialDevice::IsWbDevice() const
9,516 ✔
310
{
311
    return WbDevice;
9,516 ✔
312
}
313

314
void TSerialDevice::SetWbDevice(bool wbDevice)
1,268 ✔
315
{
316
    WbDevice = wbDevice;
1,268 ✔
317
}
1,268 ✔
318

319
const std::string& TSerialDevice::GetWbFwVersion() const
535 ✔
320
{
321
    return WbFwVersion;
535 ✔
322
}
323

324
void TSerialDevice::SetWbFwVersion(const std::string& wbFwVersion)
251 ✔
325
{
326
    WbFwVersion = wbFwVersion;
251 ✔
327
}
251 ✔
328

329
void TSerialDevice::SetDisconnected()
149 ✔
330
{
331
    SetSupportsHoles(true);
149 ✔
332
    SetConnectionState(TDeviceConnectionState::DISCONNECTED);
149 ✔
333
}
149 ✔
334

335
PRegister TSerialDevice::AddRegister(PRegisterConfig config)
6,450 ✔
336
{
337
    auto reg = std::make_shared<TRegister>(shared_from_this(), config);
6,450 !
338
    Registers.push_back(reg);
6,450 !
339
    return reg;
6,450 ✔
UNCOV
340
}
×
341

342
const std::list<PRegister>& TSerialDevice::GetRegisters() const
1,914 ✔
343
{
344
    return Registers;
1,914 ✔
345
}
346

UNCOV
347
std::chrono::steady_clock::time_point TSerialDevice::GetLastReadTime() const
×
348
{
UNCOV
349
    return LastReadTime;
×
350
}
351

352
void TSerialDevice::SetLastReadTime(std::chrono::steady_clock::time_point readTime)
9,734 ✔
353
{
354
    LastReadTime = readTime;
9,734 ✔
355
}
9,734 ✔
356

357
std::chrono::steady_clock::time_point TSerialDevice::GetLastWriteTime() const
258 ✔
358
{
359
    return LastWriteTime;
258 ✔
360
}
361

362
void TSerialDevice::SetLastWriteTime(std::chrono::steady_clock::time_point writeTime)
256 ✔
363
{
364
    LastWriteTime = writeTime;
256 ✔
365
}
256 ✔
366

367
void TSerialDevice::AddOnConnectionStateChangedCallback(TSerialDevice::TDeviceCallback callback)
1,530 ✔
368
{
369
    ConnectionStateChangedCallbacks.push_back(callback);
1,530 ✔
370
}
1,530 ✔
371

372
void TSerialDevice::SetConnectionState(TDeviceConnectionState state)
10,406 ✔
373
{
374
    if (ConnectionState == state) {
10,406 ✔
375
        return;
9,757 ✔
376
    }
377
    ConnectionState = state;
649 ✔
378
    if (state == TDeviceConnectionState::CONNECTED) {
649 ✔
379
        // clear serial number on connection as it can be other device than before disconnection
380
        if (SnRegister) {
500 ✔
381
            SnRegister->SetValue(TRegisterValue());
4 !
382
        }
383
        LOG(Info) << "device " << ToString() << " is connected";
500 !
384
    } else {
385
        LOG(Warn) << "device " << ToString() << " is disconnected";
149 !
386
    }
387
    for (auto& callback: ConnectionStateChangedCallbacks) {
2,171 ✔
388
        callback(shared_from_this());
1,522 !
389
    }
390
}
391

UNCOV
392
PRegister TSerialDevice::GetSnRegister() const
×
393
{
UNCOV
394
    return SnRegister;
×
395
}
396

397
void TSerialDevice::SetSnRegister(PRegisterConfig regConfig)
4 ✔
398
{
399
    auto regIt = std::find_if(Registers.begin(), Registers.end(), [&regConfig](const PRegister& reg) {
4 !
400
        return reg->GetConfig() == regConfig;
24 !
401
    });
402
    if (regIt == Registers.end()) {
4 !
UNCOV
403
        SnRegister = std::make_shared<TRegister>(shared_from_this(), regConfig);
×
404
    } else {
405
        SnRegister = *regIt;
4 ✔
406
    }
407
}
4 ✔
408

409
void TSerialDevice::AddSetupItem(PDeviceSetupItemConfig item)
700 ✔
410
{
411
    auto key = item->GetRegisterConfig()->ToString();
700 !
412
    auto addrIt = SetupItemsByAddress.find(key);
700 !
413
    if (addrIt != SetupItemsByAddress.end()) {
700 ✔
414
        std::stringstream ss;
4 !
415
        ss << "Setup command \"" << item->GetName() << "\" (" << key << ") from \"" << DeviceConfig()->DeviceType
12 !
416
           << "\" has a duplicate command \"" << addrIt->second->Name << "\" ";
12 !
417
        if (item->GetRawValue() == addrIt->second->RawValue) {
4 !
UNCOV
418
            ss << "with the same register value.";
×
419
        } else {
420
            ss << "with different register value. IT WILL BREAK TEMPLATE OPERATION";
4 !
421
        }
422
        LOG(Warn) << ss.str();
4 !
423
    } else {
4 ✔
424
        auto setupItem = std::make_shared<TDeviceSetupItem>(item, shared_from_this());
696 !
425
        SetupItemsByAddress.insert({key, setupItem});
696 !
426
        SetupItems.insert(setupItem);
696 !
427
    }
696 ✔
428
}
700 ✔
429

430
const TDeviceSetupItems& TSerialDevice::GetSetupItems() const
1,261 ✔
431
{
432
    return SetupItems;
1,261 ✔
433
}
434

435
std::chrono::milliseconds TSerialDevice::GetFrameTimeout(TPort& port) const
674 ✔
436
{
437
    return _DeviceConfig->FrameTimeout;
674 ✔
438
}
439

440
std::chrono::milliseconds TSerialDevice::GetResponseTimeout(TPort& port) const
10,405 ✔
441
{
442
    return _DeviceConfig->ResponseTimeout;
10,405 ✔
443
}
444

445
TUInt32SlaveId::TUInt32SlaveId(const std::string& slaveId, bool allowBroadcast): HasBroadcastSlaveId(false)
2,620 ✔
446
{
447
    if (allowBroadcast) {
2,620 ✔
448
        if (slaveId.empty()) {
86 ✔
449
            SlaveId = 0;
50 ✔
450
            HasBroadcastSlaveId = true;
50 ✔
451
            return;
50 ✔
452
        }
453
    }
454
    try {
455
        SlaveId = std::stoul(slaveId, /* pos = */ 0, /* base = */ 0);
2,570 !
UNCOV
456
    } catch (const std::logic_error& e) {
×
UNCOV
457
        throw TSerialDeviceException("slave ID \"" + slaveId + "\" is not convertible to integer");
×
UNCOV
458
    }
×
459
}
2,570 ✔
460

461
bool TUInt32SlaveId::operator==(const TUInt32SlaveId& id) const
548 ✔
462
{
463
    if (HasBroadcastSlaveId || id.HasBroadcastSlaveId) {
548 !
UNCOV
464
        return true;
×
465
    }
466
    return SlaveId == id.SlaveId;
548 ✔
467
}
468

469
uint64_t CopyDoubleToUint64(double value)
34 ✔
470
{
471
    uint64_t res = 0;
34 ✔
472
    static_assert((sizeof(res) >= sizeof(value)), "Can't fit double into uint64_t");
473
    memcpy(&res, &value, sizeof(value));
34 ✔
474
    return res;
34 ✔
475
}
476

477
TDeviceConfig::TDeviceConfig(const std::string& name, const std::string& slave_id, const std::string& protocol)
1,538 ✔
478
    : Name(name),
1,538 !
479
      SlaveId(slave_id),
1,538 !
480
      Protocol(protocol)
3,076 !
481
{}
1,538 ✔
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