• 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

74.72
/src/serial_port_driver.cpp
1
#include "serial_port_driver.h"
2
#include "log.h"
3

4
#include <wblib/wbmqtt.h>
5

6
#include <algorithm>
7
#include <cassert>
8
#include <iostream>
9
#include <sstream>
10

11
#include "port/serial_port.h"
12
#include "port/tcp_port.h"
13

14
using namespace std;
15
using namespace WBMQTT;
16

17
#define LOG(logger) ::logger.Log() << "[serial port driver] "
18

19
TSerialPortDriver::TSerialPortDriver(WBMQTT::PDeviceDriver mqttDriver,
112✔
20
                                     PPortConfig portConfig,
21
                                     const WBMQTT::TPublishParameters& publishPolicy,
22
                                     size_t lowPriorityRateLimit)
112✔
23
    : MqttDriver(mqttDriver),
24
      Config(portConfig),
25
      PublishPolicy(publishPolicy)
112✔
26
{
27
    Description = Config->Port->GetDescription(false);
112✔
28
    SerialClient = PSerialClient(new TSerialClient(Config->Port,
224✔
29
                                                   Config->OpenCloseSettings,
224✔
30
                                                   std::chrono::steady_clock::now,
31
                                                   lowPriorityRateLimit));
224✔
32
}
112✔
33

34
const std::string& TSerialPortDriver::GetShortDescription() const
×
35
{
36
    return Description;
×
37
}
38

39
void TSerialPortDriver::SetUpDevices()
112✔
40
{
41
    SerialClient->SetReadCallback([this](PRegister reg) { OnValueRead(reg); });
1,752✔
42
    SerialClient->SetErrorCallback([this](PRegister reg) { UpdateError(reg); });
394✔
43

44
    LOG(Debug) << "setting up devices at " << Config->Port->GetDescription();
112✔
45

46
    try {
47
        auto tx = MqttDriver->BeginTx();
224✔
48

49
        for (const auto& device: Config->Devices) {
234✔
50
            device->Device->AddOnConnectionStateChangedCallback(
244✔
51
                [this](PSerialDevice dev) { OnDeviceConnectionStateChanged(dev); });
396✔
52
            auto mqttDevice = tx->CreateDevice(From(device->Device)).GetValue();
366✔
53
            Devices.push_back(device->Device);
122✔
54
            std::vector<PDeviceChannel> channels;
122✔
55
            // init channels' registers
56
            for (const auto& channelConfig: device->Channels) {
1,050✔
57
                try {
58
                    auto channel = std::make_shared<TDeviceChannel>(device->Device, channelConfig);
1,856✔
59
                    channel->Control = mqttDevice->CreateControl(tx, From(channel)).GetValue();
928✔
60
                    for (const auto& reg: channel->Registers) {
1,896✔
61
                        RegisterToChannelMap.emplace(reg, channel);
968✔
62
                    }
63
                    channels.push_back(channel);
928✔
64
                } catch (const exception& e) {
×
65
                    LOG(Error) << "unable to create control: '" << e.what() << "'";
×
66
                }
67
            }
68
            mqttDevice->RemoveUnusedControls(tx).Sync();
122✔
69
            SerialClient->AddDevice(device->Device);
122✔
70
            DeviceToChannelsMap.emplace(device->Device, channels);
122✔
71
        }
72
    } catch (const exception& e) {
×
73
        LOG(Error) << "unable to create device: '" << e.what() << "' Cleaning.";
×
74
        ClearDevices();
×
75
        throw;
×
76
    } catch (...) {
×
77
        LOG(Error) << "unable to create device or control. Cleaning.";
×
78
        ClearDevices();
×
79
        throw;
×
80
    }
81
}
112✔
82

83
void TSerialPortDriver::HandleControlOnValueEvent(const WBMQTT::TControlOnValueEvent& event)
122✔
84
{
85
    const auto& value = event.RawValue;
122✔
86
    const auto& linkData = event.Control->GetUserData().As<TControlLinkData>();
122✔
87

88
    const auto& portDriver = linkData.PortDriver.lock();
122✔
89
    const auto& channel = linkData.DeviceChannel.lock();
122✔
90

91
    if (!portDriver || !channel) {
122✔
92
        if (!portDriver) {
×
93
            LOG(Error) << "event for non existent port driver from control '" << event.Control->GetDevice()->GetId()
×
94
                       << "/" << event.Control->GetId() << "' value: '" << value << "'";
×
95
        }
96

97
        if (!channel) {
×
98
            LOG(Error) << "event for non existent device channel from control '" << event.Control->GetDevice()->GetId()
×
99
                       << "/" << event.Control->GetId() << "' value: '" << value << "'";
×
100
        }
101

102
        return;
×
103
    }
104

105
    portDriver->SetValueToChannel(channel, value);
122✔
106
}
107

108
void TSerialPortDriver::SetValueToChannel(const PDeviceChannel& channel, const string& value)
122✔
109
{
110
    const auto& registers = channel->Registers;
122✔
111

112
    std::vector<std::string> valueItems = StringSplit(value, ';');
122✔
113

114
    if (valueItems.size() != registers.size()) {
122✔
115
        LOG(Warn) << "invalid value for " << channel->Describe() << ": '" << value << "'";
×
116
        return;
×
117
    }
118

119
    for (size_t i = 0; i < registers.size(); ++i) {
268✔
120
        PRegister reg = registers[i];
146✔
121
        LOG(Debug) << "setting device register: " << reg->ToString() << " <- " << valueItems[i];
146✔
122

123
        try {
124
            auto valueToSet = valueItems[i];
146✔
125
            if (!channel->OnValue.empty() && valueItems[i] == "1") {
146✔
126
                valueToSet = channel->OnValue;
12✔
127
            } else if (!channel->OffValue.empty() && valueItems[i] == "0") {
134✔
128
                valueToSet = channel->OffValue;
6✔
129
            }
130
            SerialClient->SetTextValue(reg, valueToSet);
146✔
131

132
        } catch (std::exception& err) {
×
133
            LOG(Warn) << "invalid value for " << channel->Describe() << ": '" << value << "' : " << err.what();
×
134
            return;
×
135
        }
136
    }
137
}
138

139
void TSerialPortDriver::OnValueRead(PRegister reg)
1,652✔
140
{
141
    auto it = RegisterToChannelMap.find(reg);
1,652✔
142
    if (it == RegisterToChannelMap.end()) {
1,652✔
143
        LOG(Warn) << "got unexpected register from serial client";
×
144
        return;
×
145
    }
146
    if (it->second->HasValuesOfAllRegisters()) {
1,652✔
147
        // change publish policy for sporadic registers to publish register data on every read, even if it not changed
148
        // needed for DALI bus devices polling
149
        auto publishPolicy = PublishPolicy;
1,628✔
150
        if ((reg->GetConfig()->SporadicMode == TRegisterConfig::TSporadicMode::ONLY_EVENTS &&
3,256✔
151
             reg->IsExcludedFromPolling()) ||
4,878✔
152
            reg->Device()->IsSporadicOnly())
3,250✔
153
        {
154
            publishPolicy.Policy = TPublishParameters::PublishAll;
6✔
155
        }
156
        it->second->UpdateValueAndError(*MqttDriver, publishPolicy);
1,628✔
157
    }
158
}
159

160
void TSerialPortDriver::UpdateError(PRegister reg)
282✔
161
{
162
    auto it = RegisterToChannelMap.find(reg);
282✔
163
    if (it == RegisterToChannelMap.end()) {
282✔
164
        LOG(Warn) << "got unexpected register from serial client";
×
165
        return;
×
166
    }
167

168
    it->second->UpdateError(*MqttDriver);
282✔
169
}
170

171
void TSerialPortDriver::OnDeviceConnectionStateChanged(PSerialDevice device)
152✔
172
{
173
    if (device->GetConnectionState() == TDeviceConnectionState::DISCONNECTED) {
152✔
174
        auto it = DeviceToChannelsMap.find(device);
22✔
175
        if (it != DeviceToChannelsMap.end()) {
22✔
176
            for (auto& channel: it->second) {
66✔
177
                channel->DoNotPublishNextZeroPressCounter();
44✔
178
            }
179
        }
180
    }
181

182
    auto tx = MqttDriver->BeginTx();
304✔
183
    auto mqttDevice = tx->GetDevice(device->DeviceConfig()->Id);
304✔
184
    auto localMqttDevice = dynamic_pointer_cast<TLocalDevice>(mqttDevice);
304✔
185
    if (localMqttDevice) {
152✔
186
        localMqttDevice->SetError(tx, (device->GetConnectionState() == TDeviceConnectionState::DISCONNECTED) ? "r" : "")
304✔
187
            .Sync();
152✔
188
    }
189
}
152✔
190

191
void TSerialPortDriver::Cycle(std::chrono::steady_clock::time_point now)
1,082✔
192
{
193
    try {
194
        SerialClient->Cycle();
1,082✔
195
    } catch (const TSerialDeviceException& e) {
×
196
        LOG(Error) << "FATAL: " << e.what() << ". Stopping event loops.";
×
197
        exit(1);
×
198
    }
199
}
1,082✔
200

201
void TSerialPortDriver::ClearDevices() noexcept
94✔
202
{
203
    try {
204
        {
205
            auto tx = MqttDriver->BeginTx();
188✔
206

207
            for (const auto& device: Devices) {
196✔
208
                try {
209
                    tx->RemoveDeviceById(device->DeviceConfig()->Id).Sync();
102✔
210
                    LOG(Debug) << "device " << device->DeviceConfig()->Id << " removed successfully";
102✔
211
                } catch (const exception& e) {
×
212
                    LOG(Warn) << "exception during device removal: " << e.what();
×
213
                } catch (...) {
×
214
                    LOG(Warn) << "unknown exception during device removal";
×
215
                }
216
            }
217
        }
218
        Devices.clear();
94✔
219
        RegisterToChannelMap.clear();
94✔
220
        DeviceToChannelsMap.clear();
94✔
221
    } catch (const exception& e) {
×
222
        LOG(Warn) << "TSerialPortDriver::ClearDevices(): " << e.what();
×
223
    } catch (...) {
×
224
        LOG(Warn) << "TSerialPortDriver::ClearDevices(): unknown exception";
×
225
    }
226
}
94✔
227

228
TLocalDeviceArgs TSerialPortDriver::From(const PSerialDevice& device)
122✔
229
{
230
    return TLocalDeviceArgs{}
244✔
231
        .SetId(device->DeviceConfig()->Id)
244✔
232
        .SetTitle(device->DeviceConfig()->Name)
244✔
233
        .SetIsVirtual(true)
122✔
234
        .SetDoLoadPrevious(true);
244✔
235
}
236

237
TControlArgs TSerialPortDriver::From(const PDeviceChannel& channel)
928✔
238
{
239
    auto args = TControlArgs{}
928✔
240
                    .SetId(channel->MqttId)
928✔
241
                    .SetOrder(channel->Order)
928✔
242
                    .SetType(channel->Type)
928✔
243
                    .SetReadonly(channel->ReadOnly)
928✔
244
                    .SetUserData(TControlLinkData{shared_from_this(), channel})
1,856✔
245
                    .SetUnits(channel->Units);
928✔
246

247
    if (isnan(channel->Max)) {
928✔
248
        if (channel->Type == "range" || channel->Type == "dimmer") {
836✔
249
            args.SetMax(65535);
×
250
        }
251
    } else {
252
        args.SetMax(channel->Max);
92✔
253
    }
254

255
    if (!isnan(channel->Min)) {
928✔
256
        args.SetMin(channel->Min);
×
257
    }
258

259
    if (channel->Precision != 0.0) {
928✔
260
        args.SetPrecision(channel->Precision);
8✔
261
    }
262

263
    for (const auto& tr: channel->GetTitles()) {
928✔
264
        args.SetTitle(tr.second, tr.first);
×
265
    }
266

267
    for (const auto& it: channel->GetEnumTitles()) {
928✔
268
        args.SetEnumValueTitles(it.first, it.second);
×
269
    }
270

271
    if (std::any_of(channel->Registers.cbegin(), channel->Registers.cend(), [](const auto& reg) {
928✔
272
            return reg->GetConfig()->TypeName == "press_counter";
968✔
273
        }))
274
    {
275
        args.SetDurable();
×
276
    }
277

278
    return args;
928✔
279
}
280

281
PSerialClient TSerialPortDriver::GetSerialClient()
8✔
282
{
283
    return SerialClient;
8✔
284
}
285

286
TDeviceChannel::TDeviceChannel(PSerialDevice device, PDeviceChannelConfig config)
928✔
287
    : TDeviceChannelConfig(*config),
928✔
288
      Device(device),
289
      PublishNextZeroPressCounter(true)
928✔
290
{}
928✔
291

292
std::string TDeviceChannel::Describe() const
×
293
{
294
    const auto& name = GetName();
×
295
    if (name != MqttId) {
×
296
        return "channel '" + name + "' (MQTT control '" + MqttId + "') of device '" + DeviceId + "'";
×
297
    }
298
    return "channel '" + name + "' of device '" + DeviceId + "'";
×
299
}
300

301
void TDeviceChannel::UpdateValueAndError(WBMQTT::TDeviceDriver& deviceDriver,
1,628✔
302
                                         const WBMQTT::TPublishParameters& publishPolicy)
303
{
304
    std::string value;
1,628✔
305
    try {
306
        value = GetTextValue();
1,628✔
307
    } catch (const TRegisterValueException& err) {
×
308
        // Register value is not defined, still able to update error
309
        // This can happen on successful events read after unsuccessful events read
310
        // when some registers aren't yet polled for the first time
311
        if (::Debug.IsEnabled()) {
×
312
            LOG(Debug) << "Trying to publish " << Describe() << " with undefined value";
×
313
        }
314
        UpdateError(deviceDriver);
×
315
        return;
×
316
    }
317
    auto error = GetErrorText();
1,628✔
318
    bool errorIsChanged = (CachedErrorText != error);
1,628✔
319
    if (ShouldNotPublishPressCounter()) {
1,628✔
320
        if (errorIsChanged) {
×
321
            PublishError(deviceDriver, error);
×
322
        }
323
        CachedCurrentValue = value;
×
324
        return;
×
325
    }
326
    PublishNextZeroPressCounter = true;
1,628✔
327
    switch (publishPolicy.Policy) {
1,628✔
328
        case TPublishParameters::PublishOnlyOnChange: {
704✔
329
            if (CachedCurrentValue != value) {
704✔
330
                PublishValueAndError(deviceDriver, value, error);
400✔
331
            } else {
332
                if (errorIsChanged) {
304✔
333
                    PublishError(deviceDriver, error);
30✔
334
                }
335
            }
336
            break;
704✔
337
        }
338
        case TPublishParameters::PublishAll: {
6✔
339
            PublishValueAndError(deviceDriver, value, error);
6✔
340
            break;
6✔
341
        }
342
        case TPublishParameters::PublishSomeUnchanged: {
918✔
343
            auto now = std::chrono::steady_clock::now();
918✔
344
            if (errorIsChanged || (CachedCurrentValue != value) ||
1,166✔
345
                (now - LastControlUpdate >= publishPolicy.PublishUnchangedInterval))
1,166✔
346
            {
347
                PublishValueAndError(deviceDriver, value, error);
670✔
348
            }
349
            break;
918✔
350
        }
351
    }
352
}
353

354
void TDeviceChannel::UpdateError(WBMQTT::TDeviceDriver& deviceDriver)
282✔
355
{
356
    PublishError(deviceDriver, GetErrorText());
282✔
357
}
282✔
358

359
std::string TDeviceChannel::GetErrorText() const
1,910✔
360
{
361
    TRegister::TErrorState errorState;
1,910✔
362
    for (auto r: Registers) {
4,136✔
363
        errorState |= r->GetErrorState();
2,226✔
364
    }
365

366
    const std::unordered_map<TRegister::TError, std::string> errorNames = {
367
        {TRegister::TError::ReadError, "r"},
×
368
        {TRegister::TError::WriteError, "w"},
×
369
        {TRegister::TError::PollIntervalMissError, "p"}};
11,460✔
370
    std::string errorText;
1,910✔
371
    for (size_t i = 0; i < TRegister::TError::MAX_ERRORS; ++i) {
7,640✔
372
        if (errorState.test(i)) {
5,730✔
373
            auto itName = errorNames.find(static_cast<TRegister::TError>(i));
326✔
374
            if (itName != errorNames.end()) {
326✔
375
                errorText += itName->second;
326✔
376
            }
377
        }
378
    }
379
    return errorText;
3,820✔
380
}
381

382
void TDeviceChannel::PublishValueAndError(WBMQTT::TDeviceDriver& deviceDriver,
1,076✔
383
                                          const std::string& value,
384
                                          const std::string& error)
385
{
386
    if (::Debug.IsEnabled()) {
1,076✔
387
        std::stringstream ss;
×
388
        ss << Describe() << " <-- " << value;
×
389
        if (!error.empty()) {
×
390
            ss << ", error: \"" << error << "\"";
×
391
        }
392
        LOG(Debug) << ss.str();
×
393
    }
394
    CachedCurrentValue = value;
1,076✔
395
    CachedErrorText = error;
1,076✔
396
    LastControlUpdate = std::chrono::steady_clock::now();
1,076✔
397
    {
398
        auto tx = deviceDriver.BeginTx();
1,076✔
399
        Control->UpdateRawValueAndError(tx, value, error).Sync();
1,076✔
400
    }
401
}
1,076✔
402

403
void TDeviceChannel::PublishError(WBMQTT::TDeviceDriver& deviceDriver, const std::string& error)
312✔
404
{
405
    if (CachedErrorText.empty() || (CachedErrorText != error)) {
312✔
406
        CachedErrorText = error;
204✔
407
        auto tx = deviceDriver.BeginTx();
204✔
408
        Control->SetError(tx, error).Sync();
204✔
409
    }
410
}
312✔
411

412
std::string TDeviceChannel::GetTextValue() const
1,628✔
413
{
414
    if (Registers.size() == 1) {
1,628✔
415
        if (!OnValue.empty()) {
1,500✔
416
            if (ConvertFromRawValue(*Registers.front()->GetConfig(), Registers.front()->GetValue()) == OnValue) {
62✔
417
                LOG(Debug) << "OnValue: " << OnValue << "; value: 1";
24✔
418
                return "1";
24✔
419
            }
420
        }
421
        if (!OffValue.empty()) {
1,476✔
422
            if (ConvertFromRawValue(*Registers.front()->GetConfig(), Registers.front()->GetValue()) == OffValue) {
38✔
423
                LOG(Debug) << "OnValue: " << OffValue << "; value: 0";
8✔
424
                return "0";
8✔
425
            }
426
        }
427
    }
428
    std::string value;
3,192✔
429
    bool first = true;
1,596✔
430
    for (const auto& r: Registers) {
3,448✔
431
        if (!first) {
1,852✔
432
            value += ";";
256✔
433
        }
434
        first = false;
1,852✔
435
        value += ConvertFromRawValue(*r->GetConfig(), r->GetValue());
1,852✔
436
    }
437
    return value;
1,596✔
438
}
439

440
bool TDeviceChannel::HasValuesOfAllRegisters() const
1,652✔
441
{
442
    for (const auto& r: Registers) {
3,572✔
443
        if (r->GetAvailable() == TRegisterAvailability::UNKNOWN) {
1,944✔
444
            return false;
24✔
445
        }
446
    }
447
    return true;
1,628✔
448
}
449

450
bool TDeviceChannel::ShouldNotPublishPressCounter() const
1,628✔
451
{
452
    for (const auto& r: Registers) {
3,512✔
453
        if (r->GetConfig()->TypeName == "press_counter" && !PublishNextZeroPressCounter) {
1,884✔
454
            try {
455
                if (r->GetValue().Get<uint16_t>() == 0) {
×
456
                    return true;
×
457
                }
458
            } catch (const TRegisterValueException&) {
×
459
            }
460
        }
461
    }
462
    return false;
1,628✔
463
}
464

465
void TDeviceChannel::DoNotPublishNextZeroPressCounter()
44✔
466
{
467
    PublishNextZeroPressCounter = false;
44✔
468
}
44✔
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