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

wirenboard / wb-mqtt-smartweb / 2

31 Jul 2026 10:01AM UTC coverage: 33.897% (-0.4%) from 34.328%
2

Pull #40

github

476708
ekateluv
Lower coverage threshold to 33
Pull Request #40: Use exported vars instead of MAKEFLAGS for coverage options

307 of 744 branches covered (41.26%)

501 of 1478 relevant lines covered (33.9%)

20.03 hits per line

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

84.35
/src/config_parser.cpp
1
#include "config_parser.h"
2

3
#include <filesystem>
4
#include <set>
5

6
#include "log.h"
7

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

10
namespace
11
{
12
    std::unique_ptr<ISmartWebCodec> GetCodec(const Json::Value& data)
446✔
13
    {
14
        if (data.isMember("encoding")) {
446✔
15
            auto enc = data["encoding"].asString();
446✔
16

17
            if (WBMQTT::StringStartsWith(enc, "schedule")) {
892✔
18
                throw std::runtime_error("Encoding '" + enc + "' is not supported");
24✔
19
            }
20
            if (enc == "byte")
422✔
21
                return std::make_unique<TIntCodec<int8_t, 1>>();
×
22
            if (enc == "short")
422✔
23
                return std::make_unique<TIntCodec<int16_t, 1>>();
×
24
            if (enc == "short10")
422✔
25
                return std::make_unique<TIntCodec<int16_t, 10>>();
234✔
26
            if (enc == "short100")
188✔
27
                return std::make_unique<TIntCodec<int16_t, 100>>();
×
28
            if (enc == "ushort")
188✔
29
                return std::make_unique<TIntCodec<uint16_t, 1>>();
×
30
            if (enc == "uint1K")
188✔
31
                return std::make_unique<TIntCodec<uint32_t, 1000>>();
×
32
            if (enc == "uint60K")
188✔
33
                return std::make_unique<TIntCodec<uint32_t, 60000>>();
14✔
34
            if (enc == "ubyte") {
174✔
35
                if (data.isMember("values")) {
174✔
36
                    std::map<uint8_t, std::string> values;
36✔
37
                    const auto& ar = data["values"];
36✔
38
                    for (Json::Value::const_iterator it = ar.begin(); it != ar.end(); ++it) {
504✔
39
                        values.insert({atoi(it.name().c_str()), it->asString()});
468✔
40
                    }
41
                    return std::make_unique<TEnumCodec>(values);
36✔
42
                }
36✔
43
                return std::make_unique<TIntCodec<uint8_t, 1>>();
138✔
44
            }
45
        }
446✔
46
        return std::make_unique<TIntCodec<int16_t, 10>>(); // default codec
×
47
    }
48

49
    std::shared_ptr<TSmartWebParameter> LoadParameter(const Json::Value& param,
606✔
50
                                                      const std::string& name,
51
                                                      const TSmartWebClass* programClass,
52
                                                      uint32_t orderBase)
53
    {
54
        auto p = std::make_shared<TSmartWebParameter>();
606✔
55
        p->Id = param["id"].asUInt();
606✔
56
        p->Name = name;
606✔
57
        p->Type = param.get("type", "value").asString();
606✔
58
        p->ProgramClass = programClass;
606✔
59
        p->Order = orderBase + p->Id;
606✔
60
        return p;
606✔
61
    }
×
62

63
    uint32_t LoadInputs(const Json::Value& data, TSmartWebClass* programClass)
16✔
64
    {
65
        if (!data.isMember("inputs")) {
16✔
66
            return 0;
×
67
        }
68
        uint32_t maxId = 0;
16✔
69
        const auto& ar = data["inputs"];
16✔
70
        for (Json::Value::const_iterator it = ar.begin(); it != ar.end(); ++it) {
168✔
71
            auto p = LoadParameter(*it, it.name(), programClass, 0);
76✔
72
            if (p->Type == "onOff") {
76✔
73
                p->Codec = std::make_unique<TOnOffSensorCodec>();
24✔
74
            } else {
75
                p->Codec = std::make_unique<TSensorCodec>();
52✔
76
            }
77
            LOG(WBMQTT::Debug) << "Input '" << p->Name << "' " << p->Type << " id " << p->Id;
76✔
78
            programClass->Inputs.insert({p->Id, p});
76✔
79
            maxId = std::max(maxId, p->Id);
76✔
80
        }
76✔
81
        return maxId + 1;
16✔
82
    }
83

84
    uint32_t LoadOutputs(const Json::Value& data, TSmartWebClass* programClass, uint32_t orderBase)
16✔
85
    {
86
        if (!data.isMember("outputs")) {
16✔
87
            return orderBase;
×
88
        }
89
        uint32_t maxId = 0;
16✔
90
        const auto& ar = data["outputs"];
16✔
91
        for (Json::Value::const_iterator it = ar.begin(); it != ar.end(); ++it) {
184✔
92
            auto p = LoadParameter(*it, it.name(), programClass, orderBase);
84✔
93
            if (p->Type == "PWM") {
84✔
94
                p->Codec = std::make_unique<TPwmCodec>();
36✔
95
            } else {
96
                p->Codec = std::make_unique<TOutputCodec>();
48✔
97
            }
98
            LOG(WBMQTT::Debug) << "Output '" << p->Name << "' " << p->Type << " id " << p->Id;
84✔
99
            programClass->Outputs.insert({p->Id, p});
84✔
100
            maxId = std::max(maxId, p->Id);
84✔
101
        }
84✔
102
        return orderBase + maxId + 1;
16✔
103
    }
104

105
    uint32_t LoadParameters(const Json::Value& data, TSmartWebClass* programClass, uint32_t orderBase)
16✔
106
    {
107
        if (!data.isMember("parameters")) {
16✔
108
            return orderBase;
×
109
        }
110
        uint32_t maxId = 0;
16✔
111
        const auto& ar = data["parameters"];
16✔
112
        for (Json::Value::const_iterator it = ar.begin(); it != ar.end(); ++it) {
462✔
113
            try {
114
                auto p = LoadParameter(*it, it.name(), programClass, orderBase);
446✔
115
                p->ReadOnly = false;
446✔
116
                WBMQTT::JSON::Get((*it), "readOnly", p->ReadOnly);
892✔
117
                p->Codec = GetCodec(*it);
446✔
118
                if (p->Type == "onOff") {
422✔
119
                    p->Codec = std::make_unique<TOnOffSensorCodec>();
26✔
120
                }
121
                if (p->Type == "temperature" && p->ReadOnly) {
422✔
122
                    p->Codec = std::make_unique<TSensorCodec>();
50✔
123
                }
124
                LOG(WBMQTT::Debug) << "Parameter '" << p->Name << "', " << p->Type << ", id " << p->Id << ", "
422✔
125
                                   << p->Codec->GetName() << (p->ReadOnly ? ", read only" : "");
422✔
126
                programClass->Parameters.insert({p->Id, p});
422✔
127
                maxId = std::max(maxId, p->Id);
422✔
128
            } catch (const std::exception& e) {
470✔
129
                LOG(WBMQTT::Warn) << "Parameter '" << it.name() << "' is ignored. " << e.what();
24✔
130
            }
24✔
131
        }
132
        return orderBase + maxId + 1;
16✔
133
    }
134

135
    /**
136
     * @brief Exception class thrown on open directory failure.
137
     */
138
    class TNoDirError: public std::runtime_error
139
    {
140
    public:
141
        TNoDirError(const std::string& msg): std::runtime_error(msg)
×
142
        {}
×
143
    };
144

145
    void IterateDir(const std::string& dirName, std::function<bool(const std::string&)> fn)
4✔
146
    {
147
        try {
148
            const std::filesystem::path dirPath{dirName};
4✔
149
            std::set<std::filesystem::path> sortedByPath;
4✔
150

151
            for (auto& entry: std::filesystem::directory_iterator(dirPath))
16✔
152
                sortedByPath.insert(entry.path());
16✔
153

154
            for (auto& filePath: sortedByPath) {
16✔
155
                const auto filenameStr = filePath.filename().string();
12✔
156
                if (fn(filenameStr)) {
12✔
157
                    return;
×
158
                }
159
            }
12✔
160
        } catch (std::filesystem::filesystem_error const& ex) {
4✔
161
            throw TNoDirError(ex.what());
×
162
        }
×
163
    }
164

165
    std::string IterateDirByPattern(const std::string& dirName,
4✔
166
                                    const std::string& pattern,
167
                                    std::function<bool(const std::string&)> fn)
168
    {
169
        std::string res;
4✔
170
        IterateDir(dirName, [&](const auto& name) {
4✔
171
            if (name.find(pattern) != std::string::npos) {
12✔
172
                std::string d(dirName + "/" + name);
8✔
173
                if (fn(d)) {
8✔
174
                    res = d;
×
175
                    return true;
×
176
                }
177
            }
8✔
178
            return false;
12✔
179
        });
180
        return res;
4✔
181
    }
×
182

183
    void LoadSmartWebToMqttConfig(TSmartWebToMqttConfig& config,
4✔
184
                                  const Json::Value& configJson,
185
                                  const std::string& classesDir,
186
                                  const Json::Value& classSchema,
187
                                  TDeviceClassSource source)
188
    {
189
        if (configJson.isMember("poll_interval_ms")) {
4✔
190
            config.PollInterval = std::chrono::milliseconds(configJson["poll_interval_ms"].asUInt());
4✔
191
        }
192

193
        try {
194
            IterateDirByPattern(classesDir, ".json", [classSchema, &config, source](const std::string& filePath) {
8✔
195
                try {
196
                    auto classJson = WBMQTT::JSON::Parse(filePath);
8✔
197
                    WBMQTT::JSON::Validate(classJson, classSchema);
8✔
198
                    LoadSmartWebClass(config, classJson, source);
8✔
199
                } catch (const std::exception& e) {
8✔
200
                    LOG(WBMQTT::Error) << "Failed to parse " << filePath << "\n" << e.what();
×
201
                }
×
202

203
                return false; // continue scan
8✔
204
            });
205
        } catch (std::filesystem::filesystem_error const& ex) {
×
206
            LOG(WBMQTT::Error) << "Cannot open " << classesDir << " directory: " << ex.what();
×
207
            return;
×
208
        }
×
209
    }
210

211
    void LoadTiming(TMqttToSmartWebConfig& controller, const std::string& mqtt_channel, const Json::Value& configJson)
4✔
212
    {
213
        auto& mqtt_channel_timing = controller.MqttChannelsTiming[mqtt_channel];
4✔
214
        mqtt_channel_timing.refresh_last_update_timepoint();
4✔
215
        if (configJson.isMember("value_timeout_min")) {
4✔
216
            mqtt_channel_timing.ValueTimeoutMin = TTimeIntervalMin(configJson["value_timeout_min"].asInt());
×
217
        }
218
    }
4✔
219

220
    TMqttToSmartWebConfig LoadMqttToSmartWebController(const Json::Value& configJson)
2✔
221
    {
222
        TMqttToSmartWebConfig res;
2✔
223
        res.ProgramId = configJson["controller_id"].asUInt();
2✔
224

225
        if (configJson.isMember("sensors")) {
2✔
226
            for (const auto& sensor: configJson["sensors"]) {
6✔
227
                const auto& mqtt_channel = sensor["channel"].asString();
2✔
228
                LoadTiming(res, mqtt_channel, sensor);
2✔
229
                SmartWeb::TParameterInfo parameter_info{0};
2✔
230
                parameter_info.parameter_id = SmartWeb::Controller::Parameters::SENSOR;
2✔
231
                parameter_info.program_type = SmartWeb::PT_CONTROLLER;
2✔
232
                parameter_info.index = sensor["sensor_index"].asUInt();
2✔
233

234
                LOG(WBMQTT::Info) << "Controller: " << (int)res.ProgramId << " map sensor {"
2✔
235
                                  << "parameter_index: " << (int)parameter_info.index << ", "
2✔
236
                                  << "raw " << (int)parameter_info.raw << "} to {channel: " << mqtt_channel << "};";
2✔
237

238
                if (res.ParameterMapping.count(parameter_info.raw)) {
2✔
239
                    throw std::runtime_error("Malformed JSON config: duplicate sensor");
×
240
                }
241

242
                res.ParameterMapping[parameter_info.raw].from_string(mqtt_channel);
2✔
243
                res.ParameterCount = std::max(res.ParameterCount, uint8_t(parameter_info.index + 1));
2✔
244

245
                // Sensor is also accesible as output with index = sensor_index - 1
246
                auto outputIndex = parameter_info.index - 1;
2✔
247

248
                if (res.OutputMapping[outputIndex].is_initialized()) {
2✔
249
                    throw std::runtime_error("Malformed JSON config: duplicate output " + std::to_string(outputIndex));
×
250
                }
251

252
                res.OutputMapping[outputIndex].from_string(mqtt_channel);
2✔
253
            }
2✔
254
        }
255

256
        if (configJson.isMember("parameters")) {
2✔
257
            for (const auto& parameter: configJson["parameters"]) {
6✔
258
                const auto& mqtt_channel = parameter["channel"].asString();
2✔
259
                LoadTiming(res, mqtt_channel, configJson);
2✔
260
                SmartWeb::TParameterInfo parameter_info{0};
2✔
261
                parameter_info.parameter_id = parameter["parameter_id"].asUInt();
2✔
262
                parameter_info.program_type = parameter["program_type"].asUInt();
2✔
263
                parameter_info.index = parameter["parameter_index"].asUInt();
2✔
264

265
                LOG(WBMQTT::Info) << "Controller: " << (int)res.ProgramId << " map parameter {"
2✔
266
                                  << "program_type: " << (int)parameter_info.program_type << ", "
2✔
267
                                  << "parameter_id: " << (int)parameter_info.parameter_id << ", "
2✔
268
                                  << "parameter_index: " << (int)parameter_info.index << ", "
2✔
269
                                  << "raw " << (int)parameter_info.raw << "} to {channel: " << mqtt_channel << "};";
2✔
270

271
                if (res.ParameterMapping.count(parameter_info.raw)) {
2✔
272
                    throw std::runtime_error("Malformed JSON config: duplicate parameter");
×
273
                }
274

275
                res.ParameterMapping[parameter_info.raw].from_string(mqtt_channel);
2✔
276
                res.ParameterCount = std::max(res.ParameterCount, uint8_t(parameter_info.index + 1));
2✔
277
            }
2✔
278
        }
279

280
        if (!configJson.isMember("parameters") && !configJson.isMember("sensors")) {
2✔
281
            throw std::runtime_error("Malformed JSON config: no parameter or sensor in mapping");
×
282
        }
283
        return res;
2✔
284
    }
×
285

286
    void LoadMqttToSmartWebConfig(TConfig& config, const Json::Value& configJson)
2✔
287
    {
288
        if (configJson.isMember("debug")) {
2✔
289
            config.Debug = configJson["debug"].asBool();
2✔
290
        }
291

292
        if (configJson.isMember("interface_name")) {
2✔
293
            config.InterfaceName = configJson["interface_name"].asString();
2✔
294
        }
295

296
        for (const auto& controller: configJson["controllers"]) {
4✔
297
            try {
298
                config.Controllers.push_back(LoadMqttToSmartWebController(controller));
2✔
299
            } catch (std::exception& e) {
×
300
                LOG(WBMQTT::Error) << e.what();
×
301
            }
×
302
        }
303
    }
2✔
304
}
305

306
void LoadSmartWebClass(TSmartWebToMqttConfig& config, const Json::Value& data, TDeviceClassSource source)
16✔
307
{
308
    const auto programType = data["programType"].asUInt();
16✔
309
    const auto className = data["class"].asString();
16✔
310

311
    auto classIt = config.Classes.find(programType);
16✔
312

313
    if (classIt != config.Classes.end()) {
16✔
314
        if (classIt->second->Source == source) {
2✔
315
            LOG(WBMQTT::Warn) << "Program type: " << programType << " is already defined";
×
316
            return;
×
317
        }
318

319
        // Reject changes if there is an attempt to overwrite the user class with a built-in class
320
        if (classIt->second->Source == TDeviceClassSource::USER) {
2✔
321
            return;
×
322
        }
323
    }
324

325
    auto cl = std::make_shared<TSmartWebClass>();
16✔
326
    cl->Type = programType;
16✔
327
    cl->Name = className;
16✔
328
    cl->Source = source;
16✔
329
    LOG(WBMQTT::Debug) << "Loading class '" << cl->Name << "' (program type = " << programType << ")";
16✔
330

331
    if (data.isMember("implements")) {
16✔
332
        for (const auto& parent: data["implements"]) {
32✔
333
            cl->ParentClasses.push_back(parent.asString());
16✔
334
        }
335
    }
336

337
    uint32_t orderBase = LoadInputs(data, cl.get());
16✔
338
    orderBase = LoadOutputs(data, cl.get(), orderBase);
16✔
339
    LoadParameters(data, cl.get(), orderBase);
16✔
340

341
    if (classIt != config.Classes.end()) {
16✔
342
        LOG(WBMQTT::Info) << "Overriding a built-in device class '" << className << "' in *.d/classes";
2✔
343
        classIt->second = cl;
2✔
344
    } else {
345
        config.Classes.insert({programType, cl});
14✔
346
    }
347

348
    LOG(WBMQTT::Info) << "Class '" << cl->Name << "' (program type = " << programType << ") is loaded";
16✔
349
}
16✔
350

351
void LoadConfig(TConfig& config,
2✔
352
                const std::string& configFilePath,
353
                const std::string& pathToDeviceClassDirectory,
354
                const std::string& pathToBuiltInDeviceClassDirectory,
355
                const std::string& configSchemaFileName,
356
                const std::string& classSchemaFileName)
357
{
358
    Json::Value configJson = WBMQTT::JSON::Parse(configFilePath);
2✔
359
    WBMQTT::JSON::Validate(configJson, WBMQTT::JSON::Parse(configSchemaFileName));
2✔
360

361
    LoadMqttToSmartWebConfig(config, configJson);
2✔
362

363
    Json::Value classSchema = WBMQTT::JSON::Parse(classSchemaFileName);
2✔
364
    LoadSmartWebToMqttConfig(config.SmartWebToMqtt,
2✔
365
                             configJson,
366
                             pathToBuiltInDeviceClassDirectory,
367
                             classSchema,
368
                             TDeviceClassSource::BUILTIN);
369
    LoadSmartWebToMqttConfig(config.SmartWebToMqtt,
2✔
370
                             configJson,
371
                             pathToDeviceClassDirectory,
372
                             classSchema,
373
                             TDeviceClassSource::USER);
374
}
2✔
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