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

wirenboard / wb-mqtt-opcua / 3

01 Aug 2025 11:29AM UTC coverage: 29.848%. Remained the same
3

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),
160
          Config(config),
NEW
161
          Driver(driver)
×
162
    {
NEW
163
        if (!Server) {
×
NEW
164
            throw std::runtime_error("OPC UA server initilization failed");
×
165
        }
166

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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