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

wirenboard / wb-mqtt-serial / 666

08 Jul 2025 11:10AM UTC coverage: 73.854% (+0.1%) from 73.706%
666

push

github

web-flow
Port handling refactoring (#960)

Pass port by reference when needed instead of storing it in every device

6444 of 9057 branches covered (71.15%)

526 of 700 new or added lines in 56 files covered. (75.14%)

6 existing lines in 5 files now uncovered.

12341 of 16710 relevant lines covered (73.85%)

305.53 hits per line

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

31.88
/src/devices/dlms_device.cpp
1
#include "dlms_device.h"
2
#include "common_utils.h"
3
#include "log.h"
4

5
#include <fstream>
6

7
#include "GXDLMSConverter.h"
8
#include "GXDLMSObject.h"
9
#include "GXDLMSObjectFactory.h"
10
#include "GXDLMSSapAssignment.h"
11
#include "GXDLMSTranslator.h"
12

13
#define LOG(logger) ::logger.Log() << "[" << DeviceConfig()->Name << " (" << ToString() << ")] "
14

15
namespace
16
{
17
    const size_t MAX_PACKET_SIZE = 200;
18

19
    const auto OBIS_CODE_HINTS_FULL_FILE_PATH = "/usr/share/wb-mqtt-serial/obis-hints.json";
20

21
    const std::chrono::milliseconds DEFAULT_RESPONSE_TIMEOUT(1000);
22
    const std::chrono::milliseconds DEFAULT_FRAME_TIMEOUT(20);
23

24
    const std::string MAUFACTURER_SPECIFIC_CODES[] = {"1.128-199.0-199,255.0-255.0-255.0-255",
25
                                                      "1.0-199,255.128-199,240.0-255.0-255.0-255",
26
                                                      "1.0-199,255.0-199,255.128-254.0-255.0-255",
27
                                                      "1.0-199,255.0-199,255.0-255.128-254.0-255",
28
                                                      "1.0-199,255.0-199,255.0-255.0-255.128-254"};
29

30
    struct TObisCodeHint
31
    {
32
        std::string Description;
33
        std::string MqttControl;
34
    };
35

36
    typedef std::unordered_map<std::string, TObisCodeHint> TObisCodeHints;
37

38
    class TObisRegisterAddress: public IRegisterAddress
39
    {
40
        std::string LogicalName;
41

42
    public:
43
        TObisRegisterAddress(const std::string& ln): LogicalName(ln)
144✔
44
        {
45
            auto bytes = WBMQTT::StringSplit(ln, ".");
432✔
46
            if (bytes.size() != 6) {
144✔
47
                throw TConfigParserException("Bad OBIS code '" + ln + "'");
×
48
            }
49
            for (const auto& b: bytes) {
1,008✔
50
                if (!std::all_of(b.cbegin(), b.cend(), ::isdigit)) {
864✔
51
                    throw TConfigParserException("Bad OBIS code '" + ln + "'");
×
52
                }
53
            }
54
        }
144✔
55

56
        const std::string& GetLogicalName() const
1✔
57
        {
58
            return LogicalName;
1✔
59
        }
60

61
        std::string ToString() const override
×
62
        {
63
            return LogicalName;
×
64
        }
65

66
        int Compare(const IRegisterAddress& addr) const override
×
67
        {
68
            const auto& a = dynamic_cast<const TObisRegisterAddress&>(addr);
×
69
            return LogicalName.compare(a.LogicalName);
×
70
        }
71

72
        IRegisterAddress* CalcNewAddress(uint32_t /*offset*/,
×
73
                                         uint32_t /*stride*/,
74
                                         uint32_t /*registerByteWidth*/,
75
                                         uint32_t /*addressByteStep*/) const override
76
        {
77
            return new TObisRegisterAddress(LogicalName);
×
78
        }
79
    };
80

81
    class TObisRegisterAddressFactory: public IRegisterAddressFactory
82
    {
83
        TObisRegisterAddress BaseRegisterAddress;
84

85
    public:
86
        TObisRegisterAddressFactory(): BaseRegisterAddress("0.0.0.0.0.0")
143✔
87
        {}
143✔
88

89
        TRegisterDesc LoadRegisterAddress(const Json::Value& regCfg,
1✔
90
                                          const IRegisterAddress& deviceBaseAddress,
91
                                          uint32_t stride,
92
                                          uint32_t registerByteWidth) const override
93
        {
94
            TRegisterDesc res;
1✔
95
            res.Address = std::make_shared<TObisRegisterAddress>(regCfg["address"].asString());
1✔
96
            return res;
1✔
97
        }
98

99
        const IRegisterAddress& GetBaseRegisterAddress() const override
1✔
100
        {
101
            return BaseRegisterAddress;
1✔
102
        }
103
    };
104

105
    class TDlmsDeviceFactory: public IDeviceFactory
106
    {
107
    public:
108
        TDlmsDeviceFactory()
143✔
109
            : IDeviceFactory(std::make_unique<TObisRegisterAddressFactory>(),
×
110
                             "#/definitions/dlms_device",
111
                             "#/definitions/dlms_channel")
143✔
112
        {}
143✔
113

114
        PSerialDevice CreateDevice(const Json::Value& data,
1✔
115
                                   PDeviceConfig deviceConfig,
116
                                   PProtocol protocol) const override
117
        {
118
            TDlmsDeviceConfig cfg;
×
119
            cfg.DeviceConfig = deviceConfig;
1✔
120
            WBMQTT::JSON::Get(data, "dlms_client_address", cfg.ClientAddress);
1✔
121
            cfg.Authentication = static_cast<DLMS_AUTHENTICATION>(data.get("dlms_auth", cfg.Authentication).asInt());
1✔
122
            cfg.InterfaceType = static_cast<DLMS_INTERFACE_TYPE>(data.get("dlms_interface", cfg.InterfaceType).asInt());
1✔
123
            WBMQTT::JSON::Get(data, "dlms_disconnect_retry_timeout_ms", cfg.DisconnectRetryTimeout);
1✔
124

125
            return std::make_shared<TDlmsDevice>(cfg, protocol);
2✔
126
        }
127
    };
128

129
    std::string GetErrorMessage(int err)
×
130
    {
131
        return std::to_string(err) + ", " + CGXDLMSConverter::GetErrorMessage(err);
×
132
    }
133

134
    std::string AlignName(const std::string& name)
×
135
    {
136
        // OBIS code has following structure X.X.X.X.X.X, where X in [0, 255]
137
        // So the longest printed code will be 6*4-1 characters
138
        return name + std::string(6 * 4 - 1 - name.size(), ' ');
×
139
    }
140

141
    std::string GetDescription(const std::string& logicalName,
×
142
                               CGXDLMSObject* obj,
143
                               CGXDLMSConverter* cnv,
144
                               const TObisCodeHints& obisHints)
145
    {
146
        std::string res;
×
147
        auto it = obisHints.find(logicalName);
×
148
        if (it != obisHints.end() && !it->second.Description.empty()) {
×
149
            res = it->second.Description;
×
150
        }
151
        std::vector<std::string> descriptions;
×
152
        auto tmp = logicalName;
×
153
        cnv->GetDescription(tmp, obj->GetObjectType(), descriptions);
×
154
        if (descriptions.empty()) {
×
155
            return res;
×
156
        }
157
        if (res.empty()) {
×
158
            return descriptions[0];
×
159
        }
160
        return res + " (" + descriptions[0] + ")";
×
161
    }
162

163
    bool IsChannelEnabled(const std::string& logicalName, const TObisCodeHints& obisHints)
×
164
    {
165
        auto it = obisHints.find(logicalName);
×
166
        return (it != obisHints.end() && !it->second.MqttControl.empty());
×
167
    }
168

169
    bool IsManufacturerSpecific(const std::string& logicalName)
×
170
    {
171
        CGXStandardObisCodeCollection codes;
×
172
        for (auto code: MAUFACTURER_SPECIFIC_CODES) {
×
173
            std::string dummy;
×
174
            codes.push_back(new CGXStandardObisCode(GXHelpers::Split(code, ".", false), dummy, dummy, dummy));
×
175
        }
176
        bool res = false;
×
177
        std::vector<CGXStandardObisCode*> list;
×
178
        if (DLMS_ERROR_CODE_OK == codes.Find(logicalName, DLMS_OBJECT_TYPE_NONE, list) && !list.empty()) {
×
179
            res = (list.front()->GetDescription() != "Invalid");
×
180
        }
181
        for (auto code: list) {
×
182
            delete code;
×
183
        }
184
        return res;
×
185
    }
186

187
    std::string GetChannelName(const std::string& logicalName,
×
188
                               const std::string& description,
189
                               const TObisCodeHints& obisHints)
190
    {
191
        auto it = obisHints.find(logicalName);
×
192
        if (it != obisHints.end() && !it->second.MqttControl.empty()) {
×
193
            return it->second.MqttControl;
×
194
        }
195
        if (description.empty() || IsManufacturerSpecific(logicalName)) {
×
196
            return logicalName;
×
197
        }
198
        return description;
×
199
    }
200

201
    std::string GetType(CGXDLMSObject* obj)
×
202
    {
203
        auto reg = dynamic_cast<CGXDLMSRegister*>(obj);
×
204
        if (!reg) {
×
205
            return "value";
×
206
        }
207
        switch (reg->GetUnit()) {
×
208
            case 9:
×
209
                return "temperature";
×
210
            case 22:
×
211
                return "power";
×
212
            case 23:
×
213
                return "pressure";
×
214
            case 25:
×
215
                return "power";
×
216
            case 32:
×
217
                return "power_consumption";
×
218
            case 33:
×
219
                return "current";
×
220
            case 35:
×
221
                return "voltage";
×
222
            case 38:
×
223
                return "resistance";
×
224
            case 46:
×
225
                return "power_consumption";
×
226
        }
227
        return "value";
×
228
    }
229

230
    struct TChannelDescription
231
    {
232
        Json::Value channel;
233
        std::optional<std::string> title;
234
    };
235

236
    TChannelDescription MakeChannelDescription(CGXDLMSObject* obj,
×
237
                                               CGXDLMSConverter* cnv,
238
                                               const TObisCodeHints& obisHints)
239
    {
240
        std::string logicalName;
×
241
        obj->GetLogicalName(logicalName);
×
242

243
        std::optional<std::string> title;
×
244

245
        auto description = GetDescription(logicalName, obj, cnv, obisHints);
×
246
        auto channelName = GetChannelName(logicalName, description, obisHints);
×
247
        if (!util::IsValidMqttTopicString(channelName)) {
×
248
            title = channelName;
×
249
            channelName = util::ConvertToValidMqttTopicString(channelName);
×
250
        }
251
        Json::Value res;
×
252
        res["name"] = channelName;
×
253
        res["reg_type"] = "default";
×
254
        res["address"] = logicalName;
×
255
        res["type"] = GetType(obj);
×
256
        if (!IsChannelEnabled(logicalName, obisHints)) {
×
257
            res["enabled"] = false;
×
258
        }
259
        std::cout << AlignName(logicalName) << " " << description << " -> " << channelName << std::endl;
×
260
        return {res, title};
×
261
    }
262

263
    TObisCodeHints LoadObisCodeHints()
×
264
    {
265
        TObisCodeHints res;
×
266
        try {
267
            for (auto& c: WBMQTT::JSON::Parse(OBIS_CODE_HINTS_FULL_FILE_PATH)) {
×
268
                if (c.isMember("obis")) {
×
269
                    res.insert({c["obis"].asString(), {c["description"].asString(), c["control"].asString()}});
×
270
                }
271
            }
272
        } catch (const std::exception& e) {
×
273
            std::cout << e.what() << std::endl;
×
274
        }
275
        return res;
×
276
    }
277

278
    void Print(const CGXDLMSObjectCollection& objs, bool printAttributes, const TObisCodeHints& obisHints)
×
279
    {
280
        CGXDLMSConverter cnv;
×
281
        for (auto obj: objs) {
×
282
            std::string logicalName;
×
283
            obj->GetLogicalName(logicalName);
×
284

285
            std::string typeName = CGXDLMSConverter::ToString(obj->GetObjectType());
×
286
            if (WBMQTT::StringStartsWith(typeName, "GXDLMS")) {
×
287
                typeName.erase(0, 6);
×
288
            }
289
            std::cout << AlignName(logicalName) << " " << typeName << ", "
×
290
                      << GetDescription(logicalName, obj, &cnv, obisHints);
×
291
            if (printAttributes) {
×
292
                if ((obj->GetObjectType() == DLMS_OBJECT_TYPE_PROFILE_GENERIC) ||
×
293
                    (dynamic_cast<CGXDLMSCustomObject*>(obj) != nullptr))
×
294
                {
295
                    std::cout << " (unsupported by wb-mqtt-serial)" << std::endl;
×
296
                    continue;
×
297
                }
298
                std::cout << std::endl;
×
299
                std::vector<std::string> values;
×
300
                obj->GetValues(values);
×
301
                size_t i = 1;
×
302
                for (const auto& v: values) {
×
303
                    std::cout << "\t" << i << ": " << v << std::endl;
×
304
                    ++i;
×
305
                }
306
            } else {
307
                std::cout << std::endl;
×
308
            }
309
        }
310
    }
311

312
    Json::Value GenerateDlmsDeviceTemplate(const std::string& name,
×
313
                                           const TDlmsDeviceConfig& deviceConfig,
314
                                           const CGXDLMSObjectCollection& objs,
315
                                           const TObisCodeHints& obisHints)
316
    {
317
        CGXDLMSConverter cnv;
×
318
        Json::Value res;
×
319
        res["device_type"] = name;
×
320
        auto& device = res["device"];
×
321
        device["name"] = name;
×
322
        device["id"] = name;
×
323
        device["dlms_auth"] = deviceConfig.Authentication;
×
324
        device["dlms_client_address"] = deviceConfig.ClientAddress;
×
325
        if (!deviceConfig.DeviceConfig->Password.empty()) {
×
326
            Json::Value ar(Json::arrayValue);
×
327
            for (auto b: deviceConfig.DeviceConfig->Password) {
×
328
                ar.append(b);
×
329
            }
330
            device["password"] = ar;
×
331
        }
332
        device["protocol"] = "dlms";
×
333
        device["response_timeout_ms"] = static_cast<Json::Int>(DEFAULT_RESPONSE_TIMEOUT.count());
×
334
        device["frame_timeout_ms"] = static_cast<Json::Int>(DEFAULT_FRAME_TIMEOUT.count());
×
335
        device["channels"] = Json::Value(Json::arrayValue);
×
336
        auto& channels = device["channels"];
×
337
        auto& translations = device["translations"]["en"];
×
338
        for (auto obj: objs) {
×
339
            if (obj->GetObjectType() == DLMS_OBJECT_TYPE_REGISTER) {
×
340
                const auto description = MakeChannelDescription(obj, &cnv, obisHints);
×
341
                channels.append(description.channel);
×
342
                if (description.title) {
×
343
                    translations[description.channel["name"].asString()] = *description.title;
×
344
                }
345
            }
346
        }
347
        return res;
×
348
    }
349

350
    const TObisRegisterAddress& ToTObisRegisterAddress(const TRegisterConfig& reg)
1✔
351
    {
352
        try {
353
            return dynamic_cast<const TObisRegisterAddress&>(reg.GetAddress());
1✔
354
        } catch (const std::bad_cast&) {
×
355
            throw TSerialDeviceTransientErrorException("Address of " + reg.ToString() +
×
356
                                                       " can't be casted to TObisRegisterAddress");
×
357
        }
358
    }
359
}
360

361
void TDlmsDevice::Register(TSerialDeviceFactory& factory)
143✔
362
{
363
    factory.RegisterProtocol(
143✔
364
        new TUint32SlaveIdProtocol("dlms", TRegisterTypes({{0, "default", "value", Double, true}})),
429✔
365
        new TDlmsDeviceFactory());
143✔
366
}
143✔
367

368
TDlmsDevice::TDlmsDevice(const TDlmsDeviceConfig& config, PProtocol protocol)
1✔
369
    : TSerialDevice(config.DeviceConfig, protocol),
1✔
370
      TUInt32SlaveId(config.DeviceConfig->SlaveId),
2✔
371
      DisconnectRetryTimeout(config.DisconnectRetryTimeout)
1✔
372
{
373
    auto pwd = config.DeviceConfig->Password;
1✔
374
    if (pwd.empty() || pwd.back() != 0) {
1✔
375
        // CGXDLMSSecureClient wants a zero-terminated string as a password
376
        pwd.push_back(0);
1✔
377
    }
378

379
    int serverAddress = config.LogicalDeviceAddress;
1✔
380
    if (serverAddress < 0x80) {
1✔
381
        if (SlaveId > 0) {
1✔
382
            serverAddress <<= (SlaveId < 0x80 ? 7 : 14);
1✔
383
        }
384
    } else {
385
        serverAddress <<= 14;
×
386
    }
387
    serverAddress += SlaveId;
1✔
388

389
    // Use Logical name referencing as it is only option for SPODES
390
    Client = std::make_unique<CGXDLMSSecureClient>(true,
1✔
391
                                                   config.ClientAddress,
1✔
392
                                                   serverAddress,
393
                                                   config.Authentication,
1✔
394
                                                   (const char*)(&pwd[0]),
2✔
395
                                                   config.InterfaceType);
1✔
396
}
1✔
397

398
void TDlmsDevice::CheckCycle(TPort& port,
5✔
399
                             std::function<int(std::vector<CGXByteBuffer>&)> requestsGenerator,
400
                             std::function<int(CGXReplyData&)> responseParser,
401
                             const std::string& errorMsg)
402
{
403
    std::vector<CGXByteBuffer> data;
10✔
404
    CGXReplyData reply;
10✔
405
    auto res = requestsGenerator(data);
5✔
406
    if (res != DLMS_ERROR_CODE_OK) {
5✔
407
        throw TSerialDeviceTransientErrorException(errorMsg + ". Can't generate request: " + GetErrorMessage(res));
×
408
    }
409
    for (auto& buf: data) {
10✔
410
        try {
411
            ReadDataBlock(port, buf.GetData(), buf.GetSize(), reply);
5✔
412
        } catch (const std::exception& e) {
×
413
            throw TSerialDeviceTransientErrorException(errorMsg + ". " + e.what());
×
414
        }
415
    }
416
    res = responseParser(reply);
5✔
417
    if (res != DLMS_ERROR_CODE_OK) {
5✔
418
        if (res == DLMS_ERROR_CODE_APPLICATION_CONTEXT_NAME_NOT_SUPPORTED) {
×
419
            throw TSerialDevicePermanentRegisterException("Logical Name referencing is not supported");
×
420
        }
421
        throw TSerialDeviceTransientErrorException(errorMsg + ". Bad response: " + GetErrorMessage(res));
×
422
    }
423
}
5✔
424

425
void TDlmsDevice::ReadAttribute(TPort& port, const std::string& addr, int attribute, CGXDLMSObject& obj)
2✔
426
{
427
    CheckCycle(
2✔
428
        port,
429
        [&](auto& data) { return Client->Read(&obj, attribute, data); },
2✔
430
        [&](auto& reply) { return Client->UpdateValue(obj, attribute, reply.GetValue()); },
2✔
431
        "Getting " + addr + ":" + std::to_string(attribute) + " failed");
4✔
432
}
2✔
433

434
TRegisterValue TDlmsDevice::ReadRegisterImpl(TPort& port, const TRegisterConfig& reg)
1✔
435
{
436
    auto addr = ToTObisRegisterAddress(reg).GetLogicalName();
2✔
437
    auto obj = Client->GetObjects().FindByLN(DLMS_OBJECT_TYPE_REGISTER, addr);
1✔
438
    if (!obj) {
1✔
439
        obj = CGXDLMSObjectFactory::CreateObject(DLMS_OBJECT_TYPE_REGISTER, addr);
1✔
440
        if (!obj) {
1✔
441
            throw TSerialDeviceTransientErrorException("Can't create register object");
×
442
        }
443
        Client->GetObjects().push_back(obj);
1✔
444
    }
445

446
    bool forceValueRead = true;
1✔
447

448
    const auto REGISTER_VALUE_ATTRIBUTE_INDEX = 2;
1✔
449
    std::vector<int> attributes;
2✔
450
    obj->GetAttributeIndexToRead(false, attributes);
1✔
451
    for (auto pos: attributes) {
3✔
452
        ReadAttribute(port, addr, pos, *obj);
2✔
453
        if (pos == REGISTER_VALUE_ATTRIBUTE_INDEX) {
2✔
454
            forceValueRead = false;
1✔
455
        }
456
    }
457

458
    // Some devices doesn't set read access, let's force read value
459
    if (forceValueRead) {
1✔
NEW
460
        ReadAttribute(port, addr, REGISTER_VALUE_ATTRIBUTE_INDEX, *obj);
×
461
    }
462

463
    auto r = static_cast<CGXDLMSRegister*>(obj);
1✔
464

465
    if (!r->GetValue().IsNumber()) {
1✔
466
        throw TSerialDevicePermanentRegisterException(addr + " value is not a number");
×
467
    }
468

469
    return TRegisterValue{CopyDoubleToUint64(r->GetValue().ToDouble())};
2✔
470
}
471

472
void TDlmsDevice::PrepareImpl(TPort& port)
1✔
473
{
474
    try {
475
        Disconnect(port);
1✔
476
    } catch (...) {
×
477
        if (DisconnectRetryTimeout == std::chrono::milliseconds::zero()) {
×
478
            throw;
×
479
        }
480
        // If device doesn't respond, give it some time of silence on bus and try to disconnect again
NEW
481
        port.SleepSinceLastInteraction(DisconnectRetryTimeout);
×
NEW
482
        Disconnect(port);
×
483
    }
484
    InitializeConnection(port);
1✔
485
    SetTransferResult(true);
1✔
486
}
1✔
487

488
void TDlmsDevice::Disconnect(TPort& port)
1✔
489
{
490
    if (Client->GetInterfaceType() == DLMS_INTERFACE_TYPE_WRAPPER ||
2✔
491
        Client->GetCiphering()->GetSecurity() != DLMS_SECURITY_NONE)
1✔
492
    {
493
        try {
NEW
494
            CheckCycle(
×
495
                port,
NEW
496
                [&](auto& data) { return Client->ReleaseRequest(data); },
×
NEW
497
                [&](auto& reply) { return DLMS_ERROR_CODE_OK; },
×
NEW
498
                "ReleaseRequest failed");
×
499
        } catch (const std::exception& e) {
×
500
            LOG(Warn) << e.what();
×
501
        }
502
    }
503
    CheckCycle(
1✔
504
        port,
505
        [&](auto& data) { return Client->DisconnectRequest(data, true); },
1✔
506
        [&](auto& reply) { return DLMS_ERROR_CODE_OK; },
1✔
507
        "DisconnectRequest failed");
2✔
508
}
1✔
509

NEW
510
void TDlmsDevice::EndSession(TPort& port)
×
511
{
NEW
512
    Disconnect(port);
×
513
}
514

515
void TDlmsDevice::InitializeConnection(TPort& port)
1✔
516
{
517
    LOG(Debug) << "Initialize connection";
1✔
518

519
    // Get meter's send and receive buffers size.
520
    CheckCycle(
1✔
521
        port,
522
        [&](auto& data) { return Client->SNRMRequest(data); },
1✔
523
        [&](auto& reply) { return Client->ParseUAResponse(reply.GetData()); },
1✔
524
        "SNRMRequest failed");
2✔
525

526
    try {
527
        CheckCycle(
1✔
528
            port,
529
            [&](auto& data) { return Client->AARQRequest(data); },
1✔
530
            [&](auto& reply) { return Client->ParseAAREResponse(reply.GetData()); },
1✔
531
            "AARQRequest failed");
2✔
532

533
        // Get challenge if HLS authentication is used.
534
        if (Client->GetAuthentication() > DLMS_AUTHENTICATION_LOW) {
1✔
NEW
535
            CheckCycle(
×
536
                port,
NEW
537
                [&](auto& data) { return Client->GetApplicationAssociationRequest(data); },
×
NEW
538
                [&](auto& reply) { return Client->ParseApplicationAssociationResponse(reply.GetData()); },
×
NEW
539
                "Authentication failed");
×
540
        }
541
    } catch (const std::exception&) {
×
542
        try {
NEW
543
            Disconnect(port);
×
544
        } catch (...) {
×
545
        }
546
        throw;
×
547
    }
548
    LOG(Debug) << "Connection is initialized";
1✔
549
}
1✔
550

551
void TDlmsDevice::SendData(TPort& port, const uint8_t* data, size_t size)
5✔
552
{
553
    port.SleepSinceLastInteraction(GetFrameTimeout(port) + DeviceConfig()->RequestDelay);
5✔
554
    port.WriteBytes(data, size);
5✔
555
}
5✔
556

NEW
557
void TDlmsDevice::SendData(TPort& port, const std::string& str)
×
558
{
NEW
559
    SendData(port, reinterpret_cast<const uint8_t*>(str.c_str()), str.size());
×
560
}
561

562
void TDlmsDevice::ReadDataBlock(TPort& port, const uint8_t* data, size_t size, CGXReplyData& reply)
5✔
563
{
564
    if (size == 0) {
5✔
565
        return;
×
566
    }
567
    ReadDLMSPacket(port, data, size, reply);
5✔
568
    while (reply.IsMoreData()) {
5✔
569
        int ret;
570
        CGXByteBuffer bb;
×
571
        if ((ret = Client->ReceiverReady(reply.GetMoreData(), bb)) != 0) {
×
572
            throw std::runtime_error("Read block failed: " + GetErrorMessage(ret));
×
573
        }
NEW
574
        ReadDLMSPacket(port, bb.GetData(), bb.GetSize(), reply);
×
575
    }
576
}
577

578
void TDlmsDevice::ReadData(TPort& port, CGXByteBuffer& reply)
5✔
579
{
580
    Read(port, 0x7E, reply);
5✔
581
}
5✔
582

583
void TDlmsDevice::Read(TPort& port, unsigned char eop, CGXByteBuffer& reply)
5✔
584
{
585
    size_t lastReadIndex = 0;
5✔
586
    auto frameCompleteFn = [&](uint8_t* buf, size_t size) {
175✔
587
        if (size > 5) {
175✔
588
            auto pos = size - 1;
150✔
589
            for (; pos != lastReadIndex; --pos) {
315✔
590
                if (buf[pos] == eop) {
170✔
591
                    return true;
5✔
592
                }
593
            }
594
            lastReadIndex = size - 1;
145✔
595
        }
596
        return false;
170✔
597
    };
5✔
598

599
    uint8_t buf[MAX_PACKET_SIZE];
600
    auto bytesRead =
601
        port.ReadFrame(buf, sizeof(buf), GetResponseTimeout(port), GetFrameTimeout(port), frameCompleteFn).Count;
5✔
602
    reply.Set(buf, bytesRead);
5✔
603
}
5✔
604

605
void TDlmsDevice::ReadDLMSPacket(TPort& port, const uint8_t* data, size_t size, CGXReplyData& reply)
5✔
606
{
607
    if (size == 0) {
5✔
608
        return;
×
609
    }
610
    SendData(port, data, size);
5✔
611
    int ret = DLMS_ERROR_CODE_FALSE;
5✔
612
    CGXByteBuffer bb;
10✔
613
    // Loop until whole DLMS packet is received.
614
    while (ret == DLMS_ERROR_CODE_FALSE) {
10✔
615
        ReadData(port, bb);
5✔
616
        ret = Client->GetData(bb, reply);
5✔
617
    }
618
    if (ret != DLMS_ERROR_CODE_OK) {
5✔
619
        throw std::runtime_error("Read DLMS packet failed: " + GetErrorMessage(ret));
×
620
    }
621
}
622

NEW
623
void TDlmsDevice::GetAssociationView(TPort& port)
×
624
{
625
    LOG(Debug) << "Get association view ...";
×
NEW
626
    CheckCycle(
×
627
        port,
NEW
628
        [&](auto& data) { return Client->GetObjectsRequest(data); },
×
NEW
629
        [&](auto& reply) { return Client->ParseObjects(reply.GetData(), true); },
×
NEW
630
        "Getting objects from association view failed");
×
631
}
632

NEW
633
std::map<int, std::string> TDlmsDevice::GetLogicalDevices(TPort& port)
×
634
{
NEW
635
    Disconnect(port);
×
NEW
636
    InitializeConnection(port);
×
637
    LOG(Debug) << "Getting SAP Assignment ...";
×
638
    auto obj = CGXDLMSObjectFactory::CreateObject(DLMS_OBJECT_TYPE_SAP_ASSIGNMENT);
×
639
    if (!obj) {
×
640
        throw std::runtime_error("Can't create CGXDLMSSapAssignment object");
×
641
    }
642
    Client->GetObjects().push_back(obj);
×
643
    const auto SAP_ASSIGNEMENT_LIST_ATTRIBUTE_INDEX = 2;
×
644
    CheckCycle(
×
645
        port,
646
        [&](auto& data) { return Client->Read(obj, SAP_ASSIGNEMENT_LIST_ATTRIBUTE_INDEX, data); },
×
647
        [&](auto& reply) { return Client->UpdateValue(*obj, SAP_ASSIGNEMENT_LIST_ATTRIBUTE_INDEX, reply.GetValue()); },
×
648
        "SAP Assignment attribute read failed");
×
649
    auto res = static_cast<CGXDLMSSapAssignment*>(obj)->GetSapAssignmentList();
×
NEW
650
    Disconnect(port);
×
651
    return res;
×
652
}
653

NEW
654
const CGXDLMSObjectCollection& TDlmsDevice::ReadAllObjects(TPort& port, bool readAttributes)
×
655
{
656
    LOG(Debug) << "Getting association view ... ";
×
NEW
657
    Disconnect(port);
×
NEW
658
    InitializeConnection(port);
×
NEW
659
    GetAssociationView(port);
×
660
    LOG(Debug) << "Getting objects ...";
×
661
    auto& objs = Client->GetObjects();
×
662
    if (readAttributes) {
×
663
        for (auto obj: objs) {
×
664
            if ((obj->GetObjectType() == DLMS_OBJECT_TYPE_PROFILE_GENERIC) ||
×
665
                (dynamic_cast<CGXDLMSCustomObject*>(obj) != nullptr))
×
666
            {
667
                continue;
×
668
            }
669
            std::vector<int> attributes;
×
670
            obj->GetAttributeIndexToRead(true, attributes);
×
671
            for (auto pos: attributes) {
×
672
                try {
NEW
673
                    CheckCycle(
×
674
                        port,
NEW
675
                        [&](auto& data) { return Client->Read(obj, pos, data); },
×
NEW
676
                        [&](auto& reply) { return Client->UpdateValue(*obj, pos, reply.GetValue()); },
×
NEW
677
                        "");
×
UNCOV
678
                } catch (...) {
×
679
                }
680
            }
681
        }
682
    }
NEW
683
    EndSession(port);
×
684
    return objs;
×
685
}
686

687
void DLMS::PrintDeviceTemplateGenerationOptionsUsage()
×
688
{
689
    std::cout << "dlms_hdlc protocol options:" << std::endl
×
690
              << "  - client address, typical values:" << std::endl
×
691
              << "      16 - public client" << std::endl
×
692
              << "      32 - meterings reader" << std::endl
×
693
              << "      48 - configuration tool" << std::endl
×
694
              << "  - authentication mechanism:" << std::endl
×
695
              << "     lowest" << std::endl
×
696
              << "     low" << std::endl
×
697
              << "     high" << std::endl
×
698
              << "     MD5" << std::endl
×
699
              << "     SHA1" << std::endl
×
700
              << "     GMAC" << std::endl
×
701
              << "     SHA256" << std::endl
×
702
              << "     ECDSA" << std::endl
×
703
              << "  - password" << std::endl;
×
704
}
705

706
void DLMS::GenerateDeviceTemplate(TDeviceTemplateGenerationMode mode,
×
707
                                  PPort port,
708
                                  const std::string& phisycalDeviceAddress,
709
                                  const std::string& destinationDir,
710
                                  const std::vector<std::string>& options)
711
{
712
    TDlmsDeviceConfig deviceConfig;
×
713
    deviceConfig.DeviceConfig = std::make_shared<TDeviceConfig>();
×
714

715
    if (options.size() > 0) {
×
716
        deviceConfig.ClientAddress = atoi(options[0].c_str());
×
717
    }
718

719
    if (options.size() > 1) {
×
720
        const std::unordered_map<std::string, DLMS_AUTHENTICATION> auths = {{"lowest", DLMS_AUTHENTICATION_NONE},
×
721
                                                                            {"low", DLMS_AUTHENTICATION_LOW},
×
722
                                                                            {"high", DLMS_AUTHENTICATION_HIGH},
×
723
                                                                            {"MD5", DLMS_AUTHENTICATION_HIGH_MD5},
×
724
                                                                            {"SHA1", DLMS_AUTHENTICATION_HIGH_SHA1},
×
725
                                                                            {"GMAC", DLMS_AUTHENTICATION_HIGH_GMAC},
×
726
                                                                            {"SHA256", DLMS_AUTHENTICATION_HIGH_SHA256},
×
727
                                                                            {"ECDSA", DLMS_AUTHENTICATION_HIGH_ECDSA}};
×
728
        auto modeIt = auths.find(options[1]);
×
729
        if (modeIt == auths.end()) {
×
730
            throw std::runtime_error("Unknown authentication mode: " + options[1]);
×
731
        }
732
        deviceConfig.Authentication = modeIt->second;
×
733
    }
734

735
    if (options.size() > 2) {
×
736
        deviceConfig.DeviceConfig->Password.insert(deviceConfig.DeviceConfig->Password.begin(),
×
737
                                                   options[2].begin(),
×
738
                                                   options[2].end());
×
739
    }
740

741
    deviceConfig.DeviceConfig->SlaveId = phisycalDeviceAddress;
×
742
    deviceConfig.DeviceConfig->ResponseTimeout = DEFAULT_RESPONSE_TIMEOUT;
×
743
    deviceConfig.DeviceConfig->FrameTimeout = DEFAULT_FRAME_TIMEOUT;
×
744

745
    TSerialDeviceFactory deviceFactory;
×
746
    TDlmsDevice::Register(deviceFactory);
×
747
    port->Open();
×
NEW
748
    TDlmsDevice device(deviceConfig, deviceFactory.GetProtocol("dlms"));
×
749
    std::cout << "Getting logical devices..." << std::endl;
×
NEW
750
    auto logicalDevices = device.GetLogicalDevices(*port);
×
751
    std::cout << "Logical devices:" << std::endl;
×
752
    for (const auto& ld: logicalDevices) {
×
753
        std::cout << ld.first << ": " << ld.second << std::endl;
×
754
    }
755
    std::cout << std::endl;
×
756
    for (const auto& ld: logicalDevices) {
×
757
        std::cout << "Getting objects for " << ld.second << " ..." << std::endl;
×
758
        deviceConfig.LogicalDeviceAddress = ld.first;
×
NEW
759
        TDlmsDevice d(deviceConfig, deviceFactory.GetProtocol("dlms"));
×
NEW
760
        auto objs = d.ReadAllObjects(*port, mode != 0);
×
761
        TObisCodeHints obisHints = LoadObisCodeHints();
×
762
        switch (mode) {
×
763
            case PRINT: {
×
764
                Print(objs, false, obisHints);
×
765
                break;
×
766
            }
767
            case VERBOSE_PRINT: {
×
768
                Print(objs, true, obisHints);
×
769
                break;
×
770
            }
771
            case GENERATE_TEMPLATE: {
×
772
                Json::StreamWriterBuilder builder;
×
773
                builder["indentation"] = "  ";
×
774
                std::unique_ptr<Json::StreamWriter> writer(builder.newStreamWriter());
×
775
                auto templateFile = destinationDir + "/" + ld.second + ".conf";
×
776
                std::ofstream f(templateFile);
×
777
                writer->write(GenerateDlmsDeviceTemplate(ld.second, deviceConfig, objs, obisHints), &f);
×
778
                std::cout << templateFile << " is generated" << std::endl;
×
779
                break;
×
780
            }
781
            case MAX_DEVICE_TEMPLATE_GENERATION_MODE: {
×
782
                // Do nothing as calling code should not pass the value
783
                break;
×
784
            }
785
        }
786
    }
787
}
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