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

wirenboard / wb-mqtt-serial / 2

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

Pull #1045

github

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

6873 of 9161 branches covered (75.02%)

12966 of 16879 relevant lines covered (76.82%)

1651.61 hits per line

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

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 DLMS_DEFAULT_RESPONSE_TIMEOUT(1000);
22
    const std::chrono::milliseconds DLMS_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)
290✔
44
        {
45
            auto bytes = WBMQTT::StringSplit(ln, ".");
870✔
46
            if (bytes.size() != 6) {
290✔
47
                throw TConfigParserException("Bad OBIS code '" + ln + "'");
×
48
            }
49
            for (const auto& b: bytes) {
2,030✔
50
                if (!std::all_of(b.cbegin(), b.cend(), ::isdigit)) {
1,740✔
51
                    throw TConfigParserException("Bad OBIS code '" + ln + "'");
×
52
                }
53
            }
54
        }
290✔
55

56
        const std::string& GetLogicalName() const
2✔
57
        {
58
            return LogicalName;
2✔
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")
288✔
87
        {}
288✔
88

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

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

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

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

125
            return std::make_shared<TDlmsDevice>(cfg, protocol);
4✔
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>(DLMS_DEFAULT_RESPONSE_TIMEOUT.count());
×
334
        device["frame_timeout_ms"] = static_cast<Json::Int>(DLMS_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)
2✔
351
    {
352
        try {
353
            return dynamic_cast<const TObisRegisterAddress&>(reg.GetAddress());
2✔
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)
288✔
362
{
363
    factory.RegisterProtocol(
288✔
364
        new TUint32SlaveIdProtocol("dlms", TRegisterTypes({{0, "default", "value", Double, true}})),
864✔
365
        new TDlmsDeviceFactory());
288✔
366
}
288✔
367

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

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

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

398
void TDlmsDevice::CheckCycle(TPort& port,
10✔
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;
20✔
404
    CGXReplyData reply;
20✔
405
    auto res = requestsGenerator(data);
10✔
406
    if (res != DLMS_ERROR_CODE_OK) {
10✔
407
        throw TSerialDeviceTransientErrorException(errorMsg + ". Can't generate request: " + GetErrorMessage(res));
×
408
    }
409
    for (auto& buf: data) {
20✔
410
        try {
411
            ReadDataBlock(port, buf.GetData(), buf.GetSize(), reply);
10✔
412
        } catch (const std::exception& e) {
×
413
            throw TSerialDeviceTransientErrorException(errorMsg + ". " + e.what());
×
414
        }
415
    }
416
    res = responseParser(reply);
10✔
417
    if (res != DLMS_ERROR_CODE_OK) {
10✔
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
}
10✔
424

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

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

446
    bool forceValueRead = true;
2✔
447

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

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

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

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

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

472
void TDlmsDevice::PrepareImpl(TPort& port)
2✔
473
{
474
    try {
475
        Disconnect(port);
2✔
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
481
        port.SleepSinceLastInteraction(DisconnectRetryTimeout);
×
482
        Disconnect(port);
×
483
    }
484
    InitializeConnection(port);
2✔
485
    SetTransferResult(true);
2✔
486
}
2✔
487

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

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

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

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

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

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

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

557
void TDlmsDevice::SendData(TPort& port, const std::string& str)
×
558
{
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)
10✔
563
{
564
    if (size == 0) {
10✔
565
        return;
×
566
    }
567
    ReadDLMSPacket(port, data, size, reply);
10✔
568
    while (reply.IsMoreData()) {
10✔
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
        }
574
        ReadDLMSPacket(port, bb.GetData(), bb.GetSize(), reply);
×
575
    }
576
}
577

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

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

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

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

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

633
std::map<int, std::string> TDlmsDevice::GetLogicalDevices(TPort& port)
×
634
{
635
    Disconnect(port);
×
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();
×
650
    Disconnect(port);
×
651
    return res;
×
652
}
653

654
const CGXDLMSObjectCollection& TDlmsDevice::ReadAllObjects(TPort& port, bool readAttributes)
×
655
{
656
    LOG(Debug) << "Getting association view ... ";
×
657
    Disconnect(port);
×
658
    InitializeConnection(port);
×
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 {
673
                    CheckCycle(
×
674
                        port,
675
                        [&](auto& data) { return Client->Read(obj, pos, data); },
×
676
                        [&](auto& reply) { return Client->UpdateValue(*obj, pos, reply.GetValue()); },
×
677
                        "");
×
678
                } catch (...) {
×
679
                }
680
            }
681
        }
682
    }
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 = DLMS_DEFAULT_RESPONSE_TIMEOUT;
×
743
    deviceConfig.DeviceConfig->FrameTimeout = DLMS_DEFAULT_FRAME_TIMEOUT;
×
744

745
    TSerialDeviceFactory deviceFactory;
×
746
    TDlmsDevice::Register(deviceFactory);
×
747
    port->Open();
×
748
    TDlmsDevice device(deviceConfig, deviceFactory.GetProtocol("dlms"));
×
749
    std::cout << "Getting logical devices..." << std::endl;
×
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;
×
759
        TDlmsDevice d(deviceConfig, deviceFactory.GetProtocol("dlms"));
×
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