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

wirenboard / wb-mqtt-serial / 673

15 Jul 2025 07:07AM UTC coverage: 73.028% (-0.8%) from 73.863%
673

push

github

web-flow
Add device/Load and device/Set RPC

6463 of 9217 branches covered (70.12%)

51 of 322 new or added lines in 13 files covered. (15.84%)

7 existing lines in 2 files now uncovered.

12368 of 16936 relevant lines covered (73.03%)

373.64 hits per line

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

84.28
/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,332✔
16
{
17
    return Name;
4,332✔
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)
105✔
36
    : Name(config->GetName()),
105✔
37
      ParameterId(config->GetParameterId()),
105✔
38
      RawValue(config->GetRawValue()),
39
      HumanReadableValue(config->GetValue()),
105✔
40
      RegisterConfig(config->GetRegisterConfig()),
41
      Device(device)
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
821✔
97
{
98
    return Protocol()->GetName() + ":" + DeviceConfig()->SlaveId;
1,642✔
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, GetSetupItems());
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)
138✔
132
{
133
    try {
134
        WriteRegisterImpl(port, *reg->GetConfig(), value);
151✔
135
        SetTransferResult(true);
125✔
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
}
125✔
144

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

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

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, bool breakOnError)
277✔
161
{
162
    for (auto& reg: range->RegisterList()) {
785✔
163
        try {
164
            if (reg->GetAvailable() != TRegisterAvailability::UNAVAILABLE) {
508✔
165
                port.SleepSinceLastInteraction(DeviceConfig()->RequestDelay);
508✔
166
                auto value = ReadRegisterImpl(port, *reg->GetConfig());
1,016✔
167
                SetTransferResult(true);
460✔
168
                reg->SetValue(value);
460✔
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);
×
NEW
175
            if (breakOnError) {
×
NEW
176
                throw;
×
177
            }
178
        } catch (const TSerialDevicePermanentRegisterException& e) {
×
179
            reg->SetAvailable(TRegisterAvailability::UNAVAILABLE);
×
180
            reg->SetError(TRegister::TError::ReadError);
×
181
            LOG(Warn) << "TSerialDevice::ReadRegister(): " << e.what() << " [slave_id is "
×
182
                      << reg->Device()->ToString() + "] Register " << reg->ToString()
×
183
                      << " is now marked as unsupported";
×
184
            SetTransferResult(true);
×
NEW
185
            if (breakOnError) {
×
NEW
186
                throw;
×
187
            }
188
        } catch (const TSerialDeviceException& e) {
96✔
189
            reg->SetError(TRegister::TError::ReadError);
48✔
190
            auto& logger = (ConnectionState == TDeviceConnectionState::DISCONNECTED) ? Debug : Warn;
48✔
191
            LOG(logger) << "TSerialDevice::ReadRegister(): " << e.what() << " [slave_id is "
48✔
192
                        << reg->Device()->ToString() + "]";
48✔
193
            SetTransferResult(false);
48✔
194
            if (breakOnError) {
48✔
NEW
195
                throw;
×
196
            }
197
        }
198
    }
199
    InvalidateReadCache();
277✔
200
}
277✔
201

202
void TSerialDevice::SetTransferResult(bool ok)
1,156✔
203
{
204
    // disable reconnect functionality option
205
    if (_DeviceConfig->DeviceTimeout.count() < 0 || _DeviceConfig->DeviceMaxFailCycles < 0) {
1,156✔
206
        return;
×
207
    }
208

209
    if (ok) {
1,156✔
210
        LastSuccessfulCycle = std::chrono::steady_clock::now();
1,036✔
211
        SetConnectionState(TDeviceConnectionState::CONNECTED);
1,036✔
212
        RemainingFailCycles = _DeviceConfig->DeviceMaxFailCycles;
1,036✔
213
    } else {
214

215
        if (RemainingFailCycles > 0) {
120✔
216
            --RemainingFailCycles;
37✔
217
        }
218

219
        if (RemainingFailCycles == 0 && (ConnectionState != TDeviceConnectionState::DISCONNECTED) &&
191✔
220
            (std::chrono::steady_clock::now() - LastSuccessfulCycle > _DeviceConfig->DeviceTimeout))
191✔
221
        {
222
            SetDisconnected();
27✔
223
        }
224
    }
225
}
226

227
TDeviceConnectionState TSerialDevice::GetConnectionState() const
3,021✔
228
{
229
    return ConnectionState;
3,021✔
230
}
231

232
void TSerialDevice::WriteSetupRegisters(TPort& port, const TDeviceSetupItems& setupItems, bool breakOnError)
70✔
233
{
234
    for (const auto& item: setupItems) {
106✔
235
        WriteRegisterImpl(port, *item->RegisterConfig, item->RawValue);
44✔
236
        LOG(Info) << item->ToString();
36✔
237
    }
238
    if (!setupItems.empty()) {
62✔
239
        SetTransferResult(true);
16✔
240
    }
241
}
62✔
242

243
PDeviceConfig TSerialDevice::DeviceConfig() const
7,240✔
244
{
245
    return _DeviceConfig;
7,240✔
246
}
247

248
PProtocol TSerialDevice::Protocol() const
1,550✔
249
{
250
    return _Protocol;
1,550✔
251
}
252

253
bool TSerialDevice::GetSupportsHoles() const
401✔
254
{
255
    return SupportsHoles;
401✔
256
}
257

258
void TSerialDevice::SetSupportsHoles(bool supportsHoles)
31✔
259
{
260
    SupportsHoles = supportsHoles;
31✔
261
}
31✔
262

263
bool TSerialDevice::IsSporadicOnly() const
1,177✔
264
{
265
    return SporadicOnly;
1,177✔
266
}
267

268
void TSerialDevice::SetSporadicOnly(bool sporadicOnly)
191✔
269
{
270
    SporadicOnly = sporadicOnly;
191✔
271
}
191✔
272

273
void TSerialDevice::SetDisconnected()
29✔
274
{
275
    SetSupportsHoles(true);
29✔
276
    SetConnectionState(TDeviceConnectionState::DISCONNECTED);
29✔
277
}
29✔
278

279
PRegister TSerialDevice::AddRegister(PRegisterConfig config)
1,757✔
280
{
281
    auto reg = std::make_shared<TRegister>(shared_from_this(), config);
1,757✔
282
    Registers.push_back(reg);
1,757✔
283
    return reg;
1,757✔
284
}
285

286
const std::list<PRegister>& TSerialDevice::GetRegisters() const
400✔
287
{
288
    return Registers;
400✔
289
}
290

291
std::chrono::steady_clock::time_point TSerialDevice::GetLastReadTime() const
×
292
{
293
    return LastReadTime;
×
294
}
295

296
void TSerialDevice::SetLastReadTime(std::chrono::steady_clock::time_point readTime)
716✔
297
{
298
    LastReadTime = readTime;
716✔
299
}
716✔
300

301
void TSerialDevice::AddOnConnectionStateChangedCallback(TSerialDevice::TDeviceCallback callback)
254✔
302
{
303
    ConnectionStateChangedCallbacks.push_back(callback);
254✔
304
}
254✔
305

306
void TSerialDevice::SetConnectionState(TDeviceConnectionState state)
1,065✔
307
{
308
    if (ConnectionState == state) {
1,065✔
309
        return;
913✔
310
    }
311
    ConnectionState = state;
152✔
312
    if (state == TDeviceConnectionState::CONNECTED) {
152✔
313
        // clear serial number on connection as it can be other device than before disconnection
314
        if (SnRegister) {
123✔
315
            SnRegister->SetValue(TRegisterValue());
2✔
316
        }
317
        LOG(Info) << "device " << ToString() << " is connected";
123✔
318
    } else {
319
        LOG(Warn) << "device " << ToString() << " is disconnected";
29✔
320
    }
321
    for (auto& callback: ConnectionStateChangedCallbacks) {
470✔
322
        callback(shared_from_this());
318✔
323
    }
324
}
325

326
PRegister TSerialDevice::GetSnRegister() const
×
327
{
328
    return SnRegister;
×
329
}
330

331
void TSerialDevice::SetSnRegister(PRegisterConfig regConfig)
2✔
332
{
333
    auto regIt = std::find_if(Registers.begin(), Registers.end(), [&regConfig](const PRegister& reg) {
24✔
334
        return reg->GetConfig() == regConfig;
12✔
335
    });
2✔
336
    if (regIt == Registers.end()) {
2✔
337
        SnRegister = std::make_shared<TRegister>(shared_from_this(), regConfig);
×
338
    } else {
339
        SnRegister = *regIt;
2✔
340
    }
341
}
2✔
342

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

364
const TDeviceSetupItems& TSerialDevice::GetSetupItems() const
153✔
365
{
366
    return SetupItems;
153✔
367
}
368

369
std::chrono::milliseconds TSerialDevice::GetFrameTimeout(TPort& port) const
331✔
370
{
371
    return _DeviceConfig->FrameTimeout;
331✔
372
}
373

374
std::chrono::milliseconds TSerialDevice::GetResponseTimeout(TPort& port) const
715✔
375
{
376
    return _DeviceConfig->ResponseTimeout;
715✔
377
}
378

379
TUInt32SlaveId::TUInt32SlaveId(const std::string& slaveId, bool allowBroadcast): HasBroadcastSlaveId(false)
732✔
380
{
381
    if (allowBroadcast) {
732✔
382
        if (slaveId.empty()) {
18✔
383
            SlaveId = 0;
×
384
            HasBroadcastSlaveId = true;
×
385
            return;
×
386
        }
387
    }
388
    try {
389
        SlaveId = std::stoul(slaveId, /* pos = */ 0, /* base = */ 0);
732✔
390
    } catch (const std::logic_error& e) {
×
391
        throw TSerialDeviceException("slave ID \"" + slaveId + "\" is not convertible to integer");
×
392
    }
393
}
732✔
394

395
bool TUInt32SlaveId::operator==(const TUInt32SlaveId& id) const
224✔
396
{
397
    if (HasBroadcastSlaveId || id.HasBroadcastSlaveId) {
224✔
398
        return true;
×
399
    }
400
    return SlaveId == id.SlaveId;
224✔
401
}
402

403
uint64_t CopyDoubleToUint64(double value)
17✔
404
{
405
    uint64_t res = 0;
17✔
406
    static_assert((sizeof(res) >= sizeof(value)), "Can't fit double into uint64_t");
407
    memcpy(&res, &value, sizeof(value));
17✔
408
    return res;
17✔
409
}
410

411
TDeviceConfig::TDeviceConfig(const std::string& name, const std::string& slave_id, const std::string& protocol)
291✔
412
    : Name(name),
413
      SlaveId(slave_id),
414
      Protocol(protocol)
291✔
415
{}
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