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

wirenboard / wb-mqtt-serial / 724

24 Nov 2025 06:55AM UTC coverage: 76.769% (-0.3%) from 77.06%
724

push

github

web-flow
Remove unsupported registers data from device/LoadConfig RPC reply

6863 of 9153 branches covered (74.98%)

7 of 85 new or added lines in 4 files covered. (8.24%)

5 existing lines in 1 file now uncovered.

12951 of 16870 relevant lines covered (76.77%)

826.04 hits per line

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

20.09
/src/rpc/rpc_device_load_config_task.cpp
1
#include "rpc_device_load_config_task.h"
2
#include "config_merge_template.h"
3
#include "port/serial_port.h"
4
#include "rpc_helpers.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

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

NEW
53
    void WriteModbusRegister(TPort& port,
×
54
                             PRPCDeviceLoadConfigRequest rpcRequest,
55
                             PRegisterConfig registerConfig,
56
                             const TRegisterValue& value)
57
    {
NEW
58
        auto slaveId = static_cast<uint8_t>(std::stoi(rpcRequest->Device->DeviceConfig()->SlaveId));
×
NEW
59
        std::unique_ptr<Modbus::IModbusTraits> traits;
×
NEW
60
        if (rpcRequest->ProtocolParams.protocol->GetName() == "modbus-tcp") {
×
NEW
61
            traits = std::make_unique<Modbus::TModbusTCPTraits>();
×
62
        } else {
NEW
63
            traits = std::make_unique<Modbus::TModbusRTUTraits>();
×
64
        }
NEW
65
        Modbus::TRegisterCache cache;
×
NEW
66
        for (int i = 0; i <= MAX_RETRIES; ++i) {
×
67
            try {
NEW
68
                Modbus::WriteRegister(*traits,
×
69
                                      port,
70
                                      slaveId,
NEW
71
                                      *registerConfig,
×
72
                                      value,
73
                                      cache,
NEW
74
                                      std::chrono::microseconds(0),
×
NEW
75
                                      rpcRequest->ResponseTimeout,
×
NEW
76
                                      rpcRequest->FrameTimeout);
×
NEW
77
            } catch (const Modbus::TModbusExceptionError& err) {
×
NEW
78
                if (err.GetExceptionCode() == Modbus::ILLEGAL_FUNCTION ||
×
NEW
79
                    err.GetExceptionCode() == Modbus::ILLEGAL_DATA_ADDRESS ||
×
NEW
80
                    err.GetExceptionCode() == Modbus::ILLEGAL_DATA_VALUE)
×
81
                {
NEW
82
                    throw;
×
83
                }
NEW
84
            } catch (const Modbus::TErrorBase& err) {
×
NEW
85
                if (i == MAX_RETRIES) {
×
NEW
86
                    throw;
×
87
                }
NEW
88
            } catch (const TResponseTimeoutException& e) {
×
NEW
89
                if (i == MAX_RETRIES) {
×
NEW
90
                    throw;
×
91
                }
92
            }
93
        }
94
    }
95

UNCOV
96
    std::string ReadWbRegister(TPort& port, PRPCDeviceLoadConfigRequest rpcRequest, const std::string& registerName)
×
97
    {
98
        std::string error;
×
99
        try {
100
            auto config = WbRegisters::GetRegisterConfig(registerName);
×
101
            TRegisterValue value;
×
NEW
102
            ReadModbusRegister(port, rpcRequest, config, value);
×
103
            return value.Get<std::string>();
×
104
        } catch (const Modbus::TErrorBase& err) {
×
105
            error = err.what();
×
106
        } catch (const TResponseTimeoutException& e) {
×
107
            error = e.what();
×
108
        }
109
        LOG(Warn) << port.GetDescription() << " modbus:" << rpcRequest->Device->DeviceConfig()->SlaveId
×
110
                  << " unable to read \"" << registerName << "\" register: " << error;
×
111
        throw TRPCException(error, TRPCResultCode::RPC_WRONG_PARAM_VALUE);
×
112
    }
113

114
    std::string ReadDeviceModel(TPort& port, PRPCDeviceLoadConfigRequest rpcRequest)
×
115
    {
116
        try {
117
            return ReadWbRegister(port, rpcRequest, WbRegisters::DEVICE_MODEL_EX_REGISTER_NAME);
×
118
        } catch (const Modbus::TErrorBase& err) {
×
119
            return ReadWbRegister(port, rpcRequest, WbRegisters::DEVICE_MODEL_REGISTER_NAME);
×
120
        }
121
    }
122

123
    std::string ReadFwVersion(TPort& port, PRPCDeviceLoadConfigRequest rpcRequest)
×
124
    {
125
        return ReadWbRegister(port, rpcRequest, WbRegisters::FW_VERSION_REGISTER_NAME);
×
126
    }
127

NEW
128
    void SetContinuousRead(TPort& port, PRPCDeviceLoadConfigRequest rpcRequest, bool enabled)
×
129
    {
NEW
130
        std::string error;
×
131
        try {
NEW
132
            auto config = WbRegisters::GetRegisterConfig(WbRegisters::CONTINUOUS_READ_REGISTER_NAME);
×
NEW
133
            WriteModbusRegister(port, rpcRequest, config, TRegisterValue(enabled));
×
NEW
134
        } catch (const Modbus::TErrorBase& err) {
×
NEW
135
            error = err.what();
×
NEW
136
        } catch (const TResponseTimeoutException& e) {
×
NEW
137
            error = e.what();
×
138
        }
NEW
139
        if (!error.empty()) {
×
NEW
140
            LOG(Warn) << port.GetDescription() << " modbus:" << rpcRequest->Device->DeviceConfig()->SlaveId
×
NEW
141
                      << " unable to write \"" << WbRegisters::CONTINUOUS_READ_REGISTER_NAME
×
NEW
142
                      << "\" register: " << error;
×
NEW
143
            throw TRPCException(error, TRPCResultCode::RPC_WRONG_PARAM_VALUE);
×
144
        }
145
    }
146

UNCOV
147
    void CheckTemplate(PPort port, PRPCDeviceLoadConfigRequest rpcRequest, std::string& model, std::string& version)
×
148
    {
149
        if (model.empty()) {
×
150
            model = ReadDeviceModel(*port, rpcRequest);
×
151
        }
152
        if (version.empty()) {
×
153
            version = ReadFwVersion(*port, rpcRequest);
×
154
        }
155
        for (const auto& item: rpcRequest->DeviceTemplate->GetHardware()) {
×
156
            if (item.Signature == model) {
×
157
                if (util::CompareVersionStrings(version, item.Fw) >= 0) {
×
158
                    return;
×
159
                }
160
                throw TRPCException("Device \"" + model + "\" firmware version " + version +
×
161
                                        " is lower than selected template minimal supported version " + item.Fw,
×
162
                                    TRPCResultCode::RPC_WRONG_PARAM_VALUE);
×
163
            }
164
        }
165
        throw TRPCException("Device \"" + model + "\" with firmware version " + version +
×
166
                                " is incompatible with selected template device models",
×
167
                            TRPCResultCode::RPC_WRONG_PARAM_VALUE);
×
168
    }
169

NEW
170
    void ClearUnsupportedParameters(TPort& port,
×
171
                                    PRPCDeviceLoadConfigRequest rpcRequest,
172
                                    TRPCRegisterList& registerList,
173
                                    Json::Value& parameters)
174
    {
NEW
175
        auto continuousRead = true;
×
NEW
176
        for (auto it = registerList.begin(); it != registerList.end(); ++it) {
×
NEW
177
            const auto& item = *it;
×
178
            // this code checks registers only for 16-bit register unsupported value 0xFFFE
179
            // it must be modified to check larger registers like 24, 32 or 64-bits
NEW
180
            if (item.CheckUnsupported && item.Register->GetValue().Get<uint16_t>() == 0xFFFE) {
×
NEW
181
                if (continuousRead) {
×
NEW
182
                    SetContinuousRead(port, rpcRequest, false);
×
NEW
183
                    continuousRead = false;
×
184
                }
185
                try {
NEW
186
                    TRegisterValue value;
×
NEW
187
                    ReadModbusRegister(port, rpcRequest, item.Register->GetConfig(), value);
×
NEW
188
                } catch (const Modbus::TModbusExceptionError& err) {
×
NEW
189
                    parameters.removeMember(item.Id);
×
190
                }
191
            }
192
        }
NEW
193
        if (!continuousRead && rpcRequest->DeviceFromConfig) {
×
NEW
194
            SetContinuousRead(port, rpcRequest, true);
×
195
        }
196
    }
197

UNCOV
198
    void ExecRPCRequest(PPort port, PRPCDeviceLoadConfigRequest rpcRequest)
×
199
    {
200
        if (!rpcRequest->OnResult) {
×
201
            return;
×
202
        }
203

204
        Json::Value templateParams = rpcRequest->DeviceTemplate->GetTemplate()["parameters"];
×
205
        if (templateParams.empty()) {
×
206
            rpcRequest->OnResult(Json::Value(Json::objectValue));
×
207
            return;
×
208
        }
209

210
        std::string id = rpcRequest->ParametersCache.GetId(*port, rpcRequest->Device->DeviceConfig()->SlaveId);
×
211
        std::string deviceModel;
×
212
        std::string fwVersion;
×
213
        Json::Value parameters;
×
214
        if (rpcRequest->ParametersCache.Contains(id)) {
×
215
            Json::Value cache = rpcRequest->ParametersCache.Get(id);
×
216
            deviceModel = cache["model"].asString();
×
217
            fwVersion = cache["fw"].asString();
×
218
            parameters = cache["parameters"];
×
219
        }
220
        if (parameters.isNull()) {
×
221
            for (const auto& item: rpcRequest->Device->GetSetupItems()) {
×
222
                if (!item->ParameterId.empty()) {
×
223
                    parameters[item->ParameterId] = RawValueToJSON(*item->RegisterConfig, item->RawValue);
×
224
                }
225
            }
226
        }
227

228
        port->SkipNoise();
×
229

230
        if (rpcRequest->IsWBDevice) {
×
231
            CheckTemplate(port, rpcRequest, deviceModel, fwVersion);
×
232
        }
233

234
        std::list<std::string> paramsList;
×
235
        auto registerList = CreateRegisterList(
236
            rpcRequest->ProtocolParams,
×
237
            rpcRequest->Device,
×
238
            rpcRequest->Group.empty() ? templateParams
×
239
                                      : GetTemplateParamsGroup(templateParams, rpcRequest->Group, paramsList),
×
240
            parameters,
241
            fwVersion,
NEW
242
            rpcRequest->IsWBDevice);
×
UNCOV
243
        ReadRegisterList(*port, rpcRequest->Device, registerList, parameters, MAX_RETRIES);
×
NEW
244
        ClearUnsupportedParameters(*port, rpcRequest, registerList, parameters);
×
245

246
        Json::Value result(Json::objectValue);
×
247
        if (!deviceModel.empty()) {
×
248
            result["model"] = deviceModel;
×
249
        }
250
        if (!fwVersion.empty()) {
×
251
            result["fw"] = fwVersion;
×
252
        }
253
        if (!paramsList.empty()) {
×
254
            for (const auto& id: paramsList) {
×
NEW
255
                if (parameters.isMember(id)) {
×
NEW
256
                    result["parameters"][id] = parameters[id];
×
257
                }
258
            }
259
        } else {
260
            result["parameters"] = parameters;
×
261
        }
262
        CheckParametersConditions(templateParams, result["parameters"]);
×
263
        rpcRequest->OnResult(result);
×
264

265
        if (rpcRequest->DeviceFromConfig) {
×
266
            result["parameters"] = parameters;
×
267
            rpcRequest->ParametersCache.Add(id, result);
×
268
        }
269
    }
270
} // namespace
271

272
TRPCDeviceLoadConfigRequest::TRPCDeviceLoadConfigRequest(const TDeviceProtocolParams& protocolParams,
×
273
                                                         PSerialDevice device,
274
                                                         PDeviceTemplate deviceTemplate,
275
                                                         bool deviceFromConfig,
276
                                                         TRPCDeviceParametersCache& parametersCache)
×
277
    : TRPCDeviceRequest(protocolParams, device, deviceTemplate, deviceFromConfig),
278
      ParametersCache(parametersCache)
×
279
{
280
    IsWBDevice =
×
281
        !DeviceTemplate->GetHardware().empty() || DeviceTemplate->GetTemplate()["enable_wb_continuous_read"].asBool();
×
282
}
283

284
PRPCDeviceLoadConfigRequest ParseRPCDeviceLoadConfigRequest(const Json::Value& request,
×
285
                                                            const TDeviceProtocolParams& protocolParams,
286
                                                            PSerialDevice device,
287
                                                            PDeviceTemplate deviceTemplate,
288
                                                            bool deviceFromConfig,
289
                                                            TRPCDeviceParametersCache& parametersCache,
290
                                                            WBMQTT::TMqttRpcServer::TResultCallback onResult,
291
                                                            WBMQTT::TMqttRpcServer::TErrorCallback onError)
292
{
293
    auto res = std::make_shared<TRPCDeviceLoadConfigRequest>(protocolParams,
294
                                                             device,
295
                                                             deviceTemplate,
296
                                                             deviceFromConfig,
297
                                                             parametersCache);
×
298
    res->ParseSettings(request, onResult, onError);
×
299
    res->Group = request["group"].asString();
×
300
    return res;
×
301
}
302

303
TRPCDeviceLoadConfigSerialClientTask::TRPCDeviceLoadConfigSerialClientTask(PRPCDeviceLoadConfigRequest request)
×
304
    : Request(request)
×
305
{
306
    ExpireTime = std::chrono::steady_clock::now() + Request->TotalTimeout;
×
307
}
308

309
ISerialClientTask::TRunResult TRPCDeviceLoadConfigSerialClientTask::Run(
×
310
    PFeaturePort port,
311
    TSerialClientDeviceAccessHandler& lastAccessedDevice,
312
    const std::list<PSerialDevice>& polledDevices)
313
{
314
    if (std::chrono::steady_clock::now() > ExpireTime) {
×
315
        if (Request->OnError) {
×
316
            Request->OnError(WBMQTT::E_RPC_REQUEST_TIMEOUT, "RPC request timeout");
×
317
        }
318
        return ISerialClientTask::TRunResult::OK;
×
319
    }
320
    try {
321
        if (!port->IsOpen()) {
×
322
            port->Open();
×
323
        }
324
        lastAccessedDevice.PrepareToAccess(*port, nullptr);
×
325
        if (!Request->DeviceFromConfig) {
×
326
            TSerialPortSettingsGuard settingsGuard(port, Request->SerialPortSettings);
×
327
        }
328
        ExecRPCRequest(port, Request);
×
329
    } catch (const std::exception& error) {
×
330
        if (Request->OnError) {
×
331
            Request->OnError(WBMQTT::E_RPC_SERVER_ERROR, std::string("Port IO error: ") + error.what());
×
332
        }
333
    }
334
    return ISerialClientTask::TRunResult::OK;
×
335
}
336

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

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