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

wirenboard / wb-mqtt-serial / 7

02 Jul 2025 12:44PM UTC coverage: 73.768% (-0.07%) from 73.835%
7

Pull #956

github

u236
move ExecRPCDeviceLoadConfigRequest to namespace
Pull Request #956: Refactor device/LoadConfig RPC

6427 of 9048 branches covered (71.03%)

0 of 86 new or added lines in 4 files covered. (0.0%)

3 existing lines in 2 files now uncovered.

12278 of 16644 relevant lines covered (73.77%)

306.79 hits per line

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

23.91
/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
    PSerialDevice CreateDevice(PPort port,
×
14
                               PRPCDeviceLoadConfigRequest rpcRequest,
15
                               PDeviceTemplate deviceTemplate,
16
                               const TDeviceProtocolParams& protocolParams)
17
    {
NEW
18
        auto config = std::make_shared<TDeviceConfig>("RPC Device", rpcRequest->SlaveId, deviceTemplate->GetProtocol());
×
NEW
19
        if (deviceTemplate->GetProtocol() == "modbus") {
×
20
            config->MaxRegHole = Modbus::MAX_HOLE_CONTINUOUS_16_BIT_REGISTERS;
×
21
            config->MaxBitHole = Modbus::MAX_HOLE_CONTINUOUS_1_BIT_REGISTERS;
×
22
            config->MaxReadRegisters = Modbus::MAX_READ_REGISTERS;
×
23
        }
NEW
24
        return protocolParams.factory->CreateDevice(deviceTemplate->GetTemplate(),
×
25
                                                    config,
26
                                                    port,
27
                                                    protocolParams.protocol);
×
28
    }
29

30
    bool ReadModbusRegister(Modbus::IModbusTraits& traits,
×
31
                            TPort& port,
32
                            PRPCDeviceLoadConfigRequest rpcRequest,
33
                            PRegisterConfig registerConfig,
34
                            TRegisterValue& value)
35
    {
36
        uint8_t slaveId = static_cast<uint8_t>(std::stoi(rpcRequest->SlaveId));
×
37
        for (int i = 0; i <= MAX_RETRIES; ++i) {
×
38
            try {
39
                value = Modbus::ReadRegister(traits,
×
40
                                             port,
41
                                             slaveId,
42
                                             *registerConfig,
×
43
                                             std::chrono::microseconds(0),
×
44
                                             rpcRequest->ResponseTimeout,
×
45
                                             rpcRequest->FrameTimeout);
×
46
                return true;
×
47
            } catch (const Modbus::TModbusExceptionError& err) {
×
48
                if (err.GetExceptionCode() == Modbus::ILLEGAL_FUNCTION ||
×
49
                    err.GetExceptionCode() == Modbus::ILLEGAL_DATA_ADDRESS ||
×
50
                    err.GetExceptionCode() == Modbus::ILLEGAL_DATA_VALUE)
×
51
                {
52
                    break;
×
53
                }
54
            } catch (const Modbus::TErrorBase& err) {
×
55
                if (i == MAX_RETRIES) {
×
56
                    throw;
×
57
                }
58
            } catch (const TResponseTimeoutException& e) {
×
59
                if (i == MAX_RETRIES) {
×
60
                    throw;
×
61
                }
62
            }
63
        }
64

65
        return false;
×
66
    }
67

68
    std::string ReadFirmwareVersion(TPort& port, PRPCDeviceLoadConfigRequest rpcRequest)
×
69
    {
NEW
70
        if (!rpcRequest->IsWBDevice) {
×
71
            return std::string();
×
72
        }
73
        std::string error;
×
74
        try {
75
            Modbus::TModbusRTUTraits traits;
×
76
            auto config = WbRegisters::GetRegisterConfig(WbRegisters::FW_VERSION_REGISTER_NAME);
×
77
            TRegisterValue value;
×
78
            std::string version;
×
79
            if (ReadModbusRegister(traits, port, rpcRequest, config, value)) {
×
80
                version = value.Get<std::string>();
×
81
            }
82
            return version;
×
83
        } catch (const Modbus::TErrorBase& err) {
×
84
            error = err.what();
×
85
        } catch (const TResponseTimeoutException& e) {
×
86
            error = e.what();
×
87
        }
88
        LOG(Warn) << port.GetDescription() << " modbus:" << rpcRequest->SlaveId << " unable to read \""
×
89
                  << WbRegisters::FW_VERSION_REGISTER_NAME << "\" register: " << error;
×
90
        throw TRPCException(error, TRPCResultCode::RPC_WRONG_PARAM_VALUE);
×
91
    }
92

93
    void ReadParameters(PSerialDevice device, TRPCRegisterList& registerList, Json::Value& parameters)
×
94
    {
95
        if (registerList.size() == 0) {
×
96
            return;
×
97
        }
98
        TRegisterComparePredicate compare;
99
        std::sort(registerList.begin(),
×
100
                  registerList.end(),
101
                  [compare](std::pair<std::string, PRegister>& a, std::pair<std::string, PRegister>& b) {
×
102
                      return compare(b.second, a.second);
×
103
                  });
104

105
        for (int i = 0; i <= MAX_RETRIES; i++) {
×
106
            try {
107
                device->Prepare(TDevicePrepareMode::WITHOUT_SETUP);
×
108
                break;
×
109
            } catch (const TSerialDeviceException& e) {
×
110
                if (i == MAX_RETRIES) {
×
111
                    LOG(Warn) << device->Port()->GetDescription() << " " << device->Protocol()->GetName() << ":"
×
112
                              << device->DeviceConfig()->SlaveId << " unable to prepare session: " << e.what();
×
113
                    throw TRPCException(e.what(), TRPCResultCode::RPC_WRONG_PARAM_VALUE);
×
114
                }
115
            }
116
        }
117

118
        size_t index = 0;
×
119
        bool success = true;
×
120
        while (index < registerList.size() && success) {
×
121
            auto range = device->CreateRegisterRange();
×
122
            auto offset = index;
×
123
            while (index < registerList.size() &&
×
124
                   range->Add(registerList[index].second, std::chrono::milliseconds::max()))
×
125
            {
126
                ++index;
×
127
            }
128
            success = true;
×
129
            for (int i = 0; i <= MAX_RETRIES; ++i) {
×
130
                device->ReadRegisterRange(range);
×
131
                while (offset < index) {
×
132
                    if (registerList[offset++].second->GetErrorState().count()) {
×
133
                        success = false;
×
134
                        break;
×
135
                    }
136
                }
137
                if (success) {
×
138
                    break;
×
139
                }
140
            }
141
        }
142

143
        try {
144
            device->EndSession();
×
145
        } catch (const TSerialDeviceException& e) {
×
146
            LOG(Warn) << device->Port()->GetDescription() << " " << device->Protocol()->GetName() << ":"
×
147
                      << device->DeviceConfig()->SlaveId << " unable to end session: " << e.what();
×
148
        }
149

150
        if (!success) {
×
151
            std::string error = "unable to read parameters register range";
×
152
            LOG(Warn) << device->Port()->GetDescription() << " " << device->Protocol()->GetName() << ":"
×
153
                      << device->DeviceConfig()->SlaveId << " " << error;
×
154
            throw TRPCException(error, TRPCResultCode::RPC_WRONG_PARAM_VALUE);
×
155
        }
156

157
        for (size_t i = 0; i < registerList.size(); ++i) {
×
158
            auto& reg = registerList[i];
×
159
            parameters[reg.first] = RawValueToJSON(*reg.second->GetConfig(), reg.second->GetValue());
×
160
        }
161
    }
162

NEW
163
    void ExecRPCRequest(PPort port, PSerialDevice device, PRPCDeviceLoadConfigRequest rpcRequest)
×
164
    {
NEW
165
        if (!rpcRequest->OnResult) {
×
NEW
166
            return;
×
167
        }
168

NEW
169
        rpcRequest->UpdateParams(device);
×
NEW
170
        auto deviceTemplate = rpcRequest->GetDeviceTemplate();
×
NEW
171
        Json::Value templateParams = deviceTemplate->GetTemplate()["parameters"];
×
NEW
172
        if (templateParams.empty()) {
×
NEW
173
            rpcRequest->OnResult(Json::Value(Json::objectValue));
×
NEW
174
            return;
×
175
        }
176

177
        TDeviceProtocolParams protocolParams =
NEW
178
            rpcRequest->DeviceFactory.GetProtocolParams(deviceTemplate->GetProtocol());
×
NEW
179
        bool useCache = true;
×
NEW
180
        if (device == nullptr) {
×
NEW
181
            device = CreateDevice(port, rpcRequest, deviceTemplate, protocolParams);
×
NEW
182
            useCache = false;
×
183
        }
184

NEW
185
        std::string id = rpcRequest->ParametersCache.GetId(*port, rpcRequest->SlaveId);
×
NEW
186
        std::string fwVersion;
×
NEW
187
        Json::Value parameters;
×
NEW
188
        if (rpcRequest->ParametersCache.Contains(id)) {
×
NEW
189
            Json::Value cache = rpcRequest->ParametersCache.Get(id);
×
NEW
190
            fwVersion = cache["fw"].asString();
×
NEW
191
            parameters = cache["parameters"];
×
192
        }
NEW
193
        if (parameters.isNull()) {
×
NEW
194
            for (const auto& item: device->GetSetupItems()) {
×
NEW
195
                if (!item->ParameterId.empty()) {
×
NEW
196
                    parameters[item->ParameterId] = RawValueToJSON(*item->RegisterConfig, item->RawValue);
×
197
                }
198
            }
199
        }
200

NEW
201
        port->SkipNoise();
×
NEW
202
        if (fwVersion.empty()) {
×
NEW
203
            fwVersion = ReadFirmwareVersion(*port, rpcRequest);
×
204
        }
205

NEW
206
        std::list<std::string> paramsList;
×
207
        TRPCRegisterList registerList = CreateRegisterList(
208
            protocolParams,
209
            device,
NEW
210
            rpcRequest->Group.empty() ? templateParams
×
NEW
211
                                      : GetTemplateParamsGroup(templateParams, rpcRequest->Group, paramsList),
×
212
            parameters,
NEW
213
            fwVersion);
×
NEW
214
        ReadParameters(device, registerList, parameters);
×
215

NEW
216
        Json::Value result;
×
NEW
217
        if (!fwVersion.empty()) {
×
NEW
218
            result["fw"] = fwVersion;
×
219
        }
NEW
220
        if (!paramsList.empty()) {
×
NEW
221
            for (const auto& id: paramsList) {
×
NEW
222
                result["parameters"][id] = parameters[id];
×
223
            }
224
        } else {
NEW
225
            result["parameters"] = parameters;
×
226
        }
NEW
227
        CheckParametersConditions(templateParams, result["parameters"]);
×
NEW
228
        rpcRequest->OnResult(result);
×
229

NEW
230
        if (useCache) {
×
NEW
231
            result["parameters"] = parameters;
×
NEW
232
            rpcRequest->ParametersCache.Add(id, result);
×
233
        }
234
    }
235
} // namespace
236

237
TRPCDeviceLoadConfigRequest::TRPCDeviceLoadConfigRequest(const TSerialDeviceFactory& deviceFactory,
×
238
                                                         PTemplateMap templates,
239
                                                         TRPCDeviceParametersCache& parametersCache)
×
240
    : DeviceFactory(deviceFactory),
241
      Templates(templates),
242
      ParametersCache(parametersCache)
×
243
{}
244

NEW
245
void TRPCDeviceLoadConfigRequest::UpdateParams(PSerialDevice device)
×
246
{
NEW
247
    if (device == nullptr)
×
NEW
248
        return;
×
249

NEW
250
    SlaveId = device->DeviceConfig()->SlaveId;
×
NEW
251
    DeviceType = device->DeviceConfig()->DeviceType;
×
252
}
253

NEW
254
PDeviceTemplate TRPCDeviceLoadConfigRequest::GetDeviceTemplate()
×
255
{
NEW
256
    auto deviceTemplate = Templates->GetTemplate(DeviceType);
×
NEW
257
    if (deviceTemplate->WithSubdevices()) {
×
NEW
258
        throw TRPCException("Device \"" + DeviceType + "\" is not supported by this RPC",
×
NEW
259
                            TRPCResultCode::RPC_WRONG_PARAM_VALUE);
×
260
    }
261

262
    IsWBDevice =
×
NEW
263
        !deviceTemplate->GetHardware().empty() || deviceTemplate->GetTemplate()["enable_wb_continuous_read"].asBool();
×
264

NEW
265
    Json::Value responseTimeout = deviceTemplate->GetTemplate()["response_timeout_ms"];
×
266
    if (responseTimeout.isInt()) {
×
267
        ResponseTimeout = std::chrono::milliseconds(responseTimeout.asInt());
×
268
    }
269

NEW
270
    Json::Value frameTimeout = deviceTemplate->GetTemplate()["frame_timeout_ms"];
×
271
    if (frameTimeout.isInt()) {
×
272
        FrameTimeout = std::chrono::milliseconds(frameTimeout.asInt());
×
273
    }
274

NEW
275
    return deviceTemplate;
×
276
}
277

278
PRPCDeviceLoadConfigRequest ParseRPCDeviceLoadConfigRequest(const Json::Value& request,
×
279
                                                            const TSerialDeviceFactory& deviceFactory,
280
                                                            PTemplateMap templates,
281
                                                            TRPCDeviceParametersCache& parametersCache,
282
                                                            WBMQTT::TMqttRpcServer::TResultCallback onResult,
283
                                                            WBMQTT::TMqttRpcServer::TErrorCallback onError)
284
{
NEW
285
    auto res = std::make_shared<TRPCDeviceLoadConfigRequest>(deviceFactory, templates, parametersCache);
×
286
    res->SerialPortSettings = ParseRPCSerialPortSettings(request);
×
287
    res->SlaveId = request["slave_id"].asString();
×
NEW
288
    res->DeviceType = request["device_type"].asString();
×
289
    res->Group = request["group"].asString();
×
290
    WBMQTT::JSON::Get(request, "response_timeout", res->ResponseTimeout);
×
291
    WBMQTT::JSON::Get(request, "frame_timeout", res->FrameTimeout);
×
292
    WBMQTT::JSON::Get(request, "total_timeout", res->TotalTimeout);
×
NEW
293
    res->OnResult = onResult;
×
NEW
294
    res->OnError = onError;
×
UNCOV
295
    return res;
×
296
}
297

298
TRPCDeviceLoadConfigSerialClientTask::TRPCDeviceLoadConfigSerialClientTask(PRPCDeviceLoadConfigRequest request)
×
UNCOV
299
    : Request(request)
×
300
{
301
    ExpireTime = std::chrono::steady_clock::now() + Request->TotalTimeout;
×
302
}
303

304
ISerialClientTask::TRunResult TRPCDeviceLoadConfigSerialClientTask::Run(
×
305
    PPort port,
306
    TSerialClientDeviceAccessHandler& lastAccessedDevice,
307
    const std::list<PSerialDevice>& polledDevices)
308
{
309
    if (std::chrono::steady_clock::now() > ExpireTime) {
×
310
        if (Request->OnError) {
×
311
            Request->OnError(WBMQTT::E_RPC_REQUEST_TIMEOUT, "RPC request timeout");
×
312
        }
313
        return ISerialClientTask::TRunResult::OK;
×
314
    }
315

316
    try {
317
        if (!port->IsOpen()) {
×
318
            port->Open();
×
319
        }
320
        lastAccessedDevice.PrepareToAccess(nullptr);
×
321
        TSerialPortSettingsGuard settingsGuard(port, Request->SerialPortSettings);
×
NEW
322
        ExecRPCRequest(port, Device, Request);
×
323
    } catch (const std::exception& error) {
×
324
        if (Request->OnError) {
×
325
            Request->OnError(WBMQTT::E_RPC_SERVER_ERROR, std::string("Port IO error: ") + error.what());
×
326
        }
327
    }
328

329
    return ISerialClientTask::TRunResult::OK;
×
330
}
331

332
Json::Value GetTemplateParamsGroup(const Json::Value& templateParams,
2✔
333
                                   const std::string& group,
334
                                   std::list<std::string>& paramsList)
335
{
336
    Json::Value result;
2✔
337
    std::list<std::string> conditionList;
4✔
338
    bool check = true;
2✔
339
    while (check) {
7✔
340
        check = false;
5✔
341
        for (auto it = templateParams.begin(); it != templateParams.end(); ++it) {
43✔
342
            const Json::Value& data = *it;
38✔
343
            std::string id = templateParams.isObject() ? it.key().asString() : data["id"].asString();
38✔
344
            if (std::find(conditionList.begin(), conditionList.end(), id) == conditionList.end() &&
69✔
345
                data["group"].asString() != group)
69✔
346
            {
347
                continue;
12✔
348
            }
349
            if (std::find(paramsList.begin(), paramsList.end(), id) == paramsList.end()) {
26✔
350
                paramsList.push_back(id);
11✔
351
                result[id] = data;
11✔
352
            }
353
            if (data["condition"].isNull()) {
26✔
354
                continue;
15✔
355
            }
356
            Expressions::TLexer lexer;
11✔
357
            auto tokens = lexer.GetTokens(data["condition"].asString());
22✔
358
            for (const auto& token: tokens) {
55✔
359
                if (token.Type == Expressions::TTokenType::Ident && token.Value != "isDefined" &&
55✔
360
                    std::find(conditionList.begin(), conditionList.end(), token.Value) == conditionList.end())
55✔
361
                {
362
                    conditionList.push_back(token.Value);
4✔
363
                    check = true;
4✔
364
                }
365
            }
366
        }
367
    }
368
    return result;
4✔
369
}
370

371
TRPCRegisterList CreateRegisterList(const TDeviceProtocolParams& protocolParams,
4✔
372
                                    const PSerialDevice& device,
373
                                    const Json::Value& templateParams,
374
                                    const Json::Value& parameters,
375
                                    const std::string& fwVersion)
376
{
377
    TRPCRegisterList registerList;
4✔
378
    for (auto it = templateParams.begin(); it != templateParams.end(); ++it) {
30✔
379
        const Json::Value& data = *it;
26✔
380
        std::string id = templateParams.isObject() ? it.key().asString() : data["id"].asString();
26✔
381
        bool duplicate = false;
26✔
382
        for (const auto& item: registerList) {
67✔
383
            if (item.first == id) {
42✔
384
                duplicate = true;
1✔
385
                break;
1✔
386
            }
387
        }
388
        if (duplicate || data["address"].isNull() || data["readonly"].asBool() || !parameters[id].isNull()) {
26✔
389
            continue;
5✔
390
        }
391
        if (!fwVersion.empty()) {
21✔
392
            std::string fw = data["fw"].asString();
12✔
393
            if (!fw.empty() && util::CompareVersionStrings(fw, fwVersion) > 0) {
12✔
394
                continue;
4✔
395
            }
396
        }
397
        auto config = LoadRegisterConfig(data,
398
                                         *protocolParams.protocol->GetRegTypes(),
17✔
399
                                         std::string(),
34✔
400
                                         *protocolParams.factory,
17✔
401
                                         protocolParams.factory->GetRegisterAddressFactory().GetBaseRegisterAddress(),
17✔
402
                                         0);
34✔
403
        auto reg = std::make_shared<TRegister>(device, config.RegisterConfig);
17✔
404
        reg->SetAvailable(TRegisterAvailability::AVAILABLE);
17✔
405
        registerList.push_back(std::make_pair(id, reg));
17✔
406
    }
407
    return registerList;
4✔
408
}
409

410
void CheckParametersConditions(const Json::Value& templateParams, Json::Value& parameters)
2✔
411
{
412
    TJsonParams jsonParams(parameters);
4✔
413
    TExpressionsCache expressionsCache;
4✔
414
    bool check = true;
2✔
415
    while (check) {
6✔
416
        std::unordered_map<std::string, bool> matches;
8✔
417
        for (auto it = templateParams.begin(); it != templateParams.end(); ++it) {
34✔
418
            const Json::Value& registerData = *it;
30✔
419
            std::string id = templateParams.isObject() ? it.key().asString() : registerData["id"].asString();
30✔
420
            if (!parameters.isMember(id)) {
30✔
421
                continue;
15✔
422
            }
423
            bool match = CheckCondition(registerData, jsonParams, &expressionsCache);
15✔
424
            if (matches.find(id) == matches.end() || match) {
15✔
425
                matches[id] = match;
15✔
426
            }
427
        }
428
        check = false;
4✔
429
        for (auto it = matches.begin(); it != matches.end(); ++it) {
17✔
430
            if (!it->second) {
13✔
431
                parameters.removeMember(it->first);
3✔
432
                check = true;
3✔
433
            }
434
        }
435
    }
436
    if (parameters.isNull()) {
2✔
437
        parameters = Json::Value(Json::objectValue);
×
438
    }
439
}
2✔
440

441
Json::Value RawValueToJSON(const TRegisterConfig& reg, TRegisterValue val)
×
442
{
443
    switch (reg.Format) {
×
444
        case U8:
×
445
            return val.Get<uint8_t>();
×
446
        case S8:
×
447
            return val.Get<int8_t>();
×
448
        case S16:
×
449
            return val.Get<int16_t>();
×
450
        case S24: {
×
451
            uint32_t v = val.Get<uint64_t>() & 0xffffff;
×
452
            if (v & 0x800000)
×
453
                v |= 0xff000000;
×
454
            return static_cast<int32_t>(v);
×
455
        }
456
        case S32:
×
457
            return val.Get<int32_t>();
×
458
        case S64:
×
459
            return val.Get<int64_t>();
×
460
        case BCD8:
×
461
            return PackedBCD2Int(val.Get<uint64_t>(), WordSizes::W8_SZ);
×
462
        case BCD16:
×
463
            return PackedBCD2Int(val.Get<uint64_t>(), WordSizes::W16_SZ);
×
464
        case BCD24:
×
465
            return PackedBCD2Int(val.Get<uint64_t>(), WordSizes::W24_SZ);
×
466
        case BCD32:
×
467
            return PackedBCD2Int(val.Get<uint64_t>(), WordSizes::W32_SZ);
×
468
        case Float: {
×
469
            float v;
470
            auto rawValue = val.Get<uint64_t>();
×
471

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

475
            return v;
×
476
        }
477
        case Double: {
×
478
            double v;
479
            auto rawValue = val.Get<uint64_t>();
×
480
            memcpy(&v, &rawValue, sizeof(v));
×
481
            return v;
×
482
        }
483
        case Char8:
×
484
            return std::string(1, val.Get<uint8_t>());
×
485
        case String:
×
486
        case String8:
487
            return val.Get<std::string>();
×
488
        default:
×
489
            return val.Get<uint64_t>();
×
490
    }
491
}
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

© 2025 Coveralls, Inc