• 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

86.1
/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)
3,456✔
11
    : Name(name),
12
      RegTypes(std::make_shared<TRegisterTypeMap>(reg_types))
3,456✔
13
{}
3,456✔
14

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

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

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

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

35
TDeviceSetupItem::TDeviceSetupItem(PDeviceSetupItemConfig config, PSerialDevice device, PRegisterConfig registerConfig)
105✔
36
    : Name(config->GetName()),
105✔
37
      ParameterId(config->GetParameterId()),
105✔
38
      RawValue(config->GetRawValue()),
39
      HumanReadableValue(config->GetValue()),
105✔
40
      Device(device),
41
      RegisterConfig(registerConfig)
420✔
42
{}
105✔
43

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

57
bool TDeviceSetupItemComparePredicate::operator()(const PDeviceSetupItem& a, const PDeviceSetupItem& b) const
209✔
58
{
59
    if (a->RegisterConfig->Type != b->RegisterConfig->Type) {
209✔
60
        return a->RegisterConfig->Type < b->RegisterConfig->Type;
9✔
61
    }
62
    auto compare = a->RegisterConfig->GetWriteAddress().Compare(b->RegisterConfig->GetWriteAddress());
200✔
63
    if (compare == 0) {
200✔
64
        return a->RegisterConfig->GetDataOffset() < b->RegisterConfig->GetDataOffset();
6✔
65
    }
66
    return compare < 0;
194✔
67
}
68

69
TUint32SlaveIdProtocol::TUint32SlaveIdProtocol(const std::string& name,
2,286✔
70
                                               const TRegisterTypes& reg_types,
71
                                               bool allowBroadcast)
2,286✔
72
    : IProtocol(name, reg_types),
73
      AllowBroadcast(allowBroadcast)
2,286✔
74
{}
2,286✔
75

76
bool TUint32SlaveIdProtocol::IsSameSlaveId(const std::string& id1, const std::string& id2) const
191✔
77
{
78
    return (TUInt32SlaveId(id1, AllowBroadcast) == TUInt32SlaveId(id2, AllowBroadcast));
191✔
79
}
80

81
bool TUint32SlaveIdProtocol::SupportsBroadcast() const
4✔
82
{
83
    return AllowBroadcast;
4✔
84
}
85

86
TSerialDevice::TSerialDevice(PDeviceConfig config, PProtocol protocol)
291✔
87
    : _DeviceConfig(config),
88
      _Protocol(protocol),
89
      LastSuccessfulCycle(),
90
      ConnectionState(TDeviceConnectionState::UNKNOWN),
91
      RemainingFailCycles(0),
92
      SupportsHoles(true),
93
      SporadicOnly(true)
291✔
94
{}
291✔
95

96
std::string TSerialDevice::ToString() const
815✔
97
{
98
    return Protocol()->GetName() + ":" + DeviceConfig()->SlaveId;
1,630✔
99
}
100

101
PRegisterRange TSerialDevice::CreateRegisterRange() const
110✔
102
{
103
    return PRegisterRange(new TSameAddressRegisterRange());
110✔
104
}
105

106
void TSerialDevice::Prepare(TPort& port, TDevicePrepareMode prepareMode)
165✔
107
{
108
    bool deviceWasDisconnected = (ConnectionState != TDeviceConnectionState::CONNECTED);
165✔
109
    try {
110
        PrepareImpl(port);
165✔
111
        if (prepareMode == TDevicePrepareMode::WITH_SETUP_IF_WAS_DISCONNECTED && deviceWasDisconnected) {
162✔
112
            WriteSetupRegisters(port);
126✔
113
        }
114
    } catch (const TSerialDeviceException& ex) {
42✔
115
        SetTransferResult(false);
21✔
116
        throw;
21✔
117
    }
118
}
144✔
119

120
void TSerialDevice::PrepareImpl(TPort& port)
164✔
121
{
122
    port.SleepSinceLastInteraction(GetFrameTimeout(port));
164✔
123
}
164✔
124

125
void TSerialDevice::EndSession(TPort& port)
29✔
126
{}
29✔
127

128
void TSerialDevice::InvalidateReadCache()
293✔
129
{}
293✔
130

131
void TSerialDevice::WriteRegister(TPort& port, PRegister reg, const TRegisterValue& value)
132✔
132
{
133
    try {
134
        WriteRegisterImpl(port, *reg->GetConfig(), value);
145✔
135
        SetTransferResult(true);
119✔
136
    } catch (const TSerialDevicePermanentRegisterException& e) {
16✔
137
        SetTransferResult(true);
3✔
138
        throw;
3✔
139
    } catch (const TSerialDeviceException& ex) {
20✔
140
        SetTransferResult(false);
10✔
141
        throw;
10✔
142
    }
143
}
119✔
144

145
void TSerialDevice::WriteRegister(TPort& port, PRegister reg, uint64_t value)
17✔
146
{
147
    WriteRegister(port, reg, TRegisterValue{value});
20✔
148
}
14✔
149

NEW
150
TRegisterValue TSerialDevice::ReadRegisterImpl(TPort& port, const TRegisterConfig& reg)
×
151
{
152
    throw TSerialDeviceException("single register reading is not supported");
×
153
}
154

NEW
155
void TSerialDevice::WriteRegisterImpl(TPort& port, const TRegisterConfig& reg, const TRegisterValue& value)
×
156
{
157
    throw TSerialDeviceException(ToString() + ": register writing is not supported");
×
158
}
159

160
void TSerialDevice::ReadRegisterRange(TPort& port, PRegisterRange range)
277✔
161
{
162
    for (auto& reg: range->RegisterList()) {
769✔
163
        try {
164
            if (reg->GetAvailable() != TRegisterAvailability::UNAVAILABLE) {
492✔
165
                port.SleepSinceLastInteraction(DeviceConfig()->RequestDelay);
492✔
166
                auto value = ReadRegisterImpl(port, *reg->GetConfig());
984✔
167
                SetTransferResult(true);
444✔
168
                reg->SetValue(value);
444✔
169
            }
170
        } catch (const TSerialDeviceInternalErrorException& e) {
48✔
171
            reg->SetError(TRegister::TError::ReadError);
×
172
            LOG(Warn) << "TSerialDevice::ReadRegister(): " << e.what() << " [slave_id is "
×
173
                      << reg->Device()->ToString() + "]";
×
174
            SetTransferResult(true);
×
175
        } catch (const TSerialDevicePermanentRegisterException& e) {
×
176
            reg->SetAvailable(TRegisterAvailability::UNAVAILABLE);
×
177
            reg->SetError(TRegister::TError::ReadError);
×
178
            LOG(Warn) << "TSerialDevice::ReadRegister(): " << e.what() << " [slave_id is "
×
179
                      << reg->Device()->ToString() + "] Register " << reg->ToString()
×
180
                      << " is now marked as unsupported";
×
181
            SetTransferResult(true);
×
182
        } catch (const TSerialDeviceException& e) {
96✔
183
            reg->SetError(TRegister::TError::ReadError);
48✔
184
            auto& logger = (ConnectionState == TDeviceConnectionState::DISCONNECTED) ? Debug : Warn;
48✔
185
            LOG(logger) << "TSerialDevice::ReadRegister(): " << e.what() << " [slave_id is "
48✔
186
                        << reg->Device()->ToString() + "]";
48✔
187
            SetTransferResult(false);
48✔
188
        }
189
    }
190
    InvalidateReadCache();
277✔
191
}
277✔
192

193
void TSerialDevice::SetTransferResult(bool ok)
1,134✔
194
{
195
    // disable reconnect functionality option
196
    if (_DeviceConfig->DeviceTimeout.count() < 0 || _DeviceConfig->DeviceMaxFailCycles < 0) {
1,134✔
197
        return;
×
198
    }
199

200
    if (ok) {
1,134✔
201
        LastSuccessfulCycle = std::chrono::steady_clock::now();
1,014✔
202
        SetConnectionState(TDeviceConnectionState::CONNECTED);
1,014✔
203
        RemainingFailCycles = _DeviceConfig->DeviceMaxFailCycles;
1,014✔
204
    } else {
205

206
        if (RemainingFailCycles > 0) {
120✔
207
            --RemainingFailCycles;
37✔
208
        }
209

210
        if (RemainingFailCycles == 0 && (ConnectionState != TDeviceConnectionState::DISCONNECTED) &&
191✔
211
            (std::chrono::steady_clock::now() - LastSuccessfulCycle > _DeviceConfig->DeviceTimeout))
191✔
212
        {
213
            SetDisconnected();
27✔
214
        }
215
    }
216
}
217

218
TDeviceConnectionState TSerialDevice::GetConnectionState() const
2,971✔
219
{
220
    return ConnectionState;
2,971✔
221
}
222

223
void TSerialDevice::WriteSetupRegisters(TPort& port)
70✔
224
{
225
    for (const auto& item: SetupItems) {
106✔
226
        WriteRegisterImpl(port, *item->RegisterConfig, item->RawValue);
44✔
227
        LOG(Info) << item->ToString();
36✔
228
    }
229
    if (!SetupItems.empty()) {
62✔
230
        SetTransferResult(true);
16✔
231
    }
232
}
62✔
233

234
PDeviceConfig TSerialDevice::DeviceConfig() const
7,180✔
235
{
236
    return _DeviceConfig;
7,180✔
237
}
238

239
PProtocol TSerialDevice::Protocol() const
1,544✔
240
{
241
    return _Protocol;
1,544✔
242
}
243

244
bool TSerialDevice::GetSupportsHoles() const
401✔
245
{
246
    return SupportsHoles;
401✔
247
}
248

249
void TSerialDevice::SetSupportsHoles(bool supportsHoles)
31✔
250
{
251
    SupportsHoles = supportsHoles;
31✔
252
}
31✔
253

254
bool TSerialDevice::IsSporadicOnly() const
1,139✔
255
{
256
    return SporadicOnly;
1,139✔
257
}
258

259
void TSerialDevice::SetSporadicOnly(bool sporadicOnly)
191✔
260
{
261
    SporadicOnly = sporadicOnly;
191✔
262
}
191✔
263

264
void TSerialDevice::SetDisconnected()
29✔
265
{
266
    SetSupportsHoles(true);
29✔
267
    SetConnectionState(TDeviceConnectionState::DISCONNECTED);
29✔
268
}
29✔
269

270
PRegister TSerialDevice::AddRegister(PRegisterConfig config)
1,719✔
271
{
272
    auto reg = std::make_shared<TRegister>(shared_from_this(), config);
1,719✔
273
    Registers.push_back(reg);
1,719✔
274
    return reg;
1,719✔
275
}
276

277
const std::list<PRegister>& TSerialDevice::GetRegisters() const
400✔
278
{
279
    return Registers;
400✔
280
}
281

282
std::chrono::steady_clock::time_point TSerialDevice::GetLastReadTime() const
×
283
{
284
    return LastReadTime;
×
285
}
286

287
void TSerialDevice::SetLastReadTime(std::chrono::steady_clock::time_point readTime)
716✔
288
{
289
    LastReadTime = readTime;
716✔
290
}
716✔
291

292
void TSerialDevice::AddOnConnectionStateChangedCallback(TSerialDevice::TDeviceCallback callback)
254✔
293
{
294
    ConnectionStateChangedCallbacks.push_back(callback);
254✔
295
}
254✔
296

297
void TSerialDevice::SetConnectionState(TDeviceConnectionState state)
1,043✔
298
{
299
    if (ConnectionState == state) {
1,043✔
300
        return;
891✔
301
    }
302
    ConnectionState = state;
152✔
303
    if (state == TDeviceConnectionState::CONNECTED) {
152✔
304
        // clear serial number on connection as it can be other device than before disconnection
305
        if (SnRegister) {
123✔
306
            SnRegister->SetValue(TRegisterValue());
2✔
307
        }
308
        LOG(Info) << "device " << ToString() << " is connected";
123✔
309
    } else {
310
        LOG(Warn) << "device " << ToString() << " is disconnected";
29✔
311
    }
312
    for (auto& callback: ConnectionStateChangedCallbacks) {
470✔
313
        callback(shared_from_this());
318✔
314
    }
315
}
316

317
PRegister TSerialDevice::GetSnRegister() const
×
318
{
319
    return SnRegister;
×
320
}
321

322
void TSerialDevice::SetSnRegister(PRegisterConfig regConfig)
2✔
323
{
324
    auto regIt = std::find_if(Registers.begin(), Registers.end(), [&regConfig](const PRegister& reg) {
24✔
325
        return reg->GetConfig() == regConfig;
12✔
326
    });
2✔
327
    if (regIt == Registers.end()) {
2✔
328
        SnRegister = std::make_shared<TRegister>(shared_from_this(), regConfig);
×
329
    } else {
330
        SnRegister = *regIt;
2✔
331
    }
332
}
2✔
333

334
void TSerialDevice::AddSetupItem(PDeviceSetupItemConfig item)
107✔
335
{
336
    auto key = item->GetRegisterConfig()->ToString();
214✔
337
    auto addrIt = SetupItemsByAddress.find(key);
107✔
338
    if (addrIt != SetupItemsByAddress.end()) {
107✔
339
        std::stringstream ss;
2✔
340
        ss << "Setup command \"" << item->GetName() << "\" (" << key << ") from \"" << DeviceConfig()->DeviceType
6✔
341
           << "\" has a duplicate command \"" << addrIt->second->Name << "\" ";
6✔
342
        if (item->GetRawValue() == addrIt->second->RawValue) {
2✔
343
            ss << "with the same register value.";
×
344
        } else {
345
            ss << "with different register value. IT WILL BREAK TEMPLATE OPERATION";
2✔
346
        }
347
        LOG(Warn) << ss.str();
2✔
348
    } else {
349
        auto setupItem = std::make_shared<TDeviceSetupItem>(item, shared_from_this(), item->GetRegisterConfig());
210✔
350
        SetupItemsByAddress.insert({key, setupItem});
105✔
351
        SetupItems.insert(setupItem);
105✔
352
    }
353
}
107✔
354

355
const TDeviceSetupItems& TSerialDevice::GetSetupItems() const
83✔
356
{
357
    return SetupItems;
83✔
358
}
359

360
std::chrono::milliseconds TSerialDevice::GetFrameTimeout(TPort& port) const
331✔
361
{
362
    return _DeviceConfig->FrameTimeout;
331✔
363
}
364

365
std::chrono::milliseconds TSerialDevice::GetResponseTimeout(TPort& port) const
715✔
366
{
367
    return _DeviceConfig->ResponseTimeout;
715✔
368
}
369

370
TUInt32SlaveId::TUInt32SlaveId(const std::string& slaveId, bool allowBroadcast): HasBroadcastSlaveId(false)
732✔
371
{
372
    if (allowBroadcast) {
732✔
373
        if (slaveId.empty()) {
18✔
374
            SlaveId = 0;
×
375
            HasBroadcastSlaveId = true;
×
376
            return;
×
377
        }
378
    }
379
    try {
380
        SlaveId = std::stoul(slaveId, /* pos = */ 0, /* base = */ 0);
732✔
381
    } catch (const std::logic_error& e) {
×
382
        throw TSerialDeviceException("slave ID \"" + slaveId + "\" is not convertible to integer");
×
383
    }
384
}
732✔
385

386
bool TUInt32SlaveId::operator==(const TUInt32SlaveId& id) const
224✔
387
{
388
    if (HasBroadcastSlaveId || id.HasBroadcastSlaveId) {
224✔
389
        return true;
×
390
    }
391
    return SlaveId == id.SlaveId;
224✔
392
}
393

394
uint64_t CopyDoubleToUint64(double value)
17✔
395
{
396
    uint64_t res = 0;
17✔
397
    static_assert((sizeof(res) >= sizeof(value)), "Can't fit double into uint64_t");
398
    memcpy(&res, &value, sizeof(value));
17✔
399
    return res;
17✔
400
}
401

402
TDeviceConfig::TDeviceConfig(const std::string& name, const std::string& slave_id, const std::string& protocol)
291✔
403
    : Name(name),
404
      SlaveId(slave_id),
405
      Protocol(protocol)
291✔
406
{}
291✔
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