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

wirenboard / wb-mqtt-serial / 6

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

push

github

web-flow
Remove unnecessary files

6860 of 9109 branches covered (75.31%)

12949 of 16839 relevant lines covered (76.9%)

749.85 hits per line

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

87.75
/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 "config_merge_template.h"
16
#include "config_schema_generator.h"
17
#include "old_serial_config.h"
18

19
#include "devices/modbus_device.h"
20

21
#ifndef __EMSCRIPTEN__
22
#include "port/tcp_port.h"
23
#include "port/tcp_port_settings.h"
24

25
#include "port/serial_port.h"
26
#include "port/serial_port_settings.h"
27

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

49
#define LOG(logger) ::logger.Log() << "[serial config] "
50

51
using namespace std;
52
using namespace WBMQTT::JSON;
53

54
namespace
55
{
56
    const char* DefaultProtocol = "modbus";
57

58
    template<class T> T Read(const Json::Value& root, const std::string& key, const T& defaultValue)
14,104✔
59
    {
60
        T value;
5,060✔
61
        if (Get(root, key, value)) {
14,104✔
62
            return value;
2,284✔
63
        }
64
        return defaultValue;
11,820✔
65
    }
66

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

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

88
    uint64_t ToUint64(const Json::Value& v, const string& title)
1,382✔
89
    {
90
        if (v.isUInt()) {
1,382✔
91
            return v.asUInt64();
909✔
92
        }
93

94
        if (v.isInt()) {
473✔
95
            auto val = v.asInt64();
×
96
            if (val >= 0) {
×
97
                return val;
×
98
            }
99
        }
100

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

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

116
    double GetDouble(const Json::Value& obj, const std::string& key)
99✔
117
    {
118
        return ToDouble(obj[key], key);
99✔
119
    }
120

121
    uint64_t GetUint64(const Json::Value& obj, const std::string& key)
1,125✔
122
    {
123
        return ToUint64(obj[key], key);
1,125✔
124
    }
125

126
    std::string GetIntegerString(const Json::Value& obj, const std::string& key)
117✔
127
    {
128
        auto v = obj[key];
234✔
129
        if (v.isInt()) {
117✔
130
            return std::to_string(v.asInt64());
79✔
131
        }
132

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

142
    bool IsSerialNumberChannel(const Json::Value& channel_data)
983✔
143
    {
144
        const std::vector<std::string> serialNames{"Serial", "serial_number", "Serial NO"};
5,898✔
145
        return serialNames.end() !=
1,966✔
146
               std::find(serialNames.begin(), serialNames.end(), channel_data.get("name", std::string()).asString());
2,949✔
147
    }
148

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

171
    const TRegisterType& GetRegisterType(const Json::Value& itemData, const TRegisterTypeMap& typeMap)
1,156✔
172
    {
173
        if (itemData.isMember("reg_type")) {
1,156✔
174
            std::string type = itemData["reg_type"].asString();
1,089✔
175
            try {
176
                return typeMap.Find(type);
1,089✔
177
            } catch (...) {
×
178
                throw TConfigParserException("invalid register type: " + type);
×
179
            }
180
        }
181
        return typeMap.GetDefaultType();
67✔
182
    }
183

184
    std::optional<std::chrono::milliseconds> GetReadRateLimit(const Json::Value& data)
1,459✔
185
    {
186
        std::chrono::milliseconds res(-1);
1,459✔
187
        Get(data, "read_rate_limit_ms", res);
1,459✔
188
        if (res < 0ms) {
1,459✔
189
            return std::nullopt;
1,447✔
190
        }
191
        return std::make_optional(res);
12✔
192
    }
193

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

404
        Get(channel_data, "units", channel->Units);
981✔
405

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

410
        deviceWithChannels.Channels.push_back(channel);
981✔
411
    }
412

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

611
        return std::make_shared<TFeaturePort>(port, false);
12✔
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);
×
623
    }
624
#endif
625

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

637
        auto port_config = make_shared<TPortConfig>();
188✔
638

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

642
        auto port_type = port_data.get("port_type", "serial").asString();
193✔
643

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

647
        port_config->Port = portFactory(port_data, rpcConfig);
94✔
648

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

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

657
        handlerConfig->AddPortConfig(port_config);
89✔
658
    }
659
}
660

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

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

676
#ifndef __EMSCRIPTEN__
677
PFeaturePort DefaultPortFactory(const Json::Value& port_data, PRPCConfig rpcConfig)
21✔
678
{
679
    auto port_type = port_data.get("port_type", "serial").asString();
63✔
680
    if (port_type == "serial") {
21✔
681
        return OpenSerialPort(port_data, rpcConfig);
17✔
682
    }
683
    if (port_type == "tcp") {
4✔
684
        return OpenTcpPort(port_data, rpcConfig);
4✔
685
    }
686
    if (port_type == "modbus tcp") {
×
687
        return OpenModbusTcpPort(port_data, rpcConfig);
×
688
    }
689
    throw TConfigParserException("invalid port_type: '" + port_type + "'");
×
690
}
691
#endif
692

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

700
void AddRegisterType(Json::Value& configSchema, const std::string& registerType)
19✔
701
{
702
    configSchema["definitions"]["reg_type"]["enum"].append(registerType);
19✔
703
}
19✔
704

705
void CheckDuplicatePorts(const THandlerConfig& handlerConfig)
77✔
706
{
707
    std::unordered_set<std::string> paths;
154✔
708
    for (const auto& port: handlerConfig.PortConfigs) {
166✔
709
        if (!paths.insert(port->Port->GetDescription(false)).second) {
89✔
710
            throw TConfigParserException("Duplicate port: " + port->Port->GetDescription(false));
×
711
        }
712
    }
713
}
77✔
714

715
void CheckDuplicateDeviceIds(const THandlerConfig& handlerConfig)
77✔
716
{
717
    std::unordered_set<std::string> ids;
154✔
718
    for (const auto& port: handlerConfig.PortConfigs) {
164✔
719
        for (const auto& device: port->Devices) {
263✔
720
            if (!ids.insert(device->Device->DeviceConfig()->Id).second) {
176✔
721
                throw TConfigParserException(
1✔
722
                    "Duplicate MQTT device id: " + device->Device->DeviceConfig()->Id +
2✔
723
                    ", set device MQTT ID explicitly to fix (see https://wb.wiki/serial-id-collision)");
3✔
724
            }
725
        }
726
    }
727
}
76✔
728

729
#ifndef __EMSCRIPTEN__
730
PHandlerConfig LoadConfig(const std::string& configFileName,
90✔
731
                          TSerialDeviceFactory& deviceFactory,
732
                          const Json::Value& commonDeviceSchema,
733
                          TTemplateMap& templates,
734
                          PRPCConfig rpcConfig,
735
                          const Json::Value& portsSchema,
736
                          TProtocolConfedSchemasMap& protocolSchemas,
737
                          TPortFactoryFn portFactory)
738
{
739
    PHandlerConfig handlerConfig(new THandlerConfig);
90✔
740
    Json::Value Root(Parse(configFileName));
180✔
741
    FixOldConfigFormat(Root, templates);
90✔
742

743
    try {
744
        ValidateConfig(Root, deviceFactory, commonDeviceSchema, portsSchema, templates, protocolSchemas);
90✔
745
    } catch (const std::runtime_error& e) {
16✔
746
        throw std::runtime_error("File: " + configFileName + " error: " + e.what());
8✔
747
    }
748

749
    // wb6 - single core - max 100 registers per second
750
    // wb7 - 4 cores - max 800 registers per second
751
    handlerConfig->LowPriorityRegistersRateLimit = (1 == get_nprocs_conf()) ? 100 : 800;
82✔
752
    Get(Root, "rate_limit", handlerConfig->LowPriorityRegistersRateLimit);
82✔
753

754
    Get(Root, "debug", handlerConfig->Debug);
82✔
755

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

765
    const Json::Value& array = Root["ports"];
82✔
766
    for (Json::Value::ArrayIndex index = 0; index < array.size(); ++index) {
171✔
767
        // old default prefix for compat
768
        LoadPort(handlerConfig,
188✔
769
                 array[index],
770
                 "wb-modbus-" + std::to_string(index) + "-",
203✔
771
                 templates,
772
                 rpcConfig,
773
                 deviceFactory,
774
                 portFactory);
391✔
775
    }
776

777
    CheckDuplicatePorts(*handlerConfig);
77✔
778
    CheckDuplicateDeviceIds(*handlerConfig);
77✔
779

780
    return handlerConfig;
152✔
781
}
782
#endif
783

784
void TPortConfig::AddDevice(PSerialDeviceWithChannels device)
181✔
785
{
786
    // try to find duplicate of this device
787
    for (auto dev: Devices) {
406✔
788
        if (dev->Device->Protocol() == device->Device->Protocol()) {
225✔
789
            if (dev->Device->Protocol()->IsSameSlaveId(dev->Device->DeviceConfig()->SlaveId,
672✔
790
                                                       device->Device->DeviceConfig()->SlaveId))
448✔
791
            {
792
                stringstream ss;
4✔
793
                ss << "id \"" << device->Device->DeviceConfig()->SlaveId << "\" of device \""
2✔
794
                   << device->Device->DeviceConfig()->Name
4✔
795
                   << "\" is already set to device \"" + device->Device->DeviceConfig()->Name + "\"";
6✔
796
                throw TConfigParserException(ss.str());
2✔
797
            }
798
        }
799
    }
800

801
    Devices.push_back(device);
179✔
802
}
179✔
803

804
TDeviceChannelConfig::TDeviceChannelConfig(const std::string& type,
981✔
805
                                           const std::string& deviceId,
806
                                           int order,
807
                                           bool readOnly,
808
                                           const std::string& mqttId,
809
                                           const std::vector<PRegister>& regs)
981✔
810
    : MqttId(mqttId),
811
      Type(type),
812
      DeviceId(deviceId),
813
      Order(order),
814
      ReadOnly(readOnly),
815
      Registers(regs)
981✔
816
{}
981✔
817

818
const std::string& TDeviceChannelConfig::GetName() const
64✔
819
{
820
    auto it = Titles.find("en");
64✔
821
    if (it != Titles.end()) {
64✔
822
        return it->second;
5✔
823
    }
824
    return MqttId;
59✔
825
}
826

827
const TTitleTranslations& TDeviceChannelConfig::GetTitles() const
528✔
828
{
829
    return Titles;
528✔
830
}
831

832
void TDeviceChannelConfig::SetTitle(const std::string& name, const std::string& lang)
13✔
833
{
834
    if (!lang.empty()) {
13✔
835
        Titles[lang] = name;
13✔
836
    }
837
}
13✔
838

839
const std::map<std::string, TTitleTranslations>& TDeviceChannelConfig::GetEnumTitles() const
466✔
840
{
841
    return EnumTitles;
466✔
842
}
843

844
void TDeviceChannelConfig::SetEnumTitles(const std::string& value, const TTitleTranslations& titles)
4✔
845
{
846
    if (!value.empty()) {
4✔
847
        EnumTitles[value] = titles;
4✔
848
    }
849
}
4✔
850

851
TDeviceSetupItemConfig::TDeviceSetupItemConfig(const std::string& name,
108✔
852
                                               PRegisterConfig reg,
853
                                               const std::string& value,
854
                                               const std::string& parameterId)
108✔
855
    : Name(name),
856
      RegisterConfig(reg),
857
      Value(value),
858
      ParameterId(parameterId)
113✔
859
{
860
    try {
861
        RawValue = ConvertToRawValue(*reg, Value);
108✔
862
    } catch (const std::exception& e) {
2✔
863
        throw TConfigParserException("\"" + name + "\" bad value \"" + value + "\"");
1✔
864
    }
865
}
107✔
866

867
const std::string& TDeviceSetupItemConfig::GetName() const
107✔
868
{
869
    return Name;
107✔
870
}
871

872
const std::string& TDeviceSetupItemConfig::GetValue() const
105✔
873
{
874
    return Value;
105✔
875
}
876

877
const std::string& TDeviceSetupItemConfig::GetParameterId() const
105✔
878
{
879
    return ParameterId;
105✔
880
}
881

882
TRegisterValue TDeviceSetupItemConfig::GetRawValue() const
107✔
883
{
884
    return RawValue;
107✔
885
}
886

887
PRegisterConfig TDeviceSetupItemConfig::GetRegisterConfig() const
212✔
888
{
889
    return RegisterConfig;
212✔
890
}
891

892
void THandlerConfig::AddPortConfig(PPortConfig portConfig)
89✔
893
{
894
    PortConfigs.push_back(portConfig);
89✔
895
}
89✔
896

897
TConfigParserException::TConfigParserException(const std::string& message)
6✔
898
    : std::runtime_error("Error parsing config file: " + message)
6✔
899
{}
6✔
900

901
bool IsSubdeviceChannel(const Json::Value& channelSchema)
×
902
{
903
    return (channelSchema.isMember("oneOf") || channelSchema.isMember("device_type"));
×
904
}
905

906
std::string GetProtocolName(const Json::Value& deviceDescription)
38✔
907
{
908
    std::string p;
38✔
909
    Get(deviceDescription, "protocol", p);
38✔
910
    if (p.empty()) {
38✔
911
        p = DefaultProtocol;
27✔
912
    }
913
    return p;
38✔
914
}
915

916
void LoadChannels(TSerialDeviceWithChannels& deviceWithChannels,
182✔
917
                  const Json::Value& deviceData,
918
                  const std::optional<std::chrono::milliseconds>& defaultReadRateLimit,
919
                  const TRegisterTypeMap& typeMap,
920
                  const TLoadingContext& context)
921
{
922
    if (deviceData.isMember("channels")) {
182✔
923
        for (const auto& channel_data: deviceData["channels"]) {
1,157✔
924
            LoadChannel(deviceWithChannels, channel_data, context, typeMap);
976✔
925
        }
926
    }
927

928
    if (deviceWithChannels.Channels.empty()) {
181✔
929
        LOG(Warn) << "device " << deviceWithChannels.Device->DeviceConfig()->Name << " has no channels";
×
930
    } else if (deviceWithChannels.Device->IsSporadicOnly()) {
181✔
931
        LOG(Debug) << "device " << deviceWithChannels.Device->DeviceConfig()->Name
×
932
                   << " has only sporadic channels enabled";
×
933
    }
934

935
    auto readRateLimit = GetReadRateLimit(deviceData);
181✔
936
    if (!readRateLimit) {
181✔
937
        readRateLimit = defaultReadRateLimit;
177✔
938
    }
939
    for (auto channel: deviceWithChannels.Channels) {
1,161✔
940
        for (auto reg: channel->Registers) {
2,016✔
941
            if (!reg->GetConfig()->ReadRateLimit) {
1,036✔
942
                reg->GetConfig()->ReadRateLimit = readRateLimit;
1,032✔
943
            }
944
        }
945
    }
946
}
181✔
947

948
void TSerialDeviceFactory::RegisterProtocol(PProtocol protocol, IDeviceFactory* deviceFactory)
3,488✔
949
{
950
    TDeviceProtocolParams params = {protocol, std::shared_ptr<IDeviceFactory>(deviceFactory)};
3,488✔
951
    Protocols.insert(std::make_pair(protocol->GetName(), params));
3,488✔
952
}
3,488✔
953

954
TDeviceProtocolParams TSerialDeviceFactory::GetProtocolParams(const std::string& protocolName) const
434✔
955
{
956
    auto it = Protocols.find(protocolName);
434✔
957
    if (it == Protocols.end()) {
434✔
958
        throw TSerialDeviceException("unknown protocol: " + protocolName);
×
959
    }
960
    return it->second;
868✔
961
}
962

963
PProtocol TSerialDeviceFactory::GetProtocol(const std::string& protocolName) const
152✔
964
{
965
    return GetProtocolParams(protocolName).protocol;
152✔
966
}
967

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

973
const std::string& TSerialDeviceFactory::GetCustomChannelSchemaRef(const std::string& protocolName) const
37✔
974
{
975
    return GetProtocolParams(protocolName).factory->GetCustomChannelSchemaRef();
37✔
976
}
977

978
std::vector<std::string> TSerialDeviceFactory::GetProtocolNames() const
1✔
979
{
980
    std::vector<std::string> res;
1✔
981
    for (const auto& bucket: Protocols) {
25✔
982
        res.emplace_back(bucket.first);
24✔
983
    }
984
    return res;
1✔
985
}
986

987
PSerialDeviceWithChannels TSerialDeviceFactory::CreateDevice(const Json::Value& deviceConfigJson,
184✔
988
                                                             const TCreateDeviceParams& params,
989
                                                             TTemplateMap& templates)
990
{
991
    TDeviceConfigLoadParams loadParams;
368✔
992
    loadParams.Defaults = params.Defaults;
184✔
993
    const auto* cfg = &deviceConfigJson;
184✔
994
    unique_ptr<Json::Value> mergedConfig;
184✔
995
    if (deviceConfigJson.isMember("device_type")) {
184✔
996
        auto deviceType = deviceConfigJson["device_type"].asString();
60✔
997
        auto deviceTemplate = templates.GetTemplate(deviceType);
32✔
998
        loadParams.DeviceTemplateTitle = deviceTemplate->GetTitle();
30✔
999
        mergedConfig = std::make_unique<Json::Value>(
28✔
1000
            MergeDeviceConfigWithTemplate(deviceConfigJson, deviceType, deviceTemplate->GetTemplate()));
58✔
1001
        cfg = mergedConfig.get();
28✔
1002
        loadParams.Translations = &deviceTemplate->GetTemplate()["translations"];
28✔
1003
    }
1004
    std::string protocolName = DefaultProtocol;
364✔
1005
    Get(*cfg, "protocol", protocolName);
182✔
1006

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

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

1025
    return deviceWithChannels;
362✔
1026
}
1027

1028
IDeviceFactory::IDeviceFactory(std::unique_ptr<IRegisterAddressFactory> registerAddressFactory,
3,488✔
1029
                               const std::string& commonDeviceSchemaRef,
1030
                               const std::string& customChannelSchemaRef)
3,488✔
1031
    : CommonDeviceSchemaRef(commonDeviceSchemaRef),
1032
      CustomChannelSchemaRef(customChannelSchemaRef),
1033
      RegisterAddressFactory(std::move(registerAddressFactory))
3,488✔
1034
{}
3,488✔
1035

1036
const std::string& IDeviceFactory::GetCommonDeviceSchemaRef() const
61✔
1037
{
1038
    return CommonDeviceSchemaRef;
61✔
1039
}
1040

1041
const std::string& IDeviceFactory::GetCustomChannelSchemaRef() const
37✔
1042
{
1043
    return CustomChannelSchemaRef;
37✔
1044
}
1045

1046
const IRegisterAddressFactory& IDeviceFactory::GetRegisterAddressFactory() const
1,355✔
1047
{
1048
    return *RegisterAddressFactory;
1,355✔
1049
}
1050

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

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

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

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

1064
    LoadCommonDeviceParameters(*res, dev);
182✔
1065

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

1070
    return res;
182✔
1071
}
1072

1073
TLoadRegisterConfigResult LoadRegisterConfig(const Json::Value& registerData,
1,156✔
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;
1,156✔
1081
    TRegisterType regType = GetRegisterType(registerData, typeMap);
2,312✔
1082
    res.DefaultControlType = regType.DefaultControlType.empty() ? "text" : regType.DefaultControlType;
1,156✔
1083

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

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

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

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

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

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

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

1132
    res.RegisterConfig = TRegisterConfig::Create(regType.Index,
1,156✔
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);
1,156✔
1143

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

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

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

1158
void RegisterProtocols(TSerialDeviceFactory& deviceFactory)
144✔
1159
{
1160
    TModbusDevice::Register(deviceFactory);
144✔
1161

1162
#ifndef __EMSCRIPTEN__
1163
    TEnergomeraIecWithFastReadDevice::Register(deviceFactory);
144✔
1164
    TEnergomeraIecModeCDevice::Register(deviceFactory);
144✔
1165
    TIVTMDevice::Register(deviceFactory);
144✔
1166
    TLLSDevice::Register(deviceFactory);
144✔
1167
    TMercury200Device::Register(deviceFactory);
144✔
1168
    TMercury230Device::Register(deviceFactory);
144✔
1169
    TMilurDevice::Register(deviceFactory);
144✔
1170
    TModbusIODevice::Register(deviceFactory);
144✔
1171
    TNevaDevice::Register(deviceFactory);
144✔
1172
    TPulsarDevice::Register(deviceFactory);
144✔
1173
    TS2KDevice::Register(deviceFactory);
144✔
1174
    TUnielDevice::Register(deviceFactory);
144✔
1175
    TDlmsDevice::Register(deviceFactory);
144✔
1176
    Dooya::TDevice::Register(deviceFactory);
144✔
1177
    WinDeco::TDevice::Register(deviceFactory);
144✔
1178
    Somfy::TDevice::Register(deviceFactory);
144✔
1179
    Aok::TDevice::Register(deviceFactory);
144✔
1180
    TIecModeCDevice::Register(deviceFactory);
144✔
1181
    TEnergomeraCeDevice::Register(deviceFactory);
144✔
1182
#endif
1183
}
144✔
1184

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

1212
    if (register_data.isMember("string_data_size")) {
1,148✔
1213
        res.BitWidth = register_data["string_data_size"].asUInt() * sizeof(char) * 8;
8✔
1214
    }
1215
    return res;
1,148✔
1216
}
1217

1218
TUint32RegisterAddressFactory::TUint32RegisterAddressFactory(size_t bytesPerRegister)
2,768✔
1219
    : BaseRegisterAddress(0),
1220
      BytesPerRegister(bytesPerRegister)
2,768✔
1221
{}
2,768✔
1222

1223
TRegisterDesc TUint32RegisterAddressFactory::LoadRegisterAddress(const Json::Value& regCfg,
1,092✔
1224
                                                                 const IRegisterAddress& deviceBaseAddress,
1225
                                                                 uint32_t stride,
1226
                                                                 uint32_t registerByteWidth) const
1227
{
1228
    TRegisterDesc res;
1,092✔
1229

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

1246
const IRegisterAddress& TUint32RegisterAddressFactory::GetBaseRegisterAddress() const
194✔
1247
{
1248
    return BaseRegisterAddress;
194✔
1249
}
1250

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

1268
const IRegisterAddress& TStringRegisterAddressFactory::GetBaseRegisterAddress() const
4✔
1269
{
1270
    return BaseRegisterAddress;
4✔
1271
}
1272

1273
bool HasNoEmptyProperty(const Json::Value& regCfg, const std::string& propertyName)
2,184✔
1274
{
1275
    return regCfg.isMember(propertyName) &&
3,277✔
1276
           !(regCfg[propertyName].isString() && regCfg[propertyName].asString().empty());
3,277✔
1277
}
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