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

wirenboard / wb-mqtt-serial / 7

26 Nov 2025 06:26AM UTC coverage: 76.773% (-0.1%) from 76.899%
7

push

github

u236
Merge remote-tracking branch 'origin/master' into wasm

6865 of 9158 branches covered (74.96%)

17 of 112 new or added lines in 10 files covered. (15.18%)

6 existing lines in 2 files now uncovered.

12957 of 16877 relevant lines covered (76.77%)

825.72 hits per line

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

12.87
/src/rpc/rpc_device_handler.cpp
1
#include "rpc_device_handler.h"
2
#include "rpc_device_load_config_task.h"
3
#include "rpc_device_load_task.h"
4
#include "rpc_device_probe_task.h"
5
#include "rpc_device_set_task.h"
6
#include "rpc_helpers.h"
7

8
#define LOG(logger) ::logger.Log() << "[RPC] "
9

10
void TRPCDeviceParametersCache::RegisterCallbacks(PHandlerConfig handlerConfig)
×
11
{
12
    for (const auto& portConfig: handlerConfig->PortConfigs) {
×
13
        for (const auto& device: portConfig->Devices) {
×
14
            std::string id = GetId(*portConfig->Port, device->Device->DeviceConfig()->SlaveId);
×
15
            device->Device->AddOnConnectionStateChangedCallback([this, id](PSerialDevice device) {
×
16
                if (device->GetConnectionState() == TDeviceConnectionState::DISCONNECTED) {
×
17
                    Remove(id);
×
18
                }
19
            });
×
20
        }
21
    }
22
}
23

24
std::string TRPCDeviceParametersCache::GetId(const TPort& port, const std::string& slaveId) const
×
25
{
26
    return port.GetDescription(false) + ":" + slaveId;
×
27
}
28

29
void TRPCDeviceParametersCache::Add(const std::string& id, const Json::Value& value)
×
30
{
31
    std::unique_lock lock(Mutex);
×
32
    DeviceParameters[id] = value;
×
33
}
34

35
void TRPCDeviceParametersCache::Remove(const std::string& id)
×
36
{
37
    std::unique_lock lock(Mutex);
×
38
    DeviceParameters.erase(id);
×
39
}
40

41
bool TRPCDeviceParametersCache::Contains(const std::string& id) const
×
42
{
43
    std::unique_lock lock(Mutex);
×
44
    return DeviceParameters.find(id) != DeviceParameters.end();
×
45
}
46

47
const Json::Value& TRPCDeviceParametersCache::Get(const std::string& id, const Json::Value& defaultValue) const
×
48
{
49
    std::unique_lock lock(Mutex);
×
50
    auto it = DeviceParameters.find(id);
×
51
    return it != DeviceParameters.end() ? it->second : defaultValue;
×
52
};
53

54
#ifndef __EMSCRIPTEN__
55
TRPCDeviceHelper::TRPCDeviceHelper(const Json::Value& request,
×
56
                                   const TSerialDeviceFactory& deviceFactory,
57
                                   PTemplateMap templates,
58
                                   TSerialClientTaskRunner& serialClientTaskRunner)
×
59
{
60
    auto params = serialClientTaskRunner.GetSerialClientParams(request);
×
61
    if (params.Device == nullptr) {
×
62
        DeviceTemplate = templates->GetTemplate(request["device_type"].asString());
×
63
        auto protocolName = DeviceTemplate->GetProtocol();
×
64
        if (protocolName == "modbus" && request["modbus_mode"].asString() == "TCP") {
×
65
            protocolName += "-tcp";
×
66
        }
67
        ProtocolParams = deviceFactory.GetProtocolParams(protocolName);
×
68
        auto config = std::make_shared<TDeviceConfig>("RPC Device", request["slave_id"].asString(), protocolName);
×
69
        if (ProtocolParams.protocol->IsModbus()) {
×
70
            config->MaxRegHole = Modbus::MAX_HOLE_CONTINUOUS_16_BIT_REGISTERS;
×
71
            config->MaxBitHole = Modbus::MAX_HOLE_CONTINUOUS_1_BIT_REGISTERS;
×
72
            config->MaxReadRegisters = Modbus::MAX_READ_REGISTERS;
×
73
        }
74
        Device = ProtocolParams.factory->CreateDevice(DeviceTemplate->GetTemplate(), config, ProtocolParams.protocol);
×
75
    } else {
76
        Device = params.Device;
×
77
        DeviceTemplate = templates->GetTemplate(Device->DeviceConfig()->DeviceType);
×
78
        ProtocolParams = deviceFactory.GetProtocolParams(DeviceTemplate->GetProtocol());
×
79
        DeviceFromConfig = true;
×
80
    }
81
    if (DeviceTemplate->WithSubdevices()) {
×
82
        throw TRPCException("Device \"" + DeviceTemplate->Type + "\" is not supported by this RPC",
×
83
                            TRPCResultCode::RPC_WRONG_PARAM_VALUE);
×
84
    }
85
}
86
#endif
87

88
TRPCDeviceRequest::TRPCDeviceRequest(const TDeviceProtocolParams& protocolParams,
×
89
                                     PSerialDevice device,
90
                                     PDeviceTemplate deviceTemplate,
91
                                     bool deviceFromConfig)
×
92
    : ProtocolParams(protocolParams),
93
      Device(device),
94
      DeviceTemplate(deviceTemplate),
95
      DeviceFromConfig(deviceFromConfig)
×
96
{
97
    Json::Value responseTimeout = DeviceTemplate->GetTemplate()["response_timeout_ms"];
×
98
    if (responseTimeout.isInt()) {
×
99
        ResponseTimeout = std::chrono::milliseconds(responseTimeout.asInt());
×
100
    }
101

102
    Json::Value frameTimeout = DeviceTemplate->GetTemplate()["frame_timeout_ms"];
×
103
    if (frameTimeout.isInt()) {
×
104
        FrameTimeout = std::chrono::milliseconds(frameTimeout.asInt());
×
105
    }
106
}
107

108
void TRPCDeviceRequest::ParseSettings(const Json::Value& request,
×
109
                                      WBMQTT::TMqttRpcServer::TResultCallback onResult,
110
                                      WBMQTT::TMqttRpcServer::TErrorCallback onError)
111
{
112
    SerialPortSettings = ParseRPCSerialPortSettings(request);
×
113
    WBMQTT::JSON::Get(request, "response_timeout", ResponseTimeout);
×
114
    WBMQTT::JSON::Get(request, "frame_timeout", FrameTimeout);
×
115
    WBMQTT::JSON::Get(request, "total_timeout", TotalTimeout);
×
116
    OnResult = onResult;
×
117
    OnError = onError;
×
118
}
119

120
#ifndef __EMSCRIPTEN__
121
TRPCDeviceHandler::TRPCDeviceHandler(const std::string& requestDeviceLoadConfigSchemaFilePath,
×
122
                                     const std::string& requestDeviceLoadSchemaFilePath,
123
                                     const std::string& requestDeviceSetSchemaFilePath,
124
                                     const std::string& requestDeviceProbeSchemaFilePath,
125
                                     const std::string& requestDeviceSetPollSchemaFilePath,
126
                                     const TSerialDeviceFactory& deviceFactory,
127
                                     PTemplateMap templates,
128
                                     TSerialClientTaskRunner& serialClientTaskRunner,
129
                                     TRPCDeviceParametersCache& parametersCache,
130
                                     WBMQTT::PMqttRpcServer rpcServer)
×
131
    : DeviceFactory(deviceFactory),
132
      RequestDeviceLoadConfigSchema(LoadRPCRequestSchema(requestDeviceLoadConfigSchemaFilePath, "device/LoadConfig")),
×
133
      RequestDeviceLoadSchema(LoadRPCRequestSchema(requestDeviceLoadSchemaFilePath, "device/Load")),
×
134
      RequestDeviceSetSchema(LoadRPCRequestSchema(requestDeviceSetSchemaFilePath, "device/Set")),
×
135
      RequestDeviceProbeSchema(LoadRPCRequestSchema(requestDeviceProbeSchemaFilePath, "device/Probe")),
×
136
      RequestDeviceSetPollSchema(LoadRPCRequestSchema(requestDeviceSetPollSchemaFilePath, "device/SetPoll")),
×
137
      Templates(templates),
138
      SerialClientTaskRunner(serialClientTaskRunner),
139
      ParametersCache(parametersCache)
×
140
{
141
    rpcServer->RegisterAsyncMethod("device",
×
142
                                   "LoadConfig",
143
                                   std::bind(&TRPCDeviceHandler::LoadConfig,
×
144
                                             this,
×
145
                                             std::placeholders::_1,
146
                                             std::placeholders::_2,
147
                                             std::placeholders::_3));
×
148
    rpcServer->RegisterAsyncMethod("device",
×
149
                                   "Load",
150
                                   std::bind(&TRPCDeviceHandler::Load, //
×
151
                                             this,
×
152
                                             std::placeholders::_1,
153
                                             std::placeholders::_2,
154
                                             std::placeholders::_3));
×
155
    rpcServer->RegisterAsyncMethod("device",
×
156
                                   "Set",
157
                                   std::bind(&TRPCDeviceHandler::Set, //
×
158
                                             this,
×
159
                                             std::placeholders::_1,
160
                                             std::placeholders::_2,
161
                                             std::placeholders::_3));
×
162
    rpcServer->RegisterAsyncMethod("device",
×
163
                                   "Probe",
164
                                   std::bind(&TRPCDeviceHandler::Probe,
×
165
                                             this,
×
166
                                             std::placeholders::_1,
167
                                             std::placeholders::_2,
168
                                             std::placeholders::_3));
×
169

170
    rpcServer->RegisterMethod("device", "SetPoll", std::bind(&TRPCDeviceHandler::SetPoll, this, std::placeholders::_1));
×
171
}
172

173
void TRPCDeviceHandler::LoadConfig(const Json::Value& request,
×
174
                                   WBMQTT::TMqttRpcServer::TResultCallback onResult,
175
                                   WBMQTT::TMqttRpcServer::TErrorCallback onError)
176
{
177
    ValidateRPCRequest(request, RequestDeviceLoadConfigSchema);
×
178
    try {
179
        auto helper = TRPCDeviceHelper(request, DeviceFactory, Templates, SerialClientTaskRunner);
×
180
        auto rpcRequest = ParseRPCDeviceLoadConfigRequest(request,
181
                                                          helper.ProtocolParams,
182
                                                          helper.Device,
183
                                                          helper.DeviceTemplate,
184
                                                          helper.DeviceFromConfig,
185
                                                          ParametersCache,
186
                                                          onResult,
187
                                                          onError);
×
188
        SerialClientTaskRunner.RunTask(request, std::make_shared<TRPCDeviceLoadConfigSerialClientTask>(rpcRequest));
×
189
    } catch (const TRPCException& e) {
×
190
        ProcessException(e, onError);
×
191
    }
192
}
193

194
void TRPCDeviceHandler::Load(const Json::Value& request,
×
195
                             WBMQTT::TMqttRpcServer::TResultCallback onResult,
196
                             WBMQTT::TMqttRpcServer::TErrorCallback onError)
197
{
198
    ValidateRPCRequest(request, RequestDeviceLoadSchema);
×
199
    try {
200
        auto helper = TRPCDeviceHelper(request, DeviceFactory, Templates, SerialClientTaskRunner);
×
201
        auto rpcRequest = ParseRPCDeviceLoadRequest(request,
202
                                                    helper.ProtocolParams,
203
                                                    helper.Device,
204
                                                    helper.DeviceTemplate,
205
                                                    helper.DeviceFromConfig,
206
                                                    onResult,
207
                                                    onError);
×
208
        SerialClientTaskRunner.RunTask(request, std::make_shared<TRPCDeviceLoadSerialClientTask>(rpcRequest));
×
209
    } catch (const TRPCException& e) {
×
210
        ProcessException(e, onError);
×
211
    }
212
}
213

214
void TRPCDeviceHandler::Set(const Json::Value& request,
×
215
                            WBMQTT::TMqttRpcServer::TResultCallback onResult,
216
                            WBMQTT::TMqttRpcServer::TErrorCallback onError)
217
{
218
    ValidateRPCRequest(request, RequestDeviceSetSchema);
×
219
    try {
220
        auto helper = TRPCDeviceHelper(request, DeviceFactory, Templates, SerialClientTaskRunner);
×
221
        auto rpcRequest = ParseRPCDeviceSetRequest(request,
222
                                                   helper.ProtocolParams,
223
                                                   helper.Device,
224
                                                   helper.DeviceTemplate,
225
                                                   helper.DeviceFromConfig,
226
                                                   onResult,
227
                                                   onError);
×
228
        SerialClientTaskRunner.RunTask(request, std::make_shared<TRPCDeviceSetSerialClientTask>(rpcRequest));
×
229
    } catch (const TRPCException& e) {
×
230
        ProcessException(e, onError);
×
231
    }
232
}
233

234
void TRPCDeviceHandler::Probe(const Json::Value& request,
×
235
                              WBMQTT::TMqttRpcServer::TResultCallback onResult,
236
                              WBMQTT::TMqttRpcServer::TErrorCallback onError)
237
{
238
    ValidateRPCRequest(request, RequestDeviceProbeSchema);
×
239
    try {
240
        SerialClientTaskRunner.RunTask(request,
×
241
                                       std::make_shared<TRPCDeviceProbeSerialClientTask>(request, onResult, onError));
×
242
    } catch (const TRPCException& e) {
×
243
        ProcessException(e, onError);
×
244
    }
245
}
246

247
Json::Value TRPCDeviceHandler::SetPoll(const Json::Value& request)
×
248
{
249
    ValidateRPCRequest(request, RequestDeviceSetPollSchema);
×
250
    auto params = SerialClientTaskRunner.GetSerialClientParams(request);
×
251
    if (!params.SerialClient || !params.Device) {
×
252
        throw TRPCException("Port or device not found", TRPCResultCode::RPC_WRONG_PARAM_VALUE);
×
253
    }
254
    try {
255
        if (!request["poll"].asBool()) {
×
256
            params.SerialClient->SuspendPoll(params.Device, std::chrono::steady_clock::now());
×
257
        } else {
258
            params.SerialClient->ResumePoll(params.Device);
×
259
        }
260
    } catch (const std::runtime_error& e) {
×
261
        LOG(Warn) << e.what();
×
262
        throw TRPCException(e.what(), TRPCResultCode::RPC_WRONG_PARAM_VALUE);
×
263
    }
264
    return Json::Value(Json::objectValue);
×
265
}
266
#endif
267

268
TRPCRegisterList CreateRegisterList(const TDeviceProtocolParams& protocolParams,
4✔
269
                                    const PSerialDevice& device,
270
                                    const Json::Value& templateItems,
271
                                    const Json::Value& knownItems,
272
                                    const std::string& fwVersion,
273
                                    bool checkUnsupported)
274
{
275
    TRPCRegisterList registerList;
4✔
276
    for (auto it = templateItems.begin(); it != templateItems.end(); ++it) {
30✔
277
        const auto& item = *it;
26✔
278
        auto id = templateItems.isObject() ? it.key().asString() : item["id"].asString();
26✔
279
        bool duplicate = false;
26✔
280
        for (const auto& item: registerList) {
67✔
281
            if (item.Id == id) {
42✔
282
                duplicate = true;
1✔
283
                break;
1✔
284
            }
285
        }
286
        if (duplicate || item["address"].isNull() || item["readonly"].asBool() || !knownItems[id].isNull()) {
26✔
287
            continue;
5✔
288
        }
289
        if (!fwVersion.empty()) {
21✔
290
            std::string fw = item["fw"].asString();
12✔
291
            if (!fw.empty() && util::CompareVersionStrings(fw, fwVersion) > 0) {
12✔
292
                continue;
4✔
293
            }
294
        }
295

296
        auto config = LoadRegisterConfig(item,
297
                                         *protocolParams.protocol->GetRegTypes(),
17✔
298
                                         std::string(),
34✔
299
                                         *protocolParams.factory,
17✔
300
                                         protocolParams.factory->GetRegisterAddressFactory().GetBaseRegisterAddress(),
17✔
301
                                         0);
34✔
302
        TRPCRegister reg = {id, std::make_shared<TRegister>(device, config.RegisterConfig), checkUnsupported};
34✔
303
        reg.Register->SetAvailable(TRegisterAvailability::AVAILABLE);
17✔
304

305
        // this code checks enums and ranges only for 16-bit register unsupported value 0xFFFE
306
        // it must be modified to check larger registers like 24, 32 or 64-bits
307
        if (reg.CheckUnsupported) {
17✔
308
            int unsupportedValue =
NEW
309
                config.RegisterConfig->Format == S16 ? static_cast<int16_t>(0xFFFE) : static_cast<uint16_t>(0xFFFE);
×
NEW
310
            if (item.isMember("enum")) {
×
NEW
311
                const auto& list = item["enum"];
×
NEW
312
                for (auto it = list.begin(); it != list.end(); ++it) {
×
NEW
313
                    if ((*it).asInt() == unsupportedValue) {
×
NEW
314
                        reg.CheckUnsupported = false;
×
NEW
315
                        break;
×
316
                    }
317
                }
318
            } else {
NEW
319
                if (item["min"].asInt() <= unsupportedValue && item["max"].asInt() >= unsupportedValue) {
×
NEW
320
                    reg.CheckUnsupported = false;
×
321
                }
322
            }
323
        }
324

325
        registerList.push_back(reg);
17✔
326
    }
327
    return registerList;
4✔
328
}
329

330
void ReadRegisterList(TPort& port,
×
331
                      PSerialDevice device,
332
                      TRPCRegisterList& registerList,
333
                      Json::Value& result,
334
                      int maxRetries)
335
{
336
    if (registerList.size() == 0) {
×
337
        return;
×
338
    }
339

340
    TRegisterComparePredicate compare;
NEW
341
    std::sort(registerList.begin(), registerList.end(), [compare](TRPCRegister& a, TRPCRegister& b) {
×
NEW
342
        return compare(b.Register, a.Register);
×
343
    });
344

345
    std::string error;
×
346
    for (int i = 0; i <= maxRetries; i++) {
×
347
        try {
348
            device->Prepare(port, TDevicePrepareMode::WITHOUT_SETUP);
×
349
            break;
×
350
        } catch (const TSerialDeviceException& e) {
×
351
            if (i == maxRetries) {
×
352
                error = std::string("Failed to prepare session: ") + e.what();
×
353
                LOG(Warn) << port.GetDescription() << " " << device->ToString() << ": " << error;
×
354
                throw TRPCException(error, TRPCResultCode::RPC_WRONG_PARAM_VALUE);
×
355
            }
356
        }
357
    }
358

359
    size_t index = 0;
×
360
    while (index < registerList.size() && error.empty()) {
×
NEW
361
        auto first = registerList[index].Register;
×
362
        auto range = device->CreateRegisterRange();
×
363
        while (index < registerList.size() &&
×
NEW
364
               range->Add(port, registerList[index].Register, std::chrono::milliseconds::max()))
×
365
        {
366
            ++index;
×
367
        }
368
        for (int i = 0; i <= maxRetries; ++i) {
×
369
            try {
370
                device->ReadRegisterRange(port, range, true);
×
371
                break;
×
372
            } catch (const TSerialDeviceException& e) {
×
373
                if (i == maxRetries) {
×
374
                    error = "Failed to read " + std::to_string(range->RegisterList().size()) +
×
375
                            " registers starting from <" + first->GetConfig()->ToString() + ">: " + e.what();
×
376
                }
377
            }
378
        }
379
    }
380

381
    try {
382
        device->EndSession(port);
×
383
    } catch (const TSerialDeviceException& e) {
×
384
        LOG(Warn) << port.GetDescription() << " " << device->ToString() << " unable to end session: " << e.what();
×
385
    }
386

387
    if (!error.empty()) {
×
388
        LOG(Warn) << port.GetDescription() << " " << device->ToString() << ": " << error;
×
389
        throw TRPCException(error, TRPCResultCode::RPC_WRONG_PARAM_VALUE);
×
390
    }
391

392
    for (size_t i = 0; i < registerList.size(); ++i) {
×
393
        auto& reg = registerList[i];
×
NEW
394
        result[reg.Id] = RawValueToJSON(*reg.Register->GetConfig(), reg.Register->GetValue());
×
395
    }
396
}
397

NEW
398
Json::Value RawValueToJSON(const TRegisterConfig& reg, TRegisterValue val)
×
399
{
NEW
400
    auto str = ConvertFromRawValue(reg, val);
×
401
    try {
NEW
402
        return std::stod(str.c_str(), 0);
×
NEW
403
    } catch (const std::invalid_argument&) {
×
NEW
404
        return str;
×
405
    }
406
}
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