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

wirenboard / wb-mqtt-serial / 672

15 Jul 2025 04:53AM UTC coverage: 73.863% (+0.03%) from 73.833%
672

push

github

web-flow
Fix channel settings negative hex value parsing

6458 of 9076 branches covered (71.15%)

44 of 49 new or added lines in 2 files covered. (89.8%)

1 existing line in 1 file now uncovered.

12364 of 16739 relevant lines covered (73.86%)

378.04 hits per line

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

88.31
/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)
14,078✔
54
    {
55
        T value;
5,048✔
56
        if (Get(root, key, value)) {
14,078✔
57
            return value;
2,274✔
58
        }
59
        return defaultValue;
11,804✔
60
    }
61

62
    int GetIntFromString(const std::string& value, const std::string& errorPrefix)
67✔
63
    {
64
        try {
65
            // use std::stoul to parse negative hex value string without sign (greater than 0x7fffffff)
66
            return static_cast<int>(std::stoul(value, 0, 0));
67✔
67
        } catch (const std::logic_error&) {
×
68
            throw TConfigParserException(errorPrefix + ": plain integer or '0x..' hex string expected instead of '" +
×
69
                                         value + "'");
×
70
        }
71
    }
72

73
    double ToDouble(const Json::Value& v, const std::string& title)
99✔
74
    {
75
        if (v.isNumeric())
99✔
76
            return v.asDouble();
62✔
77
        if (!v.isString()) {
37✔
78
            throw TConfigParserException(title + ": number or '0x..' hex string expected");
×
79
        }
80
        return GetIntFromString(v.asString(), title);
37✔
81
    }
82

83
    uint64_t ToUint64(const Json::Value& v, const string& title)
1,380✔
84
    {
85
        if (v.isUInt()) {
1,380✔
86
            return v.asUInt64();
907✔
87
        }
88

89
        if (v.isInt()) {
473✔
NEW
90
            auto val = v.asInt64();
×
91
            if (val >= 0) {
×
92
                return val;
×
93
            }
94
        }
95

96
        if (v.isString()) {
473✔
97
            auto val = v.asString();
473✔
98
            if (val.find("-") == std::string::npos) {
473✔
99
                // don't try to parse strings containing munus sign
100
                try {
101
                    return stoull(val, /*pos= */ 0, /*base= */ 0);
473✔
102
                } catch (const logic_error& e) {
×
103
                }
104
            }
105
        }
106

107
        throw TConfigParserException(
×
NEW
108
            title + ": 64-bit plain unsigned integer or '0x..' hex string expected instead of '" + v.asString() + "'");
×
109
    }
110

111
    double GetDouble(const Json::Value& obj, const std::string& key)
99✔
112
    {
113
        return ToDouble(obj[key], key);
99✔
114
    }
115

116
    uint64_t GetUint64(const Json::Value& obj, const std::string& key)
1,123✔
117
    {
118
        return ToUint64(obj[key], key);
1,123✔
119
    }
120

121
    std::string GetIntegerString(const Json::Value& obj, const std::string& key)
117✔
122
    {
123
        auto v = obj[key];
234✔
124
        if (v.isInt()) {
117✔
125
            return std::to_string(v.asInt64());
79✔
126
        }
127

128
        auto val = v.asString();
76✔
129
        try {
130
            return std::to_string(stoll(val, /*pos= */ 0, /*base= */ 0));
38✔
NEW
131
        } catch (const logic_error& e) {
×
NEW
132
            throw TConfigParserException(key + ": 64-bit plain integer or '0x..' hex string expected instead of '" +
×
NEW
133
                                         val + "': " + e.what());
×
134
        }
135
    }
136

137
    bool IsSerialNumberChannel(const Json::Value& channel_data)
981✔
138
    {
139
        const std::vector<std::string> serialNames{"Serial", "serial_number", "Serial NO"};
5,886✔
140
        return serialNames.end() !=
1,962✔
141
               std::find(serialNames.begin(), serialNames.end(), channel_data.get("name", std::string()).asString());
2,943✔
142
    }
143

144
    bool ReadChannelsReadonlyProperty(const Json::Value& register_data,
2,308✔
145
                                      const std::string& key,
146
                                      bool templateReadonly,
147
                                      const std::string& override_error_message_prefix,
148
                                      const std::string& register_type)
149
    {
150
        if (!register_data.isMember(key)) {
2,308✔
151
            return templateReadonly;
2,291✔
152
        }
153
        auto& val = register_data[key];
17✔
154
        if (!val.isConvertibleTo(Json::booleanValue)) {
17✔
155
            return templateReadonly;
×
156
        }
157
        bool readonly = val.asBool();
17✔
158
        if (templateReadonly && !readonly) {
17✔
159
            LOG(Warn) << override_error_message_prefix << " unable to make register of type \"" << register_type
2✔
160
                      << "\" writable";
1✔
161
            return true;
1✔
162
        }
163
        return readonly;
16✔
164
    }
165

166
    const TRegisterType& GetRegisterType(const Json::Value& itemData, const TRegisterTypeMap& typeMap)
1,154✔
167
    {
168
        if (itemData.isMember("reg_type")) {
1,154✔
169
            std::string type = itemData["reg_type"].asString();
1,087✔
170
            try {
171
                return typeMap.Find(type);
1,087✔
172
            } catch (...) {
×
173
                throw TConfigParserException("invalid register type: " + type);
×
174
            }
175
        }
176
        return typeMap.GetDefaultType();
67✔
177
    }
178

179
    std::optional<std::chrono::milliseconds> GetReadRateLimit(const Json::Value& data)
1,455✔
180
    {
181
        std::chrono::milliseconds res(-1);
1,455✔
182
        try {
183
            Get(data, "poll_interval", res);
1,455✔
184
        } catch (...) { // poll_interval is deprecated, so ignore it, if it has wrong format
×
185
        }
186
        Get(data, "read_rate_limit_ms", res);
1,455✔
187
        if (res < 0ms) {
1,455✔
188
            return std::nullopt;
1,443✔
189
        }
190
        return std::make_optional(res);
12✔
191
    }
192

193
    std::optional<std::chrono::milliseconds> GetReadPeriod(const Json::Value& data)
1,182✔
194
    {
195
        std::chrono::milliseconds res(-1);
1,182✔
196
        Get(data, "read_period_ms", res);
1,182✔
197
        if (res < 0ms) {
1,182✔
198
            return std::nullopt;
1,160✔
199
        }
200
        return std::make_optional(res);
22✔
201
    }
202

203
    struct TLoadingContext
204
    {
205
        // Full path to loaded item composed from device and channels names
206
        std::string name_prefix;
207

208
        // MQTT topic prefix. It could be different from name_prefix
209
        std::string mqtt_prefix;
210
        const IDeviceFactory& factory;
211
        const IRegisterAddress& device_base_address;
212
        size_t stride = 0;
213
        TTitleTranslations translated_name_prefixes;
214
        const Json::Value* translations = nullptr;
215

216
        TLoadingContext(const IDeviceFactory& f, const IRegisterAddress& base_address)
188✔
217
            : factory(f),
188✔
218
              device_base_address(base_address)
188✔
219
        {}
188✔
220
    };
221

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

253
            for (const auto& it: context.translated_name_prefixes) {
148✔
254
                // There are translatied prefixes, but no translation for the name.
255
                // Take translated prefix and add the name as is
256
                if (!res.count(it.first)) {
11✔
257
                    res[it.first] = it.second + " " + name;
9✔
258
                }
259
            }
260

261
            // Name is different from MQTT id and there isn't english translated prefix
262
            // Take MQTT ID prefix if any and add the name as is
263
            if (!res.count("en") && idIsDefined) {
274✔
264
                if (context.mqtt_prefix.empty()) {
3✔
265
                    res["en"] = name;
1✔
266
                } else {
267
                    res["en"] = context.mqtt_prefix + " " + name;
2✔
268
                }
269
            }
270
            return res;
137✔
271
        }
272

273
        // No translations for names at all.
274
        // Just compose english name if it is different from MQTT id
275
        auto trIt = context.translated_name_prefixes.find("en");
852✔
276
        if (trIt != context.translated_name_prefixes.end()) {
852✔
277
            res["en"] = trIt->second + name;
×
278
        } else {
279
            if (idIsDefined) {
852✔
280
                res["en"] = name;
4✔
281
            }
282
        }
283
        return res;
852✔
284
    }
285

286
    void LoadSimpleChannel(TSerialDeviceWithChannels& deviceWithChannels,
979✔
287
                           const Json::Value& channel_data,
288
                           const TLoadingContext& context,
289
                           const TRegisterTypeMap& typeMap)
290
    {
291
        std::string mqtt_channel_name(channel_data["name"].asString());
979✔
292
        bool idIsDefined = false;
979✔
293
        if (channel_data.isMember("id")) {
979✔
294
            mqtt_channel_name = channel_data["id"].asString();
3✔
295
            idIsDefined = true;
3✔
296
        }
297
        if (!context.mqtt_prefix.empty()) {
979✔
298
            mqtt_channel_name = context.mqtt_prefix + " " + mqtt_channel_name;
8✔
299
        }
300
        auto errorMsgPrefix = "Channel \"" + mqtt_channel_name + "\"";
979✔
301
        std::string default_type_str;
979✔
302
        std::vector<PRegister> registers;
979✔
303
        if (channel_data.isMember("consists_of")) {
979✔
304

305
            auto read_rate_limit_ms = GetReadRateLimit(channel_data);
28✔
306
            auto read_period = GetReadPeriod(channel_data);
28✔
307

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

343
        std::string type_str(Read(channel_data, "type", default_type_str));
2,937✔
344
        if (type_str == "wo-switch" || type_str == "pushbutton") {
979✔
345
            if (type_str == "wo-switch") {
×
346
                type_str = "switch";
×
347
            }
348
            for (auto& reg: registers) {
×
349
                reg->GetConfig()->AccessType = TRegisterConfig::EAccessType::WRITE_ONLY;
×
350
            }
351
        }
352

353
        int order = deviceWithChannels.Channels.size() + 1;
979✔
354
        PDeviceChannelConfig channel(
355
            new TDeviceChannelConfig(type_str,
356
                                     deviceWithChannels.Device->DeviceConfig()->Id,
979✔
357
                                     order,
358
                                     (registers[0]->GetConfig()->AccessType == TRegisterConfig::EAccessType::READ_ONLY),
1,958✔
359
                                     mqtt_channel_name,
360
                                     registers));
1,958✔
361

362
        for (const auto& it: Translate(channel_data["name"].asString(), idIsDefined, context)) {
992✔
363
            channel->SetTitle(it.second, it.first);
13✔
364
        }
365

366
        if (channel_data.isMember("enum") && channel_data.isMember("enum_titles")) {
979✔
367
            const auto& enumValues = channel_data["enum"];
2✔
368
            const auto& enumTitles = channel_data["enum_titles"];
2✔
369
            if (enumValues.size() == enumTitles.size()) {
2✔
370
                for (Json::ArrayIndex i = 0; i < enumValues.size(); ++i) {
6✔
371
                    channel->SetEnumTitles(enumValues[i].asString(),
12✔
372
                                           Translate(enumTitles[i].asString(), true, context));
8✔
373
                }
374
            } else {
375
                LOG(Warn) << errorMsgPrefix << ": enum and enum_titles should have the same size -- "
×
376
                          << deviceWithChannels.Device->DeviceConfig()->DeviceType;
×
377
            }
378
        }
379

380
        if (channel_data.isMember("max")) {
979✔
381
            channel->Max = GetDouble(channel_data, "max");
98✔
382
        }
383
        if (channel_data.isMember("min")) {
979✔
384
            channel->Min = GetDouble(channel_data, "min");
1✔
385
        }
386
        if (channel_data.isMember("on_value")) {
979✔
387
            if (registers.size() != 1)
59✔
388
                throw TConfigParserException("on_value is allowed only for single-valued controls -- " +
×
389
                                             deviceWithChannels.Device->DeviceConfig()->DeviceType);
×
390
            channel->OnValue = GetIntegerString(channel_data, "on_value");
59✔
391
        }
392
        if (channel_data.isMember("off_value")) {
979✔
393
            if (registers.size() != 1)
58✔
394
                throw TConfigParserException("off_value is allowed only for single-valued controls -- " +
×
395
                                             deviceWithChannels.Device->DeviceConfig()->DeviceType);
×
396
            channel->OffValue = GetIntegerString(channel_data, "off_value");
58✔
397
        }
398

399
        if (registers.size() == 1) {
979✔
400
            channel->Precision = registers[0]->GetConfig()->RoundTo;
951✔
401
        }
402

403
        Get(channel_data, "units", channel->Units);
979✔
404

405
        if (IsSerialNumberChannel(channel_data) && registers.size()) {
979✔
406
            deviceWithChannels.Device->SetSnRegister(registers[0]->GetConfig());
2✔
407
        }
408

409
        deviceWithChannels.Channels.push_back(channel);
979✔
410
    }
411

412
    void LoadChannel(TSerialDeviceWithChannels& deviceWithChannels,
413
                     const Json::Value& channel_data,
414
                     const TLoadingContext& context,
415
                     const TRegisterTypeMap& typeMap);
416

417
    void LoadSetupItems(TSerialDevice& device,
418
                        const Json::Value& item_data,
419
                        const TRegisterTypeMap& typeMap,
420
                        const TLoadingContext& context);
421

422
    void LoadSubdeviceChannel(TSerialDeviceWithChannels& deviceWithChannels,
7✔
423
                              const Json::Value& channel_data,
424
                              const TLoadingContext& context,
425
                              const TRegisterTypeMap& typeMap)
426
    {
427
        uint32_t shift = 0;
7✔
428
        if (channel_data.isMember("shift")) {
7✔
429
            shift = static_cast<uint32_t>(GetUint64(channel_data, "shift"));
7✔
430
        }
431
        std::unique_ptr<IRegisterAddress> baseAddress(context.device_base_address.CalcNewAddress(shift, 0, 0, 0));
14✔
432

433
        TLoadingContext newContext(context.factory, *baseAddress);
14✔
434
        newContext.translations = context.translations;
7✔
435
        auto name = channel_data["name"].asString();
14✔
436
        newContext.name_prefix = name;
7✔
437
        if (!context.name_prefix.empty()) {
7✔
438
            newContext.name_prefix = context.name_prefix + " " + newContext.name_prefix;
4✔
439
        }
440

441
        newContext.mqtt_prefix = name;
7✔
442
        bool idIsDefined = false;
7✔
443
        if (channel_data.isMember("id")) {
7✔
444
            newContext.mqtt_prefix = channel_data["id"].asString();
3✔
445
            idIsDefined = true;
3✔
446
        }
447

448
        // Empty id is used if we don't want to add channel name to resulting MQTT topic name
449
        // This case we also don't add translation to resulting translated channel name
450
        if (!(idIsDefined && newContext.mqtt_prefix.empty())) {
7✔
451
            newContext.translated_name_prefixes = Translate(name, idIsDefined, context);
6✔
452
        }
453

454
        if (!context.mqtt_prefix.empty()) {
7✔
455
            if (newContext.mqtt_prefix.empty()) {
2✔
456
                newContext.mqtt_prefix = context.mqtt_prefix;
×
457
            } else {
458
                newContext.mqtt_prefix = context.mqtt_prefix + " " + newContext.mqtt_prefix;
2✔
459
            }
460
        }
461

462
        newContext.stride = Read(channel_data, "stride", 0);
7✔
463
        LoadSetupItems(*deviceWithChannels.Device, channel_data, typeMap, newContext);
7✔
464

465
        if (channel_data.isMember("channels")) {
6✔
466
            for (const auto& ch: channel_data["channels"]) {
20✔
467
                LoadChannel(deviceWithChannels, ch, newContext, typeMap);
14✔
468
            }
469
        }
470
    }
6✔
471

472
    void LoadChannel(TSerialDeviceWithChannels& deviceWithChannels,
988✔
473
                     const Json::Value& channel_data,
474
                     const TLoadingContext& context,
475
                     const TRegisterTypeMap& typeMap)
476
    {
477
        if (channel_data.isMember("enabled") && !channel_data["enabled"].asBool()) {
988✔
478
            if (IsSerialNumberChannel(channel_data)) {
2✔
479
                deviceWithChannels.Device->SetSnRegister(LoadRegisterConfig(channel_data,
×
480
                                                                            typeMap,
481
                                                                            std::string(),
×
482
                                                                            context.factory,
483
                                                                            context.device_base_address,
484
                                                                            context.stride)
×
485
                                                             .RegisterConfig);
×
486
            }
487
            return;
2✔
488
        }
489
        if (channel_data.isMember("device_type")) {
986✔
490
            LoadSubdeviceChannel(deviceWithChannels, channel_data, context, typeMap);
7✔
491
        } else {
492
            LoadSimpleChannel(deviceWithChannels, channel_data, context, typeMap);
979✔
493
        }
494
        if (deviceWithChannels.Device->IsSporadicOnly() && !channel_data["sporadic"].asBool()) {
985✔
495
            deviceWithChannels.Device->SetSporadicOnly(false);
181✔
496
        }
497
    }
498

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

521
    void LoadSetupItems(TSerialDevice& device,
188✔
522
                        const Json::Value& device_data,
523
                        const TRegisterTypeMap& typeMap,
524
                        const TLoadingContext& context)
525
    {
526
        if (device_data.isMember("setup")) {
188✔
527
            for (const auto& setupItem: device_data["setup"])
151✔
528
                LoadSetupItem(device, setupItem, typeMap, context);
102✔
529
        }
530
    }
187✔
531

532
    void LoadCommonDeviceParameters(TDeviceConfig& device_config, const Json::Value& device_data)
181✔
533
    {
534
        if (device_data.isMember("password")) {
181✔
535
            device_config.Password.clear();
2✔
536
            for (const auto& passwordItem: device_data["password"]) {
11✔
537
                device_config.Password.push_back(static_cast<uint8_t>(ToUint64(passwordItem, "password item")));
9✔
538
            }
539
        }
540

541
        if (device_data.isMember("delay_ms")) {
181✔
542
            LOG(Warn) << "\"delay_ms\" is not supported, use \"frame_timeout_ms\" instead";
×
543
        }
544

545
        Get(device_data, "frame_timeout_ms", device_config.FrameTimeout);
181✔
546
        if (device_config.FrameTimeout.count() < 0) {
181✔
547
            device_config.FrameTimeout = DefaultFrameTimeout;
×
548
        }
549
        Get(device_data, "response_timeout_ms", device_config.ResponseTimeout);
181✔
550
        Get(device_data, "device_timeout_ms", device_config.DeviceTimeout);
181✔
551
        Get(device_data, "device_max_fail_cycles", device_config.DeviceMaxFailCycles);
181✔
552
        Get(device_data, "max_write_fail_time_s", device_config.MaxWriteFailTime);
181✔
553
        Get(device_data, "max_reg_hole", device_config.MaxRegHole);
181✔
554
        Get(device_data, "max_bit_hole", device_config.MaxBitHole);
181✔
555
        Get(device_data, "max_read_registers", device_config.MaxReadRegisters);
181✔
556
        Get(device_data, "min_read_registers", device_config.MinReadRegisters);
181✔
557
        Get(device_data, "max_write_registers", device_config.MaxWriteRegisters);
181✔
558
        Get(device_data, "guard_interval_us", device_config.RequestDelay);
181✔
559
        Get(device_data, "stride", device_config.Stride);
181✔
560
        Get(device_data, "shift", device_config.Shift);
181✔
561
        Get(device_data, "access_level", device_config.AccessLevel);
181✔
562
        Get(device_data, "min_request_interval", device_config.MinRequestInterval);
181✔
563
    }
181✔
564

565
    void LoadDevice(PPortConfig port_config,
183✔
566
                    const Json::Value& device_data,
567
                    const std::string& default_id,
568
                    TTemplateMap& templates,
569
                    TSerialDeviceFactory& deviceFactory)
570
    {
571
        if (device_data.isMember("enabled") && !device_data["enabled"].asBool())
183✔
572
            return;
×
573

574
        TSerialDeviceFactory::TCreateDeviceParams params;
188✔
575
        params.Defaults.Id = default_id;
183✔
576
        params.Defaults.RequestDelay = port_config->RequestDelay;
183✔
577
        params.Defaults.ResponseTimeout = port_config->ResponseTimeout;
183✔
578
        params.Defaults.ReadRateLimit = port_config->ReadRateLimit;
183✔
579
        params.IsModbusTcp = port_config->IsModbusTcp;
183✔
580
        port_config->AddDevice(deviceFactory.CreateDevice(device_data, params, templates));
185✔
581
    }
582

583
    PPort OpenSerialPort(const Json::Value& port_data, PRPCConfig rpcConfig)
17✔
584
    {
585
        TSerialPortSettings settings(port_data["path"].asString());
34✔
586

587
        Get(port_data, "baud_rate", settings.BaudRate);
17✔
588

589
        if (port_data.isMember("parity"))
17✔
590
            settings.Parity = port_data["parity"].asCString()[0];
10✔
591

592
        Get(port_data, "data_bits", settings.DataBits);
17✔
593
        Get(port_data, "stop_bits", settings.StopBits);
17✔
594

595
        PPort port = std::make_shared<TSerialPort>(settings);
17✔
596

597
        rpcConfig->AddSerialPort(settings);
17✔
598

599
        return port;
34✔
600
    }
601

602
    PPort OpenTcpPort(const Json::Value& port_data, PRPCConfig rpcConfig)
4✔
603
    {
604
        TTcpPortSettings settings(port_data["address"].asString(), port_data["port"].asUInt());
8✔
605

606
        PPort port = std::make_shared<TTcpPort>(settings);
4✔
607

608
        rpcConfig->AddTCPPort(settings);
4✔
609

610
        return port;
8✔
611
    }
612

613
    void LoadPort(PHandlerConfig handlerConfig,
93✔
614
                  const Json::Value& port_data,
615
                  const std::string& id_prefix,
616
                  TTemplateMap& templates,
617
                  PRPCConfig rpcConfig,
618
                  TSerialDeviceFactory& deviceFactory,
619
                  TPortFactoryFn portFactory)
620
    {
621
        if (port_data.isMember("enabled") && !port_data["enabled"].asBool())
93✔
622
            return;
×
623

624
        auto port_config = make_shared<TPortConfig>();
186✔
625

626
        Get(port_data, "response_timeout_ms", port_config->ResponseTimeout);
93✔
627
        Get(port_data, "guard_interval_us", port_config->RequestDelay);
93✔
628
        port_config->ReadRateLimit = GetReadRateLimit(port_data);
93✔
629

630
        auto port_type = port_data.get("port_type", "serial").asString();
191✔
631

632
        Get(port_data, "connection_timeout_ms", port_config->OpenCloseSettings.MaxFailTime);
93✔
633
        Get(port_data, "connection_max_fail_cycles", port_config->OpenCloseSettings.ConnectionMaxFailCycles);
93✔
634

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

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

641
        handlerConfig->AddPortConfig(port_config);
88✔
642
    }
643
}
644

645
std::string DecorateIfNotEmpty(const std::string& prefix, const std::string& str, const std::string& postfix)
72✔
646
{
647
    if (str.empty()) {
72✔
648
        return std::string();
×
649
    }
650
    return prefix + str + postfix;
144✔
651
}
652

653
void SetIfExists(Json::Value& dst, const std::string& dstKey, const Json::Value& src, const std::string& srcKey)
144✔
654
{
655
    if (src.isMember(srcKey)) {
144✔
656
        dst[dstKey] = src[srcKey];
44✔
657
    }
658
}
144✔
659

660
std::pair<PPort, bool> DefaultPortFactory(const Json::Value& port_data, PRPCConfig rpcConfig)
21✔
661
{
662
    auto port_type = port_data.get("port_type", "serial").asString();
63✔
663
    if (port_type == "serial") {
21✔
664
        return {OpenSerialPort(port_data, rpcConfig), false};
17✔
665
    }
666
    if (port_type == "tcp") {
4✔
667
        return {OpenTcpPort(port_data, rpcConfig), false};
4✔
668
    }
669
    if (port_type == "modbus tcp") {
×
670
        return {OpenTcpPort(port_data, rpcConfig), true};
×
671
    }
672
    throw TConfigParserException("invalid port_type: '" + port_type + "'");
×
673
}
674

675
Json::Value LoadConfigTemplatesSchema(const std::string& templateSchemaFileName, const Json::Value& commonDeviceSchema)
19✔
676
{
677
    Json::Value schema = WBMQTT::JSON::Parse(templateSchemaFileName);
19✔
678
    AppendParams(schema["definitions"], commonDeviceSchema["definitions"]);
19✔
679
    return schema;
19✔
680
}
681

682
void AddRegisterType(Json::Value& configSchema, const std::string& registerType)
19✔
683
{
684
    configSchema["definitions"]["reg_type"]["enum"].append(registerType);
19✔
685
}
19✔
686

687
void CheckDuplicatePorts(const THandlerConfig& handlerConfig)
76✔
688
{
689
    std::unordered_set<std::string> paths;
152✔
690
    for (const auto& port: handlerConfig.PortConfigs) {
164✔
691
        if (!paths.insert(port->Port->GetDescription(false)).second) {
88✔
692
            throw TConfigParserException("Duplicate port: " + port->Port->GetDescription(false));
×
693
        }
694
    }
695
}
76✔
696

697
void CheckDuplicateDeviceIds(const THandlerConfig& handlerConfig)
76✔
698
{
699
    std::unordered_set<std::string> ids;
152✔
700
    for (const auto& port: handlerConfig.PortConfigs) {
162✔
701
        for (const auto& device: port->Devices) {
261✔
702
            if (!ids.insert(device->Device->DeviceConfig()->Id).second) {
175✔
703
                throw TConfigParserException(
1✔
704
                    "Duplicate MQTT device id: " + device->Device->DeviceConfig()->Id +
2✔
705
                    ", set device MQTT ID explicitly to fix (see https://wb.wiki/serial-id-collision)");
3✔
706
            }
707
        }
708
    }
709
}
75✔
710

711
PHandlerConfig LoadConfig(const std::string& configFileName,
92✔
712
                          TSerialDeviceFactory& deviceFactory,
713
                          const Json::Value& commonDeviceSchema,
714
                          TTemplateMap& templates,
715
                          PRPCConfig rpcConfig,
716
                          const Json::Value& portsSchema,
717
                          TProtocolConfedSchemasMap& protocolSchemas,
718
                          TPortFactoryFn portFactory)
719
{
720
    PHandlerConfig handlerConfig(new THandlerConfig);
92✔
721
    Json::Value Root(Parse(configFileName));
184✔
722

723
    try {
724
        ValidateConfig(Root, deviceFactory, commonDeviceSchema, portsSchema, templates, protocolSchemas);
92✔
725
    } catch (const std::runtime_error& e) {
22✔
726
        throw std::runtime_error("File: " + configFileName + " error: " + e.what());
11✔
727
    }
728

729
    // wb6 - single core - max 100 registers per second
730
    // wb7 - 4 cores - max 800 registers per second
731
    handlerConfig->LowPriorityRegistersRateLimit = (1 == get_nprocs_conf()) ? 100 : 800;
81✔
732
    Get(Root, "rate_limit", handlerConfig->LowPriorityRegistersRateLimit);
81✔
733

734
    Get(Root, "debug", handlerConfig->Debug);
81✔
735

736
    auto maxUnchangedInterval = DefaultMaxUnchangedInterval;
81✔
737
    Get(Root, "max_unchanged_interval", maxUnchangedInterval);
81✔
738
    if (maxUnchangedInterval.count() > 0 && maxUnchangedInterval < MaxUnchangedIntervalLowLimit) {
81✔
739
        LOG(Warn) << "\"max_unchanged_interval\" is set to " << MaxUnchangedIntervalLowLimit.count() << " instead of "
×
740
                  << maxUnchangedInterval.count();
×
741
        maxUnchangedInterval = MaxUnchangedIntervalLowLimit;
×
742
    }
743
    handlerConfig->PublishParameters.Set(maxUnchangedInterval.count());
81✔
744

745
    const Json::Value& array = Root["ports"];
81✔
746
    for (Json::Value::ArrayIndex index = 0; index < array.size(); ++index) {
169✔
747
        // old default prefix for compat
748
        LoadPort(handlerConfig,
186✔
749
                 array[index],
750
                 "wb-modbus-" + std::to_string(index) + "-",
201✔
751
                 templates,
752
                 rpcConfig,
753
                 deviceFactory,
754
                 portFactory);
387✔
755
    }
756

757
    CheckDuplicatePorts(*handlerConfig);
76✔
758
    CheckDuplicateDeviceIds(*handlerConfig);
76✔
759

760
    return handlerConfig;
150✔
761
}
762

763
void TPortConfig::AddDevice(PSerialDeviceWithChannels device)
180✔
764
{
765
    // try to find duplicate of this device
766
    for (auto dev: Devices) {
405✔
767
        if (dev->Device->Protocol() == device->Device->Protocol()) {
225✔
768
            if (dev->Device->Protocol()->IsSameSlaveId(dev->Device->DeviceConfig()->SlaveId,
672✔
769
                                                       device->Device->DeviceConfig()->SlaveId))
448✔
770
            {
771
                stringstream ss;
4✔
772
                ss << "id \"" << device->Device->DeviceConfig()->SlaveId << "\" of device \""
2✔
773
                   << device->Device->DeviceConfig()->Name
4✔
774
                   << "\" is already set to device \"" + device->Device->DeviceConfig()->Name + "\"";
6✔
775
                throw TConfigParserException(ss.str());
2✔
776
            }
777
        }
778
    }
779

780
    Devices.push_back(device);
178✔
781
}
178✔
782

783
TDeviceChannelConfig::TDeviceChannelConfig(const std::string& type,
979✔
784
                                           const std::string& deviceId,
785
                                           int order,
786
                                           bool readOnly,
787
                                           const std::string& mqttId,
788
                                           const std::vector<PRegister>& regs)
979✔
789
    : MqttId(mqttId),
790
      Type(type),
791
      DeviceId(deviceId),
792
      Order(order),
793
      ReadOnly(readOnly),
794
      Registers(regs)
979✔
795
{}
979✔
796

797
const std::string& TDeviceChannelConfig::GetName() const
64✔
798
{
799
    auto it = Titles.find("en");
64✔
800
    if (it != Titles.end()) {
64✔
801
        return it->second;
5✔
802
    }
803
    return MqttId;
59✔
804
}
805

806
const TTitleTranslations& TDeviceChannelConfig::GetTitles() const
526✔
807
{
808
    return Titles;
526✔
809
}
810

811
void TDeviceChannelConfig::SetTitle(const std::string& name, const std::string& lang)
13✔
812
{
813
    if (!lang.empty()) {
13✔
814
        Titles[lang] = name;
13✔
815
    }
816
}
13✔
817

818
const std::map<std::string, TTitleTranslations>& TDeviceChannelConfig::GetEnumTitles() const
464✔
819
{
820
    return EnumTitles;
464✔
821
}
822

823
void TDeviceChannelConfig::SetEnumTitles(const std::string& value, const TTitleTranslations& titles)
4✔
824
{
825
    if (!value.empty()) {
4✔
826
        EnumTitles[value] = titles;
4✔
827
    }
828
}
4✔
829

830
TDeviceSetupItemConfig::TDeviceSetupItemConfig(const std::string& name,
108✔
831
                                               PRegisterConfig reg,
832
                                               const std::string& value,
833
                                               const std::string& parameterId)
108✔
834
    : Name(name),
835
      RegisterConfig(reg),
836
      Value(value),
837
      ParameterId(parameterId)
113✔
838
{
839
    try {
840
        RawValue = ConvertToRawValue(*reg, Value);
108✔
841
    } catch (const std::exception& e) {
2✔
842
        throw TConfigParserException("\"" + name + "\" bad value \"" + value + "\"");
1✔
843
    }
844
}
107✔
845

846
const std::string& TDeviceSetupItemConfig::GetName() const
107✔
847
{
848
    return Name;
107✔
849
}
850

851
const std::string& TDeviceSetupItemConfig::GetValue() const
105✔
852
{
853
    return Value;
105✔
854
}
855

856
const std::string& TDeviceSetupItemConfig::GetParameterId() const
105✔
857
{
858
    return ParameterId;
105✔
859
}
860

861
TRegisterValue TDeviceSetupItemConfig::GetRawValue() const
107✔
862
{
863
    return RawValue;
107✔
864
}
865

866
PRegisterConfig TDeviceSetupItemConfig::GetRegisterConfig() const
212✔
867
{
868
    return RegisterConfig;
212✔
869
}
870

871
void THandlerConfig::AddPortConfig(PPortConfig portConfig)
88✔
872
{
873
    PortConfigs.push_back(portConfig);
88✔
874
}
88✔
875

876
TConfigParserException::TConfigParserException(const std::string& message)
6✔
877
    : std::runtime_error("Error parsing config file: " + message)
6✔
878
{}
6✔
879

880
bool IsSubdeviceChannel(const Json::Value& channelSchema)
×
881
{
882
    return (channelSchema.isMember("oneOf") || channelSchema.isMember("device_type"));
×
883
}
884

885
void AppendParams(Json::Value& dst, const Json::Value& src)
19✔
886
{
887
    for (auto it = src.begin(); it != src.end(); ++it) {
1,007✔
888
        dst[it.name()] = *it;
988✔
889
    }
890
}
19✔
891

892
std::string GetProtocolName(const Json::Value& deviceDescription)
38✔
893
{
894
    std::string p;
38✔
895
    Get(deviceDescription, "protocol", p);
38✔
896
    if (p.empty()) {
38✔
897
        p = DefaultProtocol;
27✔
898
    }
899
    return p;
38✔
900
}
901

902
void LoadChannels(TSerialDeviceWithChannels& deviceWithChannels,
181✔
903
                  const Json::Value& deviceData,
904
                  const std::optional<std::chrono::milliseconds>& defaultReadRateLimit,
905
                  const TRegisterTypeMap& typeMap,
906
                  const TLoadingContext& context)
907
{
908
    if (deviceData.isMember("channels")) {
181✔
909
        for (const auto& channel_data: deviceData["channels"]) {
1,154✔
910
            LoadChannel(deviceWithChannels, channel_data, context, typeMap);
974✔
911
        }
912
    }
913

914
    if (deviceWithChannels.Channels.empty()) {
180✔
915
        LOG(Warn) << "device " << deviceWithChannels.Device->DeviceConfig()->Name << " has no channels";
×
916
    } else if (deviceWithChannels.Device->IsSporadicOnly()) {
180✔
917
        LOG(Debug) << "device " << deviceWithChannels.Device->DeviceConfig()->Name
×
918
                   << " has only sporadic channels enabled";
×
919
    }
920

921
    auto readRateLimit = GetReadRateLimit(deviceData);
180✔
922
    if (!readRateLimit) {
180✔
923
        readRateLimit = defaultReadRateLimit;
176✔
924
    }
925
    for (auto channel: deviceWithChannels.Channels) {
1,158✔
926
        for (auto reg: channel->Registers) {
2,012✔
927
            if (!reg->GetConfig()->ReadRateLimit) {
1,034✔
928
                reg->GetConfig()->ReadRateLimit = readRateLimit;
1,030✔
929
            }
930
        }
931
    }
932
}
180✔
933

934
void TSerialDeviceFactory::RegisterProtocol(PProtocol protocol, IDeviceFactory* deviceFactory)
3,456✔
935
{
936
    TDeviceProtocolParams params = {protocol, std::shared_ptr<IDeviceFactory>(deviceFactory)};
3,456✔
937
    Protocols.insert(std::make_pair(protocol->GetName(), params));
3,456✔
938
}
3,456✔
939

940
TDeviceProtocolParams TSerialDeviceFactory::GetProtocolParams(const std::string& protocolName) const
429✔
941
{
942
    auto it = Protocols.find(protocolName);
429✔
943
    if (it == Protocols.end()) {
429✔
944
        throw TSerialDeviceException("unknown protocol: " + protocolName);
×
945
    }
946
    return it->second;
858✔
947
}
948

949
PProtocol TSerialDeviceFactory::GetProtocol(const std::string& protocolName) const
148✔
950
{
951
    return GetProtocolParams(protocolName).protocol;
148✔
952
}
953

954
const std::string& TSerialDeviceFactory::GetCommonDeviceSchemaRef(const std::string& protocolName) const
61✔
955
{
956
    return GetProtocolParams(protocolName).factory->GetCommonDeviceSchemaRef();
61✔
957
}
958

959
const std::string& TSerialDeviceFactory::GetCustomChannelSchemaRef(const std::string& protocolName) const
37✔
960
{
961
    return GetProtocolParams(protocolName).factory->GetCustomChannelSchemaRef();
37✔
962
}
963

964
std::vector<std::string> TSerialDeviceFactory::GetProtocolNames() const
1✔
965
{
966
    std::vector<std::string> res;
1✔
967
    for (const auto& bucket: Protocols) {
25✔
968
        res.emplace_back(bucket.first);
24✔
969
    }
970
    return res;
1✔
971
}
972

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

993
    if (params.IsModbusTcp) {
181✔
994
        if (!GetProtocol(protocolName)->IsModbus()) {
×
995
            throw TSerialDeviceException("Protocol \"" + protocolName + "\" is not compatible with Modbus TCP");
×
996
        }
997
        protocolName += "-tcp";
×
998
    }
999

1000
    TDeviceProtocolParams protocolParams = GetProtocolParams(protocolName);
362✔
1001
    auto deviceConfig = LoadDeviceConfig(*cfg, protocolParams.protocol, loadParams);
362✔
1002
    auto deviceWithChannels = std::make_shared<TSerialDeviceWithChannels>();
181✔
1003
    deviceWithChannels->Device = protocolParams.factory->CreateDevice(*cfg, deviceConfig, protocolParams.protocol);
181✔
1004
    TLoadingContext context(*protocolParams.factory,
181✔
1005
                            protocolParams.factory->GetRegisterAddressFactory().GetBaseRegisterAddress());
362✔
1006
    context.translations = loadParams.Translations;
181✔
1007
    auto regTypes = protocolParams.protocol->GetRegTypes();
362✔
1008
    LoadSetupItems(*deviceWithChannels->Device, *cfg, *regTypes, context);
181✔
1009
    LoadChannels(*deviceWithChannels, *cfg, params.Defaults.ReadRateLimit, *regTypes, context);
181✔
1010

1011
    return deviceWithChannels;
360✔
1012
}
1013

1014
IDeviceFactory::IDeviceFactory(std::unique_ptr<IRegisterAddressFactory> registerAddressFactory,
3,456✔
1015
                               const std::string& commonDeviceSchemaRef,
1016
                               const std::string& customChannelSchemaRef)
3,456✔
1017
    : CommonDeviceSchemaRef(commonDeviceSchemaRef),
1018
      CustomChannelSchemaRef(customChannelSchemaRef),
1019
      RegisterAddressFactory(std::move(registerAddressFactory))
3,456✔
1020
{}
3,456✔
1021

1022
const std::string& IDeviceFactory::GetCommonDeviceSchemaRef() const
61✔
1023
{
1024
    return CommonDeviceSchemaRef;
61✔
1025
}
1026

1027
const std::string& IDeviceFactory::GetCustomChannelSchemaRef() const
37✔
1028
{
1029
    return CustomChannelSchemaRef;
37✔
1030
}
1031

1032
const IRegisterAddressFactory& IDeviceFactory::GetRegisterAddressFactory() const
1,352✔
1033
{
1034
    return *RegisterAddressFactory;
1,352✔
1035
}
1036

1037
PDeviceConfig LoadDeviceConfig(const Json::Value& dev, PProtocol protocol, const TDeviceConfigLoadParams& parameters)
181✔
1038
{
1039
    auto res = std::make_shared<TDeviceConfig>();
181✔
1040

1041
    Get(dev, "device_type", res->DeviceType);
181✔
1042

1043
    res->Id = Read(dev, "id", parameters.Defaults.Id);
181✔
1044
    Get(dev, "name", res->Name);
181✔
1045

1046
    if (dev.isMember("slave_id")) {
181✔
1047
        if (dev["slave_id"].isString())
181✔
1048
            res->SlaveId = dev["slave_id"].asString();
104✔
1049
        else // legacy
1050
            res->SlaveId = std::to_string(dev["slave_id"].asInt());
77✔
1051
    }
1052

1053
    LoadCommonDeviceParameters(*res, dev);
181✔
1054

1055
    if (res->RequestDelay.count() == 0) {
181✔
1056
        res->RequestDelay = parameters.Defaults.RequestDelay;
172✔
1057
    }
1058

1059
    if (parameters.Defaults.ResponseTimeout > res->ResponseTimeout) {
181✔
1060
        res->ResponseTimeout = parameters.Defaults.ResponseTimeout;
7✔
1061
    }
1062
    if (res->ResponseTimeout.count() == -1) {
181✔
1063
        res->ResponseTimeout = DefaultResponseTimeout;
151✔
1064
    }
1065

1066
    return res;
181✔
1067
}
1068

1069
TLoadRegisterConfigResult LoadRegisterConfig(const Json::Value& registerData,
1,154✔
1070
                                             const TRegisterTypeMap& typeMap,
1071
                                             const std::string& readonlyOverrideErrorMessagePrefix,
1072
                                             const IDeviceFactory& factory,
1073
                                             const IRegisterAddress& deviceBaseAddress,
1074
                                             size_t stride)
1075
{
1076
    TLoadRegisterConfigResult res;
1,154✔
1077
    TRegisterType regType = GetRegisterType(registerData, typeMap);
2,308✔
1078
    res.DefaultControlType = regType.DefaultControlType.empty() ? "text" : regType.DefaultControlType;
1,154✔
1079

1080
    if (registerData.isMember("format")) {
1,154✔
1081
        regType.DefaultFormat = RegisterFormatFromName(registerData["format"].asString());
593✔
1082
    }
1083

1084
    if (registerData.isMember("word_order")) {
1,154✔
1085
        regType.DefaultWordOrder = WordOrderFromName(registerData["word_order"].asString());
21✔
1086
    }
1087

1088
    if (registerData.isMember("byte_order")) {
1,154✔
1089
        regType.DefaultByteOrder = ByteOrderFromName(registerData["byte_order"].asString());
20✔
1090
    }
1091

1092
    double scale = Read(registerData, "scale", 1.0); // TBD: check for zero, too
1,154✔
1093
    double offset = Read(registerData, "offset", 0.0);
1,154✔
1094
    double round_to = Read(registerData, "round_to", 0.0);
1,154✔
1095
    TRegisterConfig::TSporadicMode sporadicMode = TRegisterConfig::TSporadicMode::DISABLED;
1,154✔
1096
    if (Read(registerData, "sporadic", false)) {
1,154✔
1097
        sporadicMode = TRegisterConfig::TSporadicMode::ONLY_EVENTS;
×
1098
    }
1099
    if (Read(registerData, "semi-sporadic", false)) {
1,154✔
1100
        sporadicMode = TRegisterConfig::TSporadicMode::EVENTS_AND_POLLING;
×
1101
    }
1102

1103
    bool readonly = ReadChannelsReadonlyProperty(registerData,
1,154✔
1104
                                                 "readonly",
1105
                                                 regType.ReadOnly,
1106
                                                 readonlyOverrideErrorMessagePrefix,
1107
                                                 regType.Name);
2,308✔
1108
    // For compatibility with old configs
1109
    readonly = ReadChannelsReadonlyProperty(registerData,
1,154✔
1110
                                            "channel_readonly",
1111
                                            readonly,
1112
                                            readonlyOverrideErrorMessagePrefix,
1113
                                            regType.Name);
2,308✔
1114

1115
    auto registerDesc =
1116
        factory.GetRegisterAddressFactory().LoadRegisterAddress(registerData,
1,154✔
1117
                                                                deviceBaseAddress,
1118
                                                                stride,
1119
                                                                RegisterFormatByteWidth(regType.DefaultFormat));
2,308✔
1120

1121
    if ((regType.DefaultFormat == RegisterFormat::String || regType.DefaultFormat == RegisterFormat::String8) &&
1,154✔
1122
        registerDesc.DataWidth == 0)
8✔
1123
    {
1124
        throw TConfigParserException(readonlyOverrideErrorMessagePrefix +
×
1125
                                     ": String size is not set for register string format");
×
1126
    }
1127

1128
    res.RegisterConfig = TRegisterConfig::Create(regType.Index,
1,154✔
1129
                                                 registerDesc,
1130
                                                 regType.DefaultFormat,
1131
                                                 scale,
1132
                                                 offset,
1133
                                                 round_to,
1134
                                                 sporadicMode,
1135
                                                 readonly,
1136
                                                 regType.Name,
1137
                                                 regType.DefaultWordOrder,
1138
                                                 regType.DefaultByteOrder);
1,154✔
1139

1140
    if (registerData.isMember("error_value")) {
1,154✔
1141
        res.RegisterConfig->ErrorValue = TRegisterValue{ToUint64(registerData["error_value"], "error_value")};
238✔
1142
    }
1143

1144
    if (registerData.isMember("unsupported_value")) {
1,154✔
1145
        res.RegisterConfig->UnsupportedValue =
10✔
1146
            TRegisterValue{ToUint64(registerData["unsupported_value"], "unsupported_value")};
20✔
1147
    }
1148

1149
    res.RegisterConfig->ReadRateLimit = GetReadRateLimit(registerData);
1,154✔
1150
    res.RegisterConfig->ReadPeriod = GetReadPeriod(registerData);
1,154✔
1151
    return res;
2,308✔
1152
}
1153

1154
void RegisterProtocols(TSerialDeviceFactory& deviceFactory)
143✔
1155
{
1156
    TEnergomeraIecWithFastReadDevice::Register(deviceFactory);
143✔
1157
    TEnergomeraIecModeCDevice::Register(deviceFactory);
143✔
1158
    TIVTMDevice::Register(deviceFactory);
143✔
1159
    TLLSDevice::Register(deviceFactory);
143✔
1160
    TMercury200Device::Register(deviceFactory);
143✔
1161
    TMercury230Device::Register(deviceFactory);
143✔
1162
    TMilurDevice::Register(deviceFactory);
143✔
1163
    TModbusDevice::Register(deviceFactory);
143✔
1164
    TModbusIODevice::Register(deviceFactory);
143✔
1165
    TNevaDevice::Register(deviceFactory);
143✔
1166
    TPulsarDevice::Register(deviceFactory);
143✔
1167
    TS2KDevice::Register(deviceFactory);
143✔
1168
    TUnielDevice::Register(deviceFactory);
143✔
1169
    TDlmsDevice::Register(deviceFactory);
143✔
1170
    Dooya::TDevice::Register(deviceFactory);
143✔
1171
    WinDeco::TDevice::Register(deviceFactory);
143✔
1172
    Somfy::TDevice::Register(deviceFactory);
143✔
1173
    Aok::TDevice::Register(deviceFactory);
143✔
1174
    TIecModeCDevice::Register(deviceFactory);
143✔
1175
    TEnergomeraCeDevice::Register(deviceFactory);
143✔
1176
}
143✔
1177

1178
TRegisterBitsAddress LoadRegisterBitsAddress(const Json::Value& register_data, const std::string& jsonPropertyName)
1,146✔
1179
{
1180
    TRegisterBitsAddress res;
1,146✔
1181
    const auto& addressValue = register_data[jsonPropertyName];
1,146✔
1182
    if (addressValue.isString()) {
1,146✔
1183
        const auto& addressStr = addressValue.asString();
496✔
1184
        auto pos1 = addressStr.find(':');
248✔
1185
        if (pos1 == string::npos) {
248✔
1186
            res.Address = static_cast<uint32_t>(GetUint64(register_data, jsonPropertyName));
218✔
1187
        } else {
1188
            res.Address = GetIntFromString(addressStr.substr(0, pos1), jsonPropertyName);
30✔
1189
            auto pos2 = addressStr.find(':', pos1 + 1);
30✔
1190
            auto bitOffset = stoul(addressStr.substr(pos1 + 1, pos2));
30✔
1191
            if (bitOffset > 255) {
30✔
UNCOV
1192
                throw TConfigParserException(
×
1193
                    "address parsing failed: bit shift must be in range [0, 255] (address string: '" + addressStr +
×
1194
                    "')");
×
1195
            }
1196
            res.BitOffset = bitOffset;
30✔
1197
            if (pos2 != string::npos) {
30✔
1198
                res.BitWidth = stoul(addressStr.substr(pos2 + 1));
30✔
1199
            }
1200
        }
1201
    } else {
1202
        res.Address = static_cast<uint32_t>(GetUint64(register_data, jsonPropertyName));
898✔
1203
    }
1204

1205
    if (register_data.isMember("string_data_size")) {
1,146✔
1206
        res.BitWidth = register_data["string_data_size"].asUInt() * sizeof(char) * 8;
8✔
1207
    }
1208
    return res;
1,146✔
1209
}
1210

1211
TUint32RegisterAddressFactory::TUint32RegisterAddressFactory(size_t bytesPerRegister)
2,741✔
1212
    : BaseRegisterAddress(0),
1213
      BytesPerRegister(bytesPerRegister)
2,741✔
1214
{}
2,741✔
1215

1216
TRegisterDesc TUint32RegisterAddressFactory::LoadRegisterAddress(const Json::Value& regCfg,
1,090✔
1217
                                                                 const IRegisterAddress& deviceBaseAddress,
1218
                                                                 uint32_t stride,
1219
                                                                 uint32_t registerByteWidth) const
1220
{
1221
    TRegisterDesc res;
1,090✔
1222

1223
    if (HasNoEmptyProperty(regCfg, SerialConfig::ADDRESS_PROPERTY_NAME)) {
1,090✔
1224
        auto addr = LoadRegisterBitsAddress(regCfg, SerialConfig::ADDRESS_PROPERTY_NAME);
1,090✔
1225
        res.DataOffset = addr.BitOffset;
1,090✔
1226
        res.DataWidth = addr.BitWidth;
1,090✔
1227
        res.Address = std::shared_ptr<IRegisterAddress>(
1,090✔
1228
            deviceBaseAddress.CalcNewAddress(addr.Address, stride, registerByteWidth, BytesPerRegister));
1,090✔
1229
        res.WriteAddress = res.Address;
1,090✔
1230
    }
1231
    if (HasNoEmptyProperty(regCfg, SerialConfig::WRITE_ADDRESS_PROPERTY_NAME)) {
1,090✔
1232
        auto writeAddress = LoadRegisterBitsAddress(regCfg, SerialConfig::WRITE_ADDRESS_PROPERTY_NAME);
1✔
1233
        res.WriteAddress = std::shared_ptr<IRegisterAddress>(
1✔
1234
            deviceBaseAddress.CalcNewAddress(writeAddress.Address, stride, registerByteWidth, BytesPerRegister));
1✔
1235
    }
1236
    return res;
1,090✔
1237
}
1238

1239
const IRegisterAddress& TUint32RegisterAddressFactory::GetBaseRegisterAddress() const
193✔
1240
{
1241
    return BaseRegisterAddress;
193✔
1242
}
1243

1244
TRegisterDesc TStringRegisterAddressFactory::LoadRegisterAddress(const Json::Value& regCfg,
×
1245
                                                                 const IRegisterAddress& deviceBaseAddress,
1246
                                                                 uint32_t stride,
1247
                                                                 uint32_t registerByteWidth) const
1248
{
1249
    TRegisterDesc res;
×
1250
    if (HasNoEmptyProperty(regCfg, SerialConfig::ADDRESS_PROPERTY_NAME)) {
×
1251
        res.Address = std::make_shared<TStringRegisterAddress>(regCfg[SerialConfig::ADDRESS_PROPERTY_NAME].asString());
×
1252
        res.WriteAddress = res.Address;
×
1253
    }
1254
    if (HasNoEmptyProperty(regCfg, SerialConfig::WRITE_ADDRESS_PROPERTY_NAME)) {
×
1255
        res.WriteAddress =
1256
            std::make_shared<TStringRegisterAddress>(regCfg[SerialConfig::WRITE_ADDRESS_PROPERTY_NAME].asString());
×
1257
    }
1258
    return res;
×
1259
}
1260

1261
const IRegisterAddress& TStringRegisterAddressFactory::GetBaseRegisterAddress() const
4✔
1262
{
1263
    return BaseRegisterAddress;
4✔
1264
}
1265

1266
bool HasNoEmptyProperty(const Json::Value& regCfg, const std::string& propertyName)
2,180✔
1267
{
1268
    return regCfg.isMember(propertyName) &&
3,271✔
1269
           !(regCfg[propertyName].isString() && regCfg[propertyName].asString().empty());
3,271✔
1270
}
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