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

wirenboard / wb-mqtt-serial / 673

15 Jul 2025 07:07AM UTC coverage: 73.028% (-0.8%) from 73.863%
673

push

github

web-flow
Add device/Load and device/Set RPC

6463 of 9217 branches covered (70.12%)

51 of 322 new or added lines in 13 files covered. (15.84%)

7 existing lines in 2 files now uncovered.

12368 of 16936 relevant lines covered (73.03%)

373.64 hits per line

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

23.83
/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
    bool 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
                return true;
×
30
            } catch (const Modbus::TModbusExceptionError& err) {
×
31
                if (err.GetExceptionCode() == Modbus::ILLEGAL_FUNCTION ||
×
32
                    err.GetExceptionCode() == Modbus::ILLEGAL_DATA_ADDRESS ||
×
33
                    err.GetExceptionCode() == Modbus::ILLEGAL_DATA_VALUE)
×
34
                {
35
                    break;
×
36
                }
37
            } catch (const Modbus::TErrorBase& err) {
×
38
                if (i == MAX_RETRIES) {
×
39
                    throw;
×
40
                }
41
            } catch (const TResponseTimeoutException& e) {
×
42
                if (i == MAX_RETRIES) {
×
43
                    throw;
×
44
                }
45
            }
46
        }
47

48
        return false;
×
49
    }
50

51
    std::string ReadFirmwareVersion(TPort& port, PRPCDeviceLoadConfigRequest rpcRequest)
×
52
    {
53
        if (!rpcRequest->IsWBDevice) {
×
54
            return std::string();
×
55
        }
56
        std::string error;
×
57
        try {
58
            Modbus::TModbusRTUTraits traits;
×
59
            auto config = WbRegisters::GetRegisterConfig(WbRegisters::FW_VERSION_REGISTER_NAME);
×
60
            TRegisterValue value;
×
61
            std::string version;
×
62
            if (ReadModbusRegister(traits, port, rpcRequest, config, value)) {
×
63
                version = value.Get<std::string>();
×
64
            }
65
            return version;
×
66
        } catch (const Modbus::TErrorBase& err) {
×
67
            error = err.what();
×
68
        } catch (const TResponseTimeoutException& e) {
×
69
            error = e.what();
×
70
        }
71
        LOG(Warn) << port.GetDescription() << " modbus:" << rpcRequest->Device->DeviceConfig()->SlaveId
×
72
                  << " unable to read \"" << WbRegisters::FW_VERSION_REGISTER_NAME << "\" register: " << error;
×
73
        throw TRPCException(error, TRPCResultCode::RPC_WRONG_PARAM_VALUE);
×
74
    }
75

76
    void ExecRPCRequest(PPort port, PRPCDeviceLoadConfigRequest rpcRequest)
×
77
    {
78
        if (!rpcRequest->OnResult) {
×
79
            return;
×
80
        }
81

82
        Json::Value templateParams = rpcRequest->DeviceTemplate->GetTemplate()["parameters"];
×
83
        if (templateParams.empty()) {
×
84
            rpcRequest->OnResult(Json::Value(Json::objectValue));
×
85
            return;
×
86
        }
87

88
        std::string id = rpcRequest->ParametersCache.GetId(*port, rpcRequest->Device->DeviceConfig()->SlaveId);
×
89
        std::string fwVersion;
×
90
        Json::Value parameters;
×
91
        if (rpcRequest->ParametersCache.Contains(id)) {
×
92
            Json::Value cache = rpcRequest->ParametersCache.Get(id);
×
93
            fwVersion = cache["fw"].asString();
×
94
            parameters = cache["parameters"];
×
95
        }
96
        if (parameters.isNull()) {
×
97
            for (const auto& item: rpcRequest->Device->GetSetupItems()) {
×
98
                if (!item->ParameterId.empty()) {
×
99
                    parameters[item->ParameterId] = RawValueToJSON(*item->RegisterConfig, item->RawValue);
×
100
                }
101
            }
102
        }
103

104
        port->SkipNoise();
×
105
        if (fwVersion.empty()) {
×
106
            fwVersion = ReadFirmwareVersion(*port, rpcRequest);
×
107
        }
108

109
        std::list<std::string> paramsList;
×
110
        auto registerList = CreateRegisterList(
111
            rpcRequest->ProtocolParams,
×
112
            rpcRequest->Device,
×
113
            rpcRequest->Group.empty() ? templateParams
×
114
                                      : GetTemplateParamsGroup(templateParams, rpcRequest->Group, paramsList),
×
115
            parameters,
116
            fwVersion);
×
NEW
117
        ReadRegisterList(*port, rpcRequest->Device, registerList, parameters, MAX_RETRIES);
×
118

119
        Json::Value result;
×
120
        if (!fwVersion.empty()) {
×
121
            result["fw"] = fwVersion;
×
122
        }
123
        if (!paramsList.empty()) {
×
124
            for (const auto& id: paramsList) {
×
125
                result["parameters"][id] = parameters[id];
×
126
            }
127
        } else {
128
            result["parameters"] = parameters;
×
129
        }
130
        CheckParametersConditions(templateParams, result["parameters"]);
×
131
        rpcRequest->OnResult(result);
×
132

133
        if (rpcRequest->DeviceFromConfig) {
×
134
            result["parameters"] = parameters;
×
135
            rpcRequest->ParametersCache.Add(id, result);
×
136
        }
137
    }
138
} // namespace
139

140
TRPCDeviceLoadConfigRequest::TRPCDeviceLoadConfigRequest(const TDeviceProtocolParams& protocolParams,
×
141
                                                         PSerialDevice device,
142
                                                         PDeviceTemplate deviceTemplate,
143
                                                         bool deviceFromConfig,
144
                                                         TRPCDeviceParametersCache& parametersCache)
×
145
    : TRPCDeviceRequest(protocolParams, device, deviceTemplate, deviceFromConfig),
UNCOV
146
      ParametersCache(parametersCache)
×
147
{
148
    IsWBDevice =
×
149
        !DeviceTemplate->GetHardware().empty() || DeviceTemplate->GetTemplate()["enable_wb_continuous_read"].asBool();
×
150
}
151

152
PRPCDeviceLoadConfigRequest ParseRPCDeviceLoadConfigRequest(const Json::Value& request,
×
153
                                                            const TDeviceProtocolParams& protocolParams,
154
                                                            PSerialDevice device,
155
                                                            PDeviceTemplate deviceTemplate,
156
                                                            bool deviceFromConfig,
157
                                                            TRPCDeviceParametersCache& parametersCache,
158
                                                            WBMQTT::TMqttRpcServer::TResultCallback onResult,
159
                                                            WBMQTT::TMqttRpcServer::TErrorCallback onError)
160
{
161
    auto res = std::make_shared<TRPCDeviceLoadConfigRequest>(protocolParams,
162
                                                             device,
163
                                                             deviceTemplate,
164
                                                             deviceFromConfig,
165
                                                             parametersCache);
×
NEW
166
    res->ParseSettings(request, onResult, onError);
×
167
    res->Group = request["group"].asString();
×
168
    return res;
×
169
}
170

171
TRPCDeviceLoadConfigSerialClientTask::TRPCDeviceLoadConfigSerialClientTask(PRPCDeviceLoadConfigRequest request)
×
172
    : Request(request)
×
173
{
174
    ExpireTime = std::chrono::steady_clock::now() + Request->TotalTimeout;
×
175
}
176

177
ISerialClientTask::TRunResult TRPCDeviceLoadConfigSerialClientTask::Run(
×
178
    PPort port,
179
    TSerialClientDeviceAccessHandler& lastAccessedDevice,
180
    const std::list<PSerialDevice>& polledDevices)
181
{
182
    if (std::chrono::steady_clock::now() > ExpireTime) {
×
183
        if (Request->OnError) {
×
184
            Request->OnError(WBMQTT::E_RPC_REQUEST_TIMEOUT, "RPC request timeout");
×
185
        }
186
        return ISerialClientTask::TRunResult::OK;
×
187
    }
188
    try {
UNCOV
189
        if (!port->IsOpen()) {
×
190
            port->Open();
×
191
        }
192
        lastAccessedDevice.PrepareToAccess(*port, nullptr);
×
193
        if (!Request->DeviceFromConfig) {
×
194
            TSerialPortSettingsGuard settingsGuard(port, Request->SerialPortSettings);
×
195
        }
196
        ExecRPCRequest(port, Request);
×
197
    } catch (const std::exception& error) {
×
198
        if (Request->OnError) {
×
199
            Request->OnError(WBMQTT::E_RPC_SERVER_ERROR, std::string("Port IO error: ") + error.what());
×
200
        }
201
    }
UNCOV
202
    return ISerialClientTask::TRunResult::OK;
×
203
}
204

205
Json::Value GetTemplateParamsGroup(const Json::Value& templateParams,
2✔
206
                                   const std::string& group,
207
                                   std::list<std::string>& paramsList)
208
{
209
    Json::Value result;
2✔
210
    std::list<std::string> conditionList;
4✔
211
    bool check = true;
2✔
212
    while (check) {
7✔
213
        check = false;
5✔
214
        for (auto it = templateParams.begin(); it != templateParams.end(); ++it) {
43✔
215
            const Json::Value& data = *it;
38✔
216
            std::string id = templateParams.isObject() ? it.key().asString() : data["id"].asString();
38✔
217
            if (std::find(conditionList.begin(), conditionList.end(), id) == conditionList.end() &&
69✔
218
                data["group"].asString() != group)
69✔
219
            {
220
                continue;
12✔
221
            }
222
            if (std::find(paramsList.begin(), paramsList.end(), id) == paramsList.end()) {
26✔
223
                paramsList.push_back(id);
11✔
224
                result[id] = data;
11✔
225
            }
226
            if (data["condition"].isNull()) {
26✔
227
                continue;
15✔
228
            }
229
            Expressions::TLexer lexer;
11✔
230
            auto tokens = lexer.GetTokens(data["condition"].asString());
22✔
231
            for (const auto& token: tokens) {
55✔
232
                if (token.Type == Expressions::TTokenType::Ident && token.Value != "isDefined" &&
55✔
233
                    std::find(conditionList.begin(), conditionList.end(), token.Value) == conditionList.end())
55✔
234
                {
235
                    conditionList.push_back(token.Value);
4✔
236
                    check = true;
4✔
237
                }
238
            }
239
        }
240
    }
241
    return result;
4✔
242
}
243

244
void CheckParametersConditions(const Json::Value& templateParams, Json::Value& parameters)
2✔
245
{
246
    TJsonParams jsonParams(parameters);
4✔
247
    TExpressionsCache expressionsCache;
4✔
248
    bool check = true;
2✔
249
    while (check) {
6✔
250
        std::unordered_map<std::string, bool> matches;
8✔
251
        for (auto it = templateParams.begin(); it != templateParams.end(); ++it) {
34✔
252
            const Json::Value& registerData = *it;
30✔
253
            std::string id = templateParams.isObject() ? it.key().asString() : registerData["id"].asString();
30✔
254
            if (!parameters.isMember(id)) {
30✔
255
                continue;
15✔
256
            }
257
            bool match = CheckCondition(registerData, jsonParams, &expressionsCache);
15✔
258
            if (matches.find(id) == matches.end() || match) {
15✔
259
                matches[id] = match;
15✔
260
            }
261
        }
262
        check = false;
4✔
263
        for (auto it = matches.begin(); it != matches.end(); ++it) {
17✔
264
            if (!it->second) {
13✔
265
                parameters.removeMember(it->first);
3✔
266
                check = true;
3✔
267
            }
268
        }
269
    }
270
    if (parameters.isNull()) {
2✔
271
        parameters = Json::Value(Json::objectValue);
×
272
    }
273
}
2✔
274

275
Json::Value RawValueToJSON(const TRegisterConfig& reg, TRegisterValue val)
×
276
{
277
    switch (reg.Format) {
×
278
        case U8:
×
279
            return val.Get<uint8_t>();
×
280
        case S8:
×
281
            return val.Get<int8_t>();
×
282
        case S16:
×
283
            return val.Get<int16_t>();
×
284
        case S24: {
×
285
            uint32_t v = val.Get<uint64_t>() & 0xffffff;
×
286
            if (v & 0x800000)
×
287
                v |= 0xff000000;
×
288
            return static_cast<int32_t>(v);
×
289
        }
290
        case S32:
×
291
            return val.Get<int32_t>();
×
292
        case S64:
×
293
            return val.Get<int64_t>();
×
294
        case BCD8:
×
295
            return PackedBCD2Int(val.Get<uint64_t>(), WordSizes::W8_SZ);
×
296
        case BCD16:
×
297
            return PackedBCD2Int(val.Get<uint64_t>(), WordSizes::W16_SZ);
×
298
        case BCD24:
×
299
            return PackedBCD2Int(val.Get<uint64_t>(), WordSizes::W24_SZ);
×
300
        case BCD32:
×
301
            return PackedBCD2Int(val.Get<uint64_t>(), WordSizes::W32_SZ);
×
302
        case Float: {
×
303
            float v;
304
            auto rawValue = val.Get<uint64_t>();
×
305

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

309
            return v;
×
310
        }
311
        case Double: {
×
312
            double v;
313
            auto rawValue = val.Get<uint64_t>();
×
314
            memcpy(&v, &rawValue, sizeof(v));
×
315
            return v;
×
316
        }
317
        case Char8:
×
318
            return std::string(1, val.Get<uint8_t>());
×
319
        case String:
×
320
        case String8:
321
            return val.Get<std::string>();
×
322
        default:
×
323
            return val.Get<uint64_t>();
×
324
    }
325
}
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