• 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

0.0
/src/rpc/rpc_port_scan_serial_client_task.cpp
1
#include "rpc_port_scan_serial_client_task.h"
2
#include "modbus_base.h"
3
#include "modbus_common.h"
4
#include "port/serial_port.h"
5
#include "rpc_helpers.h"
6
#include "wb_registers.h"
7
#include <regex>
8

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

11
template<> bool inline WBMQTT::JSON::Is<ModbusExt::TModbusExtCommand>(const Json::Value& value)
×
12
{
13
    return value.isUInt();
×
14
}
15

16
template<> inline ModbusExt::TModbusExtCommand WBMQTT::JSON::As<ModbusExt::TModbusExtCommand>(const Json::Value& value)
×
17
{
18
    return ModbusExt::TModbusExtCommand(value.asUInt());
×
19
}
20

21
namespace
22
{
23
    const std::string READ_FW_VERSION_ERROR_ID = "com.wb.device_manager.device.read_fw_version_error";
24
    const std::string READ_FW_SIGNATURE_ERROR_ID = "com.wb.device_manager.device.read_fw_signature_error";
25
    const std::string READ_DEVICE_SIGNATURE_ERROR_ID = "com.wb.device_manager.device.read_device_signature_error";
26
    const std::string READ_SERIAL_PARAMS_ERROR_ID = "com.wb.device_manager.device.read_serial_params_error";
27
    const std::string READ_SN_ERROR_ID = "com.wb.device_manager.device.read_sn_error";
28

29
    uint32_t GetSnFromRegister(const std::string& deviceModel, uint32_t sn)
×
30
    {
31
        if (std::regex_match(deviceModel, std::regex("\\S*MAP\\d+\\S*"))) {
×
32
            return sn & 0x01FFFFFF;
×
33
        }
34
        return sn;
×
35
    }
36

37
    std::string GetParityChar(uint64_t parity)
×
38
    {
39
        const std::unordered_map<uint64_t, std::string> parityMap = {{0, "N"}, {1, "O"}, {2, "E"}};
×
40
        auto it = parityMap.find(parity);
×
41
        return (it != parityMap.end() ? it->second : "U");
×
42
    }
43

44
    void AppendError(Json::Value& errorsJson, const std::string& id, const std::string& message)
×
45
    {
46
        Json::Value errorJson;
×
47
        errorJson["id"] = id;
×
48
        errorJson["message"] = message;
×
49
        errorsJson.append(errorJson);
×
50
    }
51

52
    std::string GetDeviceSignature(RpcPortScan::TRegisterReader& reader)
×
53
    {
54
        try {
55
            return reader.Read<std::string>(WbRegisters::DEVICE_MODEL_EX_REGISTER_NAME);
×
56
        } catch (const std::exception& e) {
×
57
            return reader.Read<std::string>(WbRegisters::DEVICE_MODEL_REGISTER_NAME);
×
58
        }
59
    }
60

61
    Json::Value GetDeviceDetails(RpcPortScan::TRegisterReader& reader,
×
62
                                 uint8_t slaveId,
63
                                 uint32_t snFromRegister,
64
                                 const std::list<PSerialDevice>& polledDevices)
65
    {
66
        Json::Value errorsJson(Json::arrayValue);
×
67
        Json::Value deviceJson;
×
68

69
        std::string stringSn = std::to_string(snFromRegister);
×
70
        try {
71
            deviceJson["device_signature"] = GetDeviceSignature(reader);
×
72
            stringSn = std::to_string(GetSnFromRegister(deviceJson["device_signature"].asString(), snFromRegister));
×
73
        } catch (const std::exception& e) {
×
74
            AppendError(errorsJson, READ_DEVICE_SIGNATURE_ERROR_ID, e.what());
×
75
        }
76
        deviceJson["sn"] = stringSn;
×
77

78
        try {
79
            deviceJson["fw_signature"] = reader.Read<std::string>(WbRegisters::FW_SIGNATURE_REGISTER_NAME);
×
80
        } catch (const std::exception& e) {
×
81
            AppendError(errorsJson, READ_FW_SIGNATURE_ERROR_ID, e.what());
×
82
        }
83

84
        Json::Value cfgJson;
×
85
        cfgJson["slave_id"] = slaveId;
×
86
        cfgJson["data_bits"] = 8;
×
87
        std::string serialParamsReadError;
×
88
        try {
89
            cfgJson["baud_rate"] = reader.Read<uint64_t>(WbRegisters::BAUD_RATE_REGISTER_NAME) * 100;
×
90
        } catch (const std::exception& e) {
×
91
            serialParamsReadError = e.what();
×
92
        }
93
        try {
94
            cfgJson["stop_bits"] = reader.Read<uint64_t>(WbRegisters::STOP_BITS_REGISTER_NAME);
×
95
        } catch (const std::exception& e) {
×
96
            serialParamsReadError = e.what();
×
97
        }
98
        try {
99
            cfgJson["parity"] = GetParityChar(reader.Read<uint64_t>(WbRegisters::PARITY_REGISTER_NAME));
×
100
        } catch (const std::exception& e) {
×
101
            serialParamsReadError = e.what();
×
102
        }
103
        if (!serialParamsReadError.empty()) {
×
104
            AppendError(errorsJson, READ_SERIAL_PARAMS_ERROR_ID, serialParamsReadError);
×
105
        }
106
        deviceJson["cfg"] = cfgJson;
×
107

108
        try {
109
            Json::Value fwJson;
×
110
            fwJson["version"] = reader.Read<std::string>(WbRegisters::FW_VERSION_REGISTER_NAME);
×
111
            deviceJson["fw"] = fwJson;
×
112
        } catch (const std::exception& e) {
×
113
            AppendError(errorsJson, READ_FW_VERSION_ERROR_ID, e.what());
×
114
        }
115

116
        auto stringSlaveId = std::to_string(slaveId);
×
117
        for (const auto& polledDevice: polledDevices) {
×
118
            if (polledDevice->DeviceConfig()->SlaveId == stringSlaveId && polledDevice->Protocol()->IsModbus()) {
×
119
                auto snRegister = polledDevice->GetSnRegister();
×
120
                if (snRegister) {
×
121
                    if (snRegister->GetErrorState().test(TRegister::ReadError) ||
×
122
                        snRegister->GetValue().GetType() == TRegisterValue::ValueType::Undefined)
×
123
                    {
124
                        auto registerRange = polledDevice->CreateRegisterRange();
×
125
                        registerRange->Add(reader.GetPort(), snRegister, std::chrono::milliseconds::max());
×
126
                        polledDevice->ReadRegisterRange(reader.GetPort(), registerRange);
×
127
                    }
128
                    if (snRegister->GetValue().GetType() == TRegisterValue::ValueType::Integer &&
×
129
                        snFromRegister == snRegister->GetValue().Get<uint64_t>())
×
130
                    {
131
                        deviceJson["configured_device_type"] = polledDevice->DeviceConfig()->DeviceType;
×
132
                        break;
×
133
                    }
134
                }
135
            }
136
        }
137

138
        if (!errorsJson.empty()) {
×
139
            deviceJson["errors"] = errorsJson;
×
140
        }
141

142
        return deviceJson;
×
143
    }
144

145
    Json::Value GetScannedDeviceDetails(TPort& port,
×
146
                                        const ModbusExt::TScannedDevice& scannedDevice,
147
                                        ModbusExt::TModbusExtCommand modbusExtCommand,
148
                                        const std::list<PSerialDevice>& polledDevices,
149
                                        bool modbusTcp)
150
    {
151
        std::unique_ptr<Modbus::IModbusTraits> baseTraits;
×
152
        if (modbusTcp) {
×
153
            baseTraits = std::make_unique<Modbus::TModbusTCPTraits>();
×
154
        } else {
155
            baseTraits = std::make_unique<Modbus::TModbusRTUTraits>();
×
156
        }
157
        ModbusExt::TModbusTraits modbusTraits(std::move(baseTraits));
×
158
        modbusTraits.SetModbusExtCommand(modbusExtCommand);
×
159
        modbusTraits.SetSn(scannedDevice.Sn);
×
160
        RpcPortScan::TRegisterReader reader(port, modbusTraits, scannedDevice.SlaveId);
×
161
        return ::GetDeviceDetails(reader, scannedDevice.SlaveId, scannedDevice.Sn, polledDevices);
×
162
    }
163
}
164

165
Json::Value RpcPortScan::GetDeviceDetails(RpcPortScan::TRegisterReader& reader,
×
166
                                          uint8_t slaveId,
167
                                          const std::list<PSerialDevice>& polledDevices)
168
{
169
    uint64_t sn;
170
    try {
171
        sn = reader.Read<uint64_t>(WbRegisters::SN_REGISTER_NAME);
×
172
    } catch (const TResponseTimeoutException& e) {
×
173
        return Json::Value(Json::objectValue);
×
174
    } catch (const Modbus::TErrorBase& e) {
×
175
        Json::Value errorsJson(Json::arrayValue);
×
176
        AppendError(errorsJson, READ_SN_ERROR_ID, e.what());
×
177
        Json::Value deviceJson;
×
178
        deviceJson["errors"] = errorsJson;
×
179
        return deviceJson;
×
180
    }
181
    return ::GetDeviceDetails(reader, slaveId, sn, polledDevices);
×
182
}
183

184
RpcPortScan::TRegisterReader::TRegisterReader(TPort& port, Modbus::IModbusTraits& modbusTraits, uint8_t slaveId)
×
185
    : ModbusTraits(modbusTraits),
186
      FrameTimeout(
187
          std::chrono::ceil<std::chrono::milliseconds>(port.GetSendTimeBytes(Modbus::STANDARD_FRAME_TIMEOUT_BYTES))),
×
188
      Port(port),
189
      SlaveId(slaveId)
×
190
{}
191

192
PRPCPortScanRequest ParseRPCPortScanRequest(const Json::Value& request)
×
193
{
194
    PRPCPortScanRequest res = std::make_shared<TRPCPortScanRequest>();
×
195
    res->SerialPortSettings = ParseRPCSerialPortSettings(request);
×
196
    WBMQTT::JSON::Get(request, "total_timeout", res->TotalTimeout);
×
197
    WBMQTT::JSON::Get(request, "command", res->ModbusExtCommand);
×
198
    return res;
×
199
}
200

201
void ExecRPCPortScanRequest(TPort& port,
×
202
                            PRPCPortScanRequest rpcRequest,
203
                            const std::list<PSerialDevice>& polledDevices,
204
                            bool modbusTcp)
205
{
206
    if (!rpcRequest->OnResult) {
×
207
        return;
×
208
    }
209

210
    Json::Value replyJSON;
×
211
    std::vector<ModbusExt::TScannedDevice> scannedDevices;
×
212
    std::shared_ptr<Modbus::IModbusTraits> traits;
×
213
    if (modbusTcp) {
×
214
        traits = std::make_shared<Modbus::TModbusTCPTraits>();
×
215
    } else {
216
        traits = std::make_shared<ModbusExt::TModbusRTUWithArbitrationTraits>();
×
217
    }
218
    try {
219
        switch (rpcRequest->Mode) {
×
220
            case TRPCPortScanRequest::START: {
×
221
                auto device = ModbusExt::ScanStart(port, *traits, rpcRequest->ModbusExtCommand);
×
222
                if (device) {
×
223
                    scannedDevices.push_back(*device);
×
224
                }
225
                break;
×
226
            }
227
            case TRPCPortScanRequest::NEXT: {
×
228
                auto device = ModbusExt::ScanNext(port, *traits, rpcRequest->ModbusExtCommand);
×
229
                if (device) {
×
230
                    scannedDevices.push_back(*device);
×
231
                }
232
                break;
×
233
            }
234
            case TRPCPortScanRequest::ALL: {
×
235
                ModbusExt::Scan(port, *traits, rpcRequest->ModbusExtCommand, scannedDevices);
×
236
                break;
×
237
            }
238
        }
239
    } catch (const std::exception& e) {
×
240
        replyJSON["error"] = e.what();
×
241
    }
242

243
    replyJSON["devices"] = Json::Value(Json::arrayValue);
×
244
    for (const auto& device: scannedDevices) {
×
245
        replyJSON["devices"].append(
×
246
            GetScannedDeviceDetails(port, device, rpcRequest->ModbusExtCommand, polledDevices, modbusTcp));
×
247
    }
248

249
    rpcRequest->OnResult(replyJSON);
×
250
}
251

252
TRPCPortScanSerialClientTask::TRPCPortScanSerialClientTask(const Json::Value& request,
×
253
                                                           WBMQTT::TMqttRpcServer::TResultCallback onResult,
254
                                                           WBMQTT::TMqttRpcServer::TErrorCallback onError)
×
255
    : Request(ParseRPCPortScanRequest(request))
×
256
{
257
    Request->OnResult = onResult;
×
258
    Request->OnError = onError;
×
259
    std::string mode;
×
260
    WBMQTT::JSON::Get(request, "mode", mode);
×
261
    if (mode == "start") {
×
262
        Request->Mode = TRPCPortScanRequest::START;
×
263
    } else if (mode == "next") {
×
264
        Request->Mode = TRPCPortScanRequest::NEXT;
×
265
    }
266
    ExpireTime = std::chrono::steady_clock::now() + Request->TotalTimeout;
×
267
}
268

269
ISerialClientTask::TRunResult TRPCPortScanSerialClientTask::Run(PFeaturePort port,
×
270
                                                                TSerialClientDeviceAccessHandler& lastAccessedDevice,
271
                                                                const std::list<PSerialDevice>& polledDevices)
272
{
273
    if (std::chrono::steady_clock::now() > ExpireTime) {
×
274
        if (Request->OnError) {
×
275
            Request->OnError(WBMQTT::E_RPC_REQUEST_TIMEOUT, "RPC request timeout");
×
276
        }
277
        return ISerialClientTask::TRunResult::OK;
×
278
    }
279

280
    if (!port->SupportsFastModbus() && port->IsModbusTcp()) {
×
281
        if (Request->OnResult) {
×
282
            Json::Value result;
×
NEW
283
            result["devices"] = Json::Value(Json::arrayValue);
×
UNCOV
284
            Request->OnResult(result);
×
285
        }
286
        return ISerialClientTask::TRunResult::OK;
×
287
    }
288

289
    try {
290
        if (!port->IsOpen()) {
×
291
            port->Open();
×
292
        }
293
        lastAccessedDevice.PrepareToAccess(*port, nullptr);
×
294
        TSerialPortSettingsGuard settingsGuard(port, Request->SerialPortSettings);
×
295
        ExecRPCPortScanRequest(*port, Request, polledDevices, port->IsModbusTcp());
×
296
    } catch (const std::exception& error) {
×
297
        if (Request->OnError) {
×
298
            Request->OnError(WBMQTT::E_RPC_SERVER_ERROR, std::string("Port IO error: ") + error.what());
×
299
        }
300
    }
301
    return ISerialClientTask::TRunResult::OK;
×
302
}
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