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

wirenboard / wb-mqtt-opcua / 2

01 Aug 2025 11:15AM UTC coverage: 29.848%. Remained the same
2

push

github

u236
refactor TServerImpl class

104 of 281 branches covered (37.01%)

0 of 138 new or added lines in 1 file covered. (0.0%)

2 existing lines in 1 file now uncovered.

157 of 526 relevant lines covered (29.85%)

0.9 hits per line

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

0.0
/src/OPCUAServer.cpp
1
#include "OPCUAServer.h"
2

3
#include <functional>
4
#include <stdexcept>
5
#include <vector>
6

7
#include "log.h"
8

9
#define LOG(logger) ::logger.Log() << "[OPCUA] "
10

11
namespace
12
{
13
    const char* LogCategoryNames[7] =
14
        {"network", "channel", "session", "server", "client", "userland", "securitypolicy"};
15

16
    void PrintLogMessage(WBMQTT::TLogger& logger, UA_LogCategory category, const char* msg, va_list args)
×
17
    {
18
        va_list args2;
19
        va_copy(args2, args);
×
20
        auto bufSize = 1 + vsnprintf(nullptr, 0, msg, args);
×
21
        std::string str(bufSize, '\0');
×
22
        vsnprintf(&str[0], bufSize, msg, args2);
×
23
        va_end(args2);
×
24
        logger.Log() << "[OPCUA] " << LogCategoryNames[category] << ": " << str;
×
25
    }
26

27
    extern "C" {
28
    void Log(void* context, UA_LogLevel level, UA_LogCategory category, const char* msg, va_list args)
×
29
    {
30
        switch (level) {
×
31
            case UA_LOGLEVEL_TRACE:
×
32
            case UA_LOGLEVEL_DEBUG:
33
                PrintLogMessage(Debug, category, msg, args);
×
34
                break;
×
35
            case UA_LOGLEVEL_INFO:
×
36
                PrintLogMessage(Info, category, msg, args);
×
37
                break;
×
38
            case UA_LOGLEVEL_WARNING:
×
39
                PrintLogMessage(Warn, category, msg, args);
×
40
                break;
×
41
            case UA_LOGLEVEL_ERROR:
×
42
            case UA_LOGLEVEL_FATAL:
43
                PrintLogMessage(Error, category, msg, args);
×
44
                break;
×
45
        }
46
    }
47

48
    void LogClear(void* logContext)
×
49
    {}
50

UNCOV
51
    UA_StatusCode ReadVariableCallback(UA_Server* sserver,
×
52
                                       const UA_NodeId* ssessionId,
53
                                       void* ssessionContext,
54
                                       const UA_NodeId* snodeId,
55
                                       void* snodeContext,
56
                                       UA_Boolean ssourceTimeStamp,
57
                                       const UA_NumericRange* range,
58
                                       UA_DataValue* dataValue)
59
    {
NEW
60
        OPCUA::TServerImpl* server = (OPCUA::TServerImpl*)(snodeContext);
×
NEW
61
        return server->ReadVariable(snodeId, dataValue);
×
62
    }
63

UNCOV
64
    UA_StatusCode WriteVariableCallback(UA_Server* server,
×
65
                                        const UA_NodeId* sessionId,
66
                                        void* sessionContext,
67
                                        const UA_NodeId* nodeId,
68
                                        void* nodeContext,
69
                                        const UA_NumericRange* range,
70
                                        const UA_DataValue* data)
71
    {
NEW
72
        OPCUA::TServerImpl* s = (OPCUA::TServerImpl*)(nodeContext);
×
NEW
73
        return s->WriteVariable(nodeId, data);
×
74
    }
75
    }
76

77
    UA_Logger MakeLogger()
×
78
    {
79
        UA_Logger logger = {Log, nullptr, LogClear};
×
80
        return logger;
×
81
    }
82

83
    void SetVariableAttributes(UA_VariableAttributes& attr, WBMQTT::PControl control)
×
84
    {
85
        attr.accessLevel =
×
86
            control->IsReadonly() ? UA_ACCESSLEVELMASK_READ : UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
×
87
        attr.displayName = UA_LOCALIZEDTEXT((char*)"en-US", (char*)control->GetId().c_str());
×
88
        attr.valueRank = UA_VALUERANK_SCALAR;
×
89
        attr.dataType = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATATYPE);
×
90
        try {
91
            auto v = control->GetValue();
×
92
            if (v.Is<bool>()) {
×
93
                attr.dataType = UA_NODEID_NUMERIC(0, UA_NS0ID_BOOLEAN);
×
94
                return;
×
95
            }
96
            if (v.Is<double>()) {
×
97
                attr.dataType = UA_NODEID_NUMERIC(0, UA_NS0ID_DOUBLE);
×
98
                return;
×
99
            }
100
            return;
×
101
        } catch (...) {
×
102
        }
103
    }
104

105
    void ConfigureOpcUaServer(UA_ServerConfig* serverCfg, const OPCUA::TServerConfig& config)
×
106
    {
107
        serverCfg->logger = MakeLogger();
×
108

109
        UA_ServerConfig_setBasics(serverCfg);
×
110
        serverCfg->allowEmptyVariables = UA_RULEHANDLING_ACCEPT;
×
111

112
        UA_BuildInfo_clear(&serverCfg->buildInfo);
×
113
        UA_ApplicationDescription_clear(&serverCfg->applicationDescription);
×
114
        serverCfg->applicationDescription.applicationUri = UA_STRING_ALLOC("urn:wb-mqtt-opcua.server.application");
×
115
        serverCfg->applicationDescription.productUri = UA_STRING_ALLOC("https://wirenboard.com");
×
116
        serverCfg->applicationDescription.applicationName =
117
            UA_LOCALIZEDTEXT_ALLOC("en", "Wiren Board MQTT to OPC UA gateway");
×
118
        serverCfg->applicationDescription.applicationType = UA_APPLICATIONTYPE_SERVER;
×
119

120
        if (!config.BindIp.empty()) {
×
121
            UA_String_clear(&serverCfg->customHostname);
×
122
            serverCfg->customHostname = UA_String_fromChars(config.BindIp.c_str());
×
123
        }
124

125
        auto res = UA_ServerConfig_addNetworkLayerTCP(serverCfg, config.BindPort, 0, 0);
×
126
        if (res != UA_STATUSCODE_GOOD) {
×
127
            throw std::runtime_error(std::string("OPC UA network layer configuration failed: ") +
×
128
                                     UA_StatusCode_name(res));
×
129
        }
130

131
        res = UA_ServerConfig_addSecurityPolicyNone(serverCfg, nullptr);
×
132
        if (res != UA_STATUSCODE_GOOD) {
×
133
            throw std::runtime_error(std::string("OPC UA security policy addition failed: ") + UA_StatusCode_name(res));
×
134
        }
135

136
        res = UA_AccessControl_default(serverCfg,
×
137
                                       true,
138
                                       &serverCfg->securityPolicies[serverCfg->securityPoliciesSize - 1].policyUri,
×
139
                                       0,
140
                                       nullptr);
141
        if (res != UA_STATUSCODE_GOOD) {
×
142
            throw std::runtime_error(std::string("OPC UA access control configuration failed: ") +
×
143
                                     UA_StatusCode_name(res));
×
144
        }
145

146
        res = UA_ServerConfig_addEndpoint(serverCfg, UA_SECURITY_POLICY_NONE_URI, UA_MESSAGESECURITYMODE_NONE);
×
147
        if (res != UA_STATUSCODE_GOOD) {
×
148
            throw std::runtime_error(std::string("OPC UA server endpoint allocation failed: ") +
×
149
                                     UA_StatusCode_name(res));
×
150
        }
151
    }
152

153
}
154

155
namespace OPCUA
156
{
NEW
157
    TServerImpl::TServerImpl(const TServerConfig& config, WBMQTT::PDeviceDriver driver)
×
NEW
158
        : Server(UA_Server_new()),
×
159
          IsRunning(true),
NEW
160
          Driver(driver)
×
161
    {
NEW
162
        if (!Server) {
×
NEW
163
            throw std::runtime_error("OPC UA server initilization failed");
×
164
        }
165

NEW
166
        Driver->On<WBMQTT::TControlValueEvent>(
×
NEW
167
            [&](const WBMQTT::TControlValueEvent& event) { ControlValueEventCallback(config, event); });
×
168

169
        // Load external controls
NEW
170
        std::vector<std::string> deviceIds;
×
NEW
171
        for (const auto& device: config.ObjectNodes) {
×
NEW
172
            LOG(Debug) << "'" << device.first << "' is added to filter";
×
NEW
173
            deviceIds.emplace_back(device.first);
×
174
        }
NEW
175
        Driver->SetFilter(WBMQTT::GetDeviceListFilter(deviceIds));
×
NEW
176
        Driver->WaitForReady();
×
177

178
        // Setup and run OPC UA server
NEW
179
        ConfigureOpcUaServer(UA_Server_getConfig(Server), config);
×
NEW
180
        ServerThread = std::thread([this]() {
×
NEW
181
            auto res = UA_Server_run(Server, &IsRunning);
×
182
            if (res != UA_STATUSCODE_GOOD) {
×
NEW
183
                LOG(Error) << UA_StatusCode_name(res);
×
NEW
184
                exit(1);
×
185
            }
NEW
186
        });
×
187
    }
188

NEW
189
    TServerImpl::~TServerImpl()
×
190
    {
NEW
191
        if (IsRunning) {
×
NEW
192
            IsRunning = false;
×
NEW
193
            if (ServerThread.joinable()) {
×
NEW
194
                ServerThread.join();
×
195
            }
196
        }
NEW
197
        if (Server) {
×
NEW
198
            UA_Server_delete(Server);
×
199
        }
200
    }
201

NEW
202
    bool TServerImpl::ControlExists(const std::string& nodeName)
×
203
    {
NEW
204
        std::unique_lock<std::mutex> lock(Mutex);
×
NEW
205
        return ControlMap.find(nodeName) != ControlMap.end();
×
206
    }
207

NEW
208
    void TServerImpl::AddControl(const std::string& nodeName, WBMQTT::PControl control)
×
209
    {
NEW
210
        std::unique_lock<std::mutex> lock(Mutex);
×
NEW
211
        ControlMap[nodeName] = control;
×
212
    }
213

NEW
214
    WBMQTT::PControl TServerImpl::GetControl(const std::string& nodeName)
×
215
    {
NEW
216
        std::unique_lock<std::mutex> lock(Mutex);
×
NEW
217
        auto it = ControlMap.find(nodeName);
×
NEW
218
        return it != ControlMap.end() ? it->second : nullptr;
×
219
    }
220

NEW
221
    UA_StatusCode TServerImpl::WriteVariable(const UA_NodeId* snodeId, const UA_DataValue* dataValue)
×
222
    {
NEW
223
        std::string nodeIdName((const char*)snodeId->identifier.string.data, snodeId->identifier.string.length);
×
NEW
224
        auto ctrl = GetControl(nodeIdName);
×
NEW
225
        if (!ctrl || ctrl->IsReadonly()) {
×
NEW
226
            LOG(Error) << "Variable node '" + nodeIdName + "' writing failed. "
×
NEW
227
                       << (ctrl ? "It is read only" : "It is not presented in MQTT");
×
NEW
228
            return UA_STATUSCODE_BADDEVICEFAILURE;
×
229
        }
NEW
230
        auto tx = Driver->BeginTx();
×
231
        try {
NEW
232
            if (dataValue->hasValue) {
×
NEW
233
                if (UA_Variant_hasScalarType(&dataValue->value, &UA_TYPES[UA_TYPES_BOOLEAN])) {
×
NEW
234
                    auto value = *(UA_Boolean*)dataValue->value.data;
×
NEW
235
                    ctrl->SetValue(tx, value).Sync();
×
NEW
236
                    LOG(Info) << "Variable node '" + nodeIdName + "' = " << value;
×
NEW
237
                    return UA_STATUSCODE_GOOD;
×
238
                }
NEW
239
                if (UA_Variant_hasScalarType(&dataValue->value, &UA_TYPES[UA_TYPES_DOUBLE])) {
×
NEW
240
                    auto value = *(UA_Double*)dataValue->value.data;
×
NEW
241
                    ctrl->SetValue(tx, value).Sync();
×
NEW
242
                    LOG(Info) << "Variable node '" + nodeIdName + "' = " << value;
×
NEW
243
                    return UA_STATUSCODE_GOOD;
×
244
                }
NEW
245
                if (UA_Variant_hasScalarType(&dataValue->value, &UA_TYPES[UA_TYPES_STRING])) {
×
NEW
246
                    auto value = (char*)((UA_String*)dataValue->value.data)->data;
×
NEW
247
                    ctrl->SetRawValue(tx, value).Sync();
×
NEW
248
                    LOG(Info) << "Variable node '" + nodeIdName + "' = " << value;
×
NEW
249
                    return UA_STATUSCODE_GOOD;
×
250
                }
251
            }
NEW
252
            return UA_STATUSCODE_BADDATATYPEIDUNKNOWN;
×
NEW
253
        } catch (const std::exception& e) {
×
NEW
254
            LOG(Error) << "Variable node '" + nodeIdName + "' write error: " << e.what();
×
NEW
255
            return UA_STATUSCODE_BADDEVICEFAILURE;
×
256
        }
257
    }
258

NEW
259
    UA_StatusCode TServerImpl::ReadVariable(const UA_NodeId* snodeId, UA_DataValue* dataValue)
×
260
    {
NEW
261
        std::string nodeIdName((const char*)snodeId->identifier.string.data, snodeId->identifier.string.length);
×
NEW
262
        auto ctrl = GetControl(nodeIdName);
×
NEW
263
        if (!ctrl) {
×
NEW
264
            LOG(Error) << "Control is not found '" + nodeIdName + "'";
×
NEW
265
            dataValue->hasStatus = true;
×
NEW
266
            dataValue->status = UA_STATUSCODE_BADNOCOMMUNICATION;
×
NEW
267
            return UA_STATUSCODE_GOOD;
×
268
        }
269
        try {
NEW
270
            dataValue->hasStatus = true;
×
NEW
271
            if (ctrl->GetError().find("r") != std::string::npos) {
×
NEW
272
                dataValue->status = UA_STATUSCODE_BAD;
×
273
            } else {
NEW
274
                dataValue->status = UA_STATUSCODE_GOOD;
×
275
            }
NEW
276
            auto v = ctrl->GetValue();
×
NEW
277
            if (v.Is<bool>()) {
×
NEW
278
                auto value = v.As<bool>();
×
NEW
279
                UA_Variant_setScalarCopy(&dataValue->value, &value, &UA_TYPES[UA_TYPES_BOOLEAN]);
×
280
            } else {
NEW
281
                if (v.Is<double>()) {
×
NEW
282
                    auto value = v.As<double>();
×
NEW
283
                    UA_Variant_setScalarCopy(&dataValue->value, &value, &UA_TYPES[UA_TYPES_DOUBLE]);
×
284
                } else {
NEW
285
                    UA_String stringValue = UA_String_fromChars((char*)v.As<std::string>().c_str());
×
NEW
286
                    UA_Variant_setScalarCopy(&dataValue->value, &stringValue, &UA_TYPES[UA_TYPES_STRING]);
×
NEW
287
                    UA_String_clear(&stringValue);
×
288
                }
289
            }
NEW
290
            dataValue->hasValue = true;
×
NEW
291
        } catch (const std::exception& e) {
×
NEW
292
            LOG(Error) << "Variable node '" + nodeIdName + "' read error: " << e.what();
×
NEW
293
            dataValue->hasStatus = true;
×
NEW
294
            dataValue->status = UA_STATUSCODE_BADNOCOMMUNICATION;
×
295
        }
NEW
296
        return UA_STATUSCODE_GOOD;
×
297
    }
298

NEW
299
    void TServerImpl::ControlValueEventCallback(const TServerConfig& config, const WBMQTT::TControlValueEvent& event)
×
300
    {
NEW
301
        if (event.RawValue.empty()) {
×
NEW
302
            return;
×
303
        }
NEW
304
        auto it = config.ObjectNodes.find(event.Control->GetDevice()->GetId());
×
NEW
305
        if (it == config.ObjectNodes.end()) {
×
NEW
306
            return;
×
307
        }
NEW
308
        std::string nodeName = it->first + "/" + event.Control->GetId();
×
NEW
309
        if (ControlExists(nodeName)) {
×
NEW
310
            return;
×
311
        }
NEW
312
        auto browseName = UA_QUALIFIEDNAME(1, (char*)it->first.c_str());
×
313
        auto res =
NEW
314
            UA_Server_browseSimplifiedBrowsePath(Server, UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER), 1, &browseName);
×
315
        auto parentNodeId =
NEW
316
            res.statusCode == UA_STATUSCODE_GOOD ? res.targets[0].targetId.nodeId : CreateObjectNode(it->first);
×
NEW
317
        for (auto& valueNode: it->second) {
×
NEW
318
            if (valueNode.DeviceControlPair != nodeName) {
×
NEW
319
                continue;
×
320
            }
NEW
321
            browseName = UA_QUALIFIEDNAME(1, (char*)event.Control->GetId().c_str());
×
NEW
322
            res = UA_Server_browseSimplifiedBrowsePath(Server, parentNodeId, 1, &browseName);
×
NEW
323
            if (res.statusCode != UA_STATUSCODE_GOOD) {
×
NEW
324
                AddControl(nodeName, event.Control);
×
NEW
325
                CreateVariableNode(parentNodeId, nodeName, event.Control);
×
NEW
326
                break;
×
327
            }
328
        }
329
    }
330

NEW
331
    UA_NodeId TServerImpl::CreateObjectNode(const std::string& nodeName)
×
332
    {
NEW
333
        UA_NodeId nodeId = UA_NODEID_STRING(1, (char*)nodeName.c_str());
×
NEW
334
        UA_ObjectAttributes oAttr = UA_ObjectAttributes_default;
×
NEW
335
        oAttr.displayName = UA_LOCALIZEDTEXT((char*)"en-US", (char*)nodeName.c_str());
×
NEW
336
        auto res = UA_Server_addObjectNode(Server,
×
337
                                           nodeId,
338
                                           UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
339
                                           UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
NEW
340
                                           UA_QUALIFIEDNAME(1, (char*)nodeName.c_str()),
×
341
                                           UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
342
                                           oAttr,
343
                                           nullptr,
344
                                           nullptr);
NEW
345
        if (res != UA_STATUSCODE_GOOD) {
×
NEW
346
            throw std::runtime_error("Object node '" + nodeName + "' creation failed: " + UA_StatusCode_name(res));
×
347
        }
NEW
348
        return nodeId;
×
349
    }
350

NEW
351
    void TServerImpl::CreateVariableNode(const UA_NodeId& parentNodeId,
×
352
                                         const std::string& nodeName,
353
                                         WBMQTT::PControl control)
354
    {
NEW
355
        UA_VariableAttributes oAttr = UA_VariableAttributes_default;
×
NEW
356
        SetVariableAttributes(oAttr, control);
×
357

358
        UA_DataSource dataSource;
NEW
359
        dataSource.read = ReadVariableCallback;
×
NEW
360
        dataSource.write = WriteVariableCallback;
×
361

NEW
362
        auto res = UA_Server_addDataSourceVariableNode(Server,
×
NEW
363
                                                       UA_NODEID_STRING(1, (char*)nodeName.c_str()),
×
364
                                                       parentNodeId,
365
                                                       UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
NEW
366
                                                       UA_QUALIFIEDNAME(1, (char*)control->GetId().c_str()),
×
367
                                                       UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
368
                                                       oAttr,
369
                                                       dataSource,
370
                                                       this,
371
                                                       nullptr);
NEW
372
        if (res != UA_STATUSCODE_GOOD) {
×
NEW
373
            throw std::runtime_error("Variable node '" + nodeName + "' creation failed: " + UA_StatusCode_name(res));
×
374
        }
375
    }
376

NEW
377
    std::unique_ptr<IServer> MakeServer(const TServerConfig& config, WBMQTT::PDeviceDriver driver)
×
378
    {
NEW
379
        return std::unique_ptr<IServer>(new TServerImpl(config, driver));
×
380
    }
381
}
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