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

wirenboard / wb-mqtt-serial / 2

29 Dec 2025 12:28PM UTC coverage: 76.817% (+4.0%) from 72.836%
2

Pull #1045

github

54aa0c
pgasheev
up changelog
Pull Request #1045: Fix firmware version in WB-M1W2 template

6873 of 9161 branches covered (75.02%)

12966 of 16879 relevant lines covered (76.82%)

1651.61 hits per line

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

87.83
/src/serial_config.cpp
1
#include "serial_config.h"
2
#include "file_utils.h"
3
#include "json_common.h"
4
#include "log.h"
5

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

15
#include "port/tcp_port.h"
16
#include "port/tcp_port_settings.h"
17

18
#include "port/serial_port.h"
19
#include "port/serial_port_settings.h"
20

21
#include "config_merge_template.h"
22
#include "config_schema_generator.h"
23
#include "old_serial_config.h"
24

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

46
#define LOG(logger) ::logger.Log() << "[serial config] "
47

48
using namespace std;
49
using namespace WBMQTT::JSON;
50

51
namespace
52
{
53
    const char* DefaultProtocol = "modbus";
54

55
    template<class T> T Read(const Json::Value& root, const std::string& key, const T& defaultValue)
28,208✔
56
    {
57
        T value;
10,120✔
58
        if (Get(root, key, value)) {
28,208✔
59
            return value;
4,568✔
60
        }
61
        return defaultValue;
23,640✔
62
    }
63

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

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

85
    uint64_t ToUint64(const Json::Value& v, const string& title)
2,764✔
86
    {
87
        if (v.isUInt()) {
2,764✔
88
            return v.asUInt64();
1,818✔
89
        }
90

91
        if (v.isInt()) {
946✔
92
            auto val = v.asInt64();
×
93
            if (val >= 0) {
×
94
                return val;
×
95
            }
96
        }
97

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

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

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

118
    uint64_t GetUint64(const Json::Value& obj, const std::string& key)
2,250✔
119
    {
120
        return ToUint64(obj[key], key);
2,250✔
121
    }
122

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

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

139
    bool IsSerialNumberChannel(const Json::Value& channel_data)
1,966✔
140
    {
141
        const std::vector<std::string> serialNames{"Serial", "serial_number", "Serial NO"};
11,796✔
142
        return serialNames.end() !=
3,932✔
143
               std::find(serialNames.begin(), serialNames.end(), channel_data.get("name", std::string()).asString());
5,898✔
144
    }
145

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

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

181
    std::optional<std::chrono::milliseconds> GetReadRateLimit(const Json::Value& data)
2,918✔
182
    {
183
        std::chrono::milliseconds res(-1);
2,918✔
184
        Get(data, "read_rate_limit_ms", res);
2,918✔
185
        if (res < 0ms) {
2,918✔
186
            return std::nullopt;
2,894✔
187
        }
188
        return std::make_optional(res);
24✔
189
    }
190

191
    std::optional<std::chrono::milliseconds> GetReadPeriod(const Json::Value& data)
2,368✔
192
    {
193
        std::chrono::milliseconds res(-1);
2,368✔
194
        Get(data, "read_period_ms", res);
2,368✔
195
        if (res < 0ms) {
2,368✔
196
            return std::nullopt;
2,324✔
197
        }
198
        return std::make_optional(res);
44✔
199
    }
200

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

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

214
        TLoadingContext(const IDeviceFactory& f, const IRegisterAddress& base_address)
378✔
215
            : factory(f),
378✔
216
              device_base_address(base_address)
378✔
217
        {}
378✔
218
    };
219

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

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

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

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

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

303
            auto read_rate_limit_ms = GetReadRateLimit(channel_data);
56✔
304
            auto read_period = GetReadPeriod(channel_data);
56✔
305

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

341
        std::string type_str(Read(channel_data, "type", default_type_str));
5,886✔
342
        if (type_str == "wo-switch" || type_str == "pushbutton") {
1,962✔
343
            if (type_str == "wo-switch") {
×
344
                type_str = "switch";
×
345
            }
346
            for (auto& reg: registers) {
×
347
                reg->GetConfig()->AccessType = TRegisterConfig::EAccessType::WRITE_ONLY;
×
348
            }
349
        }
350

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

360
        for (const auto& it: Translate(channel_data["name"].asString(), idIsDefined, context)) {
1,988✔
361
            channel->SetTitle(it.second, it.first);
26✔
362
        }
363

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

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

397
        if (registers.size() == 1) {
1,962✔
398
            channel->Precision = registers[0]->GetConfig()->RoundTo;
1,906✔
399
        }
400

401
        Get(channel_data, "units", channel->Units);
1,962✔
402

403
        if (IsSerialNumberChannel(channel_data) && registers.size()) {
1,962✔
404
            deviceWithChannels.Device->SetSnRegister(registers[0]->GetConfig());
4✔
405
        }
406

407
        deviceWithChannels.Channels.push_back(channel);
1,962✔
408
    }
409

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

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

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

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

439
        newContext.mqtt_prefix = name;
14✔
440
        bool idIsDefined = false;
14✔
441
        if (channel_data.isMember("id")) {
14✔
442
            newContext.mqtt_prefix = channel_data["id"].asString();
6✔
443
            idIsDefined = true;
6✔
444
        }
445

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

452
        if (!context.mqtt_prefix.empty()) {
14✔
453
            if (newContext.mqtt_prefix.empty()) {
4✔
454
                newContext.mqtt_prefix = context.mqtt_prefix;
×
455
            } else {
456
                newContext.mqtt_prefix = context.mqtt_prefix + " " + newContext.mqtt_prefix;
4✔
457
            }
458
        }
459

460
        newContext.stride = Read(channel_data, "stride", 0);
14✔
461
        LoadSetupItems(*deviceWithChannels.Device, channel_data, typeMap, newContext);
14✔
462

463
        if (channel_data.isMember("channels")) {
12✔
464
            for (const auto& ch: channel_data["channels"]) {
40✔
465
                LoadChannel(deviceWithChannels, ch, newContext, typeMap);
28✔
466
            }
467
        }
468
    }
12✔
469

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

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

519
    void LoadSetupItems(TSerialDevice& device,
378✔
520
                        const Json::Value& device_data,
521
                        const TRegisterTypeMap& typeMap,
522
                        const TLoadingContext& context)
523
    {
524
        if (device_data.isMember("setup")) {
378✔
525
            for (const auto& setupItem: device_data["setup"])
302✔
526
                LoadSetupItem(device, setupItem, typeMap, context);
204✔
527
        }
528
    }
376✔
529

530
    void LoadCommonDeviceParameters(TDeviceConfig& device_config, const Json::Value& device_data, bool isWBDevice)
364✔
531
    {
532
        if (device_data.isMember("password")) {
364✔
533
            device_config.Password.clear();
4✔
534
            for (const auto& passwordItem: device_data["password"]) {
22✔
535
                device_config.Password.push_back(static_cast<uint8_t>(ToUint64(passwordItem, "password item")));
18✔
536
            }
537
        }
538

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

543
        if (isWBDevice || device_data["enable_wb_continuous_read"].asBool()) {
364✔
544
            device_config.MaxWriteRegisters = Modbus::MAX_WRITE_REGISTERS;
4✔
545
        }
546

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

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

576
        TSerialDeviceFactory::TCreateDeviceParams params;
378✔
577
        params.Defaults.Id = default_id;
368✔
578
        params.Defaults.RequestDelay = port_config->RequestDelay;
368✔
579
        params.Defaults.ReadRateLimit = port_config->ReadRateLimit;
368✔
580
        params.IsModbusTcp = port_config->Port->IsModbusTcp();
368✔
581
        port_config->AddDevice(deviceFactory.CreateDevice(device_data, params, templates));
372✔
582
    }
583

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

588
        Get(port_data, "baud_rate", settings.BaudRate);
34✔
589

590
        if (port_data.isMember("parity"))
34✔
591
            settings.Parity = port_data["parity"].asCString()[0];
20✔
592

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

596
        auto port = std::make_shared<TSerialPort>(settings);
34✔
597

598
        rpcConfig->AddSerialPort(settings);
34✔
599

600
        return std::make_shared<TFeaturePort>(port, false);
102✔
601
    }
602

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

607
        auto port = std::make_shared<TTcpPort>(settings);
8✔
608

609
        rpcConfig->AddTCPPort(settings);
8✔
610

611
        return std::make_shared<TFeaturePort>(port, false);
24✔
612
    }
613

614
    PFeaturePort OpenModbusTcpPort(const Json::Value& port_data, PRPCConfig rpcConfig)
×
615
    {
616
        TTcpPortSettings settings(port_data["address"].asString(), port_data["port"].asUInt());
×
617

618
        auto port = std::make_shared<TTcpPort>(settings);
×
619

620
        rpcConfig->AddModbusTCPPort(settings);
×
621

622
        return std::make_shared<TFeaturePort>(port, true, port_data["connected_to_mge"].asBool());
×
623
    }
624

625
    void LoadPort(PHandlerConfig handlerConfig,
188✔
626
                  const Json::Value& port_data,
627
                  const std::string& id_prefix,
628
                  TTemplateMap& templates,
629
                  PRPCConfig rpcConfig,
630
                  TSerialDeviceFactory& deviceFactory,
631
                  TPortFactoryFn portFactory)
632
    {
633
        if (port_data.isMember("enabled") && !port_data["enabled"].asBool())
188✔
634
            return;
×
635

636
        auto port_config = make_shared<TPortConfig>();
376✔
637

638
        Get(port_data, "guard_interval_us", port_config->RequestDelay);
188✔
639
        port_config->ReadRateLimit = GetReadRateLimit(port_data);
188✔
640

641
        auto port_type = port_data.get("port_type", "serial").asString();
386✔
642

643
        Get(port_data, "connection_timeout_ms", port_config->OpenCloseSettings.MaxFailTime);
188✔
644
        Get(port_data, "connection_max_fail_cycles", port_config->OpenCloseSettings.ConnectionMaxFailCycles);
188✔
645

646
        port_config->Port = portFactory(port_data, rpcConfig);
188✔
647

648
        std::chrono::milliseconds responseTimeout = RESPONSE_TIMEOUT_NOT_SET;
188✔
649
        Get(port_data, "response_timeout_ms", responseTimeout);
188✔
650
        port_config->Port->SetMinimalResponseTimeout(responseTimeout);
188✔
651

652
        const Json::Value& array = port_data["devices"];
188✔
653
        for (Json::Value::ArrayIndex index = 0; index < array.size(); ++index)
546✔
654
            LoadDevice(port_config, array[index], id_prefix + std::to_string(index), templates, deviceFactory);
398✔
655

656
        handlerConfig->AddPortConfig(port_config);
178✔
657
    }
658
}
659

660
std::string DecorateIfNotEmpty(const std::string& prefix, const std::string& str, const std::string& postfix)
144✔
661
{
662
    if (str.empty()) {
144✔
663
        return std::string();
×
664
    }
665
    return prefix + str + postfix;
288✔
666
}
667

668
void SetIfExists(Json::Value& dst, const std::string& dstKey, const Json::Value& src, const std::string& srcKey)
288✔
669
{
670
    if (src.isMember(srcKey)) {
288✔
671
        dst[dstKey] = src[srcKey];
88✔
672
    }
673
}
288✔
674

675
PFeaturePort DefaultPortFactory(const Json::Value& port_data, PRPCConfig rpcConfig)
42✔
676
{
677
    auto port_type = port_data.get("port_type", "serial").asString();
126✔
678
    if (port_type == "serial") {
42✔
679
        return OpenSerialPort(port_data, rpcConfig);
34✔
680
    }
681
    if (port_type == "tcp") {
8✔
682
        return OpenTcpPort(port_data, rpcConfig);
8✔
683
    }
684
    if (port_type == "modbus tcp") {
×
685
        return OpenModbusTcpPort(port_data, rpcConfig);
×
686
    }
687
    throw TConfigParserException("invalid port_type: '" + port_type + "'");
×
688
}
689

690
Json::Value LoadConfigTemplatesSchema(const std::string& templateSchemaFileName, const Json::Value& commonDeviceSchema)
38✔
691
{
692
    Json::Value schema = WBMQTT::JSON::Parse(templateSchemaFileName);
38✔
693
    AppendParams(schema["definitions"], commonDeviceSchema["definitions"]);
38✔
694
    return schema;
38✔
695
}
696

697
void AddRegisterType(Json::Value& configSchema, const std::string& registerType)
38✔
698
{
699
    configSchema["definitions"]["reg_type"]["enum"].append(registerType);
38✔
700
}
38✔
701

702
void CheckDuplicatePorts(const THandlerConfig& handlerConfig)
154✔
703
{
704
    std::unordered_set<std::string> paths;
308✔
705
    for (const auto& port: handlerConfig.PortConfigs) {
332✔
706
        if (!paths.insert(port->Port->GetDescription(false)).second) {
178✔
707
            throw TConfigParserException("Duplicate port: " + port->Port->GetDescription(false));
×
708
        }
709
    }
710
}
154✔
711

712
void CheckDuplicateDeviceIds(const THandlerConfig& handlerConfig)
154✔
713
{
714
    std::unordered_set<std::string> ids;
308✔
715
    for (const auto& port: handlerConfig.PortConfigs) {
328✔
716
        for (const auto& device: port->Devices) {
526✔
717
            if (!ids.insert(device->Device->DeviceConfig()->Id).second) {
352✔
718
                throw TConfigParserException(
2✔
719
                    "Duplicate MQTT device id: " + device->Device->DeviceConfig()->Id +
4✔
720
                    ", set device MQTT ID explicitly to fix (see https://wb.wiki/serial-id-collision)");
6✔
721
            }
722
        }
723
    }
724
}
152✔
725

726
PHandlerConfig LoadConfig(const std::string& configFileName,
180✔
727
                          TSerialDeviceFactory& deviceFactory,
728
                          const Json::Value& commonDeviceSchema,
729
                          TTemplateMap& templates,
730
                          PRPCConfig rpcConfig,
731
                          const Json::Value& portsSchema,
732
                          TProtocolConfedSchemasMap& protocolSchemas,
733
                          TPortFactoryFn portFactory)
734
{
735
    PHandlerConfig handlerConfig(new THandlerConfig);
180✔
736
    Json::Value Root(Parse(configFileName));
360✔
737
    FixOldConfigFormat(Root, templates);
180✔
738

739
    try {
740
        ValidateConfig(Root, deviceFactory, commonDeviceSchema, portsSchema, templates, protocolSchemas);
180✔
741
    } catch (const std::runtime_error& e) {
32✔
742
        throw std::runtime_error("File: " + configFileName + " error: " + e.what());
16✔
743
    }
744

745
    // wb6 - single core - max 100 registers per second
746
    // wb7 - 4 cores - max 800 registers per second
747
    handlerConfig->LowPriorityRegistersRateLimit = (1 == get_nprocs_conf()) ? 100 : 800;
164✔
748
    Get(Root, "rate_limit", handlerConfig->LowPriorityRegistersRateLimit);
164✔
749

750
    Get(Root, "debug", handlerConfig->Debug);
164✔
751

752
    auto maxUnchangedInterval = DefaultMaxUnchangedInterval;
164✔
753
    Get(Root, "max_unchanged_interval", maxUnchangedInterval);
164✔
754
    if (maxUnchangedInterval.count() > 0 && maxUnchangedInterval < MaxUnchangedIntervalLowLimit) {
164✔
755
        LOG(Warn) << "\"max_unchanged_interval\" is set to " << MaxUnchangedIntervalLowLimit.count() << " instead of "
×
756
                  << maxUnchangedInterval.count();
×
757
        maxUnchangedInterval = MaxUnchangedIntervalLowLimit;
×
758
    }
759
    handlerConfig->PublishParameters.Set(maxUnchangedInterval.count());
164✔
760

761
    const Json::Value& array = Root["ports"];
164✔
762
    for (Json::Value::ArrayIndex index = 0; index < array.size(); ++index) {
342✔
763
        // old default prefix for compat
764
        LoadPort(handlerConfig,
376✔
765
                 array[index],
766
                 "wb-modbus-" + std::to_string(index) + "-",
406✔
767
                 templates,
768
                 rpcConfig,
769
                 deviceFactory,
770
                 portFactory);
782✔
771
    }
772

773
    CheckDuplicatePorts(*handlerConfig);
154✔
774
    CheckDuplicateDeviceIds(*handlerConfig);
154✔
775

776
    return handlerConfig;
304✔
777
}
778

779
void TPortConfig::AddDevice(PSerialDeviceWithChannels device)
362✔
780
{
781
    // try to find duplicate of this device
782
    for (auto dev: Devices) {
812✔
783
        if (dev->Device->Protocol() == device->Device->Protocol()) {
450✔
784
            if (dev->Device->Protocol()->IsSameSlaveId(dev->Device->DeviceConfig()->SlaveId,
1,344✔
785
                                                       device->Device->DeviceConfig()->SlaveId))
896✔
786
            {
787
                stringstream ss;
8✔
788
                ss << "id \"" << device->Device->DeviceConfig()->SlaveId << "\" of device \""
4✔
789
                   << device->Device->DeviceConfig()->Name
8✔
790
                   << "\" is already set to device \"" + device->Device->DeviceConfig()->Name + "\"";
12✔
791
                throw TConfigParserException(ss.str());
4✔
792
            }
793
        }
794
    }
795

796
    Devices.push_back(device);
358✔
797
}
358✔
798

799
TDeviceChannelConfig::TDeviceChannelConfig(const std::string& type,
1,962✔
800
                                           const std::string& deviceId,
801
                                           int order,
802
                                           bool readOnly,
803
                                           const std::string& mqttId,
804
                                           const std::vector<PRegister>& regs)
1,962✔
805
    : MqttId(mqttId),
806
      Type(type),
807
      DeviceId(deviceId),
808
      Order(order),
809
      ReadOnly(readOnly),
810
      Registers(regs)
1,962✔
811
{}
1,962✔
812

813
const std::string& TDeviceChannelConfig::GetName() const
128✔
814
{
815
    auto it = Titles.find("en");
128✔
816
    if (it != Titles.end()) {
128✔
817
        return it->second;
10✔
818
    }
819
    return MqttId;
118✔
820
}
821

822
const TTitleTranslations& TDeviceChannelConfig::GetTitles() const
1,056✔
823
{
824
    return Titles;
1,056✔
825
}
826

827
void TDeviceChannelConfig::SetTitle(const std::string& name, const std::string& lang)
26✔
828
{
829
    if (!lang.empty()) {
26✔
830
        Titles[lang] = name;
26✔
831
    }
832
}
26✔
833

834
const std::map<std::string, TTitleTranslations>& TDeviceChannelConfig::GetEnumTitles() const
932✔
835
{
836
    return EnumTitles;
932✔
837
}
838

839
void TDeviceChannelConfig::SetEnumTitles(const std::string& value, const TTitleTranslations& titles)
8✔
840
{
841
    if (!value.empty()) {
8✔
842
        EnumTitles[value] = titles;
8✔
843
    }
844
}
8✔
845

846
TDeviceSetupItemConfig::TDeviceSetupItemConfig(const std::string& name,
216✔
847
                                               PRegisterConfig reg,
848
                                               const std::string& value,
849
                                               const std::string& parameterId)
216✔
850
    : Name(name),
851
      RegisterConfig(reg),
852
      Value(value),
853
      ParameterId(parameterId)
226✔
854
{
855
    try {
856
        RawValue = ConvertToRawValue(*reg, Value);
216✔
857
    } catch (const std::exception& e) {
4✔
858
        throw TConfigParserException("\"" + name + "\" bad value \"" + value + "\"");
2✔
859
    }
860
}
214✔
861

862
const std::string& TDeviceSetupItemConfig::GetName() const
214✔
863
{
864
    return Name;
214✔
865
}
866

867
const std::string& TDeviceSetupItemConfig::GetValue() const
210✔
868
{
869
    return Value;
210✔
870
}
871

872
const std::string& TDeviceSetupItemConfig::GetParameterId() const
210✔
873
{
874
    return ParameterId;
210✔
875
}
876

877
TRegisterValue TDeviceSetupItemConfig::GetRawValue() const
214✔
878
{
879
    return RawValue;
214✔
880
}
881

882
PRegisterConfig TDeviceSetupItemConfig::GetRegisterConfig() const
424✔
883
{
884
    return RegisterConfig;
424✔
885
}
886

887
void THandlerConfig::AddPortConfig(PPortConfig portConfig)
178✔
888
{
889
    PortConfigs.push_back(portConfig);
178✔
890
}
178✔
891

892
TConfigParserException::TConfigParserException(const std::string& message)
12✔
893
    : std::runtime_error("Error parsing config file: " + message)
12✔
894
{}
12✔
895

896
bool IsSubdeviceChannel(const Json::Value& channelSchema)
×
897
{
898
    return (channelSchema.isMember("oneOf") || channelSchema.isMember("device_type"));
×
899
}
900

901
std::string GetProtocolName(const Json::Value& deviceDescription)
76✔
902
{
903
    std::string p;
76✔
904
    Get(deviceDescription, "protocol", p);
76✔
905
    if (p.empty()) {
76✔
906
        p = DefaultProtocol;
54✔
907
    }
908
    return p;
76✔
909
}
910

911
void LoadChannels(TSerialDeviceWithChannels& deviceWithChannels,
364✔
912
                  const Json::Value& deviceData,
913
                  const std::optional<std::chrono::milliseconds>& defaultReadRateLimit,
914
                  const TRegisterTypeMap& typeMap,
915
                  const TLoadingContext& context)
916
{
917
    if (deviceData.isMember("channels")) {
364✔
918
        for (const auto& channel_data: deviceData["channels"]) {
2,314✔
919
            LoadChannel(deviceWithChannels, channel_data, context, typeMap);
1,952✔
920
        }
921
    }
922

923
    if (deviceWithChannels.Channels.empty()) {
362✔
924
        LOG(Warn) << "device " << deviceWithChannels.Device->DeviceConfig()->Name << " has no channels";
×
925
    } else if (deviceWithChannels.Device->IsSporadicOnly()) {
362✔
926
        LOG(Debug) << "device " << deviceWithChannels.Device->DeviceConfig()->Name
×
927
                   << " has only sporadic channels enabled";
×
928
    }
929

930
    auto readRateLimit = GetReadRateLimit(deviceData);
362✔
931
    if (!readRateLimit) {
362✔
932
        readRateLimit = defaultReadRateLimit;
354✔
933
    }
934
    for (auto channel: deviceWithChannels.Channels) {
2,322✔
935
        for (auto reg: channel->Registers) {
4,032✔
936
            if (!reg->GetConfig()->ReadRateLimit) {
2,072✔
937
                reg->GetConfig()->ReadRateLimit = readRateLimit;
2,064✔
938
            }
939
        }
940
    }
941
}
362✔
942

943
void TSerialDeviceFactory::RegisterProtocol(PProtocol protocol, IDeviceFactory* deviceFactory)
6,976✔
944
{
945
    TDeviceProtocolParams params = {protocol, std::shared_ptr<IDeviceFactory>(deviceFactory)};
6,976✔
946
    Protocols.insert(std::make_pair(protocol->GetName(), params));
6,976✔
947
}
6,976✔
948

949
TDeviceProtocolParams TSerialDeviceFactory::GetProtocolParams(const std::string& protocolName) const
868✔
950
{
951
    auto it = Protocols.find(protocolName);
868✔
952
    if (it == Protocols.end()) {
868✔
953
        throw TSerialDeviceException("unknown protocol: " + protocolName);
×
954
    }
955
    return it->second;
1,736✔
956
}
957

958
PProtocol TSerialDeviceFactory::GetProtocol(const std::string& protocolName) const
304✔
959
{
960
    return GetProtocolParams(protocolName).protocol;
304✔
961
}
962

963
const std::string& TSerialDeviceFactory::GetCommonDeviceSchemaRef(const std::string& protocolName) const
122✔
964
{
965
    return GetProtocolParams(protocolName).factory->GetCommonDeviceSchemaRef();
122✔
966
}
967

968
const std::string& TSerialDeviceFactory::GetCustomChannelSchemaRef(const std::string& protocolName) const
74✔
969
{
970
    return GetProtocolParams(protocolName).factory->GetCustomChannelSchemaRef();
74✔
971
}
972

973
std::vector<std::string> TSerialDeviceFactory::GetProtocolNames() const
2✔
974
{
975
    std::vector<std::string> res;
2✔
976
    for (const auto& bucket: Protocols) {
50✔
977
        res.emplace_back(bucket.first);
48✔
978
    }
979
    return res;
2✔
980
}
981

982
PSerialDeviceWithChannels TSerialDeviceFactory::CreateDevice(const Json::Value& deviceConfigJson,
368✔
983
                                                             const TCreateDeviceParams& params,
984
                                                             TTemplateMap& templates)
985
{
986
    TDeviceConfigLoadParams loadParams;
736✔
987
    loadParams.Defaults = params.Defaults;
368✔
988
    const auto* cfg = &deviceConfigJson;
368✔
989
    unique_ptr<Json::Value> mergedConfig;
368✔
990
    auto isWBDevice = false;
368✔
991
    if (deviceConfigJson.isMember("device_type")) {
368✔
992
        auto deviceType = deviceConfigJson["device_type"].asString();
120✔
993
        auto deviceTemplate = templates.GetTemplate(deviceType);
64✔
994
        loadParams.DeviceTemplateTitle = deviceTemplate->GetTitle();
60✔
995
        mergedConfig = std::make_unique<Json::Value>(
56✔
996
            MergeDeviceConfigWithTemplate(deviceConfigJson, deviceType, deviceTemplate->GetTemplate()));
116✔
997
        cfg = mergedConfig.get();
56✔
998
        loadParams.Translations = &deviceTemplate->GetTemplate()["translations"];
56✔
999
        isWBDevice = !deviceTemplate->GetHardware().empty();
56✔
1000
    }
1001
    std::string protocolName = DefaultProtocol;
728✔
1002
    Get(*cfg, "protocol", protocolName);
364✔
1003

1004
    if (params.IsModbusTcp) {
364✔
1005
        if (!GetProtocol(protocolName)->IsModbus()) {
×
1006
            throw TSerialDeviceException("Protocol \"" + protocolName + "\" is not compatible with Modbus TCP");
×
1007
        }
1008
        protocolName += "-tcp";
×
1009
    }
1010

1011
    TDeviceProtocolParams protocolParams = GetProtocolParams(protocolName);
728✔
1012
    auto deviceConfig = LoadDeviceConfig(*cfg, protocolParams.protocol, loadParams, isWBDevice);
728✔
1013
    auto deviceWithChannels = std::make_shared<TSerialDeviceWithChannels>();
364✔
1014
    deviceWithChannels->Device = protocolParams.factory->CreateDevice(*cfg, deviceConfig, protocolParams.protocol);
364✔
1015
    TLoadingContext context(*protocolParams.factory,
364✔
1016
                            protocolParams.factory->GetRegisterAddressFactory().GetBaseRegisterAddress());
728✔
1017
    context.translations = loadParams.Translations;
364✔
1018
    auto regTypes = protocolParams.protocol->GetRegTypes();
728✔
1019
    LoadSetupItems(*deviceWithChannels->Device, *cfg, *regTypes, context);
364✔
1020
    LoadChannels(*deviceWithChannels, *cfg, params.Defaults.ReadRateLimit, *regTypes, context);
364✔
1021

1022
    return deviceWithChannels;
724✔
1023
}
1024

1025
IDeviceFactory::IDeviceFactory(std::unique_ptr<IRegisterAddressFactory> registerAddressFactory,
6,976✔
1026
                               const std::string& commonDeviceSchemaRef,
1027
                               const std::string& customChannelSchemaRef)
6,976✔
1028
    : CommonDeviceSchemaRef(commonDeviceSchemaRef),
1029
      CustomChannelSchemaRef(customChannelSchemaRef),
1030
      RegisterAddressFactory(std::move(registerAddressFactory))
6,976✔
1031
{}
6,976✔
1032

1033
const std::string& IDeviceFactory::GetCommonDeviceSchemaRef() const
122✔
1034
{
1035
    return CommonDeviceSchemaRef;
122✔
1036
}
1037

1038
const std::string& IDeviceFactory::GetCustomChannelSchemaRef() const
74✔
1039
{
1040
    return CustomChannelSchemaRef;
74✔
1041
}
1042

1043
const IRegisterAddressFactory& IDeviceFactory::GetRegisterAddressFactory() const
2,710✔
1044
{
1045
    return *RegisterAddressFactory;
2,710✔
1046
}
1047

1048
PDeviceConfig LoadDeviceConfig(const Json::Value& dev,
364✔
1049
                               PProtocol protocol,
1050
                               const TDeviceConfigLoadParams& parameters,
1051
                               bool isWBDevice)
1052
{
1053
    auto res = std::make_shared<TDeviceConfig>();
364✔
1054

1055
    Get(dev, "device_type", res->DeviceType);
364✔
1056

1057
    res->Id = Read(dev, "id", parameters.Defaults.Id);
364✔
1058
    Get(dev, "name", res->Name);
364✔
1059

1060
    if (dev.isMember("slave_id")) {
364✔
1061
        res->SlaveId = dev["slave_id"].asString();
364✔
1062
    }
1063

1064
    LoadCommonDeviceParameters(*res, dev, isWBDevice);
364✔
1065

1066
    if (res->RequestDelay.count() == 0) {
364✔
1067
        res->RequestDelay = parameters.Defaults.RequestDelay;
346✔
1068
    }
1069

1070
    return res;
364✔
1071
}
1072

1073
TLoadRegisterConfigResult LoadRegisterConfig(const Json::Value& registerData,
2,312✔
1074
                                             const TRegisterTypeMap& typeMap,
1075
                                             const std::string& readonlyOverrideErrorMessagePrefix,
1076
                                             const IDeviceFactory& factory,
1077
                                             const IRegisterAddress& deviceBaseAddress,
1078
                                             size_t stride)
1079
{
1080
    TLoadRegisterConfigResult res;
2,312✔
1081
    TRegisterType regType = GetRegisterType(registerData, typeMap);
4,624✔
1082
    res.DefaultControlType = regType.DefaultControlType.empty() ? "text" : regType.DefaultControlType;
2,312✔
1083

1084
    if (registerData.isMember("format")) {
2,312✔
1085
        regType.DefaultFormat = RegisterFormatFromName(registerData["format"].asString());
1,190✔
1086
    }
1087

1088
    if (registerData.isMember("word_order")) {
2,312✔
1089
        regType.DefaultWordOrder = WordOrderFromName(registerData["word_order"].asString());
42✔
1090
    }
1091

1092
    if (registerData.isMember("byte_order")) {
2,312✔
1093
        regType.DefaultByteOrder = ByteOrderFromName(registerData["byte_order"].asString());
40✔
1094
    }
1095

1096
    double scale = Read(registerData, "scale", 1.0); // TBD: check for zero, too
2,312✔
1097
    double offset = Read(registerData, "offset", 0.0);
2,312✔
1098
    double round_to = Read(registerData, "round_to", 0.0);
2,312✔
1099
    TRegisterConfig::TSporadicMode sporadicMode = TRegisterConfig::TSporadicMode::DISABLED;
2,312✔
1100
    if (Read(registerData, "sporadic", false)) {
2,312✔
1101
        sporadicMode = TRegisterConfig::TSporadicMode::ONLY_EVENTS;
2✔
1102
    }
1103
    if (Read(registerData, "semi-sporadic", false)) {
2,312✔
1104
        sporadicMode = TRegisterConfig::TSporadicMode::EVENTS_AND_POLLING;
×
1105
    }
1106

1107
    bool readonly = ReadChannelsReadonlyProperty(registerData,
2,312✔
1108
                                                 "readonly",
1109
                                                 regType.ReadOnly,
1110
                                                 readonlyOverrideErrorMessagePrefix,
1111
                                                 regType.Name);
4,624✔
1112
    // For compatibility with old configs
1113
    readonly = ReadChannelsReadonlyProperty(registerData,
2,312✔
1114
                                            "channel_readonly",
1115
                                            readonly,
1116
                                            readonlyOverrideErrorMessagePrefix,
1117
                                            regType.Name);
4,624✔
1118

1119
    auto registerDesc =
1120
        factory.GetRegisterAddressFactory().LoadRegisterAddress(registerData,
2,312✔
1121
                                                                deviceBaseAddress,
1122
                                                                stride,
1123
                                                                RegisterFormatByteWidth(regType.DefaultFormat));
4,624✔
1124

1125
    if ((regType.DefaultFormat == RegisterFormat::String || regType.DefaultFormat == RegisterFormat::String8) &&
2,312✔
1126
        registerDesc.DataWidth == 0)
16✔
1127
    {
1128
        throw TConfigParserException(readonlyOverrideErrorMessagePrefix +
×
1129
                                     ": String size is not set for register string format");
×
1130
    }
1131

1132
    res.RegisterConfig = TRegisterConfig::Create(regType.Index,
2,312✔
1133
                                                 registerDesc,
1134
                                                 regType.DefaultFormat,
1135
                                                 scale,
1136
                                                 offset,
1137
                                                 round_to,
1138
                                                 sporadicMode,
1139
                                                 readonly,
1140
                                                 regType.Name,
1141
                                                 regType.DefaultWordOrder,
1142
                                                 regType.DefaultByteOrder);
2,312✔
1143

1144
    if (registerData.isMember("error_value")) {
2,312✔
1145
        res.RegisterConfig->ErrorValue = TRegisterValue{ToUint64(registerData["error_value"], "error_value")};
476✔
1146
    }
1147

1148
    if (registerData.isMember("unsupported_value")) {
2,312✔
1149
        res.RegisterConfig->UnsupportedValue =
20✔
1150
            TRegisterValue{ToUint64(registerData["unsupported_value"], "unsupported_value")};
40✔
1151
    }
1152

1153
    res.RegisterConfig->ReadRateLimit = GetReadRateLimit(registerData);
2,312✔
1154
    res.RegisterConfig->ReadPeriod = GetReadPeriod(registerData);
2,312✔
1155
    return res;
4,624✔
1156
}
1157

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

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

1209
    if (register_data.isMember("string_data_size")) {
2,296✔
1210
        res.BitWidth = register_data["string_data_size"].asUInt() * sizeof(char) * 8;
16✔
1211
    }
1212
    return res;
2,296✔
1213
}
1214

1215
TUint32RegisterAddressFactory::TUint32RegisterAddressFactory(size_t bytesPerRegister)
5,536✔
1216
    : BaseRegisterAddress(0),
1217
      BytesPerRegister(bytesPerRegister)
5,536✔
1218
{}
5,536✔
1219

1220
TRegisterDesc TUint32RegisterAddressFactory::LoadRegisterAddress(const Json::Value& regCfg,
2,184✔
1221
                                                                 const IRegisterAddress& deviceBaseAddress,
1222
                                                                 uint32_t stride,
1223
                                                                 uint32_t registerByteWidth) const
1224
{
1225
    TRegisterDesc res;
2,184✔
1226

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

1243
const IRegisterAddress& TUint32RegisterAddressFactory::GetBaseRegisterAddress() const
388✔
1244
{
1245
    return BaseRegisterAddress;
388✔
1246
}
1247

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

1265
const IRegisterAddress& TStringRegisterAddressFactory::GetBaseRegisterAddress() const
8✔
1266
{
1267
    return BaseRegisterAddress;
8✔
1268
}
1269

1270
bool HasNoEmptyProperty(const Json::Value& regCfg, const std::string& propertyName)
4,368✔
1271
{
1272
    return regCfg.isMember(propertyName) &&
6,554✔
1273
           !(regCfg[propertyName].isString() && regCfg[propertyName].asString().empty());
6,554✔
1274
}
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