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

wirenboard / wb-mqtt-serial / 967

05 Oct 2026 09:41AM UTC coverage: 85.672% (+2.4%) from 83.268%
967

push

github

web-flow
Add config/Save RPC (#1273)

The RPC validates a configuration, writes it to the configuration file and
restarts the polling in-process. Port clients are stopped first, requests
arriving meanwhile get config-busy and queued ones get request-cancelled.

12445 of 14263 branches covered (87.25%)

1432 of 1623 new or added lines in 41 files covered. (88.23%)

8 existing lines in 4 files now uncovered.

21065 of 24588 relevant lines covered (85.67%)

1528.89 hits per line

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

82.76
/src/rpc/rpc_config_handler.cpp
1
#include "rpc_config_handler.h"
2
#include "confed_json_generator.h"
3
#include "file_utils.h"
4
#include "json_common.h"
5
#include "log.h"
6
#include "rpc_device_type_json.h"
7
#include "rpc_exception.h"
8
#include "wblib/exceptions.h"
9

10
#ifndef __EMSCRIPTEN__
11
#include "rpc_helpers.h"
12
#endif
13

14
#define LOG(logger) ::logger.Log() << "[RPC] "
15

16
namespace
17
{
18
    const std::string PROTOCOL_PREFIX = "protocol:";
19

20
#ifndef __EMSCRIPTEN__
21
    //! Logs the error and returns its "error.data" code
22
    std::string ProcessApplyConfigError(const TApplyConfigError& e)
7 ✔
23
    {
24
        switch (e.GetReason()) {
7 ✔
25
            case TApplyConfigError::TReason::Busy:
1 ✔
26
                LOG(Debug) << "config/Save: " << e.what();
1 ✔
27
                return CONFIG_BUSY_ERROR;
1 ✔
28
            case TApplyConfigError::TReason::ConfigInvalid:
1 ✔
29
                LOG(Debug) << "config/Save: the configuration is rejected: " << e.what();
1 ✔
30
                return CONFIG_INVALID_ERROR + ": " + e.what();
1 ✔
31
            case TApplyConfigError::TReason::PortTasksTimeout:
2 ✔
32
                LOG(Debug) << "config/Save: " << e.what();
2 ✔
33
                return PORT_BUSY_ERROR;
2 ✔
34
            case TApplyConfigError::TReason::WriteFailed:
2 ✔
35
                LOG(Error) << "config/Save: " << e.what();
2 ✔
36
                return WRITE_FAILED_ERROR;
2 ✔
37
            case TApplyConfigError::TReason::PollingRestartFailed:
1 ✔
38
                LOG(Error) << "config/Save: " << e.what();
1 ✔
39
                return POLLING_RESTART_FAILED_ERROR;
1 ✔
40
        }
NEW
41
        return CONFIG_BUSY_ERROR;
×
42
    }
43
#endif
44

45
    struct TDeviceTypeGroup
46
    {
47
        typedef std::vector<PDeviceTemplate> TemplatesArray;
48

49
        std::string Name;
50
        TemplatesArray Templates;
51
    };
52

53
    Json::Value MakeProtocolJson(const TProtocolConfedSchema& schema, const std::string& lang)
90 ✔
54
    {
55
        Json::Value res;
90 ✔
56
        res["name"] = schema.GetTitle(lang);
90 ✔
57
        res["deprecated"] = false;
90 ✔
58
        res["type"] = PROTOCOL_PREFIX + schema.Type;
90 ✔
59
        res["protocol"] = schema.Type;
90 ✔
60
        res["mqtt-id"] = schema.Type;
90 ✔
61
        return res;
90 ✔
62
    }
×
63

64
    Json::Value MakeDeviceTypeList(const TDeviceTypeGroup::TemplatesArray& templates, const std::string& lang)
24 ✔
65
    {
66
        Json::Value res(Json::arrayValue);
24 ✔
67
        std::for_each(templates.cbegin(), templates.cend(), [&res, &lang](const auto& t) {
24 ✔
68
            res.append(MakeDeviceTypeJson(t, lang));
270 ✔
69
        });
270 ✔
70
        return res;
24 ✔
71
    }
×
72

73
    Json::Value MakeDeviceGroupJson(const TDeviceTypeGroup& group, const std::string& lang)
24 ✔
74
    {
75
        Json::Value res;
24 ✔
76
        res["name"] = group.Name;
24 ✔
77
        res["types"] = MakeDeviceTypeList(group.Templates, lang);
24 ✔
78
        return res;
24 ✔
79
    }
×
80

81
    std::vector<Json::Value> GetProtocols(TProtocolConfedSchemasMap& protocolConfedSchemas, const std::string& lang)
6 ✔
82
    {
83
        std::vector<Json::Value> res;
6 ✔
84
        for (const auto& protocolSchema: protocolConfedSchemas.GetSchemas()) {
96 ✔
85
            res.emplace_back(MakeProtocolJson(protocolSchema.second, lang));
90 ✔
86
        }
87
        std::sort(res.begin(), res.end(), [](const auto& p1, const auto& p2) {
6 ✔
88
            return p1["name"].asString() < p2["name"].asString();
306 ✔
89
        });
90
        return res;
6 ✔
91
    }
×
92

93
    std::vector<TDeviceTypeGroup> OrderTemplates(const std::vector<PDeviceTemplate>& templates,
6 ✔
94
                                                 const Json::Value& groupTranslations,
95
                                                 const std::string& lang)
96
    {
97
        std::map<std::string, std::vector<PDeviceTemplate>> groups;
6 ✔
98
        std::vector<PDeviceTemplate> groupWb;
6 ✔
99
        std::vector<PDeviceTemplate> groupWbOld;
6 ✔
100

101
        for (const auto& templatePtr: templates) {
276 ✔
102
            const auto& group = templatePtr->GetGroup();
270 ✔
103
            if (group == WB_GROUP_NAME) {
270 ✔
104
                groupWb.push_back(templatePtr);
×
105
            } else if (group == WB_OLD_GROUP_NAME) {
270 ✔
106
                groupWbOld.push_back(templatePtr);
×
107
            } else if (group.empty()) {
270 ✔
108
                groups[CUSTOM_GROUP_NAME].push_back(templatePtr);
264 ✔
109
            } else {
110
                groups[group].push_back(templatePtr);
6 ✔
111
            }
112
        }
113

114
        auto titleSortFn = [&lang](const auto& t1, const auto& t2) { return t1->GetTitle(lang) < t2->GetTitle(lang); };
1,716 ✔
115
        std::for_each(groups.begin(), groups.end(), [&](auto& group) {
6 ✔
116
            std::sort(group.second.begin(), group.second.end(), titleSortFn);
12 ✔
117
        });
12 ✔
118
        std::sort(groupWb.begin(), groupWb.end(), titleSortFn);
6 ✔
119
        std::sort(groupWbOld.begin(), groupWbOld.end(), titleSortFn);
6 ✔
120

121
        std::vector<TDeviceTypeGroup> res;
6 ✔
122
        std::transform(groups.begin(), groups.end(), std::back_inserter(res), [&](auto& group) {
6 ✔
123
            return TDeviceTypeGroup{GetGroupTranslation(group.first, lang, groupTranslations), std::move(group.second)};
48 ✔
124
        });
12 ✔
125

126
        std::sort(res.begin(), res.end(), [](const auto& g1, const auto& g2) { return g1.Name < g2.Name; });
18 ✔
127
        res.insert(
6 ✔
128
            res.begin(),
6 ✔
129
            TDeviceTypeGroup{GetGroupTranslation(WB_OLD_GROUP_NAME, lang, groupTranslations), std::move(groupWbOld)});
6 ✔
130
        res.insert(res.begin(),
6 ✔
131
                   TDeviceTypeGroup{GetGroupTranslation(WB_GROUP_NAME, lang, groupTranslations), std::move(groupWb)});
6 ✔
132
        return res;
6 ✔
133
    }
6 ✔
134
}
135

136
#ifndef __EMSCRIPTEN__
137
TRPCConfigHandler::TRPCConfigHandler(const std::string& configPath,
24 ✔
138
                                     const Json::Value& portsSchema,
139
                                     PTemplateMap templates,
140
                                     TDevicesConfedSchemasMap& deviceConfedSchemas,
141
                                     TProtocolConfedSchemasMap& protocolConfedSchemas,
142
                                     const Json::Value& groupTranslations,
143
                                     TSerialDriverCore& serialDriverCore,
144
                                     WBMQTT::PMqttRpcServer rpcServer)
24 ✔
145
    : ConfigPath(configPath),
24 ✔
146
      PortsSchema(portsSchema),
24 ✔
147
      Templates(templates),
24 ✔
148
      DeviceConfedSchemas(deviceConfedSchemas),
24 ✔
149
      ProtocolConfedSchemas(protocolConfedSchemas),
24 ✔
150
      GroupTranslations(groupTranslations),
24 ✔
151
      SerialDriverCore(serialDriverCore)
24 ✔
152
{
153
    rpcServer->RegisterMethod("config", "Load", std::bind(&TRPCConfigHandler::LoadConfig, this, std::placeholders::_1));
96 ✔
154
    rpcServer->RegisterMethod("config",
120 ✔
155
                              "GetSchema",
156
                              std::bind(&TRPCConfigHandler::GetSchema, this, std::placeholders::_1));
48 ✔
157
    rpcServer->RegisterAsyncMethod("config",
120 ✔
158
                                   "Save",
NEW
159
                                   std::bind(&TRPCConfigHandler::SaveConfig,
×
160
                                             this,
48 ✔
161
                                             std::placeholders::_1,
162
                                             std::placeholders::_2,
163
                                             std::placeholders::_3));
164
}
24 ✔
165

166
TRPCConfigHandler::~TRPCConfigHandler()
24 ✔
167
{
168
    if (SaveThread.joinable()) {
24 ✔
169
        SaveThread.join();
22 ✔
170
    }
171
}
24 ✔
172
#else
173
TRPCConfigHandler::TRPCConfigHandler(const Json::Value& portsSchema,
174
                                     PTemplateMap templates,
175
                                     TDevicesConfedSchemasMap& deviceConfedSchemas,
176
                                     TProtocolConfedSchemasMap& protocolConfedSchemas,
177
                                     const Json::Value& groupTranslations)
178
    : PortsSchema(portsSchema),
179
      Templates(templates),
180
      DeviceConfedSchemas(deviceConfedSchemas),
181
      ProtocolConfedSchemas(protocolConfedSchemas),
182
      GroupTranslations(groupTranslations)
183
{}
184
#endif
185

186
Json::Value TRPCConfigHandler::LoadConfig(const Json::Value& request)
7 ✔
187
{
188
#ifndef __EMSCRIPTEN__
189
    if (SerialDriverCore.IsBusy()) {
7 ✔
190
        throw TRPCException(CONFIG_BUSY_ERROR, TRPCResultCode::RPC_WRONG_PARAM_VALUE);
1 ✔
191
    }
192
#endif
193
    Json::Value res;
6 ✔
194
    res["config"] = MakeJsonForConfed(ConfigPath, *Templates);
6 ✔
195
    res["schema"] = PortsSchema;
6 ✔
196
    res["types"] = GetDeviceTypes(request);
6 ✔
197
    return res;
6 ✔
198
}
×
199

200
Json::Value TRPCConfigHandler::GetDeviceTypes(const Json::Value& request)
6 ✔
201
{
202
    std::string lang(request.get("lang", "en").asString());
6 ✔
203
    Json::Value res(Json::arrayValue);
6 ✔
204
    auto templateGroups = OrderTemplates(Templates->GetTemplates(), GroupTranslations, lang);
6 ✔
205
    auto customGroupName = GetGroupTranslation(CUSTOM_GROUP_NAME, lang, GroupTranslations);
6 ✔
206
    bool customGroupIsMissing = true;
6 ✔
207
    std::for_each(templateGroups.cbegin(), templateGroups.cend(), [&](const auto& group) {
6 ✔
208
        auto groupJson = MakeDeviceGroupJson(group, lang);
24 ✔
209
        if (group.Name == customGroupName) {
24 ✔
210
            customGroupIsMissing = false;
6 ✔
211
            for (auto& protocolJson: GetProtocols(ProtocolConfedSchemas, lang)) {
96 ✔
212
                groupJson["types"].append(std::move(protocolJson));
90 ✔
213
            }
214
        }
215
        res.append(groupJson);
24 ✔
216
    });
24 ✔
217
    if (customGroupIsMissing) {
6 ✔
218
        Json::Value groupJson;
×
219
        groupJson["name"] = customGroupName;
×
220
        groupJson["types"] = Json::Value(Json::arrayValue);
×
221
        for (auto& protocolJson: GetProtocols(ProtocolConfedSchemas, lang)) {
×
222
            groupJson["types"].append(std::move(protocolJson));
×
223
        }
×
224
        res.append(groupJson);
×
225
    }
×
226
    return res;
6 ✔
227
}
6 ✔
228

229
Json::Value TRPCConfigHandler::GetSchema(const Json::Value& request)
1 ✔
230
{
231
#ifndef __EMSCRIPTEN__
232
    if (SerialDriverCore.IsBusy()) {
1 ✔
233
        throw TRPCException(CONFIG_BUSY_ERROR, TRPCResultCode::RPC_WRONG_PARAM_VALUE);
1 ✔
234
    }
235
#endif
236
    std::string type = request.get("type", "").asString();
×
237
    if (type.find(PROTOCOL_PREFIX) == 0) {
×
238
        type = type.substr(PROTOCOL_PREFIX.size());
×
239
        return ProtocolConfedSchemas.GetSchema(type);
×
240
    }
241
    try {
242
        return *DeviceConfedSchemas.GetSchema(type);
×
243
    } catch (const std::runtime_error& e) {
×
244
        LOG(Error) << e.what();
×
245
        throw TRPCException("Template \"" + type + "\" schema validation failed",
×
246
                            TRPCResultCode::RPC_WRONG_PARAM_VALUE);
×
247
    }
×
248
}
×
249

250
#ifndef __EMSCRIPTEN__
251
void TRPCConfigHandler::SaveConfig(const Json::Value& request,
31 ✔
252
                                   WBMQTT::TMqttRpcServer::TResultCallback onResult,
253
                                   WBMQTT::TMqttRpcServer::TErrorCallback onError)
254
{
255
    if (!request["config"].isObject()) {
31 ✔
256
        onError(WBMQTT::E_RPC_SERVER_ERROR, "\"config\" object is required");
1 ✔
257
        return;
2 ✔
258
    }
259
    std::lock_guard<std::mutex> lock(SaveMutex);
30 ✔
260
    if (SaveInProgress) {
30 ✔
261
        onError(WBMQTT::E_RPC_SERVER_ERROR, CONFIG_BUSY_ERROR);
1 ✔
262
        return;
1 ✔
263
    }
264
    if (SaveThread.joinable()) {
29 ✔
265
        SaveThread.join();
7 ✔
266
    }
267
    SaveInProgress = true;
29 ✔
268
    // The RPC server goes on answering other requests meanwhile
269
    SaveThread = std::thread([this, request, onResult, onError]() {
58 ✔
270
        std::string error;
29 ✔
271
        try {
272
            SerialDriverCore.ApplyConfig(request["config"]);
29 ✔
273
        } catch (const TApplyConfigError& e) {
7 ✔
274
            error = ProcessApplyConfigError(e);
7 ✔
275
        } catch (const std::exception& e) {
7 ✔
NEW
276
            error = e.what();
×
NEW
277
        }
×
278
        {
279
            std::lock_guard<std::mutex> lock(SaveMutex);
29 ✔
280
            SaveInProgress = false;
29 ✔
281
        }
29 ✔
282
        if (error.empty()) {
29 ✔
283
            onResult(Json::Value(Json::objectValue));
22 ✔
284
        } else {
285
            onError(WBMQTT::E_RPC_SERVER_ERROR, error);
7 ✔
286
        }
287
    });
58 ✔
288
}
30 ✔
289
#endif
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc