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

wirenboard / wb-mqtt-serial / 6

06 Nov 2025 11:53AM UTC coverage: 76.899% (+3.6%) from 73.348%
6

push

github

web-flow
Remove unnecessary files

6860 of 9109 branches covered (75.31%)

12949 of 16839 relevant lines covered (76.9%)

749.85 hits per line

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

90.36
/src/config_merge_template.cpp
1
#include "config_merge_template.h"
2
#include "expression_evaluator.h"
3
#include "log.h"
4
#include "serial_config.h"
5

6
#define LOG(logger) ::logger.Log() << "[serial config] "
7

8
using namespace std;
9
using namespace WBMQTT::JSON;
10
using Expressions::TExpressionsCache;
11

12
namespace
13
{
14
    void RemoveDisabledChannels(Json::Value& config, const Json::Value& deviceData, TExpressionsCache& exprs)
41✔
15
    {
16
        TJsonParams params(deviceData);
82✔
17
        std::vector<Json::ArrayIndex> channelsToRemove;
82✔
18
        auto& channels = config["channels"];
41✔
19
        for (Json::ArrayIndex i = 0; i < channels.size(); ++i) {
211✔
20
            if (!CheckCondition(channels[i], params, &exprs)) {
170✔
21
                channelsToRemove.emplace_back(i);
14✔
22
            }
23
        }
24
        for (auto it = channelsToRemove.rbegin(); it != channelsToRemove.rend(); ++it) {
55✔
25
            channels.removeIndex(*it, nullptr);
14✔
26
        }
27
    }
41✔
28
}
29

30
void UpdateChannels(Json::Value& dst,
31
                    const Json::Value& userConfig,
32
                    TSubDevicesTemplateMap& channelTemplates,
33
                    const std::string& logPrefix);
34

35
void AppendSetupItems(Json::Value& deviceTemplate, const Json::Value& config, TExpressionsCache* exprs = nullptr)
61✔
36
{
37
    Json::Value newSetup(Json::arrayValue);
122✔
38

39
    TJsonParams params(config);
122✔
40

41
    if (config.isMember("setup")) {
61✔
42
        for (const auto& item: config["setup"]) {
13✔
43
            if (!item.isMember("address")) {
7✔
44
                throw TConfigParserException("Setup command '" + item["title"].asString() + "' must have address");
1✔
45
            }
46
            newSetup.append(item);
6✔
47
        }
48
    }
49

50
    if (deviceTemplate.isMember("parameters")) {
60✔
51
        Json::Value& templateParameters = deviceTemplate["parameters"];
49✔
52
        for (auto it = templateParameters.begin(); it != templateParameters.end(); ++it) {
83✔
53
            auto name = templateParameters.isArray() ? (*it)["id"].asString() : it.name();
68✔
54
            if (config.isMember(name)) {
34✔
55
                auto& cfgItem = config[name];
19✔
56
                if (cfgItem.isNumeric()) {
19✔
57
                    // Readonly parameters are used now only for web-interface organization.
58
                    // We will read them from devices in the future.
59
                    if (!it->get("readonly", false).asBool() && CheckCondition(*it, params, exprs)) {
19✔
60
                        Json::Value item(*it);
30✔
61
                        item["value"] = cfgItem;
15✔
62
                        if (!templateParameters.isArray()) {
15✔
63
                            item["id"] = name;
3✔
64
                        }
65
                        newSetup.append(item);
15✔
66
                    }
67
                } else {
68
                    LOG(Warn) << name << " is not an integer";
×
69
                }
70
                deviceTemplate.removeMember(name);
19✔
71
            }
72
        }
73
        deviceTemplate.removeMember("parameters");
49✔
74
    }
75

76
    if (deviceTemplate.isMember("setup")) {
60✔
77
        for (const auto& item: deviceTemplate["setup"]) {
67✔
78
            if (CheckCondition(item, params, exprs)) {
44✔
79
                newSetup.append(item);
43✔
80
            }
81
        }
82
    }
83

84
    if (newSetup.empty()) {
60✔
85
        deviceTemplate.removeMember("setup");
28✔
86
    } else {
87
        deviceTemplate["setup"] = newSetup;
32✔
88
    }
89
}
60✔
90

91
void SetPropertyWithNotification(Json::Value& dst, Json::Value::const_iterator itProp, const std::string& logPrefix)
139✔
92
{
93
    if (dst.isMember(itProp.name()) && dst[itProp.name()] != *itProp) {
139✔
94
        LOG(Info) << logPrefix << " override property \"" << itProp.name() << "\"";
4✔
95
    }
96
    dst[itProp.name()] = *itProp;
139✔
97
}
139✔
98

99
void CheckDeviceType(Json::Value& templateConfig, const Json::Value& userConfig, const std::string& logPrefix)
23✔
100
{
101
    if (templateConfig.isMember("oneOf")) {
23✔
102
        if (!userConfig.isMember("device_type")) {
1✔
103
            throw TConfigParserException("'" + logPrefix + "' must contain device_type");
×
104
        }
105
        for (const auto& item: templateConfig["oneOf"]) {
1✔
106
            if (item == userConfig["device_type"]) {
1✔
107
                templateConfig["device_type"] = item;
1✔
108
                return;
1✔
109
            }
110
        }
111
        throw TConfigParserException("'" + logPrefix + "' can't have type '" + userConfig["device_type"].asString() +
×
112
                                     "'");
×
113
    }
114
    if (templateConfig.isMember("device_type")) {
22✔
115
        if (!userConfig.isMember("device_type") || (userConfig["device_type"] != templateConfig["device_type"])) {
8✔
116
            throw TConfigParserException("'" + logPrefix + "' device_type must be '" +
×
117
                                         templateConfig["device_type"].asString() + "'");
×
118
        }
119
        return;
8✔
120
    }
121

122
    if (userConfig.isMember("device_type")) {
14✔
123
        throw TConfigParserException("'" + logPrefix + "' can't contain device_type");
1✔
124
    }
125
}
126

127
void MergeChannelProperties(Json::Value& templateConfig, const Json::Value& userConfig, const std::string& logPrefix)
23✔
128
{
129
    CheckDeviceType(templateConfig, userConfig, logPrefix);
23✔
130

131
    // clang-format off
132
    const std::vector<std::string> simpleChannelforbiddenOverrides({
133
        "id",
134
        "type",
135
        "reg_type",
136
        "address",
137
        "format",
138
        "max",
139
        "scale",
140
        "offset",
141
        "round_to",
142
        "on_value",
143
        "off_value",
144
        "error_value",
145
        "unsupported_value",
146
        "word_order",
147
        "byte_order",
148
        "consists_of"});
440✔
149

150
    const std::vector<std::string> subdeviceChannelforbiddenOverrides({
151
        "id",
152
        "oneOf"});
132✔
153
    // clang-format on
154

155
    const auto& forbiddenOverrides = (templateConfig.isMember("device_type") || templateConfig.isMember("oneOf"))
35✔
156
                                         ? subdeviceChannelforbiddenOverrides
157
                                         : simpleChannelforbiddenOverrides;
35✔
158

159
    for (auto itProp = userConfig.begin(); itProp != userConfig.end(); ++itProp) {
78✔
160
        if (itProp.name() == "enabled") {
56✔
161
            SetPropertyWithNotification(templateConfig, itProp, logPrefix);
4✔
162
            continue;
4✔
163
        }
164
        if (itProp.name() == "readonly") {
52✔
165
            if ((itProp->asString() != "true") && (templateConfig.isMember(itProp.name())) &&
7✔
166
                (templateConfig[itProp.name()].asString() == "true"))
4✔
167
            {
168
                LOG(Warn) << logPrefix << " \"readonly\" is already set to \"true\" in template";
1✔
169
                continue;
1✔
170
            }
171
            SetPropertyWithNotification(templateConfig, itProp, logPrefix);
2✔
172
            continue;
2✔
173
        }
174

175
        if (itProp.name() == "name") {
49✔
176
            continue;
22✔
177
        }
178
        if (itProp.name() == "device_type") {
27✔
179
            continue;
9✔
180
        }
181

182
        if (std::find(forbiddenOverrides.begin(), forbiddenOverrides.end(), itProp.name()) != forbiddenOverrides.end())
18✔
183
        {
184
            LOG(Warn) << logPrefix << " can't override property \"" << itProp.name() << "\"";
×
185
            continue;
×
186
        }
187

188
        templateConfig[itProp.name()] = *itProp;
18✔
189
    }
190
}
22✔
191

192
Json::Value MergeChannelConfigWithTemplate(const Json::Value& channelConfig,
208✔
193
                                           TSubDevicesTemplateMap& templates,
194
                                           const std::string& logPrefix)
195
{
196
    if (!channelConfig.isMember("device_type")) {
208✔
197
        return channelConfig;
190✔
198
    }
199

200
    Json::Value res(templates.GetTemplate(channelConfig["device_type"].asString()).Schema);
36✔
201
    res["device_type"] = channelConfig["device_type"];
18✔
202
    SetIfExists(res, "name", channelConfig, "name");
18✔
203
    SetIfExists(res, "shift", channelConfig, "shift");
18✔
204
    SetIfExists(res, "stride", channelConfig, "stride");
18✔
205
    SetIfExists(res, "id", channelConfig, "id");
18✔
206
    AppendSetupItems(res, channelConfig);
18✔
207
    UpdateChannels(res["channels"],
34✔
208
                   channelConfig["channels"],
209
                   templates,
210
                   logPrefix + " " + channelConfig["name"].asString());
37✔
211
    return res;
16✔
212
}
213

214
void UpdateChannels(Json::Value& channelsFromTemplate,
60✔
215
                    const Json::Value& userChannels,
216
                    TSubDevicesTemplateMap& channelTemplates,
217
                    const std::string& logPrefix)
218
{
219
    std::unordered_map<std::string, std::vector<Json::ArrayIndex>> channelNames;
120✔
220

221
    for (Json::ArrayIndex i = 0; i < channelsFromTemplate.size(); ++i) {
271✔
222
        channelNames[channelsFromTemplate[i]["name"].asString()].push_back(i);
211✔
223
    }
224

225
    for (const auto& elem: userChannels) {
83✔
226
        auto channelName(elem["name"].asString());
48✔
227
        if (channelNames.count(channelName)) {
24✔
228
            for (const auto& chIt: channelNames[channelName]) {
45✔
229
                MergeChannelProperties(channelsFromTemplate[chIt],
46✔
230
                                       elem,
231
                                       logPrefix + " channel \"" + channelName + "\"");
49✔
232
            }
233
        } else {
234
            if (elem.isMember("device_type")) {
1✔
235
                throw TConfigParserException(logPrefix + " channel \"" + channelName +
×
236
                                             "\" can't contain \"device_type\" property");
×
237
            }
238
            channelsFromTemplate.append(elem);
1✔
239
        }
240
    }
241

242
    for (auto& elem: channelsFromTemplate) {
265✔
243
        elem = MergeChannelConfigWithTemplate(elem, channelTemplates, logPrefix);
208✔
244
    }
245
}
57✔
246

247
Json::Value MergeDeviceConfigWithTemplate(const Json::Value& deviceData,
43✔
248
                                          const std::string& deviceType,
249
                                          const Json::Value& deviceTemplate)
250
{
251

252
    if (deviceTemplate.empty()) {
43✔
253
        return deviceData;
×
254
    }
255

256
    auto res(deviceTemplate);
86✔
257

258
    TSubDevicesTemplateMap subDevicesTemplates(deviceType, deviceTemplate);
86✔
259
    res.removeMember("subdevices");
43✔
260

261
    std::string deviceName;
86✔
262
    if (deviceData.isMember("name")) {
43✔
263
        deviceName = deviceData["name"].asString();
8✔
264
    } else {
265
        deviceName = deviceTemplate["name"].asString() + DecorateIfNotEmpty(" ", deviceData["slave_id"].asString());
35✔
266
    }
267
    res["name"] = deviceName;
43✔
268

269
    if (deviceTemplate.isMember("id")) {
43✔
270
        res["id"] = deviceTemplate["id"].asString() + DecorateIfNotEmpty("_", deviceData["slave_id"].asString());
37✔
271
    }
272

273
    if (deviceData.isMember("protocol")) {
43✔
274
        LOG(Warn)
×
275
            << "\"" << deviceName
×
276
            << "\" has \"protocol\" property set in config. It is ignored. Protocol from device template will be used.";
×
277
    }
278

279
    const std::unordered_set<std::string> specialProperties({"channels", "setup", "name", "protocol"});
344✔
280
    for (auto itProp = deviceData.begin(); itProp != deviceData.end(); ++itProp) {
200✔
281
        if (!specialProperties.count(itProp.name())) {
157✔
282
            SetPropertyWithNotification(res, itProp, deviceName);
133✔
283
        }
284
    }
285

286
    TExpressionsCache expressionsCache;
86✔
287
    AppendSetupItems(res, deviceData, &expressionsCache);
43✔
288
    UpdateChannels(res["channels"], deviceData["channels"], subDevicesTemplates, "\"" + deviceName + "\"");
47✔
289
    RemoveDisabledChannels(res, deviceData, expressionsCache);
41✔
290

291
    return res;
41✔
292
}
293

294
TJsonParams::TJsonParams(const Json::Value& params): Params(params)
134✔
295
{}
134✔
296

297
std::optional<int32_t> TJsonParams::Get(const std::string& name) const
56✔
298
{
299
    const auto& param = Params[name];
56✔
300
    if (param.isInt()) {
56✔
301
        return std::optional<int32_t>(param.asInt());
84✔
302
    }
303
    return std::nullopt;
14✔
304
}
305

306
bool CheckCondition(const Json::Value& item, const TJsonParams& params, TExpressionsCache* exprs)
268✔
307
{
308
    if (!exprs) {
268✔
309
        return true;
24✔
310
    }
311
    auto cond = item["condition"].asString();
488✔
312
    if (cond.empty()) {
244✔
313
        return true;
193✔
314
    }
315
    try {
316
        auto itExpr = exprs->find(cond);
51✔
317
        if (itExpr == exprs->end()) {
51✔
318
            Expressions::TParser parser;
37✔
319
            itExpr = exprs->emplace(cond, parser.Parse(cond)).first;
37✔
320
        }
321
        return Expressions::Eval(itExpr->second.get(), params);
51✔
322
    } catch (const std::exception& e) {
×
323
        throw TConfigParserException("Error during expression \"" + cond + "\" evaluation: " + e.what());
×
324
    }
325
    return false;
326
}
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