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

wirenboard / wb-mqtt-opcua / 5

30 Jul 2026 02:29PM UTC coverage: 61.438% (+1.8%) from 59.625%
5

push

github

web-flow
Regenerate empty config (#40)

203 of 320 branches covered (63.44%)

376 of 612 relevant lines covered (61.44%)

2.47 hits per line

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

68.51
/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)
15✔
17
    {
18
        va_list args2;
19
        va_copy(args2, args);
15✔
20
        auto bufSize = 1 + vsnprintf(nullptr, 0, msg, args);
15✔
21
        std::string str(bufSize, '\0');
15✔
22
        vsnprintf(&str[0], bufSize, msg, args2);
15✔
23
        va_end(args2);
15✔
24
        logger.Log() << "[OPCUA] " << LogCategoryNames[category] << ": " << str;
15✔
25
    }
15✔
26

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

48
    void LogClear(void* logContext)
3✔
49
    {}
3✔
50

51
    UA_StatusCode ReadVariableCallback(UA_Server* sserver,
2✔
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
    {
60
        OPCUA::TServerImpl* server = (OPCUA::TServerImpl*)(snodeContext);
2✔
61
        return server->ReadVariable(snodeId, dataValue);
2✔
62
    }
63

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
    {
72
        OPCUA::TServerImpl* s = (OPCUA::TServerImpl*)(nodeContext);
×
73
        return s->WriteVariable(nodeId, data);
×
74
    }
75
    }
76

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

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

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

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

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

120
        if (!config.BindIp.empty()) {
3✔
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);
3✔
126
        if (res != UA_STATUSCODE_GOOD) {
3✔
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);
3✔
132
        if (res != UA_STATUSCODE_GOOD) {
3✔
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,
6✔
137
                                       true,
138
                                       &serverCfg->securityPolicies[serverCfg->securityPoliciesSize - 1].policyUri,
3✔
139
                                       0,
140
                                       nullptr);
141
        if (res != UA_STATUSCODE_GOOD) {
3✔
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);
3✔
147
        if (res != UA_STATUSCODE_GOOD) {
3✔
148
            throw std::runtime_error(std::string("OPC UA server endpoint allocation failed: ") +
×
149
                                     UA_StatusCode_name(res));
×
150
        }
151
    }
3✔
152

153
    struct TBrowsePathResult
154
    {
155
        UA_BrowsePathResult Result;
156

157
        explicit TBrowsePathResult(UA_BrowsePathResult result): Result(result)
9✔
158
        {}
9✔
159

160
        ~TBrowsePathResult()
9✔
161
        {
9✔
162
            UA_BrowsePathResult_clear(&Result);
9✔
163
        }
9✔
164

165
        TBrowsePathResult(const TBrowsePathResult&) = delete;
166
        TBrowsePathResult& operator=(const TBrowsePathResult&) = delete;
167
    };
168

169
}
170

171
namespace OPCUA
172
{
173
    TServerImpl::TServerImpl(const TServerConfig& config, WBMQTT::PDeviceDriver driver)
3✔
174
        : Server(UA_Server_new()),
3✔
175
          IsRunning(true),
176
          Config(config),
177
          Driver(driver)
6✔
178
    {
179
        if (!Server) {
3✔
180
            throw std::runtime_error("OPC UA server initilization failed");
×
181
        }
182

183
        Driver->On<WBMQTT::TControlValueEvent>(
6✔
184
            [&](const WBMQTT::TControlValueEvent& event) { ControlValueEventCallback(event); });
6✔
185

186
        // Load external controls
187
        std::vector<std::string> deviceIds;
3✔
188
        for (const auto& device: config.ObjectNodes) {
6✔
189
            LOG(Debug) << "'" << device.first << "' is added to filter";
3✔
190
            deviceIds.emplace_back(device.first);
3✔
191
        }
192
        Driver->SetFilter(WBMQTT::GetDeviceListFilter(deviceIds));
3✔
193
        Driver->WaitForReady();
3✔
194

195
        // Setup and run OPC UA server
196
        ConfigureOpcUaServer(UA_Server_getConfig(Server), config);
3✔
197
        ServerThread = std::thread([this]() {
3✔
198
            auto res = UA_Server_run(Server, &IsRunning);
3✔
199
            if (res != UA_STATUSCODE_GOOD) {
3✔
200
                LOG(Error) << UA_StatusCode_name(res);
×
201
                exit(1);
×
202
            }
203
        });
9✔
204
    }
3✔
205

206
    TServerImpl::~TServerImpl()
10✔
207
    {
208
        if (IsRunning) {
6✔
209
            IsRunning = false;
6✔
210
            if (ServerThread.joinable()) {
6✔
211
                ServerThread.join();
6✔
212
            }
213
        }
214
        if (Server) {
6✔
215
            UA_Server_delete(Server);
6✔
216
        }
217
    }
10✔
218

219
    bool TServerImpl::ControlExists(const std::string& nodeName)
7✔
220
    {
221
        std::unique_lock<std::mutex> lock(Mutex);
7✔
222
        return ControlMap.find(nodeName) != ControlMap.end();
14✔
223
    }
224

225
    void TServerImpl::AddControl(const std::string& nodeName, WBMQTT::PControl control)
2✔
226
    {
227
        std::unique_lock<std::mutex> lock(Mutex);
4✔
228
        ControlMap[nodeName] = control;
2✔
229
    }
2✔
230

231
    void TServerImpl::RemoveControl(const std::string& nodeName)
1✔
232
    {
233
        std::unique_lock<std::mutex> lock(Mutex);
2✔
234
        ControlMap.erase(nodeName);
1✔
235
    }
1✔
236

237
    WBMQTT::PControl TServerImpl::GetControl(const std::string& nodeName)
5✔
238
    {
239
        std::unique_lock<std::mutex> lock(Mutex);
5✔
240
        auto it = ControlMap.find(nodeName);
5✔
241
        return it != ControlMap.end() ? it->second : nullptr;
15✔
242
    }
243

244
    UA_StatusCode TServerImpl::WriteVariable(const UA_NodeId* snodeId, const UA_DataValue* dataValue)
×
245
    {
246
        std::string nodeIdName((const char*)snodeId->identifier.string.data, snodeId->identifier.string.length);
×
247
        auto ctrl = GetControl(nodeIdName);
×
248
        if (!ctrl || ctrl->IsReadonly()) {
×
249
            LOG(Error) << "Variable node '" + nodeIdName + "' writing failed. "
×
250
                       << (ctrl ? "It is read only" : "It is not presented in MQTT");
×
251
            return UA_STATUSCODE_BADDEVICEFAILURE;
×
252
        }
253
        auto tx = Driver->BeginTx();
×
254
        try {
255
            if (dataValue->hasValue) {
×
256
                if (UA_Variant_hasScalarType(&dataValue->value, &UA_TYPES[UA_TYPES_BOOLEAN])) {
×
257
                    auto value = *(UA_Boolean*)dataValue->value.data;
×
258
                    ctrl->SetValue(tx, value).Sync();
×
259
                    LOG(Info) << "Variable node '" + nodeIdName + "' = " << value;
×
260
                    return UA_STATUSCODE_GOOD;
×
261
                }
262
                if (UA_Variant_hasScalarType(&dataValue->value, &UA_TYPES[UA_TYPES_DOUBLE])) {
×
263
                    auto value = *(UA_Double*)dataValue->value.data;
×
264
                    ctrl->SetValue(tx, value).Sync();
×
265
                    LOG(Info) << "Variable node '" + nodeIdName + "' = " << value;
×
266
                    return UA_STATUSCODE_GOOD;
×
267
                }
268
                if (UA_Variant_hasScalarType(&dataValue->value, &UA_TYPES[UA_TYPES_STRING])) {
×
269
                    auto value = (char*)((UA_String*)dataValue->value.data)->data;
×
270
                    ctrl->SetRawValue(tx, value).Sync();
×
271
                    LOG(Info) << "Variable node '" + nodeIdName + "' = " << value;
×
272
                    return UA_STATUSCODE_GOOD;
×
273
                }
274
            }
275
            return UA_STATUSCODE_BADDATATYPEIDUNKNOWN;
×
276
        } catch (const std::exception& e) {
×
277
            LOG(Error) << "Variable node '" + nodeIdName + "' write error: " << e.what();
×
278
            return UA_STATUSCODE_BADDEVICEFAILURE;
×
279
        }
280
    }
281

282
    UA_StatusCode TServerImpl::ReadVariable(const UA_NodeId* snodeId, UA_DataValue* dataValue)
2✔
283
    {
284
        std::string nodeIdName((const char*)snodeId->identifier.string.data, snodeId->identifier.string.length);
4✔
285
        auto ctrl = GetControl(nodeIdName);
4✔
286
        if (!ctrl) {
2✔
287
            LOG(Error) << "Control is not found '" + nodeIdName + "'";
×
288
            dataValue->hasStatus = true;
×
289
            dataValue->status = UA_STATUSCODE_BADNOCOMMUNICATION;
×
290
            return UA_STATUSCODE_GOOD;
×
291
        }
292
        try {
293
            dataValue->hasStatus = true;
2✔
294
            if (ctrl->GetError().find("r") != std::string::npos) {
2✔
295
                dataValue->status = UA_STATUSCODE_BAD;
×
296
            } else {
297
                dataValue->status = UA_STATUSCODE_GOOD;
2✔
298
            }
299
            auto v = ctrl->GetValue();
2✔
300
            if (v.Is<bool>()) {
2✔
301
                auto value = v.As<bool>();
×
302
                UA_Variant_setScalarCopy(&dataValue->value, &value, &UA_TYPES[UA_TYPES_BOOLEAN]);
×
303
            } else {
304
                if (v.Is<double>()) {
2✔
305
                    auto value = v.As<double>();
2✔
306
                    UA_Variant_setScalarCopy(&dataValue->value, &value, &UA_TYPES[UA_TYPES_DOUBLE]);
2✔
307
                } else {
308
                    UA_String stringValue = UA_String_fromChars((char*)v.As<std::string>().c_str());
×
309
                    UA_Variant_setScalarCopy(&dataValue->value, &stringValue, &UA_TYPES[UA_TYPES_STRING]);
×
310
                    UA_String_clear(&stringValue);
×
311
                }
312
            }
313
            dataValue->hasValue = true;
2✔
314
        } catch (const std::exception& e) {
×
315
            LOG(Error) << "Variable node '" + nodeIdName + "' read error: " << e.what();
×
316
            dataValue->hasStatus = true;
×
317
            dataValue->status = UA_STATUSCODE_BADNOCOMMUNICATION;
×
318
        }
319
        return UA_STATUSCODE_GOOD;
2✔
320
    }
321

322
    void TServerImpl::ControlValueEventCallback(const WBMQTT::TControlValueEvent& event)
7✔
323
    {
324
        if (event.RawValue.empty()) {
7✔
325
            return;
×
326
        }
327
        auto it = Config.ObjectNodes.find(event.Control->GetDevice()->GetId());
7✔
328
        if (it == Config.ObjectNodes.end()) {
7✔
329
            return;
×
330
        }
331
        std::string nodeName = it->first + "/" + event.Control->GetId();
7✔
332
        if (ControlExists(nodeName)) {
7✔
333
            return;
×
334
        }
335
        try {
336
            auto browseName = UA_QUALIFIEDNAME(1, (char*)it->first.c_str());
7✔
337
            TBrowsePathResult parentBpr(
338
                UA_Server_browseSimplifiedBrowsePath(Server,
339
                                                     UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
340
                                                     1,
341
                                                     &browseName));
14✔
342
            auto parentNodeId = parentBpr.Result.statusCode == UA_STATUSCODE_GOOD
7✔
343
                                    ? parentBpr.Result.targets[0].targetId.nodeId
4✔
344
                                    : CreateObjectNode(it->first);
7✔
345
            for (auto& valueNode: it->second) {
12✔
346
                if (valueNode.DeviceControlPair != nodeName) {
7✔
347
                    continue;
5✔
348
                }
349
                browseName = UA_QUALIFIEDNAME(1, (char*)event.Control->GetId().c_str());
2✔
350
                TBrowsePathResult childBpr(UA_Server_browseSimplifiedBrowsePath(Server, parentNodeId, 1, &browseName));
3✔
351
                if (childBpr.Result.statusCode != UA_STATUSCODE_GOOD) {
2✔
352
                    AddControl(nodeName, event.Control);
2✔
353
                    try {
354
                        CreateVariableNode(parentNodeId, nodeName, event.Control);
3✔
355
                    } catch (...) {
2✔
356
                        RemoveControl(nodeName);
1✔
357
                        throw;
1✔
358
                    }
359
                    break;
1✔
360
                }
361
            }
362
        } catch (const std::exception& e) {
1✔
363
            LOG(Error) << "Failed to add control '" << nodeName << "': " << e.what();
1✔
364
        }
365
    }
366

367
    UA_NodeId TServerImpl::CreateObjectNode(const std::string& nodeName)
3✔
368
    {
369
        UA_NodeId nodeId = UA_NODEID_STRING(1, (char*)nodeName.c_str());
3✔
370
        UA_ObjectAttributes oAttr = UA_ObjectAttributes_default;
3✔
371
        oAttr.displayName = UA_LOCALIZEDTEXT((char*)"en-US", (char*)nodeName.c_str());
3✔
372
        auto res = UA_Server_addObjectNode(Server,
6✔
373
                                           nodeId,
374
                                           UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
375
                                           UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
376
                                           UA_QUALIFIEDNAME(1, (char*)nodeName.c_str()),
3✔
377
                                           UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
378
                                           oAttr,
379
                                           nullptr,
380
                                           nullptr);
381
        if (res != UA_STATUSCODE_GOOD) {
3✔
382
            throw std::runtime_error("Object node '" + nodeName + "' creation failed: " + UA_StatusCode_name(res));
×
383
        }
384
        return nodeId;
6✔
385
    }
386

387
    void TServerImpl::CreateVariableNode(const UA_NodeId& parentNodeId,
1✔
388
                                         const std::string& nodeName,
389
                                         WBMQTT::PControl control)
390
    {
391
        UA_VariableAttributes oAttr = UA_VariableAttributes_default;
1✔
392
        SetVariableAttributes(oAttr, control);
1✔
393

394
        UA_DataSource dataSource;
395
        dataSource.read = ReadVariableCallback;
1✔
396
        dataSource.write = WriteVariableCallback;
1✔
397

398
        auto res = UA_Server_addDataSourceVariableNode(Server,
2✔
399
                                                       UA_NODEID_STRING(1, (char*)nodeName.c_str()),
1✔
400
                                                       parentNodeId,
401
                                                       UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
402
                                                       UA_QUALIFIEDNAME(1, (char*)control->GetId().c_str()),
1✔
403
                                                       UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
404
                                                       oAttr,
405
                                                       dataSource,
406
                                                       this,
407
                                                       nullptr);
408
        if (res != UA_STATUSCODE_GOOD) {
1✔
409
            throw std::runtime_error("Variable node '" + nodeName + "' creation failed: " + UA_StatusCode_name(res));
×
410
        }
411
    }
1✔
412

413
    std::unique_ptr<IServer> MakeServer(const TServerConfig& config, WBMQTT::PDeviceDriver driver)
×
414
    {
415
        return std::unique_ptr<IServer>(new TServerImpl(config, driver));
×
416
    }
417
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc