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

wirenboard / wb-mqtt-serial / 6

02 Jul 2025 11:08AM UTC coverage: 73.768% (-0.07%) from 73.835%
6

Pull #956

github

u236
update changelog
Pull Request #956: Refactor device/LoadConfig RPC

6427 of 9048 branches covered (71.03%)

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

2 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
} // namespace
163

164
TRPCDeviceLoadConfigRequest::TRPCDeviceLoadConfigRequest(const TSerialDeviceFactory& deviceFactory,
×
165
                                                         PTemplateMap templates,
166
                                                         TRPCDeviceParametersCache& parametersCache)
×
167
    : DeviceFactory(deviceFactory),
168
      Templates(templates),
169
      ParametersCache(parametersCache)
×
170
{}
171

NEW
172
void TRPCDeviceLoadConfigRequest::UpdateParams(PSerialDevice device)
×
173
{
NEW
174
    if (device == nullptr)
×
NEW
175
        return;
×
176

NEW
177
    SlaveId = device->DeviceConfig()->SlaveId;
×
NEW
178
    DeviceType = device->DeviceConfig()->DeviceType;
×
179
}
180

NEW
181
PDeviceTemplate TRPCDeviceLoadConfigRequest::GetDeviceTemplate()
×
182
{
NEW
183
    auto deviceTemplate = Templates->GetTemplate(DeviceType);
×
NEW
184
    if (deviceTemplate->WithSubdevices()) {
×
NEW
185
        throw TRPCException("Device \"" + DeviceType + "\" is not supported by this RPC",
×
NEW
186
                            TRPCResultCode::RPC_WRONG_PARAM_VALUE);
×
187
    }
188

189
    IsWBDevice =
×
NEW
190
        !deviceTemplate->GetHardware().empty() || deviceTemplate->GetTemplate()["enable_wb_continuous_read"].asBool();
×
191

NEW
192
    Json::Value responseTimeout = deviceTemplate->GetTemplate()["response_timeout_ms"];
×
193
    if (responseTimeout.isInt()) {
×
194
        ResponseTimeout = std::chrono::milliseconds(responseTimeout.asInt());
×
195
    }
196

NEW
197
    Json::Value frameTimeout = deviceTemplate->GetTemplate()["frame_timeout_ms"];
×
198
    if (frameTimeout.isInt()) {
×
199
        FrameTimeout = std::chrono::milliseconds(frameTimeout.asInt());
×
200
    }
201

NEW
202
    return deviceTemplate;
×
203
}
204

205
PRPCDeviceLoadConfigRequest ParseRPCDeviceLoadConfigRequest(const Json::Value& request,
×
206
                                                            const TSerialDeviceFactory& deviceFactory,
207
                                                            PTemplateMap templates,
208
                                                            TRPCDeviceParametersCache& parametersCache,
209
                                                            WBMQTT::TMqttRpcServer::TResultCallback onResult,
210
                                                            WBMQTT::TMqttRpcServer::TErrorCallback onError)
211
{
NEW
212
    auto res = std::make_shared<TRPCDeviceLoadConfigRequest>(deviceFactory, templates, parametersCache);
×
213
    res->SerialPortSettings = ParseRPCSerialPortSettings(request);
×
214
    res->SlaveId = request["slave_id"].asString();
×
NEW
215
    res->DeviceType = request["device_type"].asString();
×
216
    res->Group = request["group"].asString();
×
217
    WBMQTT::JSON::Get(request, "response_timeout", res->ResponseTimeout);
×
218
    WBMQTT::JSON::Get(request, "frame_timeout", res->FrameTimeout);
×
219
    WBMQTT::JSON::Get(request, "total_timeout", res->TotalTimeout);
×
NEW
220
    res->OnResult = onResult;
×
NEW
221
    res->OnError = onError;
×
UNCOV
222
    return res;
×
223
}
224

NEW
225
void ExecRPCDeviceLoadConfigRequest(PPort port, PSerialDevice device, PRPCDeviceLoadConfigRequest rpcRequest)
×
226
{
227
    if (!rpcRequest->OnResult) {
×
228
        return;
×
229
    }
230

NEW
231
    rpcRequest->UpdateParams(device);
×
NEW
232
    auto deviceTemplate = rpcRequest->GetDeviceTemplate();
×
NEW
233
    Json::Value templateParams = deviceTemplate->GetTemplate()["parameters"];
×
NEW
234
    if (templateParams.empty()) {
×
NEW
235
        rpcRequest->OnResult(Json::Value(Json::objectValue));
×
NEW
236
        return;
×
237
    }
238

NEW
239
    TDeviceProtocolParams protocolParams = rpcRequest->DeviceFactory.GetProtocolParams(deviceTemplate->GetProtocol());
×
240
    bool useCache = true;
×
241
    if (device == nullptr) {
×
NEW
242
        device = CreateDevice(port, rpcRequest, deviceTemplate, protocolParams);
×
243
        useCache = false;
×
244
    }
245

246
    std::string id = rpcRequest->ParametersCache.GetId(*port, rpcRequest->SlaveId);
×
247
    std::string fwVersion;
×
248
    Json::Value parameters;
×
249
    if (rpcRequest->ParametersCache.Contains(id)) {
×
250
        Json::Value cache = rpcRequest->ParametersCache.Get(id);
×
251
        fwVersion = cache["fw"].asString();
×
252
        parameters = cache["parameters"];
×
253
    }
254
    if (parameters.isNull()) {
×
255
        for (const auto& item: device->GetSetupItems()) {
×
256
            if (!item->ParameterId.empty()) {
×
257
                parameters[item->ParameterId] = RawValueToJSON(*item->RegisterConfig, item->RawValue);
×
258
            }
259
        }
260
    }
261

262
    port->SkipNoise();
×
263
    if (fwVersion.empty()) {
×
264
        fwVersion = ReadFirmwareVersion(*port, rpcRequest);
×
265
    }
266

267
    std::list<std::string> paramsList;
×
268
    TRPCRegisterList registerList = CreateRegisterList(
269
        protocolParams,
270
        device,
271
        rpcRequest->Group.empty() ? templateParams
×
272
                                  : GetTemplateParamsGroup(templateParams, rpcRequest->Group, paramsList),
×
273
        parameters,
274
        fwVersion);
×
275
    ReadParameters(device, registerList, parameters);
×
276

277
    Json::Value result;
×
278
    if (!fwVersion.empty()) {
×
279
        result["fw"] = fwVersion;
×
280
    }
281
    if (!paramsList.empty()) {
×
282
        for (const auto& id: paramsList) {
×
283
            result["parameters"][id] = parameters[id];
×
284
        }
285
    } else {
286
        result["parameters"] = parameters;
×
287
    }
288
    CheckParametersConditions(templateParams, result["parameters"]);
×
289
    rpcRequest->OnResult(result);
×
290

291
    if (useCache) {
×
292
        result["parameters"] = parameters;
×
293
        rpcRequest->ParametersCache.Add(id, result);
×
294
    }
295
}
296

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

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

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

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

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

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

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

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

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

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