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

wirenboard / wb-mqtt-serial / 2

13 Jan 2026 09:11AM UTC coverage: 76.822% (+0.005%) from 76.817%
2

Pull #1039

github

u236
Merge branch 'master' into feature/SOFT-6400
Pull Request #1039: Set unsupported registers value to "unsupported" for device/LoadConfi…

6873 of 9160 branches covered (75.03%)

0 of 4 new or added lines in 1 file covered. (0.0%)

89 existing lines in 3 files now uncovered.

12966 of 16878 relevant lines covered (76.82%)

1651.72 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
    const auto UNSUPPORTED_VALUE = "unsupported";
13

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

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

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

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

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

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

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

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

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

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

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

UNCOV
229
        port->SkipNoise();
×
230

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

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

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

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

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

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

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

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

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

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