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

wirenboard / wb-mqtt-serial / 723

21 Nov 2025 04:27AM UTC coverage: 77.06%. Remained the same
723

push

github

web-flow
Fix empty RPC responses (#1018)

6862 of 9106 branches covered (75.36%)

0 of 6 new or added lines in 5 files covered. (0.0%)

1 existing line in 1 file now uncovered.

12950 of 16805 relevant lines covered (77.06%)

829.23 hits per line

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

26.59
/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

13
    void ReadModbusRegister(Modbus::IModbusTraits& traits,
×
14
                            TPort& port,
15
                            PRPCDeviceLoadConfigRequest rpcRequest,
16
                            PRegisterConfig registerConfig,
17
                            TRegisterValue& value)
18
    {
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
            } catch (const Modbus::TModbusExceptionError& err) {
×
30
                if (err.GetExceptionCode() == Modbus::ILLEGAL_FUNCTION ||
×
31
                    err.GetExceptionCode() == Modbus::ILLEGAL_DATA_ADDRESS ||
×
32
                    err.GetExceptionCode() == Modbus::ILLEGAL_DATA_VALUE)
×
33
                {
34
                    throw;
×
35
                }
36
            } catch (const Modbus::TErrorBase& err) {
×
37
                if (i == MAX_RETRIES) {
×
38
                    throw;
×
39
                }
40
            } catch (const TResponseTimeoutException& e) {
×
41
                if (i == MAX_RETRIES) {
×
42
                    throw;
×
43
                }
44
            }
45
        }
46
    }
47

48
    std::string ReadWbRegister(TPort& port, PRPCDeviceLoadConfigRequest rpcRequest, const std::string& registerName)
×
49
    {
50
        std::string error;
×
51
        try {
52
            Modbus::TModbusRTUTraits traits;
×
53
            auto config = WbRegisters::GetRegisterConfig(registerName);
×
54
            TRegisterValue value;
×
55
            ReadModbusRegister(traits, port, rpcRequest, config, value);
×
56
            return value.Get<std::string>();
×
57
        } catch (const Modbus::TErrorBase& err) {
×
58
            error = err.what();
×
59
        } catch (const TResponseTimeoutException& e) {
×
60
            error = e.what();
×
61
        }
62
        LOG(Warn) << port.GetDescription() << " modbus:" << rpcRequest->Device->DeviceConfig()->SlaveId
×
63
                  << " unable to read \"" << registerName << "\" register: " << error;
×
64
        throw TRPCException(error, TRPCResultCode::RPC_WRONG_PARAM_VALUE);
×
65
    }
66

67
    std::string ReadDeviceModel(TPort& port, PRPCDeviceLoadConfigRequest rpcRequest)
×
68
    {
69
        try {
70
            return ReadWbRegister(port, rpcRequest, WbRegisters::DEVICE_MODEL_EX_REGISTER_NAME);
×
71
        } catch (const Modbus::TErrorBase& err) {
×
72
            return ReadWbRegister(port, rpcRequest, WbRegisters::DEVICE_MODEL_REGISTER_NAME);
×
73
        }
74
    }
75

76
    std::string ReadFwVersion(TPort& port, PRPCDeviceLoadConfigRequest rpcRequest)
×
77
    {
78
        return ReadWbRegister(port, rpcRequest, WbRegisters::FW_VERSION_REGISTER_NAME);
×
79
    }
80

81
    void CheckTemplate(PPort port, PRPCDeviceLoadConfigRequest rpcRequest, std::string& model, std::string& version)
×
82
    {
83
        if (model.empty()) {
×
84
            model = ReadDeviceModel(*port, rpcRequest);
×
85
        }
86
        if (version.empty()) {
×
87
            version = ReadFwVersion(*port, rpcRequest);
×
88
        }
89
        for (const auto& item: rpcRequest->DeviceTemplate->GetHardware()) {
×
90
            if (item.Signature == model) {
×
91
                if (util::CompareVersionStrings(version, item.Fw) >= 0) {
×
92
                    return;
×
93
                }
94
                throw TRPCException("Device \"" + model + "\" firmware version " + version +
×
95
                                        " is lower than selected template minimal supported version " + item.Fw,
×
96
                                    TRPCResultCode::RPC_WRONG_PARAM_VALUE);
×
97
            }
98
        }
99
        throw TRPCException("Device \"" + model + "\" with firmware version " + version +
×
100
                                " is incompatible with selected template device models",
×
101
                            TRPCResultCode::RPC_WRONG_PARAM_VALUE);
×
102
    }
103

104
    void ExecRPCRequest(PPort port, PRPCDeviceLoadConfigRequest rpcRequest)
×
105
    {
106
        if (!rpcRequest->OnResult) {
×
107
            return;
×
108
        }
109

110
        Json::Value templateParams = rpcRequest->DeviceTemplate->GetTemplate()["parameters"];
×
111
        if (templateParams.empty()) {
×
112
            rpcRequest->OnResult(Json::Value(Json::objectValue));
×
113
            return;
×
114
        }
115

116
        std::string id = rpcRequest->ParametersCache.GetId(*port, rpcRequest->Device->DeviceConfig()->SlaveId);
×
117
        std::string deviceModel;
×
118
        std::string fwVersion;
×
119
        Json::Value parameters;
×
120
        if (rpcRequest->ParametersCache.Contains(id)) {
×
121
            Json::Value cache = rpcRequest->ParametersCache.Get(id);
×
122
            deviceModel = cache["model"].asString();
×
123
            fwVersion = cache["fw"].asString();
×
124
            parameters = cache["parameters"];
×
125
        }
126
        if (parameters.isNull()) {
×
127
            for (const auto& item: rpcRequest->Device->GetSetupItems()) {
×
128
                if (!item->ParameterId.empty()) {
×
129
                    parameters[item->ParameterId] = RawValueToJSON(*item->RegisterConfig, item->RawValue);
×
130
                }
131
            }
132
        }
133

134
        port->SkipNoise();
×
135

136
        if (rpcRequest->IsWBDevice) {
×
137
            CheckTemplate(port, rpcRequest, deviceModel, fwVersion);
×
138
        }
139

140
        std::list<std::string> paramsList;
×
141
        auto registerList = CreateRegisterList(
142
            rpcRequest->ProtocolParams,
×
143
            rpcRequest->Device,
×
144
            rpcRequest->Group.empty() ? templateParams
×
145
                                      : GetTemplateParamsGroup(templateParams, rpcRequest->Group, paramsList),
×
146
            parameters,
147
            fwVersion);
×
148
        ReadRegisterList(*port, rpcRequest->Device, registerList, parameters, MAX_RETRIES);
×
149

NEW
150
        Json::Value result(Json::objectValue);
×
151
        if (!deviceModel.empty()) {
×
152
            result["model"] = deviceModel;
×
153
        }
154
        if (!fwVersion.empty()) {
×
155
            result["fw"] = fwVersion;
×
156
        }
157
        if (!paramsList.empty()) {
×
158
            for (const auto& id: paramsList) {
×
159
                result["parameters"][id] = parameters[id];
×
160
            }
161
        } else {
162
            result["parameters"] = parameters;
×
163
        }
164
        CheckParametersConditions(templateParams, result["parameters"]);
×
165
        rpcRequest->OnResult(result);
×
166

167
        if (rpcRequest->DeviceFromConfig) {
×
168
            result["parameters"] = parameters;
×
169
            rpcRequest->ParametersCache.Add(id, result);
×
170
        }
171
    }
172
} // namespace
173

174
TRPCDeviceLoadConfigRequest::TRPCDeviceLoadConfigRequest(const TDeviceProtocolParams& protocolParams,
×
175
                                                         PSerialDevice device,
176
                                                         PDeviceTemplate deviceTemplate,
177
                                                         bool deviceFromConfig,
178
                                                         TRPCDeviceParametersCache& parametersCache)
×
179
    : TRPCDeviceRequest(protocolParams, device, deviceTemplate, deviceFromConfig),
180
      ParametersCache(parametersCache)
×
181
{
182
    IsWBDevice =
×
183
        !DeviceTemplate->GetHardware().empty() || DeviceTemplate->GetTemplate()["enable_wb_continuous_read"].asBool();
×
184
}
185

186
PRPCDeviceLoadConfigRequest ParseRPCDeviceLoadConfigRequest(const Json::Value& request,
×
187
                                                            const TDeviceProtocolParams& protocolParams,
188
                                                            PSerialDevice device,
189
                                                            PDeviceTemplate deviceTemplate,
190
                                                            bool deviceFromConfig,
191
                                                            TRPCDeviceParametersCache& parametersCache,
192
                                                            WBMQTT::TMqttRpcServer::TResultCallback onResult,
193
                                                            WBMQTT::TMqttRpcServer::TErrorCallback onError)
194
{
195
    auto res = std::make_shared<TRPCDeviceLoadConfigRequest>(protocolParams,
196
                                                             device,
197
                                                             deviceTemplate,
198
                                                             deviceFromConfig,
199
                                                             parametersCache);
×
200
    res->ParseSettings(request, onResult, onError);
×
201
    res->Group = request["group"].asString();
×
202
    return res;
×
203
}
204

205
TRPCDeviceLoadConfigSerialClientTask::TRPCDeviceLoadConfigSerialClientTask(PRPCDeviceLoadConfigRequest request)
×
206
    : Request(request)
×
207
{
208
    ExpireTime = std::chrono::steady_clock::now() + Request->TotalTimeout;
×
209
}
210

211
ISerialClientTask::TRunResult TRPCDeviceLoadConfigSerialClientTask::Run(
×
212
    PFeaturePort port,
213
    TSerialClientDeviceAccessHandler& lastAccessedDevice,
214
    const std::list<PSerialDevice>& polledDevices)
215
{
216
    if (std::chrono::steady_clock::now() > ExpireTime) {
×
217
        if (Request->OnError) {
×
218
            Request->OnError(WBMQTT::E_RPC_REQUEST_TIMEOUT, "RPC request timeout");
×
219
        }
220
        return ISerialClientTask::TRunResult::OK;
×
221
    }
222
    try {
223
        if (!port->IsOpen()) {
×
224
            port->Open();
×
225
        }
226
        lastAccessedDevice.PrepareToAccess(*port, nullptr);
×
227
        if (!Request->DeviceFromConfig) {
×
228
            TSerialPortSettingsGuard settingsGuard(port, Request->SerialPortSettings);
×
229
        }
230
        ExecRPCRequest(port, Request);
×
231
    } catch (const std::exception& error) {
×
232
        if (Request->OnError) {
×
233
            Request->OnError(WBMQTT::E_RPC_SERVER_ERROR, std::string("Port IO error: ") + error.what());
×
234
        }
235
    }
236
    return ISerialClientTask::TRunResult::OK;
×
237
}
238

239
Json::Value GetTemplateParamsGroup(const Json::Value& templateParams,
2✔
240
                                   const std::string& group,
241
                                   std::list<std::string>& paramsList)
242
{
243
    Json::Value result;
2✔
244
    std::list<std::string> conditionList;
4✔
245
    bool check = true;
2✔
246
    while (check) {
7✔
247
        check = false;
5✔
248
        for (auto it = templateParams.begin(); it != templateParams.end(); ++it) {
43✔
249
            const Json::Value& data = *it;
38✔
250
            std::string id = templateParams.isObject() ? it.key().asString() : data["id"].asString();
38✔
251
            if (std::find(conditionList.begin(), conditionList.end(), id) == conditionList.end() &&
69✔
252
                data["group"].asString() != group)
69✔
253
            {
254
                continue;
12✔
255
            }
256
            if (std::find(paramsList.begin(), paramsList.end(), id) == paramsList.end()) {
26✔
257
                paramsList.push_back(id);
11✔
258
                result[id] = data;
11✔
259
            }
260
            if (data["condition"].isNull()) {
26✔
261
                continue;
15✔
262
            }
263
            Expressions::TLexer lexer;
11✔
264
            auto tokens = lexer.GetTokens(data["condition"].asString());
22✔
265
            for (const auto& token: tokens) {
55✔
266
                if (token.Type == Expressions::TTokenType::Ident && token.Value != "isDefined" &&
55✔
267
                    std::find(conditionList.begin(), conditionList.end(), token.Value) == conditionList.end())
55✔
268
                {
269
                    conditionList.push_back(token.Value);
4✔
270
                    check = true;
4✔
271
                }
272
            }
273
        }
274
    }
275
    return result;
4✔
276
}
277

278
void CheckParametersConditions(const Json::Value& templateParams, Json::Value& parameters)
2✔
279
{
280
    TJsonParams jsonParams(parameters);
4✔
281
    Expressions::TExpressionsCache expressionsCache;
4✔
282
    bool check = true;
2✔
283
    while (check) {
6✔
284
        std::unordered_map<std::string, bool> matches;
8✔
285
        for (auto it = templateParams.begin(); it != templateParams.end(); ++it) {
34✔
286
            const Json::Value& registerData = *it;
30✔
287
            std::string id = templateParams.isObject() ? it.key().asString() : registerData["id"].asString();
30✔
288
            if (!parameters.isMember(id)) {
30✔
289
                continue;
15✔
290
            }
291
            bool match = CheckCondition(registerData, jsonParams, &expressionsCache);
15✔
292
            if (matches.find(id) == matches.end() || match) {
15✔
293
                matches[id] = match;
15✔
294
            }
295
        }
296
        check = false;
4✔
297
        for (auto it = matches.begin(); it != matches.end(); ++it) {
17✔
298
            if (!it->second) {
13✔
299
                parameters.removeMember(it->first);
3✔
300
                check = true;
3✔
301
            }
302
        }
303
    }
304
    if (parameters.isNull()) {
2✔
305
        parameters = Json::Value(Json::objectValue);
×
306
    }
307
}
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