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

wirenboard / wb-mqtt-serial / 1

08 Jul 2025 01:20PM UTC coverage: 73.854% (+1.0%) from 72.836%
1

Pull #963

github

39d9bc
KraPete
Bump version
Pull Request #963: Bump version

6444 of 9057 branches covered (71.15%)

12341 of 16710 relevant lines covered (73.85%)

305.53 hits per line

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

88.43
/src/serial_config.cpp
1
#include "serial_config.h"
2
#include "file_utils.h"
3
#include "log.h"
4

5
#include <cstdlib>
6
#include <dirent.h>
7
#include <fstream>
8
#include <memory>
9
#include <set>
10
#include <stdexcept>
11
#include <string>
12
#include <sys/sysinfo.h>
13

14
#include "tcp_port.h"
15
#include "tcp_port_settings.h"
16

17
#include "serial_port.h"
18
#include "serial_port_settings.h"
19

20
#include "config_merge_template.h"
21
#include "config_schema_generator.h"
22

23
#include "devices/curtains/a_ok_device.h"
24
#include "devices/curtains/dooya_device.h"
25
#include "devices/curtains/somfy_sdn_device.h"
26
#include "devices/curtains/windeco_device.h"
27
#include "devices/dlms_device.h"
28
#include "devices/energomera_ce_device.h"
29
#include "devices/energomera_iec_device.h"
30
#include "devices/energomera_iec_mode_c_device.h"
31
#include "devices/iec_mode_c_device.h"
32
#include "devices/ivtm_device.h"
33
#include "devices/lls_device.h"
34
#include "devices/mercury200_device.h"
35
#include "devices/mercury230_device.h"
36
#include "devices/milur_device.h"
37
#include "devices/modbus_device.h"
38
#include "devices/modbus_io_device.h"
39
#include "devices/neva_device.h"
40
#include "devices/pulsar_device.h"
41
#include "devices/s2k_device.h"
42
#include "devices/uniel_device.h"
43

44
#define LOG(logger) ::logger.Log() << "[serial config] "
45

46
using namespace std;
47
using namespace WBMQTT::JSON;
48

49
namespace
50
{
51
    const char* DefaultProtocol = "modbus";
52

53
    template<class T> T Read(const Json::Value& root, const std::string& key, const T& defaultValue)
13,622✔
54
    {
55
        T value;
4,896✔
56
        if (Get(root, key, value)) {
13,622✔
57
            return value;
2,198✔
58
        }
59
        return defaultValue;
11,424✔
60
    }
61

62
    int GetIntFromString(const std::string& value, const std::string& errorPrefix)
257✔
63
    {
64
        try {
65
            return std::stoi(value, /*pos= */ 0, /*base= */ 0);
257✔
66
        } catch (const std::logic_error&) {
×
67
            throw TConfigParserException(errorPrefix + ": plain integer or '0x..' hex string expected instead of '" +
×
68
                                         value + "'");
×
69
        }
70
    }
71

72
    int ToInt(const Json::Value& v, const std::string& title)
1,147✔
73
    {
74
        if (v.isInt())
1,147✔
75
            return v.asInt();
957✔
76
        if (!v.isString()) {
190✔
77
            throw TConfigParserException(title + ": plain integer or '0x..' hex string expected");
×
78
        }
79
        return GetIntFromString(v.asString(), title);
190✔
80
    }
81

82
    double ToDouble(const Json::Value& v, const std::string& title)
99✔
83
    {
84
        if (v.isNumeric())
99✔
85
            return v.asDouble();
62✔
86
        if (!v.isString()) {
37✔
87
            throw TConfigParserException(title + ": number or '0x..' hex string expected");
×
88
        }
89
        return GetIntFromString(v.asString(), title);
37✔
90
    }
91

92
    uint64_t ToUint64(const Json::Value& v, const string& title)
248✔
93
    {
94
        if (v.isUInt())
248✔
95
            return v.asUInt64();
3✔
96
        if (v.isInt()) {
245✔
97
            int val = v.asInt64();
×
98
            if (val >= 0) {
×
99
                return val;
×
100
            }
101
        }
102

103
        if (v.isString()) {
245✔
104
            auto val = v.asString();
245✔
105
            if (val.find("-") == std::string::npos) {
245✔
106
                // don't try to parse strings containing munus sign
107
                try {
108
                    return stoull(val, /*pos= */ 0, /*base= */ 0);
245✔
109
                } catch (const logic_error& e) {
×
110
                }
111
            }
112
        }
113

114
        throw TConfigParserException(
×
115
            title + ": 32 bit plain unsigned integer (64 bit when quoted) or '0x..' hex string expected instead of '" +
×
116
            v.asString() + "'");
×
117
    }
118

119
    int GetInt(const Json::Value& obj, const std::string& key)
1,138✔
120
    {
121
        return ToInt(obj[key], key);
1,138✔
122
    }
123

124
    double GetDouble(const Json::Value& obj, const std::string& key)
99✔
125
    {
126
        return ToDouble(obj[key], key);
99✔
127
    }
128

129
    bool IsSerialNumberChannel(const Json::Value& channel_data)
943✔
130
    {
131
        const std::vector<std::string> serialNames{"Serial", "serial_number", "Serial NO"};
5,658✔
132
        return serialNames.end() !=
1,886✔
133
               std::find(serialNames.begin(), serialNames.end(), channel_data.get("name", std::string()).asString());
2,829✔
134
    }
135

136
    bool ReadChannelsReadonlyProperty(const Json::Value& register_data,
2,232✔
137
                                      const std::string& key,
138
                                      bool templateReadonly,
139
                                      const std::string& override_error_message_prefix,
140
                                      const std::string& register_type)
141
    {
142
        if (!register_data.isMember(key)) {
2,232✔
143
            return templateReadonly;
2,215✔
144
        }
145
        auto& val = register_data[key];
17✔
146
        if (!val.isConvertibleTo(Json::booleanValue)) {
17✔
147
            return templateReadonly;
×
148
        }
149
        bool readonly = val.asBool();
17✔
150
        if (templateReadonly && !readonly) {
17✔
151
            LOG(Warn) << override_error_message_prefix << " unable to make register of type \"" << register_type
2✔
152
                      << "\" writable";
1✔
153
            return true;
1✔
154
        }
155
        return readonly;
16✔
156
    }
157

158
    const TRegisterType& GetRegisterType(const Json::Value& itemData, const TRegisterTypeMap& typeMap)
1,116✔
159
    {
160
        if (itemData.isMember("reg_type")) {
1,116✔
161
            std::string type = itemData["reg_type"].asString();
1,049✔
162
            try {
163
                return typeMap.Find(type);
1,049✔
164
            } catch (...) {
×
165
                throw TConfigParserException("invalid register type: " + type);
×
166
            }
167
        }
168
        return typeMap.GetDefaultType();
67✔
169
    }
170

171
    std::optional<std::chrono::milliseconds> GetReadRateLimit(const Json::Value& data)
1,417✔
172
    {
173
        std::chrono::milliseconds res(-1);
1,417✔
174
        try {
175
            Get(data, "poll_interval", res);
1,417✔
176
        } catch (...) { // poll_interval is deprecated, so ignore it, if it has wrong format
×
177
        }
178
        Get(data, "read_rate_limit_ms", res);
1,417✔
179
        if (res < 0ms) {
1,417✔
180
            return std::nullopt;
1,405✔
181
        }
182
        return std::make_optional(res);
12✔
183
    }
184

185
    std::optional<std::chrono::milliseconds> GetReadPeriod(const Json::Value& data)
1,144✔
186
    {
187
        std::chrono::milliseconds res(-1);
1,144✔
188
        Get(data, "read_period_ms", res);
1,144✔
189
        if (res < 0ms) {
1,144✔
190
            return std::nullopt;
1,122✔
191
        }
192
        return std::make_optional(res);
22✔
193
    }
194

195
    struct TLoadingContext
196
    {
197
        // Full path to loaded item composed from device and channels names
198
        std::string name_prefix;
199

200
        // MQTT topic prefix. It could be different from name_prefix
201
        std::string mqtt_prefix;
202
        const IDeviceFactory& factory;
203
        const IRegisterAddress& device_base_address;
204
        size_t stride = 0;
205
        TTitleTranslations translated_name_prefixes;
206
        const Json::Value* translations = nullptr;
207

208
        TLoadingContext(const IDeviceFactory& f, const IRegisterAddress& base_address)
188✔
209
            : factory(f),
188✔
210
              device_base_address(base_address)
188✔
211
        {}
188✔
212
    };
213

214
    TTitleTranslations Translate(const std::string& name, bool idIsDefined, const TLoadingContext& context)
951✔
215
    {
216
        TTitleTranslations res;
951✔
217
        if (context.translations) {
951✔
218
            // Find translation for the name. Iterate through languages
219
            for (auto it = context.translations->begin(); it != context.translations->end(); ++it) {
152✔
220
                auto lang = it.name();
15✔
221
                auto translatedName = (*it)[name];
15✔
222
                if (!translatedName.isNull()) {
15✔
223
                    // Find prefix translated to the language or english translated prefix
224
                    auto prefixIt = context.translated_name_prefixes.find(lang);
6✔
225
                    if (prefixIt == context.translated_name_prefixes.end()) {
6✔
226
                        if (lang != "en") {
4✔
227
                            prefixIt = context.translated_name_prefixes.find("en");
4✔
228
                        }
229
                    }
230
                    // Take translation of prefix and add translated name
231
                    if (prefixIt != context.translated_name_prefixes.end()) {
6✔
232
                        res[lang] = prefixIt->second + " " + translatedName.asString();
3✔
233
                        continue;
3✔
234
                    }
235
                    // There isn't translated prefix
236
                    // Take MQTT id prefix if any and add translated name
237
                    if (context.mqtt_prefix.empty()) {
3✔
238
                        res[lang] = translatedName.asString();
2✔
239
                    } else {
240
                        res[lang] = context.mqtt_prefix + " " + translatedName.asString();
1✔
241
                    }
242
                }
243
            }
244

245
            for (const auto& it: context.translated_name_prefixes) {
148✔
246
                // There are translatied prefixes, but no translation for the name.
247
                // Take translated prefix and add the name as is
248
                if (!res.count(it.first)) {
11✔
249
                    res[it.first] = it.second + " " + name;
9✔
250
                }
251
            }
252

253
            // Name is different from MQTT id and there isn't english translated prefix
254
            // Take MQTT ID prefix if any and add the name as is
255
            if (!res.count("en") && idIsDefined) {
274✔
256
                if (context.mqtt_prefix.empty()) {
3✔
257
                    res["en"] = name;
1✔
258
                } else {
259
                    res["en"] = context.mqtt_prefix + " " + name;
2✔
260
                }
261
            }
262
            return res;
137✔
263
        }
264

265
        // No translations for names at all.
266
        // Just compose english name if it is different from MQTT id
267
        auto trIt = context.translated_name_prefixes.find("en");
814✔
268
        if (trIt != context.translated_name_prefixes.end()) {
814✔
269
            res["en"] = trIt->second + name;
×
270
        } else {
271
            if (idIsDefined) {
814✔
272
                res["en"] = name;
4✔
273
            }
274
        }
275
        return res;
814✔
276
    }
277

278
    void LoadSimpleChannel(TSerialDeviceWithChannels& deviceWithChannels,
941✔
279
                           const Json::Value& channel_data,
280
                           const TLoadingContext& context,
281
                           const TRegisterTypeMap& typeMap)
282
    {
283
        std::string mqtt_channel_name(channel_data["name"].asString());
941✔
284
        bool idIsDefined = false;
941✔
285
        if (channel_data.isMember("id")) {
941✔
286
            mqtt_channel_name = channel_data["id"].asString();
3✔
287
            idIsDefined = true;
3✔
288
        }
289
        if (!context.mqtt_prefix.empty()) {
941✔
290
            mqtt_channel_name = context.mqtt_prefix + " " + mqtt_channel_name;
8✔
291
        }
292
        auto errorMsgPrefix = "Channel \"" + mqtt_channel_name + "\"";
941✔
293
        std::string default_type_str;
941✔
294
        std::vector<PRegister> registers;
941✔
295
        if (channel_data.isMember("consists_of")) {
941✔
296

297
            auto read_rate_limit_ms = GetReadRateLimit(channel_data);
28✔
298
            auto read_period = GetReadPeriod(channel_data);
28✔
299

300
            const Json::Value& reg_data = channel_data["consists_of"];
28✔
301
            for (Json::ArrayIndex i = 0; i < reg_data.size(); ++i) {
112✔
302
                auto reg = LoadRegisterConfig(reg_data[i],
303
                                              typeMap,
304
                                              errorMsgPrefix,
305
                                              context.factory,
306
                                              context.device_base_address,
307
                                              context.stride);
168✔
308
                reg.RegisterConfig->ReadRateLimit = read_rate_limit_ms;
84✔
309
                reg.RegisterConfig->ReadPeriod = read_period;
84✔
310
                registers.push_back(deviceWithChannels.Device->AddRegister(reg.RegisterConfig));
84✔
311
                if (!i)
84✔
312
                    default_type_str = reg.DefaultControlType;
28✔
313
                else if (registers[i]->GetConfig()->AccessType != registers[0]->GetConfig()->AccessType)
56✔
314
                    throw TConfigParserException(("can't mix read-only, write-only and writable registers "
×
315
                                                  "in one channel -- ") +
×
316
                                                 deviceWithChannels.Device->DeviceConfig()->DeviceType);
×
317
            }
318
        } else {
319
            try {
320
                auto reg = LoadRegisterConfig(channel_data,
321
                                              typeMap,
322
                                              errorMsgPrefix,
323
                                              context.factory,
324
                                              context.device_base_address,
325
                                              context.stride);
913✔
326
                default_type_str = reg.DefaultControlType;
913✔
327
                registers.push_back(deviceWithChannels.Device->AddRegister(reg.RegisterConfig));
913✔
328
            } catch (const std::exception& e) {
×
329
                LOG(Warn) << deviceWithChannels.Device->ToString() << " channel \"" + mqtt_channel_name
×
330
                          << "\" is ignored: " << e.what();
×
331
                return;
×
332
            }
333
        }
334

335
        std::string type_str(Read(channel_data, "type", default_type_str));
2,823✔
336
        if (type_str == "wo-switch" || type_str == "pushbutton") {
941✔
337
            if (type_str == "wo-switch") {
×
338
                type_str = "switch";
×
339
            }
340
            for (auto& reg: registers) {
×
341
                reg->GetConfig()->AccessType = TRegisterConfig::EAccessType::WRITE_ONLY;
×
342
            }
343
        }
344

345
        int order = deviceWithChannels.Channels.size() + 1;
941✔
346
        PDeviceChannelConfig channel(
347
            new TDeviceChannelConfig(type_str,
348
                                     deviceWithChannels.Device->DeviceConfig()->Id,
941✔
349
                                     order,
350
                                     (registers[0]->GetConfig()->AccessType == TRegisterConfig::EAccessType::READ_ONLY),
1,882✔
351
                                     mqtt_channel_name,
352
                                     registers));
1,882✔
353

354
        for (const auto& it: Translate(channel_data["name"].asString(), idIsDefined, context)) {
954✔
355
            channel->SetTitle(it.second, it.first);
13✔
356
        }
357

358
        if (channel_data.isMember("enum") && channel_data.isMember("enum_titles")) {
941✔
359
            const auto& enumValues = channel_data["enum"];
2✔
360
            const auto& enumTitles = channel_data["enum_titles"];
2✔
361
            if (enumValues.size() == enumTitles.size()) {
2✔
362
                for (Json::ArrayIndex i = 0; i < enumValues.size(); ++i) {
6✔
363
                    channel->SetEnumTitles(enumValues[i].asString(),
12✔
364
                                           Translate(enumTitles[i].asString(), true, context));
8✔
365
                }
366
            } else {
367
                LOG(Warn) << errorMsgPrefix << ": enum and enum_titles should have the same size -- "
×
368
                          << deviceWithChannels.Device->DeviceConfig()->DeviceType;
×
369
            }
370
        }
371

372
        if (channel_data.isMember("max")) {
941✔
373
            channel->Max = GetDouble(channel_data, "max");
98✔
374
        }
375
        if (channel_data.isMember("min")) {
941✔
376
            channel->Min = GetDouble(channel_data, "min");
1✔
377
        }
378
        if (channel_data.isMember("on_value")) {
941✔
379
            if (registers.size() != 1)
21✔
380
                throw TConfigParserException("on_value is allowed only for single-valued controls -- " +
×
381
                                             deviceWithChannels.Device->DeviceConfig()->DeviceType);
×
382
            channel->OnValue = std::to_string(GetInt(channel_data, "on_value"));
21✔
383
        }
384
        if (channel_data.isMember("off_value")) {
941✔
385
            if (registers.size() != 1)
20✔
386
                throw TConfigParserException("off_value is allowed only for single-valued controls -- " +
×
387
                                             deviceWithChannels.Device->DeviceConfig()->DeviceType);
×
388
            channel->OffValue = std::to_string(GetInt(channel_data, "off_value"));
20✔
389
        }
390

391
        if (registers.size() == 1) {
941✔
392
            channel->Precision = registers[0]->GetConfig()->RoundTo;
913✔
393
        }
394

395
        Get(channel_data, "units", channel->Units);
941✔
396

397
        if (IsSerialNumberChannel(channel_data) && registers.size()) {
941✔
398
            deviceWithChannels.Device->SetSnRegister(registers[0]->GetConfig());
2✔
399
        }
400

401
        deviceWithChannels.Channels.push_back(channel);
941✔
402
    }
403

404
    void LoadChannel(TSerialDeviceWithChannels& deviceWithChannels,
405
                     const Json::Value& channel_data,
406
                     const TLoadingContext& context,
407
                     const TRegisterTypeMap& typeMap);
408

409
    void LoadSetupItems(TSerialDevice& device,
410
                        const Json::Value& item_data,
411
                        const TRegisterTypeMap& typeMap,
412
                        const TLoadingContext& context);
413

414
    void LoadSubdeviceChannel(TSerialDeviceWithChannels& deviceWithChannels,
7✔
415
                              const Json::Value& channel_data,
416
                              const TLoadingContext& context,
417
                              const TRegisterTypeMap& typeMap)
418
    {
419
        int shift = 0;
7✔
420
        if (channel_data.isMember("shift")) {
7✔
421
            shift = GetInt(channel_data, "shift");
7✔
422
        }
423
        std::unique_ptr<IRegisterAddress> baseAddress(context.device_base_address.CalcNewAddress(shift, 0, 0, 0));
14✔
424

425
        TLoadingContext newContext(context.factory, *baseAddress);
14✔
426
        newContext.translations = context.translations;
7✔
427
        auto name = channel_data["name"].asString();
14✔
428
        newContext.name_prefix = name;
7✔
429
        if (!context.name_prefix.empty()) {
7✔
430
            newContext.name_prefix = context.name_prefix + " " + newContext.name_prefix;
4✔
431
        }
432

433
        newContext.mqtt_prefix = name;
7✔
434
        bool idIsDefined = false;
7✔
435
        if (channel_data.isMember("id")) {
7✔
436
            newContext.mqtt_prefix = channel_data["id"].asString();
3✔
437
            idIsDefined = true;
3✔
438
        }
439

440
        // Empty id is used if we don't want to add channel name to resulting MQTT topic name
441
        // This case we also don't add translation to resulting translated channel name
442
        if (!(idIsDefined && newContext.mqtt_prefix.empty())) {
7✔
443
            newContext.translated_name_prefixes = Translate(name, idIsDefined, context);
6✔
444
        }
445

446
        if (!context.mqtt_prefix.empty()) {
7✔
447
            if (newContext.mqtt_prefix.empty()) {
2✔
448
                newContext.mqtt_prefix = context.mqtt_prefix;
×
449
            } else {
450
                newContext.mqtt_prefix = context.mqtt_prefix + " " + newContext.mqtt_prefix;
2✔
451
            }
452
        }
453

454
        newContext.stride = Read(channel_data, "stride", 0);
7✔
455
        LoadSetupItems(*deviceWithChannels.Device, channel_data, typeMap, newContext);
7✔
456

457
        if (channel_data.isMember("channels")) {
6✔
458
            for (const auto& ch: channel_data["channels"]) {
20✔
459
                LoadChannel(deviceWithChannels, ch, newContext, typeMap);
14✔
460
            }
461
        }
462
    }
6✔
463

464
    void LoadChannel(TSerialDeviceWithChannels& deviceWithChannels,
950✔
465
                     const Json::Value& channel_data,
466
                     const TLoadingContext& context,
467
                     const TRegisterTypeMap& typeMap)
468
    {
469
        if (channel_data.isMember("enabled") && !channel_data["enabled"].asBool()) {
950✔
470
            if (IsSerialNumberChannel(channel_data)) {
2✔
471
                deviceWithChannels.Device->SetSnRegister(LoadRegisterConfig(channel_data,
×
472
                                                                            typeMap,
473
                                                                            std::string(),
×
474
                                                                            context.factory,
475
                                                                            context.device_base_address,
476
                                                                            context.stride)
×
477
                                                             .RegisterConfig);
×
478
            }
479
            return;
2✔
480
        }
481
        if (channel_data.isMember("device_type")) {
948✔
482
            LoadSubdeviceChannel(deviceWithChannels, channel_data, context, typeMap);
7✔
483
        } else {
484
            LoadSimpleChannel(deviceWithChannels, channel_data, context, typeMap);
941✔
485
        }
486
        if (deviceWithChannels.Device->IsSporadicOnly() && !channel_data["sporadic"].asBool()) {
947✔
487
            deviceWithChannels.Device->SetSporadicOnly(false);
181✔
488
        }
489
    }
490

491
    void LoadSetupItem(TSerialDevice& device,
102✔
492
                       const Json::Value& item_data,
493
                       const TRegisterTypeMap& typeMap,
494
                       const TLoadingContext& context)
495
    {
496
        std::string name(Read(item_data, "title", std::string("<unnamed>")));
408✔
497
        if (!context.name_prefix.empty()) {
102✔
498
            name = context.name_prefix + " " + name;
13✔
499
        }
500
        auto reg = LoadRegisterConfig(item_data,
501
                                      typeMap,
502
                                      "Setup item \"" + name + "\"",
204✔
503
                                      context.factory,
504
                                      context.device_base_address,
505
                                      context.stride);
204✔
506
        const auto& valueItem = item_data["value"];
102✔
507
        // libjsoncpp uses format "%.17g" in asString() and outputs strings with additional small numbers
508
        auto value = valueItem.isDouble() ? WBMQTT::StringFormat("%.15g", valueItem.asDouble()) : valueItem.asString();
103✔
509
        device.AddSetupItem(PDeviceSetupItemConfig(
101✔
510
            new TDeviceSetupItemConfig(name, reg.RegisterConfig, value, item_data["id"].asString())));
206✔
511
    }
101✔
512

513
    void LoadSetupItems(TSerialDevice& device,
188✔
514
                        const Json::Value& device_data,
515
                        const TRegisterTypeMap& typeMap,
516
                        const TLoadingContext& context)
517
    {
518
        if (device_data.isMember("setup")) {
188✔
519
            for (const auto& setupItem: device_data["setup"])
151✔
520
                LoadSetupItem(device, setupItem, typeMap, context);
102✔
521
        }
522
    }
187✔
523

524
    void LoadCommonDeviceParameters(TDeviceConfig& device_config, const Json::Value& device_data)
181✔
525
    {
526
        if (device_data.isMember("password")) {
181✔
527
            device_config.Password.clear();
2✔
528
            for (const auto& passwordItem: device_data["password"])
11✔
529
                device_config.Password.push_back(ToInt(passwordItem, "password item"));
9✔
530
        }
531

532
        if (device_data.isMember("delay_ms")) {
181✔
533
            LOG(Warn) << "\"delay_ms\" is not supported, use \"frame_timeout_ms\" instead";
×
534
        }
535

536
        Get(device_data, "frame_timeout_ms", device_config.FrameTimeout);
181✔
537
        if (device_config.FrameTimeout.count() < 0) {
181✔
538
            device_config.FrameTimeout = DefaultFrameTimeout;
×
539
        }
540
        Get(device_data, "response_timeout_ms", device_config.ResponseTimeout);
181✔
541
        Get(device_data, "device_timeout_ms", device_config.DeviceTimeout);
181✔
542
        Get(device_data, "device_max_fail_cycles", device_config.DeviceMaxFailCycles);
181✔
543
        Get(device_data, "max_write_fail_time_s", device_config.MaxWriteFailTime);
181✔
544
        Get(device_data, "max_reg_hole", device_config.MaxRegHole);
181✔
545
        Get(device_data, "max_bit_hole", device_config.MaxBitHole);
181✔
546
        Get(device_data, "max_read_registers", device_config.MaxReadRegisters);
181✔
547
        Get(device_data, "min_read_registers", device_config.MinReadRegisters);
181✔
548
        Get(device_data, "max_write_registers", device_config.MaxWriteRegisters);
181✔
549
        Get(device_data, "guard_interval_us", device_config.RequestDelay);
181✔
550
        Get(device_data, "stride", device_config.Stride);
181✔
551
        Get(device_data, "shift", device_config.Shift);
181✔
552
        Get(device_data, "access_level", device_config.AccessLevel);
181✔
553
        Get(device_data, "min_request_interval", device_config.MinRequestInterval);
181✔
554
    }
181✔
555

556
    void LoadDevice(PPortConfig port_config,
183✔
557
                    const Json::Value& device_data,
558
                    const std::string& default_id,
559
                    TTemplateMap& templates,
560
                    TSerialDeviceFactory& deviceFactory)
561
    {
562
        if (device_data.isMember("enabled") && !device_data["enabled"].asBool())
183✔
563
            return;
×
564

565
        TSerialDeviceFactory::TCreateDeviceParams params;
188✔
566
        params.Defaults.Id = default_id;
183✔
567
        params.Defaults.RequestDelay = port_config->RequestDelay;
183✔
568
        params.Defaults.ResponseTimeout = port_config->ResponseTimeout;
183✔
569
        params.Defaults.ReadRateLimit = port_config->ReadRateLimit;
183✔
570
        params.IsModbusTcp = port_config->IsModbusTcp;
183✔
571
        port_config->AddDevice(deviceFactory.CreateDevice(device_data, params, templates));
185✔
572
    }
573

574
    PPort OpenSerialPort(const Json::Value& port_data, PRPCConfig rpcConfig)
17✔
575
    {
576
        TSerialPortSettings settings(port_data["path"].asString());
34✔
577

578
        Get(port_data, "baud_rate", settings.BaudRate);
17✔
579

580
        if (port_data.isMember("parity"))
17✔
581
            settings.Parity = port_data["parity"].asCString()[0];
10✔
582

583
        Get(port_data, "data_bits", settings.DataBits);
17✔
584
        Get(port_data, "stop_bits", settings.StopBits);
17✔
585

586
        PPort port = std::make_shared<TSerialPort>(settings);
17✔
587

588
        rpcConfig->AddSerialPort(settings);
17✔
589

590
        return port;
34✔
591
    }
592

593
    PPort OpenTcpPort(const Json::Value& port_data, PRPCConfig rpcConfig)
4✔
594
    {
595
        TTcpPortSettings settings(port_data["address"].asString(), GetInt(port_data, "port"));
12✔
596

597
        PPort port = std::make_shared<TTcpPort>(settings);
4✔
598

599
        rpcConfig->AddTCPPort(settings);
4✔
600

601
        return port;
8✔
602
    }
603

604
    void LoadPort(PHandlerConfig handlerConfig,
93✔
605
                  const Json::Value& port_data,
606
                  const std::string& id_prefix,
607
                  TTemplateMap& templates,
608
                  PRPCConfig rpcConfig,
609
                  TSerialDeviceFactory& deviceFactory,
610
                  TPortFactoryFn portFactory)
611
    {
612
        if (port_data.isMember("enabled") && !port_data["enabled"].asBool())
93✔
613
            return;
×
614

615
        auto port_config = make_shared<TPortConfig>();
186✔
616

617
        Get(port_data, "response_timeout_ms", port_config->ResponseTimeout);
93✔
618
        Get(port_data, "guard_interval_us", port_config->RequestDelay);
93✔
619
        port_config->ReadRateLimit = GetReadRateLimit(port_data);
93✔
620

621
        auto port_type = port_data.get("port_type", "serial").asString();
191✔
622

623
        Get(port_data, "connection_timeout_ms", port_config->OpenCloseSettings.MaxFailTime);
93✔
624
        Get(port_data, "connection_max_fail_cycles", port_config->OpenCloseSettings.ConnectionMaxFailCycles);
93✔
625

626
        std::tie(port_config->Port, port_config->IsModbusTcp) = portFactory(port_data, rpcConfig);
93✔
627

628
        const Json::Value& array = port_data["devices"];
93✔
629
        for (Json::Value::ArrayIndex index = 0; index < array.size(); ++index)
271✔
630
            LoadDevice(port_config, array[index], id_prefix + std::to_string(index), templates, deviceFactory);
198✔
631

632
        handlerConfig->AddPortConfig(port_config);
88✔
633
    }
634
}
635

636
std::string DecorateIfNotEmpty(const std::string& prefix, const std::string& str, const std::string& postfix)
72✔
637
{
638
    if (str.empty()) {
72✔
639
        return std::string();
×
640
    }
641
    return prefix + str + postfix;
144✔
642
}
643

644
void SetIfExists(Json::Value& dst, const std::string& dstKey, const Json::Value& src, const std::string& srcKey)
144✔
645
{
646
    if (src.isMember(srcKey)) {
144✔
647
        dst[dstKey] = src[srcKey];
44✔
648
    }
649
}
144✔
650

651
std::pair<PPort, bool> DefaultPortFactory(const Json::Value& port_data, PRPCConfig rpcConfig)
21✔
652
{
653
    auto port_type = port_data.get("port_type", "serial").asString();
63✔
654
    if (port_type == "serial") {
21✔
655
        return {OpenSerialPort(port_data, rpcConfig), false};
17✔
656
    }
657
    if (port_type == "tcp") {
4✔
658
        return {OpenTcpPort(port_data, rpcConfig), false};
4✔
659
    }
660
    if (port_type == "modbus tcp") {
×
661
        return {OpenTcpPort(port_data, rpcConfig), true};
×
662
    }
663
    throw TConfigParserException("invalid port_type: '" + port_type + "'");
×
664
}
665

666
Json::Value LoadConfigTemplatesSchema(const std::string& templateSchemaFileName, const Json::Value& commonDeviceSchema)
19✔
667
{
668
    Json::Value schema = WBMQTT::JSON::Parse(templateSchemaFileName);
19✔
669
    AppendParams(schema["definitions"], commonDeviceSchema["definitions"]);
19✔
670
    return schema;
19✔
671
}
672

673
void AddRegisterType(Json::Value& configSchema, const std::string& registerType)
19✔
674
{
675
    configSchema["definitions"]["reg_type"]["enum"].append(registerType);
19✔
676
}
19✔
677

678
void CheckDuplicatePorts(const THandlerConfig& handlerConfig)
76✔
679
{
680
    std::unordered_set<std::string> paths;
152✔
681
    for (const auto& port: handlerConfig.PortConfigs) {
164✔
682
        if (!paths.insert(port->Port->GetDescription(false)).second) {
88✔
683
            throw TConfigParserException("Duplicate port: " + port->Port->GetDescription(false));
×
684
        }
685
    }
686
}
76✔
687

688
void CheckDuplicateDeviceIds(const THandlerConfig& handlerConfig)
76✔
689
{
690
    std::unordered_set<std::string> ids;
152✔
691
    for (const auto& port: handlerConfig.PortConfigs) {
162✔
692
        for (const auto& device: port->Devices) {
261✔
693
            if (!ids.insert(device->Device->DeviceConfig()->Id).second) {
175✔
694
                throw TConfigParserException(
1✔
695
                    "Duplicate MQTT device id: " + device->Device->DeviceConfig()->Id +
2✔
696
                    ", set device MQTT ID explicitly to fix (see https://wb.wiki/serial-id-collision)");
3✔
697
            }
698
        }
699
    }
700
}
75✔
701

702
PHandlerConfig LoadConfig(const std::string& configFileName,
92✔
703
                          TSerialDeviceFactory& deviceFactory,
704
                          const Json::Value& commonDeviceSchema,
705
                          TTemplateMap& templates,
706
                          PRPCConfig rpcConfig,
707
                          const Json::Value& portsSchema,
708
                          TProtocolConfedSchemasMap& protocolSchemas,
709
                          TPortFactoryFn portFactory)
710
{
711
    PHandlerConfig handlerConfig(new THandlerConfig);
92✔
712
    Json::Value Root(Parse(configFileName));
184✔
713

714
    try {
715
        ValidateConfig(Root, deviceFactory, commonDeviceSchema, portsSchema, templates, protocolSchemas);
92✔
716
    } catch (const std::runtime_error& e) {
22✔
717
        throw std::runtime_error("File: " + configFileName + " error: " + e.what());
11✔
718
    }
719

720
    // wb6 - single core - max 100 registers per second
721
    // wb7 - 4 cores - max 800 registers per second
722
    handlerConfig->LowPriorityRegistersRateLimit = (1 == get_nprocs_conf()) ? 100 : 800;
81✔
723
    Get(Root, "rate_limit", handlerConfig->LowPriorityRegistersRateLimit);
81✔
724

725
    Get(Root, "debug", handlerConfig->Debug);
81✔
726

727
    auto maxUnchangedInterval = DefaultMaxUnchangedInterval;
81✔
728
    Get(Root, "max_unchanged_interval", maxUnchangedInterval);
81✔
729
    if (maxUnchangedInterval.count() > 0 && maxUnchangedInterval < MaxUnchangedIntervalLowLimit) {
81✔
730
        LOG(Warn) << "\"max_unchanged_interval\" is set to " << MaxUnchangedIntervalLowLimit.count() << " instead of "
×
731
                  << maxUnchangedInterval.count();
×
732
        maxUnchangedInterval = MaxUnchangedIntervalLowLimit;
×
733
    }
734
    handlerConfig->PublishParameters.Set(maxUnchangedInterval.count());
81✔
735

736
    const Json::Value& array = Root["ports"];
81✔
737
    for (Json::Value::ArrayIndex index = 0; index < array.size(); ++index) {
169✔
738
        // old default prefix for compat
739
        LoadPort(handlerConfig,
186✔
740
                 array[index],
741
                 "wb-modbus-" + std::to_string(index) + "-",
201✔
742
                 templates,
743
                 rpcConfig,
744
                 deviceFactory,
745
                 portFactory);
387✔
746
    }
747

748
    CheckDuplicatePorts(*handlerConfig);
76✔
749
    CheckDuplicateDeviceIds(*handlerConfig);
76✔
750

751
    return handlerConfig;
150✔
752
}
753

754
void TPortConfig::AddDevice(PSerialDeviceWithChannels device)
180✔
755
{
756
    // try to find duplicate of this device
757
    for (auto dev: Devices) {
405✔
758
        if (dev->Device->Protocol() == device->Device->Protocol()) {
225✔
759
            if (dev->Device->Protocol()->IsSameSlaveId(dev->Device->DeviceConfig()->SlaveId,
672✔
760
                                                       device->Device->DeviceConfig()->SlaveId))
448✔
761
            {
762
                stringstream ss;
4✔
763
                ss << "id \"" << device->Device->DeviceConfig()->SlaveId << "\" of device \""
2✔
764
                   << device->Device->DeviceConfig()->Name
4✔
765
                   << "\" is already set to device \"" + device->Device->DeviceConfig()->Name + "\"";
6✔
766
                throw TConfigParserException(ss.str());
2✔
767
            }
768
        }
769
    }
770

771
    Devices.push_back(device);
178✔
772
}
178✔
773

774
TDeviceChannelConfig::TDeviceChannelConfig(const std::string& type,
941✔
775
                                           const std::string& deviceId,
776
                                           int order,
777
                                           bool readOnly,
778
                                           const std::string& mqttId,
779
                                           const std::vector<PRegister>& regs)
941✔
780
    : MqttId(mqttId),
781
      Type(type),
782
      DeviceId(deviceId),
783
      Order(order),
784
      ReadOnly(readOnly),
785
      Registers(regs)
941✔
786
{}
941✔
787

788
const std::string& TDeviceChannelConfig::GetName() const
64✔
789
{
790
    auto it = Titles.find("en");
64✔
791
    if (it != Titles.end()) {
64✔
792
        return it->second;
5✔
793
    }
794
    return MqttId;
59✔
795
}
796

797
const TTitleTranslations& TDeviceChannelConfig::GetTitles() const
520✔
798
{
799
    return Titles;
520✔
800
}
801

802
void TDeviceChannelConfig::SetTitle(const std::string& name, const std::string& lang)
13✔
803
{
804
    if (!lang.empty()) {
13✔
805
        Titles[lang] = name;
13✔
806
    }
807
}
13✔
808

809
const std::map<std::string, TTitleTranslations>& TDeviceChannelConfig::GetEnumTitles() const
458✔
810
{
811
    return EnumTitles;
458✔
812
}
813

814
void TDeviceChannelConfig::SetEnumTitles(const std::string& value, const TTitleTranslations& titles)
4✔
815
{
816
    if (!value.empty()) {
4✔
817
        EnumTitles[value] = titles;
4✔
818
    }
819
}
4✔
820

821
TDeviceSetupItemConfig::TDeviceSetupItemConfig(const std::string& name,
108✔
822
                                               PRegisterConfig reg,
823
                                               const std::string& value,
824
                                               const std::string& parameterId)
108✔
825
    : Name(name),
826
      RegisterConfig(reg),
827
      Value(value),
828
      ParameterId(parameterId)
113✔
829
{
830
    try {
831
        RawValue = ConvertToRawValue(*reg, Value);
108✔
832
    } catch (const std::exception& e) {
2✔
833
        throw TConfigParserException("\"" + name + "\" bad value \"" + value + "\"");
1✔
834
    }
835
}
107✔
836

837
const std::string& TDeviceSetupItemConfig::GetName() const
107✔
838
{
839
    return Name;
107✔
840
}
841

842
const std::string& TDeviceSetupItemConfig::GetValue() const
105✔
843
{
844
    return Value;
105✔
845
}
846

847
const std::string& TDeviceSetupItemConfig::GetParameterId() const
105✔
848
{
849
    return ParameterId;
105✔
850
}
851

852
TRegisterValue TDeviceSetupItemConfig::GetRawValue() const
107✔
853
{
854
    return RawValue;
107✔
855
}
856

857
PRegisterConfig TDeviceSetupItemConfig::GetRegisterConfig() const
212✔
858
{
859
    return RegisterConfig;
212✔
860
}
861

862
void THandlerConfig::AddPortConfig(PPortConfig portConfig)
88✔
863
{
864
    PortConfigs.push_back(portConfig);
88✔
865
}
88✔
866

867
TConfigParserException::TConfigParserException(const std::string& message)
6✔
868
    : std::runtime_error("Error parsing config file: " + message)
6✔
869
{}
6✔
870

871
bool IsSubdeviceChannel(const Json::Value& channelSchema)
×
872
{
873
    return (channelSchema.isMember("oneOf") || channelSchema.isMember("device_type"));
×
874
}
875

876
void AppendParams(Json::Value& dst, const Json::Value& src)
19✔
877
{
878
    for (auto it = src.begin(); it != src.end(); ++it) {
1,007✔
879
        dst[it.name()] = *it;
988✔
880
    }
881
}
19✔
882

883
std::string GetProtocolName(const Json::Value& deviceDescription)
38✔
884
{
885
    std::string p;
38✔
886
    Get(deviceDescription, "protocol", p);
38✔
887
    if (p.empty()) {
38✔
888
        p = DefaultProtocol;
27✔
889
    }
890
    return p;
38✔
891
}
892

893
void LoadChannels(TSerialDeviceWithChannels& deviceWithChannels,
181✔
894
                  const Json::Value& deviceData,
895
                  const std::optional<std::chrono::milliseconds>& defaultReadRateLimit,
896
                  const TRegisterTypeMap& typeMap,
897
                  const TLoadingContext& context)
898
{
899
    if (deviceData.isMember("channels")) {
181✔
900
        for (const auto& channel_data: deviceData["channels"]) {
1,116✔
901
            LoadChannel(deviceWithChannels, channel_data, context, typeMap);
936✔
902
        }
903
    }
904

905
    if (deviceWithChannels.Channels.empty()) {
180✔
906
        LOG(Warn) << "device " << deviceWithChannels.Device->DeviceConfig()->Name << " has no channels";
×
907
    } else if (deviceWithChannels.Device->IsSporadicOnly()) {
180✔
908
        LOG(Debug) << "device " << deviceWithChannels.Device->DeviceConfig()->Name
×
909
                   << " has only sporadic channels enabled";
×
910
    }
911

912
    auto readRateLimit = GetReadRateLimit(deviceData);
180✔
913
    if (!readRateLimit) {
180✔
914
        readRateLimit = defaultReadRateLimit;
176✔
915
    }
916
    for (auto channel: deviceWithChannels.Channels) {
1,120✔
917
        for (auto reg: channel->Registers) {
1,936✔
918
            if (!reg->GetConfig()->ReadRateLimit) {
996✔
919
                reg->GetConfig()->ReadRateLimit = readRateLimit;
992✔
920
            }
921
        }
922
    }
923
}
180✔
924

925
void TSerialDeviceFactory::RegisterProtocol(PProtocol protocol, IDeviceFactory* deviceFactory)
3,456✔
926
{
927
    TDeviceProtocolParams params = {protocol, std::shared_ptr<IDeviceFactory>(deviceFactory)};
3,456✔
928
    Protocols.insert(std::make_pair(protocol->GetName(), params));
3,456✔
929
}
3,456✔
930

931
TDeviceProtocolParams TSerialDeviceFactory::GetProtocolParams(const std::string& protocolName) const
429✔
932
{
933
    auto it = Protocols.find(protocolName);
429✔
934
    if (it == Protocols.end()) {
429✔
935
        throw TSerialDeviceException("unknown protocol: " + protocolName);
×
936
    }
937
    return it->second;
858✔
938
}
939

940
PProtocol TSerialDeviceFactory::GetProtocol(const std::string& protocolName) const
148✔
941
{
942
    return GetProtocolParams(protocolName).protocol;
148✔
943
}
944

945
const std::string& TSerialDeviceFactory::GetCommonDeviceSchemaRef(const std::string& protocolName) const
61✔
946
{
947
    return GetProtocolParams(protocolName).factory->GetCommonDeviceSchemaRef();
61✔
948
}
949

950
const std::string& TSerialDeviceFactory::GetCustomChannelSchemaRef(const std::string& protocolName) const
37✔
951
{
952
    return GetProtocolParams(protocolName).factory->GetCustomChannelSchemaRef();
37✔
953
}
954

955
std::vector<std::string> TSerialDeviceFactory::GetProtocolNames() const
1✔
956
{
957
    std::vector<std::string> res;
1✔
958
    for (const auto& bucket: Protocols) {
25✔
959
        res.emplace_back(bucket.first);
24✔
960
    }
961
    return res;
1✔
962
}
963

964
PSerialDeviceWithChannels TSerialDeviceFactory::CreateDevice(const Json::Value& deviceConfigJson,
183✔
965
                                                             const TCreateDeviceParams& params,
966
                                                             TTemplateMap& templates)
967
{
968
    TDeviceConfigLoadParams loadParams;
366✔
969
    loadParams.Defaults = params.Defaults;
183✔
970
    const auto* cfg = &deviceConfigJson;
183✔
971
    unique_ptr<Json::Value> mergedConfig;
183✔
972
    if (deviceConfigJson.isMember("device_type")) {
183✔
973
        auto deviceType = deviceConfigJson["device_type"].asString();
60✔
974
        auto deviceTemplate = templates.GetTemplate(deviceType);
32✔
975
        loadParams.DeviceTemplateTitle = deviceTemplate->GetTitle();
30✔
976
        mergedConfig = std::make_unique<Json::Value>(
28✔
977
            MergeDeviceConfigWithTemplate(deviceConfigJson, deviceType, deviceTemplate->GetTemplate()));
58✔
978
        cfg = mergedConfig.get();
28✔
979
        loadParams.Translations = &deviceTemplate->GetTemplate()["translations"];
28✔
980
    }
981
    std::string protocolName = DefaultProtocol;
362✔
982
    Get(*cfg, "protocol", protocolName);
181✔
983

984
    if (params.IsModbusTcp) {
181✔
985
        if (!GetProtocol(protocolName)->IsModbus()) {
×
986
            throw TSerialDeviceException("Protocol \"" + protocolName + "\" is not compatible with Modbus TCP");
×
987
        }
988
        protocolName += "-tcp";
×
989
    }
990

991
    TDeviceProtocolParams protocolParams = GetProtocolParams(protocolName);
362✔
992
    auto deviceConfig = LoadDeviceConfig(*cfg, protocolParams.protocol, loadParams);
362✔
993
    auto deviceWithChannels = std::make_shared<TSerialDeviceWithChannels>();
181✔
994
    deviceWithChannels->Device = protocolParams.factory->CreateDevice(*cfg, deviceConfig, protocolParams.protocol);
181✔
995
    TLoadingContext context(*protocolParams.factory,
181✔
996
                            protocolParams.factory->GetRegisterAddressFactory().GetBaseRegisterAddress());
362✔
997
    context.translations = loadParams.Translations;
181✔
998
    auto regTypes = protocolParams.protocol->GetRegTypes();
362✔
999
    LoadSetupItems(*deviceWithChannels->Device, *cfg, *regTypes, context);
181✔
1000
    LoadChannels(*deviceWithChannels, *cfg, params.Defaults.ReadRateLimit, *regTypes, context);
181✔
1001

1002
    return deviceWithChannels;
360✔
1003
}
1004

1005
IDeviceFactory::IDeviceFactory(std::unique_ptr<IRegisterAddressFactory> registerAddressFactory,
3,456✔
1006
                               const std::string& commonDeviceSchemaRef,
1007
                               const std::string& customChannelSchemaRef)
3,456✔
1008
    : CommonDeviceSchemaRef(commonDeviceSchemaRef),
1009
      CustomChannelSchemaRef(customChannelSchemaRef),
1010
      RegisterAddressFactory(std::move(registerAddressFactory))
3,456✔
1011
{}
3,456✔
1012

1013
const std::string& IDeviceFactory::GetCommonDeviceSchemaRef() const
61✔
1014
{
1015
    return CommonDeviceSchemaRef;
61✔
1016
}
1017

1018
const std::string& IDeviceFactory::GetCustomChannelSchemaRef() const
37✔
1019
{
1020
    return CustomChannelSchemaRef;
37✔
1021
}
1022

1023
const IRegisterAddressFactory& IDeviceFactory::GetRegisterAddressFactory() const
1,314✔
1024
{
1025
    return *RegisterAddressFactory;
1,314✔
1026
}
1027

1028
PDeviceConfig LoadDeviceConfig(const Json::Value& dev, PProtocol protocol, const TDeviceConfigLoadParams& parameters)
181✔
1029
{
1030
    auto res = std::make_shared<TDeviceConfig>();
181✔
1031

1032
    Get(dev, "device_type", res->DeviceType);
181✔
1033

1034
    res->Id = Read(dev, "id", parameters.Defaults.Id);
181✔
1035
    Get(dev, "name", res->Name);
181✔
1036

1037
    if (dev.isMember("slave_id")) {
181✔
1038
        if (dev["slave_id"].isString())
181✔
1039
            res->SlaveId = dev["slave_id"].asString();
104✔
1040
        else // legacy
1041
            res->SlaveId = std::to_string(dev["slave_id"].asInt());
77✔
1042
    }
1043

1044
    LoadCommonDeviceParameters(*res, dev);
181✔
1045

1046
    if (res->RequestDelay.count() == 0) {
181✔
1047
        res->RequestDelay = parameters.Defaults.RequestDelay;
172✔
1048
    }
1049

1050
    if (parameters.Defaults.ResponseTimeout > res->ResponseTimeout) {
181✔
1051
        res->ResponseTimeout = parameters.Defaults.ResponseTimeout;
7✔
1052
    }
1053
    if (res->ResponseTimeout.count() == -1) {
181✔
1054
        res->ResponseTimeout = DefaultResponseTimeout;
151✔
1055
    }
1056

1057
    return res;
181✔
1058
}
1059

1060
TLoadRegisterConfigResult LoadRegisterConfig(const Json::Value& registerData,
1,116✔
1061
                                             const TRegisterTypeMap& typeMap,
1062
                                             const std::string& readonlyOverrideErrorMessagePrefix,
1063
                                             const IDeviceFactory& factory,
1064
                                             const IRegisterAddress& deviceBaseAddress,
1065
                                             size_t stride)
1066
{
1067
    TLoadRegisterConfigResult res;
1,116✔
1068
    TRegisterType regType = GetRegisterType(registerData, typeMap);
2,232✔
1069
    res.DefaultControlType = regType.DefaultControlType.empty() ? "text" : regType.DefaultControlType;
1,116✔
1070

1071
    if (registerData.isMember("format")) {
1,116✔
1072
        regType.DefaultFormat = RegisterFormatFromName(registerData["format"].asString());
555✔
1073
    }
1074

1075
    if (registerData.isMember("word_order")) {
1,116✔
1076
        regType.DefaultWordOrder = WordOrderFromName(registerData["word_order"].asString());
21✔
1077
    }
1078

1079
    if (registerData.isMember("byte_order")) {
1,116✔
1080
        regType.DefaultByteOrder = ByteOrderFromName(registerData["byte_order"].asString());
20✔
1081
    }
1082

1083
    double scale = Read(registerData, "scale", 1.0); // TBD: check for zero, too
1,116✔
1084
    double offset = Read(registerData, "offset", 0.0);
1,116✔
1085
    double round_to = Read(registerData, "round_to", 0.0);
1,116✔
1086
    TRegisterConfig::TSporadicMode sporadicMode = TRegisterConfig::TSporadicMode::DISABLED;
1,116✔
1087
    if (Read(registerData, "sporadic", false)) {
1,116✔
1088
        sporadicMode = TRegisterConfig::TSporadicMode::ONLY_EVENTS;
×
1089
    }
1090
    if (Read(registerData, "semi-sporadic", false)) {
1,116✔
1091
        sporadicMode = TRegisterConfig::TSporadicMode::EVENTS_AND_POLLING;
×
1092
    }
1093

1094
    bool readonly = ReadChannelsReadonlyProperty(registerData,
1,116✔
1095
                                                 "readonly",
1096
                                                 regType.ReadOnly,
1097
                                                 readonlyOverrideErrorMessagePrefix,
1098
                                                 regType.Name);
2,232✔
1099
    // For compatibility with old configs
1100
    readonly = ReadChannelsReadonlyProperty(registerData,
1,116✔
1101
                                            "channel_readonly",
1102
                                            readonly,
1103
                                            readonlyOverrideErrorMessagePrefix,
1104
                                            regType.Name);
2,232✔
1105

1106
    auto registerDesc =
1107
        factory.GetRegisterAddressFactory().LoadRegisterAddress(registerData,
1,116✔
1108
                                                                deviceBaseAddress,
1109
                                                                stride,
1110
                                                                RegisterFormatByteWidth(regType.DefaultFormat));
2,232✔
1111

1112
    if ((regType.DefaultFormat == RegisterFormat::String || regType.DefaultFormat == RegisterFormat::String8) &&
1,116✔
1113
        registerDesc.DataWidth == 0)
8✔
1114
    {
1115
        throw TConfigParserException(readonlyOverrideErrorMessagePrefix +
×
1116
                                     ": String size is not set for register string format");
×
1117
    }
1118

1119
    res.RegisterConfig = TRegisterConfig::Create(regType.Index,
1,116✔
1120
                                                 registerDesc,
1121
                                                 regType.DefaultFormat,
1122
                                                 scale,
1123
                                                 offset,
1124
                                                 round_to,
1125
                                                 sporadicMode,
1126
                                                 readonly,
1127
                                                 regType.Name,
1128
                                                 regType.DefaultWordOrder,
1129
                                                 regType.DefaultByteOrder);
1,116✔
1130

1131
    if (registerData.isMember("error_value")) {
1,116✔
1132
        res.RegisterConfig->ErrorValue = TRegisterValue{ToUint64(registerData["error_value"], "error_value")};
238✔
1133
    }
1134

1135
    if (registerData.isMember("unsupported_value")) {
1,116✔
1136
        res.RegisterConfig->UnsupportedValue =
10✔
1137
            TRegisterValue{ToUint64(registerData["unsupported_value"], "unsupported_value")};
20✔
1138
    }
1139

1140
    res.RegisterConfig->ReadRateLimit = GetReadRateLimit(registerData);
1,116✔
1141
    res.RegisterConfig->ReadPeriod = GetReadPeriod(registerData);
1,116✔
1142
    return res;
2,232✔
1143
}
1144

1145
void RegisterProtocols(TSerialDeviceFactory& deviceFactory)
143✔
1146
{
1147
    TEnergomeraIecWithFastReadDevice::Register(deviceFactory);
143✔
1148
    TEnergomeraIecModeCDevice::Register(deviceFactory);
143✔
1149
    TIVTMDevice::Register(deviceFactory);
143✔
1150
    TLLSDevice::Register(deviceFactory);
143✔
1151
    TMercury200Device::Register(deviceFactory);
143✔
1152
    TMercury230Device::Register(deviceFactory);
143✔
1153
    TMilurDevice::Register(deviceFactory);
143✔
1154
    TModbusDevice::Register(deviceFactory);
143✔
1155
    TModbusIODevice::Register(deviceFactory);
143✔
1156
    TNevaDevice::Register(deviceFactory);
143✔
1157
    TPulsarDevice::Register(deviceFactory);
143✔
1158
    TS2KDevice::Register(deviceFactory);
143✔
1159
    TUnielDevice::Register(deviceFactory);
143✔
1160
    TDlmsDevice::Register(deviceFactory);
143✔
1161
    Dooya::TDevice::Register(deviceFactory);
143✔
1162
    WinDeco::TDevice::Register(deviceFactory);
143✔
1163
    Somfy::TDevice::Register(deviceFactory);
143✔
1164
    Aok::TDevice::Register(deviceFactory);
143✔
1165
    TIecModeCDevice::Register(deviceFactory);
143✔
1166
    TEnergomeraCeDevice::Register(deviceFactory);
143✔
1167
}
143✔
1168

1169
TRegisterBitsAddress LoadRegisterBitsAddress(const Json::Value& register_data, const std::string& jsonPropertyName)
1,108✔
1170
{
1171
    TRegisterBitsAddress res;
1,108✔
1172
    const auto& addressValue = register_data[jsonPropertyName];
1,108✔
1173
    if (addressValue.isString()) {
1,108✔
1174
        const auto& addressStr = addressValue.asString();
420✔
1175
        auto pos1 = addressStr.find(':');
210✔
1176
        if (pos1 == string::npos) {
210✔
1177
            res.Address = GetInt(register_data, jsonPropertyName);
180✔
1178
        } else {
1179
            auto pos2 = addressStr.find(':', pos1 + 1);
30✔
1180

1181
            res.Address = GetIntFromString(addressStr.substr(0, pos1), jsonPropertyName);
30✔
1182
            auto bitOffset = stoul(addressStr.substr(pos1 + 1, pos2));
30✔
1183

1184
            if (bitOffset > 255) {
30✔
1185
                throw TConfigParserException(
×
1186
                    "address parsing failed: bit shift must be in range [0, 255] (address string: '" + addressStr +
×
1187
                    "')");
×
1188
            }
1189
            res.BitOffset = bitOffset;
30✔
1190
            if (pos2 != string::npos) {
30✔
1191
                res.BitWidth = stoul(addressStr.substr(pos2 + 1));
30✔
1192
            }
1193
        }
1194
    } else {
1195
        res.Address = GetInt(register_data, jsonPropertyName);
898✔
1196
    }
1197

1198
    if (register_data.isMember("string_data_size")) {
1,108✔
1199
        res.BitWidth = GetInt(register_data, "string_data_size") * sizeof(char) * 8;
8✔
1200
    }
1201
    return res;
1,108✔
1202
}
1203

1204
TUint32RegisterAddressFactory::TUint32RegisterAddressFactory(size_t bytesPerRegister)
2,741✔
1205
    : BaseRegisterAddress(0),
1206
      BytesPerRegister(bytesPerRegister)
2,741✔
1207
{}
2,741✔
1208

1209
TRegisterDesc TUint32RegisterAddressFactory::LoadRegisterAddress(const Json::Value& regCfg,
1,052✔
1210
                                                                 const IRegisterAddress& deviceBaseAddress,
1211
                                                                 uint32_t stride,
1212
                                                                 uint32_t registerByteWidth) const
1213
{
1214
    TRegisterDesc res;
1,052✔
1215

1216
    if (HasNoEmptyProperty(regCfg, SerialConfig::ADDRESS_PROPERTY_NAME)) {
1,052✔
1217
        auto addr = LoadRegisterBitsAddress(regCfg, SerialConfig::ADDRESS_PROPERTY_NAME);
1,052✔
1218
        res.DataOffset = addr.BitOffset;
1,052✔
1219
        res.DataWidth = addr.BitWidth;
1,052✔
1220
        res.Address = std::shared_ptr<IRegisterAddress>(
1,052✔
1221
            deviceBaseAddress.CalcNewAddress(addr.Address, stride, registerByteWidth, BytesPerRegister));
1,052✔
1222
        res.WriteAddress = res.Address;
1,052✔
1223
    }
1224
    if (HasNoEmptyProperty(regCfg, SerialConfig::WRITE_ADDRESS_PROPERTY_NAME)) {
1,052✔
1225
        auto writeAddress = LoadRegisterBitsAddress(regCfg, SerialConfig::WRITE_ADDRESS_PROPERTY_NAME);
1✔
1226
        res.WriteAddress = std::shared_ptr<IRegisterAddress>(
1✔
1227
            deviceBaseAddress.CalcNewAddress(writeAddress.Address, stride, registerByteWidth, BytesPerRegister));
1✔
1228
    }
1229
    return res;
1,052✔
1230
}
1231

1232
const IRegisterAddress& TUint32RegisterAddressFactory::GetBaseRegisterAddress() const
193✔
1233
{
1234
    return BaseRegisterAddress;
193✔
1235
}
1236

1237
TRegisterDesc TStringRegisterAddressFactory::LoadRegisterAddress(const Json::Value& regCfg,
×
1238
                                                                 const IRegisterAddress& deviceBaseAddress,
1239
                                                                 uint32_t stride,
1240
                                                                 uint32_t registerByteWidth) const
1241
{
1242
    TRegisterDesc res;
×
1243
    if (HasNoEmptyProperty(regCfg, SerialConfig::ADDRESS_PROPERTY_NAME)) {
×
1244
        res.Address = std::make_shared<TStringRegisterAddress>(regCfg[SerialConfig::ADDRESS_PROPERTY_NAME].asString());
×
1245
        res.WriteAddress = res.Address;
×
1246
    }
1247
    if (HasNoEmptyProperty(regCfg, SerialConfig::WRITE_ADDRESS_PROPERTY_NAME)) {
×
1248
        res.WriteAddress =
1249
            std::make_shared<TStringRegisterAddress>(regCfg[SerialConfig::WRITE_ADDRESS_PROPERTY_NAME].asString());
×
1250
    }
1251
    return res;
×
1252
}
1253

1254
const IRegisterAddress& TStringRegisterAddressFactory::GetBaseRegisterAddress() const
4✔
1255
{
1256
    return BaseRegisterAddress;
4✔
1257
}
1258

1259
bool HasNoEmptyProperty(const Json::Value& regCfg, const std::string& propertyName)
2,104✔
1260
{
1261
    return regCfg.isMember(propertyName) &&
3,157✔
1262
           !(regCfg[propertyName].isString() && regCfg[propertyName].asString().empty());
3,157✔
1263
}
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