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

wirenboard / wb-mqtt-serial / 2

29 Dec 2025 12:28PM UTC coverage: 76.817% (+4.0%) from 72.836%
2

Pull #1045

github

54aa0c
pgasheev
up changelog
Pull Request #1045: Fix firmware version in WB-M1W2 template

6873 of 9161 branches covered (75.02%)

12966 of 16879 relevant lines covered (76.82%)

1651.61 hits per line

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

20.0
/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(TPort& port,
×
14
                            PRPCDeviceLoadConfigRequest rpcRequest,
15
                            PRegisterConfig registerConfig,
16
                            TRegisterValue& value)
17
    {
18
        auto slaveId = static_cast<uint8_t>(std::stoi(rpcRequest->Device->DeviceConfig()->SlaveId));
×
19
        std::unique_ptr<Modbus::IModbusTraits> traits;
×
20
        if (rpcRequest->ProtocolParams.protocol->GetName() == "modbus-tcp") {
×
21
            traits = std::make_unique<Modbus::TModbusTCPTraits>();
×
22
        } else {
23
            traits = std::make_unique<Modbus::TModbusRTUTraits>();
×
24
        }
25
        for (int i = 0; i <= MAX_RETRIES; ++i) {
×
26
            try {
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

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

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;
×
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

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

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

170
    void ClearUnsupportedParameters(TPort& port,
×
171
                                    PRPCDeviceLoadConfigRequest rpcRequest,
172
                                    TRPCRegisterList& registerList,
173
                                    Json::Value& parameters)
174
    {
175
        auto continuousRead = true;
×
176
        for (auto it = registerList.begin(); it != registerList.end(); ++it) {
×
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
180
            if (item.CheckUnsupported && item.Register->GetValue().Get<uint16_t>() == 0xFFFE) {
×
181
                if (continuousRead) {
×
182
                    SetContinuousRead(port, rpcRequest, false);
×
183
                    continuousRead = false;
×
184
                }
185
                try {
186
                    TRegisterValue value;
×
187
                    ReadModbusRegister(port, rpcRequest, item.Register->GetConfig(), value);
×
188
                } catch (const Modbus::TModbusExceptionError& err) {
×
189
                    parameters.removeMember(item.Id);
×
190
                }
191
            }
192
        }
193
        if (!continuousRead && rpcRequest->DeviceFromConfig) {
×
194
            SetContinuousRead(port, rpcRequest, true);
×
195
        }
196
    }
197

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,
242
            rpcRequest->IsWBDevice);
×
243
        ReadRegisterList(*port, rpcRequest->Device, registerList, parameters, MAX_RETRIES);
×
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) {
×
255
                if (parameters.isMember(id)) {
×
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
            ExecRPCRequest(port, Request);
×
328
        } else {
329
            ExecRPCRequest(port, Request);
×
330
        }
331
    } catch (const std::exception& error) {
×
332
        if (Request->OnError) {
×
333
            Request->OnError(WBMQTT::E_RPC_SERVER_ERROR, std::string("Port IO error: ") + error.what());
×
334
        }
335
    }
336
    return ISerialClientTask::TRunResult::OK;
×
337
}
338

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

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