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

wirenboard / wb-mqtt-serial / 8

03 Jul 2025 11:10AM UTC coverage: 73.733%. First build
8

push

github

u236
refactor device search/create routines

6427 of 9054 branches covered (70.99%)

0 of 60 new or added lines in 3 files covered. (0.0%)

12278 of 16652 relevant lines covered (73.73%)

306.65 hits per line

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

26.1
/src/rpc/rpc_device_load_config_task.cpp
1
#include "rpc_device_load_config_task.h"
2
#include "config_merge_template.h"
3
#include "rpc_helpers.h"
4
#include "serial_port.h"
5
#include "wb_registers.h"
6

7
#define LOG(logger) ::logger.Log() << "[RPC] "
8

9
namespace
10
{
11
    const auto MAX_RETRIES = 2;
12

13
    bool ReadModbusRegister(Modbus::IModbusTraits& traits,
×
14
                            TPort& port,
15
                            PRPCDeviceLoadConfigRequest rpcRequest,
16
                            PRegisterConfig registerConfig,
17
                            TRegisterValue& value)
18
    {
NEW
19
        uint8_t slaveId = static_cast<uint8_t>(std::stoi(rpcRequest->Device->DeviceConfig()->SlaveId));
×
20
        for (int i = 0; i <= MAX_RETRIES; ++i) {
×
21
            try {
22
                value = Modbus::ReadRegister(traits,
×
23
                                             port,
24
                                             slaveId,
25
                                             *registerConfig,
×
26
                                             std::chrono::microseconds(0),
×
27
                                             rpcRequest->ResponseTimeout,
×
28
                                             rpcRequest->FrameTimeout);
×
29
                return true;
×
30
            } catch (const Modbus::TModbusExceptionError& err) {
×
31
                if (err.GetExceptionCode() == Modbus::ILLEGAL_FUNCTION ||
×
32
                    err.GetExceptionCode() == Modbus::ILLEGAL_DATA_ADDRESS ||
×
33
                    err.GetExceptionCode() == Modbus::ILLEGAL_DATA_VALUE)
×
34
                {
35
                    break;
×
36
                }
37
            } catch (const Modbus::TErrorBase& err) {
×
38
                if (i == MAX_RETRIES) {
×
39
                    throw;
×
40
                }
41
            } catch (const TResponseTimeoutException& e) {
×
42
                if (i == MAX_RETRIES) {
×
43
                    throw;
×
44
                }
45
            }
46
        }
47

48
        return false;
×
49
    }
50

51
    std::string ReadFirmwareVersion(TPort& port, PRPCDeviceLoadConfigRequest rpcRequest)
×
52
    {
53
        if (!rpcRequest->IsWBDevice) {
×
54
            return std::string();
×
55
        }
56
        std::string error;
×
57
        try {
58
            Modbus::TModbusRTUTraits traits;
×
59
            auto config = WbRegisters::GetRegisterConfig(WbRegisters::FW_VERSION_REGISTER_NAME);
×
60
            TRegisterValue value;
×
61
            std::string version;
×
62
            if (ReadModbusRegister(traits, port, rpcRequest, config, value)) {
×
63
                version = value.Get<std::string>();
×
64
            }
65
            return version;
×
66
        } catch (const Modbus::TErrorBase& err) {
×
67
            error = err.what();
×
68
        } catch (const TResponseTimeoutException& e) {
×
69
            error = e.what();
×
70
        }
NEW
71
        LOG(Warn) << port.GetDescription() << " modbus:" << rpcRequest->Device->DeviceConfig()->SlaveId
×
NEW
72
                  << " unable to read \"" << WbRegisters::FW_VERSION_REGISTER_NAME << "\" register: " << error;
×
73
        throw TRPCException(error, TRPCResultCode::RPC_WRONG_PARAM_VALUE);
×
74
    }
75

76
    void ReadParameters(PSerialDevice device, TRPCRegisterList& registerList, Json::Value& parameters)
×
77
    {
78
        if (registerList.size() == 0) {
×
79
            return;
×
80
        }
81
        TRegisterComparePredicate compare;
82
        std::sort(registerList.begin(),
×
83
                  registerList.end(),
84
                  [compare](std::pair<std::string, PRegister>& a, std::pair<std::string, PRegister>& b) {
×
85
                      return compare(b.second, a.second);
×
86
                  });
87

88
        for (int i = 0; i <= MAX_RETRIES; i++) {
×
89
            try {
90
                device->Prepare(TDevicePrepareMode::WITHOUT_SETUP);
×
91
                break;
×
92
            } catch (const TSerialDeviceException& e) {
×
93
                if (i == MAX_RETRIES) {
×
94
                    LOG(Warn) << device->Port()->GetDescription() << " " << device->Protocol()->GetName() << ":"
×
95
                              << device->DeviceConfig()->SlaveId << " unable to prepare session: " << e.what();
×
96
                    throw TRPCException(e.what(), TRPCResultCode::RPC_WRONG_PARAM_VALUE);
×
97
                }
98
            }
99
        }
100

101
        size_t index = 0;
×
102
        bool success = true;
×
103
        while (index < registerList.size() && success) {
×
104
            auto range = device->CreateRegisterRange();
×
105
            auto offset = index;
×
106
            while (index < registerList.size() &&
×
107
                   range->Add(registerList[index].second, std::chrono::milliseconds::max()))
×
108
            {
109
                ++index;
×
110
            }
111
            success = true;
×
112
            for (int i = 0; i <= MAX_RETRIES; ++i) {
×
113
                device->ReadRegisterRange(range);
×
114
                while (offset < index) {
×
115
                    if (registerList[offset++].second->GetErrorState().count()) {
×
116
                        success = false;
×
117
                        break;
×
118
                    }
119
                }
120
                if (success) {
×
121
                    break;
×
122
                }
123
            }
124
        }
125

126
        try {
127
            device->EndSession();
×
128
        } catch (const TSerialDeviceException& e) {
×
129
            LOG(Warn) << device->Port()->GetDescription() << " " << device->Protocol()->GetName() << ":"
×
130
                      << device->DeviceConfig()->SlaveId << " unable to end session: " << e.what();
×
131
        }
132

133
        if (!success) {
×
134
            std::string error = "unable to read parameters register range";
×
135
            LOG(Warn) << device->Port()->GetDescription() << " " << device->Protocol()->GetName() << ":"
×
136
                      << device->DeviceConfig()->SlaveId << " " << error;
×
137
            throw TRPCException(error, TRPCResultCode::RPC_WRONG_PARAM_VALUE);
×
138
        }
139

140
        for (size_t i = 0; i < registerList.size(); ++i) {
×
141
            auto& reg = registerList[i];
×
142
            parameters[reg.first] = RawValueToJSON(*reg.second->GetConfig(), reg.second->GetValue());
×
143
        }
144
    }
145

NEW
146
    void ExecRPCRequest(PPort port, PRPCDeviceLoadConfigRequest rpcRequest)
×
147
    {
148
        if (!rpcRequest->OnResult) {
×
149
            return;
×
150
        }
151

NEW
152
        Json::Value templateParams = rpcRequest->DeviceTemplate->GetTemplate()["parameters"];
×
153
        if (templateParams.empty()) {
×
154
            rpcRequest->OnResult(Json::Value(Json::objectValue));
×
155
            return;
×
156
        }
157

NEW
158
        std::string id = rpcRequest->ParametersCache.GetId(*port, rpcRequest->Device->DeviceConfig()->SlaveId);
×
159
        std::string fwVersion;
×
160
        Json::Value parameters;
×
161
        if (rpcRequest->ParametersCache.Contains(id)) {
×
162
            Json::Value cache = rpcRequest->ParametersCache.Get(id);
×
163
            fwVersion = cache["fw"].asString();
×
164
            parameters = cache["parameters"];
×
165
        }
166
        if (parameters.isNull()) {
×
NEW
167
            for (const auto& item: rpcRequest->Device->GetSetupItems()) {
×
168
                if (!item->ParameterId.empty()) {
×
169
                    parameters[item->ParameterId] = RawValueToJSON(*item->RegisterConfig, item->RawValue);
×
170
                }
171
            }
172
        }
173

174
        port->SkipNoise();
×
175
        if (fwVersion.empty()) {
×
176
            fwVersion = ReadFirmwareVersion(*port, rpcRequest);
×
177
        }
178

179
        std::list<std::string> paramsList;
×
180
        TRPCRegisterList registerList = CreateRegisterList(
NEW
181
            rpcRequest->ProtocolParams,
×
NEW
182
            rpcRequest->Device,
×
183
            rpcRequest->Group.empty() ? templateParams
×
184
                                      : GetTemplateParamsGroup(templateParams, rpcRequest->Group, paramsList),
×
185
            parameters,
186
            fwVersion);
×
NEW
187
        ReadParameters(rpcRequest->Device, registerList, parameters);
×
188

189
        Json::Value result;
×
190
        if (!fwVersion.empty()) {
×
191
            result["fw"] = fwVersion;
×
192
        }
193
        if (!paramsList.empty()) {
×
194
            for (const auto& id: paramsList) {
×
195
                result["parameters"][id] = parameters[id];
×
196
            }
197
        } else {
198
            result["parameters"] = parameters;
×
199
        }
200
        CheckParametersConditions(templateParams, result["parameters"]);
×
201
        rpcRequest->OnResult(result);
×
202

NEW
203
        if (rpcRequest->DeviceFromConfig) {
×
204
            result["parameters"] = parameters;
×
205
            rpcRequest->ParametersCache.Add(id, result);
×
206
        }
207
    }
208
} // namespace
209

NEW
210
TRPCDeviceLoadConfigRequest::TRPCDeviceLoadConfigRequest(const TDeviceProtocolParams& protocolParams,
×
211
                                                         PSerialDevice device,
212
                                                         PDeviceTemplate deviceTemplate,
213
                                                         bool deviceFromConfig,
214
                                                         TRPCDeviceParametersCache& parametersCache)
×
215
    : ProtocolParams(protocolParams),
216
      Device(device),
217
      DeviceTemplate(deviceTemplate),
218
      DeviceFromConfig(deviceFromConfig),
219
      ParametersCache(parametersCache)
×
220
{
221
    IsWBDevice =
×
222
        !deviceTemplate->GetHardware().empty() || deviceTemplate->GetTemplate()["enable_wb_continuous_read"].asBool();
×
223

224
    Json::Value responseTimeout = deviceTemplate->GetTemplate()["response_timeout_ms"];
×
225
    if (responseTimeout.isInt()) {
×
226
        ResponseTimeout = std::chrono::milliseconds(responseTimeout.asInt());
×
227
    }
228

229
    Json::Value frameTimeout = deviceTemplate->GetTemplate()["frame_timeout_ms"];
×
230
    if (frameTimeout.isInt()) {
×
231
        FrameTimeout = std::chrono::milliseconds(frameTimeout.asInt());
×
232
    }
233
}
234

235
PRPCDeviceLoadConfigRequest ParseRPCDeviceLoadConfigRequest(const Json::Value& request,
×
236
                                                            const TDeviceProtocolParams& protocolParams,
237
                                                            PSerialDevice device,
238
                                                            PDeviceTemplate deviceTemplate,
239
                                                            bool deviceFromConfig,
240
                                                            TRPCDeviceParametersCache& parametersCache,
241
                                                            WBMQTT::TMqttRpcServer::TResultCallback onResult,
242
                                                            WBMQTT::TMqttRpcServer::TErrorCallback onError)
243
{
244
    auto res = std::make_shared<TRPCDeviceLoadConfigRequest>(protocolParams,
245
                                                             device,
246
                                                             deviceTemplate,
247
                                                             deviceFromConfig,
NEW
248
                                                             parametersCache);
×
249
    res->SerialPortSettings = ParseRPCSerialPortSettings(request);
×
250
    res->Group = request["group"].asString();
×
251
    WBMQTT::JSON::Get(request, "response_timeout", res->ResponseTimeout);
×
252
    WBMQTT::JSON::Get(request, "frame_timeout", res->FrameTimeout);
×
253
    WBMQTT::JSON::Get(request, "total_timeout", res->TotalTimeout);
×
254
    res->OnResult = onResult;
×
255
    res->OnError = onError;
×
256
    return res;
×
257
}
258

259
TRPCDeviceLoadConfigSerialClientTask::TRPCDeviceLoadConfigSerialClientTask(PRPCDeviceLoadConfigRequest request)
×
260
    : Request(request)
×
261
{
262
    ExpireTime = std::chrono::steady_clock::now() + Request->TotalTimeout;
×
263
}
264

265
ISerialClientTask::TRunResult TRPCDeviceLoadConfigSerialClientTask::Run(
×
266
    PPort port,
267
    TSerialClientDeviceAccessHandler& lastAccessedDevice,
268
    const std::list<PSerialDevice>& polledDevices)
269
{
270
    if (std::chrono::steady_clock::now() > ExpireTime) {
×
271
        if (Request->OnError) {
×
272
            Request->OnError(WBMQTT::E_RPC_REQUEST_TIMEOUT, "RPC request timeout");
×
273
        }
274
        return ISerialClientTask::TRunResult::OK;
×
275
    }
276

277
    try {
278
        if (!port->IsOpen()) {
×
279
            port->Open();
×
280
        }
281
        lastAccessedDevice.PrepareToAccess(nullptr);
×
NEW
282
        if (!Request->DeviceFromConfig) {
×
NEW
283
            TSerialPortSettingsGuard settingsGuard(port, Request->SerialPortSettings);
×
284
        }
NEW
285
        ExecRPCRequest(port, Request);
×
286
    } catch (const std::exception& error) {
×
287
        if (Request->OnError) {
×
288
            Request->OnError(WBMQTT::E_RPC_SERVER_ERROR, std::string("Port IO error: ") + error.what());
×
289
        }
290
    }
291

292
    return ISerialClientTask::TRunResult::OK;
×
293
}
294

295
Json::Value GetTemplateParamsGroup(const Json::Value& templateParams,
2✔
296
                                   const std::string& group,
297
                                   std::list<std::string>& paramsList)
298
{
299
    Json::Value result;
2✔
300
    std::list<std::string> conditionList;
4✔
301
    bool check = true;
2✔
302
    while (check) {
7✔
303
        check = false;
5✔
304
        for (auto it = templateParams.begin(); it != templateParams.end(); ++it) {
43✔
305
            const Json::Value& data = *it;
38✔
306
            std::string id = templateParams.isObject() ? it.key().asString() : data["id"].asString();
38✔
307
            if (std::find(conditionList.begin(), conditionList.end(), id) == conditionList.end() &&
69✔
308
                data["group"].asString() != group)
69✔
309
            {
310
                continue;
12✔
311
            }
312
            if (std::find(paramsList.begin(), paramsList.end(), id) == paramsList.end()) {
26✔
313
                paramsList.push_back(id);
11✔
314
                result[id] = data;
11✔
315
            }
316
            if (data["condition"].isNull()) {
26✔
317
                continue;
15✔
318
            }
319
            Expressions::TLexer lexer;
11✔
320
            auto tokens = lexer.GetTokens(data["condition"].asString());
22✔
321
            for (const auto& token: tokens) {
55✔
322
                if (token.Type == Expressions::TTokenType::Ident && token.Value != "isDefined" &&
55✔
323
                    std::find(conditionList.begin(), conditionList.end(), token.Value) == conditionList.end())
55✔
324
                {
325
                    conditionList.push_back(token.Value);
4✔
326
                    check = true;
4✔
327
                }
328
            }
329
        }
330
    }
331
    return result;
4✔
332
}
333

334
TRPCRegisterList CreateRegisterList(const TDeviceProtocolParams& protocolParams,
4✔
335
                                    const PSerialDevice& device,
336
                                    const Json::Value& templateParams,
337
                                    const Json::Value& parameters,
338
                                    const std::string& fwVersion)
339
{
340
    TRPCRegisterList registerList;
4✔
341
    for (auto it = templateParams.begin(); it != templateParams.end(); ++it) {
30✔
342
        const Json::Value& data = *it;
26✔
343
        std::string id = templateParams.isObject() ? it.key().asString() : data["id"].asString();
26✔
344
        bool duplicate = false;
26✔
345
        for (const auto& item: registerList) {
67✔
346
            if (item.first == id) {
42✔
347
                duplicate = true;
1✔
348
                break;
1✔
349
            }
350
        }
351
        if (duplicate || data["address"].isNull() || data["readonly"].asBool() || !parameters[id].isNull()) {
26✔
352
            continue;
5✔
353
        }
354
        if (!fwVersion.empty()) {
21✔
355
            std::string fw = data["fw"].asString();
12✔
356
            if (!fw.empty() && util::CompareVersionStrings(fw, fwVersion) > 0) {
12✔
357
                continue;
4✔
358
            }
359
        }
360
        auto config = LoadRegisterConfig(data,
361
                                         *protocolParams.protocol->GetRegTypes(),
17✔
362
                                         std::string(),
34✔
363
                                         *protocolParams.factory,
17✔
364
                                         protocolParams.factory->GetRegisterAddressFactory().GetBaseRegisterAddress(),
17✔
365
                                         0);
34✔
366
        auto reg = std::make_shared<TRegister>(device, config.RegisterConfig);
17✔
367
        reg->SetAvailable(TRegisterAvailability::AVAILABLE);
17✔
368
        registerList.push_back(std::make_pair(id, reg));
17✔
369
    }
370
    return registerList;
4✔
371
}
372

373
void CheckParametersConditions(const Json::Value& templateParams, Json::Value& parameters)
2✔
374
{
375
    TJsonParams jsonParams(parameters);
4✔
376
    TExpressionsCache expressionsCache;
4✔
377
    bool check = true;
2✔
378
    while (check) {
6✔
379
        std::unordered_map<std::string, bool> matches;
8✔
380
        for (auto it = templateParams.begin(); it != templateParams.end(); ++it) {
34✔
381
            const Json::Value& registerData = *it;
30✔
382
            std::string id = templateParams.isObject() ? it.key().asString() : registerData["id"].asString();
30✔
383
            if (!parameters.isMember(id)) {
30✔
384
                continue;
15✔
385
            }
386
            bool match = CheckCondition(registerData, jsonParams, &expressionsCache);
15✔
387
            if (matches.find(id) == matches.end() || match) {
15✔
388
                matches[id] = match;
15✔
389
            }
390
        }
391
        check = false;
4✔
392
        for (auto it = matches.begin(); it != matches.end(); ++it) {
17✔
393
            if (!it->second) {
13✔
394
                parameters.removeMember(it->first);
3✔
395
                check = true;
3✔
396
            }
397
        }
398
    }
399
    if (parameters.isNull()) {
2✔
400
        parameters = Json::Value(Json::objectValue);
×
401
    }
402
}
2✔
403

404
Json::Value RawValueToJSON(const TRegisterConfig& reg, TRegisterValue val)
×
405
{
406
    switch (reg.Format) {
×
407
        case U8:
×
408
            return val.Get<uint8_t>();
×
409
        case S8:
×
410
            return val.Get<int8_t>();
×
411
        case S16:
×
412
            return val.Get<int16_t>();
×
413
        case S24: {
×
414
            uint32_t v = val.Get<uint64_t>() & 0xffffff;
×
415
            if (v & 0x800000)
×
416
                v |= 0xff000000;
×
417
            return static_cast<int32_t>(v);
×
418
        }
419
        case S32:
×
420
            return val.Get<int32_t>();
×
421
        case S64:
×
422
            return val.Get<int64_t>();
×
423
        case BCD8:
×
424
            return PackedBCD2Int(val.Get<uint64_t>(), WordSizes::W8_SZ);
×
425
        case BCD16:
×
426
            return PackedBCD2Int(val.Get<uint64_t>(), WordSizes::W16_SZ);
×
427
        case BCD24:
×
428
            return PackedBCD2Int(val.Get<uint64_t>(), WordSizes::W24_SZ);
×
429
        case BCD32:
×
430
            return PackedBCD2Int(val.Get<uint64_t>(), WordSizes::W32_SZ);
×
431
        case Float: {
×
432
            float v;
433
            auto rawValue = val.Get<uint64_t>();
×
434

435
            // codacy static code analysis fails on this memcpy call, have no idea how to fix it
436
            memcpy(&v, &rawValue, sizeof(v));
×
437

438
            return v;
×
439
        }
440
        case Double: {
×
441
            double v;
442
            auto rawValue = val.Get<uint64_t>();
×
443
            memcpy(&v, &rawValue, sizeof(v));
×
444
            return v;
×
445
        }
446
        case Char8:
×
447
            return std::string(1, val.Get<uint8_t>());
×
448
        case String:
×
449
        case String8:
450
            return val.Get<std::string>();
×
451
        default:
×
452
            return val.Get<uint64_t>();
×
453
    }
454
}
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