first commit

This commit is contained in:
flowerstonezl
2026-06-09 16:50:28 +08:00
commit 70f675a48b
93 changed files with 3802 additions and 0 deletions

View File

@@ -0,0 +1,15 @@
softbus_package(softbus_opcua)
add_library(softbus_opcua STATIC
src/UaServerEngine.cpp
src/UaAddressSpaceBuilder.cpp
src/UaModelBinder.cpp
src/UaMethodBinder.cpp
src/UaEventBridge.cpp
)
softbus_export_include(softbus_opcua "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(softbus_opcua PUBLIC softbus_core softbus_api)
softbus_apply_open62541(softbus_opcua)
softbus_apply_platform_libs(softbus_opcua)

View File

@@ -0,0 +1,27 @@
#pragma once
#include <softbus_core/models/ObjectModel.h>
#if SOFTBUS_HAS_OPCUA
#include <open62541/types.h>
#else
typedef struct UA_Server UA_Server;
#endif
struct UA_Server;
namespace softbus::opcua {
class UaModelBinder;
class UaAddressSpaceBuilder {
public:
explicit UaAddressSpaceBuilder(UA_Server* server);
bool buildFromModel(const softbus::core::ObjectModelTree& tree, UaModelBinder* binder = nullptr);
private:
UA_Server* server_{nullptr};
};
} // namespace softbus::opcua

View File

@@ -0,0 +1,22 @@
#pragma once
#include <string>
struct UA_Server;
namespace softbus::opcua {
class UaEventBridge {
public:
explicit UaEventBridge(UA_Server* server);
void emitHeartbeat(const std::string& objectRef);
void emitDeviceOnline(const std::string& stableDeviceKey);
void emitDeviceOffline(const std::string& stableDeviceKey);
void emitParseError(const std::string& objectRef, const std::string& detail);
private:
UA_Server* server_{nullptr};
};
} // namespace softbus::opcua

View File

@@ -0,0 +1,26 @@
#pragma once
#include <softbus_api/ExecuteCommand.h>
#include <functional>
#include <string>
struct UA_Server;
namespace softbus::opcua {
using ExecuteCommandHandler = std::function<bool(const softbus::api::ExecuteCommand&)>;
class UaMethodBinder {
public:
explicit UaMethodBinder(UA_Server* server);
bool bindExecuteCommand(ExecuteCommandHandler handler);
bool dispatchCommand(const softbus::api::ExecuteCommand& command);
private:
UA_Server* server_{nullptr};
ExecuteCommandHandler handler_;
};
} // namespace softbus::opcua

View File

@@ -0,0 +1,28 @@
#pragma once
#include <softbus_core/models/DOMMessage.h>
#include <map>
#include <string>
#if SOFTBUS_HAS_OPCUA
#include <open62541/types.h>
#else
typedef struct UA_NodeId UA_NodeId;
#endif
namespace softbus::opcua {
class UaModelBinder {
public:
explicit UaModelBinder(UA_Server* server);
void registerPointNode(const std::string& objectRef, const UA_NodeId& nodeId);
bool bind(const softbus::core::DOMMessage& message);
private:
UA_Server* server_{nullptr};
std::map<std::string, UA_NodeId> pointNodes_;
};
} // namespace softbus::opcua

View File

@@ -0,0 +1,40 @@
#pragma once
#include <cstdint>
#include <functional>
#include <memory>
#include <string>
struct UA_Server;
namespace softbus::opcua {
struct SecurityConfig {
bool enableTls{false};
std::string certPath;
std::string keyPath;
std::string trustListPath;
};
class UaServerEngine {
public:
UaServerEngine();
~UaServerEngine();
UaServerEngine(const UaServerEngine&) = delete;
UaServerEngine& operator=(const UaServerEngine&) = delete;
bool start(uint16_t port, const std::string& applicationName, const SecurityConfig& security);
void stop();
void iterate();
bool isRunning() const { return running_; }
UA_Server* nativeServer() { return server_; }
private:
UA_Server* server_{nullptr};
bool running_{false};
SecurityConfig security_;
};
} // namespace softbus::opcua

View File

@@ -0,0 +1,119 @@
#include <softbus_opcua/UaAddressSpaceBuilder.h>
#include <softbus_core/identity/DeviceIdentity.h>
#include <softbus_core/logging/Logger.h>
#include <softbus_opcua/UaModelBinder.h>
#if SOFTBUS_HAS_OPCUA
#include <open62541/server.h>
#endif
namespace softbus::opcua {
UaAddressSpaceBuilder::UaAddressSpaceBuilder(UA_Server* server)
: server_(server)
{
}
#if SOFTBUS_HAS_OPCUA
namespace {
UA_NodeId makeStringNodeId(const std::string& nsAndId)
{
return UA_NODEID_STRING_ALLOC(1, nsAndId.c_str());
}
bool addObject(UA_Server* server, const UA_NodeId& parent, const std::string& browseName,
UA_NodeId* outNodeId)
{
UA_ObjectAttributes attr = UA_ObjectAttributes_default;
attr.displayName = UA_LOCALIZEDTEXT_ALLOC("en-US", browseName.c_str());
const UA_NodeId nodeId = makeStringNodeId(browseName);
const UA_StatusCode status = UA_Server_addObjectNode(
server,
nodeId,
parent,
UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
UA_QUALIFIEDNAME_ALLOC(1, browseName.c_str()),
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
attr,
nullptr,
outNodeId);
UA_ObjectAttributes_clear(&attr);
return status == UA_STATUSCODE_GOOD;
}
bool addVariable(UA_Server* server, const UA_NodeId& parent, const std::string& browseName,
const std::string& nodeKey, UA_NodeId* outNodeId)
{
UA_VariableAttributes attr = UA_VariableAttributes_default;
attr.displayName = UA_LOCALIZEDTEXT_ALLOC("en-US", browseName.c_str());
attr.dataType = UA_TYPES[UA_TYPES_DOUBLE].typeId;
UA_Double initial = 0.0;
UA_Variant_setScalarCopy(&attr.value, &initial, &UA_TYPES[UA_TYPES_DOUBLE]);
attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
const UA_NodeId nodeId = makeStringNodeId(nodeKey);
const UA_StatusCode status = UA_Server_addVariableNode(
server,
nodeId,
parent,
UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
UA_QUALIFIEDNAME_ALLOC(1, browseName.c_str()),
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
attr,
nullptr,
outNodeId);
UA_VariableAttributes_clear(&attr);
return status == UA_STATUSCODE_GOOD;
}
} // namespace
#endif
bool UaAddressSpaceBuilder::buildFromModel(const softbus::core::ObjectModelTree& tree, UaModelBinder* binder)
{
#if !SOFTBUS_HAS_OPCUA
(void)tree;
return false;
#else
if (!server_) {
return false;
}
UA_NodeId siteNode;
if (!addObject(server_, UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER), tree.site, &siteNode)) {
return false;
}
UA_NodeId systemNode;
if (!addObject(server_, siteNode, tree.system, &systemNode)) {
return false;
}
UA_NodeId assetNode;
if (!addObject(server_, systemNode, tree.asset, &assetNode)) {
return false;
}
for (const auto& device : tree.devices) {
UA_NodeId deviceNode;
if (!addObject(server_, assetNode, device.stableDeviceKey, &deviceNode)) {
continue;
}
for (const auto& point : device.points) {
const auto objectRef = softbus::core::DeviceIdentity::buildObjectRef(
tree, device.stableDeviceKey, point.name);
UA_NodeId pointNode;
if (addVariable(server_, deviceNode, point.name, objectRef.toPath(), &pointNode) && binder) {
binder->registerPointNode(objectRef.toPath(), pointNode);
}
}
}
softbus::core::Logger::instance().info("UaAddressSpaceBuilder", "Address space built for ", tree.site);
return true;
#endif
}
} // namespace softbus::opcua

View File

@@ -0,0 +1,36 @@
#include <softbus_opcua/UaEventBridge.h>
#include <softbus_core/logging/Logger.h>
namespace softbus::opcua {
UaEventBridge::UaEventBridge(UA_Server* server)
: server_(server)
{
}
void UaEventBridge::emitHeartbeat(const std::string& objectRef)
{
softbus::core::Logger::instance().debug("UaEventBridge", "Heartbeat stub for ", objectRef);
(void)server_;
}
void UaEventBridge::emitDeviceOnline(const std::string& stableDeviceKey)
{
softbus::core::Logger::instance().info("UaEventBridge", "Device online stub: ", stableDeviceKey);
(void)server_;
}
void UaEventBridge::emitDeviceOffline(const std::string& stableDeviceKey)
{
softbus::core::Logger::instance().info("UaEventBridge", "Device offline stub: ", stableDeviceKey);
(void)server_;
}
void UaEventBridge::emitParseError(const std::string& objectRef, const std::string& detail)
{
softbus::core::Logger::instance().warn("UaEventBridge", "Parse error at ", objectRef, ": ", detail);
(void)server_;
}
} // namespace softbus::opcua

View File

@@ -0,0 +1,157 @@
#include <softbus_opcua/UaMethodBinder.h>
#include <softbus_core/logging/Logger.h>
#include <nlohmann/json.hpp>
#if SOFTBUS_HAS_OPCUA
#include <open62541/server.h>
#endif
namespace softbus::opcua {
#if SOFTBUS_HAS_OPCUA
namespace {
UaMethodBinder* g_methodBinder = nullptr;
UA_StatusCode executeCommandMethod(UA_Server* server,
const UA_NodeId* sessionId,
void* sessionContext,
const UA_NodeId* methodId,
void* methodContext,
const UA_NodeId* objectId,
void* objectContext,
size_t inputSize,
const UA_Variant* input,
size_t outputSize,
UA_Variant* output)
{
(void)server;
(void)sessionId;
(void)sessionContext;
(void)methodId;
(void)methodContext;
(void)objectId;
(void)objectContext;
(void)outputSize;
if (!g_methodBinder || inputSize < 1) {
return UA_STATUSCODE_BADINVALIDARGUMENT;
}
const UA_String* jsonInput = static_cast<const UA_String*>(input[0].data);
if (!jsonInput) {
return UA_STATUSCODE_BADTYPEMISMATCH;
}
const std::string jsonStr(reinterpret_cast<const char*>(jsonInput->data), jsonInput->length);
const auto j = nlohmann::json::parse(jsonStr, nullptr, false);
if (j.is_discarded()) {
return UA_STATUSCODE_BADINVALIDARGUMENT;
}
softbus::api::ExecuteCommand cmd = softbus::api::ExecuteCommand::fromJson(j);
std::string error;
if (!cmd.validate(&error)) {
const std::string result = std::string(R"({"success":false,"error":")") + error + "\"}";
UA_String out = UA_STRING_ALLOC(result.c_str());
UA_Variant_setScalarCopy(output, &out, &UA_TYPES[UA_TYPES_STRING]);
UA_String_clear(&out);
return UA_STATUSCODE_GOOD;
}
const bool ok = g_methodBinder->dispatchCommand(cmd);
const std::string result = ok ? R"({"success":true})" : R"({"success":false})";
UA_String out = UA_STRING_ALLOC(result.c_str());
UA_Variant_setScalarCopy(output, &out, &UA_TYPES[UA_TYPES_STRING]);
UA_String_clear(&out);
return UA_STATUSCODE_GOOD;
}
} // namespace
#endif
UaMethodBinder::UaMethodBinder(UA_Server* server)
: server_(server)
{
}
bool UaMethodBinder::dispatchCommand(const softbus::api::ExecuteCommand& command)
{
if (!handler_) {
return false;
}
softbus::core::Logger::instance().infoTrace("UaMethodBinder", command.traceId,
"ExecuteCommand invoked via OPC UA method");
return handler_(command);
}
bool UaMethodBinder::bindExecuteCommand(ExecuteCommandHandler handler)
{
#if !SOFTBUS_HAS_OPCUA
(void)handler;
return false;
#else
handler_ = std::move(handler);
g_methodBinder = this;
UA_ObjectAttributes objAttr = UA_ObjectAttributes_default;
objAttr.displayName = UA_LOCALIZEDTEXT_ALLOC("en-US", "SoftBusControl");
UA_NodeId controlObjectId = UA_NODEID_STRING_ALLOC(1, "SoftBusControl");
UA_NodeId createdObjectId;
UA_StatusCode status = UA_Server_addObjectNode(
server_,
controlObjectId,
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
UA_QUALIFIEDNAME_ALLOC(1, "SoftBusControl"),
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
objAttr,
nullptr,
&createdObjectId);
UA_ObjectAttributes_clear(&objAttr);
if (status != UA_STATUSCODE_GOOD) {
return false;
}
UA_Argument inputArgument;
UA_Argument_init(&inputArgument);
inputArgument.description = UA_LOCALIZEDTEXT_ALLOC("en-US", "Command JSON");
inputArgument.name = UA_STRING_ALLOC("commandJson");
inputArgument.dataType = UA_TYPES[UA_TYPES_STRING].typeId;
inputArgument.valueRank = UA_VALUERANK_SCALAR;
UA_Argument outputArgument;
UA_Argument_init(&outputArgument);
outputArgument.description = UA_LOCALIZEDTEXT_ALLOC("en-US", "Result JSON");
outputArgument.name = UA_STRING_ALLOC("resultJson");
outputArgument.dataType = UA_TYPES[UA_TYPES_STRING].typeId;
outputArgument.valueRank = UA_VALUERANK_SCALAR;
UA_MethodAttributes methodAttr = UA_MethodAttributes_default;
methodAttr.displayName = UA_LOCALIZEDTEXT_ALLOC("en-US", "ExecuteCommand");
methodAttr.executable = true;
methodAttr.userExecutable = true;
status = UA_Server_addMethodNode(
server_,
UA_NODEID_STRING_ALLOC(1, "ExecuteCommand"),
createdObjectId,
UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
UA_QUALIFIEDNAME_ALLOC(1, "ExecuteCommand"),
methodAttr,
&executeCommandMethod,
1,
&inputArgument,
1,
&outputArgument,
nullptr,
nullptr);
UA_Argument_clear(&inputArgument);
UA_Argument_clear(&outputArgument);
UA_MethodAttributes_clear(&methodAttr);
return status == UA_STATUSCODE_GOOD;
#endif
}
} // namespace softbus::opcua

View File

@@ -0,0 +1,71 @@
#include <softbus_opcua/UaModelBinder.h>
#include <softbus_core/logging/Logger.h>
#if SOFTBUS_HAS_OPCUA
#include <open62541/server.h>
#endif
namespace softbus::opcua {
UaModelBinder::UaModelBinder(UA_Server* server)
: server_(server)
{
}
void UaModelBinder::registerPointNode(const std::string& objectRef, const UA_NodeId& nodeId)
{
#if SOFTBUS_HAS_OPCUA
UA_NodeId copy{};
UA_NodeId_copy(&nodeId, &copy);
pointNodes_[objectRef] = copy;
#else
(void)objectRef;
(void)nodeId;
#endif
}
bool UaModelBinder::bind(const softbus::core::DOMMessage& message)
{
#if !SOFTBUS_HAS_OPCUA
(void)message;
return false;
#else
if (!server_) {
return false;
}
const std::string key = message.id.empty() ? message.sourceId : message.id;
const auto it = pointNodes_.find(key);
if (it == pointNodes_.end()) {
softbus::core::Logger::instance().warnTrace("UaModelBinder", message.traceId,
"No OPC UA node registered for ", key);
return false;
}
double numericValue = 0.0;
if (message.value.contains("value")) {
numericValue = message.value["value"].get<double>();
} else if (message.value.contains("registerValue")) {
numericValue = message.value["registerValue"].get<double>();
}
UA_Variant variant;
UA_Variant_init(&variant);
UA_Variant_setScalarCopy(&variant, &numericValue, &UA_TYPES[UA_TYPES_DOUBLE]);
const UA_StatusCode status = UA_Server_writeValue(server_, it->second, variant);
UA_Variant_clear(&variant);
if (status == UA_STATUSCODE_GOOD) {
softbus::core::Logger::instance().infoTrace("UaModelBinder", message.traceId,
"Bound DOMMessage to OPC UA node ", key, " value=", numericValue);
return true;
}
softbus::core::Logger::instance().errorTrace("UaModelBinder", message.traceId,
"Failed to write OPC UA node ", key);
return false;
#endif
}
} // namespace softbus::opcua

View File

@@ -0,0 +1,80 @@
#include <softbus_opcua/UaServerEngine.h>
#include <softbus_core/logging/Logger.h>
#if SOFTBUS_HAS_OPCUA
#include <open62541/server.h>
#include <open62541/server_config_default.h>
#endif
namespace softbus::opcua {
UaServerEngine::UaServerEngine() = default;
UaServerEngine::~UaServerEngine()
{
stop();
}
bool UaServerEngine::start(uint16_t port, const std::string& applicationName, const SecurityConfig& security)
{
#if !SOFTBUS_HAS_OPCUA
(void)port;
(void)applicationName;
(void)security;
softbus::core::Logger::instance().error("UaServerEngine", "OPC UA support not built in");
return false;
#else
security_ = security;
if (security.enableTls) {
softbus::core::Logger::instance().warn("UaServerEngine",
"TLS/mTLS requested but not enabled in Phase1 build");
}
server_ = UA_Server_new();
UA_ServerConfig* config = UA_Server_getConfig(server_);
UA_StatusCode status = UA_ServerConfig_setMinimal(config, port, nullptr);
if (status != UA_STATUSCODE_GOOD) {
softbus::core::Logger::instance().error("UaServerEngine", "Failed to configure server");
UA_Server_delete(server_);
server_ = nullptr;
return false;
}
config->applicationDescription.applicationName = UA_LOCALIZEDTEXT_ALLOC("en-US", applicationName.c_str());
status = UA_Server_run_startup(server_);
if (status != UA_STATUSCODE_GOOD) {
softbus::core::Logger::instance().error("UaServerEngine", "Failed to start server");
UA_Server_delete(server_);
server_ = nullptr;
return false;
}
running_ = true;
softbus::core::Logger::instance().info("UaServerEngine", "OPC UA server started on port ", port);
return true;
#endif
}
void UaServerEngine::stop()
{
#if SOFTBUS_HAS_OPCUA
if (server_) {
UA_Server_run_shutdown(server_);
UA_Server_delete(server_);
server_ = nullptr;
}
#endif
running_ = false;
}
void UaServerEngine::iterate()
{
#if SOFTBUS_HAS_OPCUA
if (server_ && running_) {
UA_Server_run_iterate(server_, false);
}
#endif
}
} // namespace softbus::opcua