Skip to content

Commit

Permalink
Merge pull request #132 from zhaoxi-scut/opcua-add-datatype
Browse files Browse the repository at this point in the history
  • Loading branch information
zhaoxi-scut committed Jul 23, 2024
2 parents ac75c24 + 58a1e19 commit ea52a51
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 81 deletions.
34 changes: 17 additions & 17 deletions doc/tutorials/modules/tools/opcua.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ OPC UA 的设计目标是建立一种通用的、独立于厂商和平台的通

3. 引用类型节点 **ReferenceType** :引用类型描述了引用的语义,而引用用于定义引用两端的节点之间的关系。最常用的引用类型如 Organizes(在 RMVL 中表示为 `rm::nodeOrganizes`),表示节点之间的层级关系,如同文件夹与文件夹内的文件,数据层级复杂的设备,需要通过多种引用类型对设备信息节点之间的关系进行描述。

4. 数据类型节点 **DataType** :数据类型节点描述了变量节点中变量的数据类型。在 OPC UA 信息模型在命名空间 `0` 中定义了多种内置的数据类型,包括整型、浮点型、 字符串等多个类型,能对变量的数据进行准确的描述。也可以自定义数据类型,比如描述二维坐标的 `2DPoint` 等类型,获得更符合数据本身的描述。@note 注意:此类节点并不能提供具体的数据构成,只是提供了数据类型的一个描述,因此 RMVL 中的 @ref opcua 不提供该功能。若计划提供数据的构成,比如包含的数据长度等信息,请使用变量类型节点 rm::VariableType 。
4. 数据类型节点 rm::DataType :数据类型节点描述了变量节点中变量的数据类型。在 OPC UA 信息模型在命名空间 `0` 中定义了多种内置的数据类型,包括整型、浮点型、 字符串等多个类型,能对变量的数据进行准确的描述。也可以自定义数据类型,比如描述二维坐标的 `2DPoint` 等类型,获得更符合数据本身的描述。@note 注意:此类节点并不能提供具体的数据构成,只是提供了数据类型的一个描述,因此 RMVL 中的 @ref opcua 仅提供内置数据类型。若计划提供数据的构成,比如包含的数据长度等信息,请使用变量类型节点 rm::VariableType 。

5. 变量类型节点 rm::VariableType :该节点提供了对变量节点的定义,是设备中各种数据的抽象。常用引用中的 HasTypeDefinition 引用节点连接数据类型节点,对数据类型进行描述(在 RMVL 中表示为 `rm::nodeHasTypeDefinition`)。用 HasProperty 引用节点对数据的语义进行描述(在 RMVL 中表示为 `rm::nodeHasProperty`)。也可以使用自定义的数据类型节点对变量的数据进行描述,具有灵活性。

Expand Down Expand Up @@ -98,7 +98,7 @@ int main()
int main()
{
// 创建 OPC UA 客户端,连接到 127.0.0.1:4840
rm::Client clt("opc.tcp://127.0.0.1:4840");
rm::Client cli("opc.tcp://127.0.0.1:4840");

/* other code */
}
Expand Down Expand Up @@ -141,12 +141,12 @@ int main()

int main()
{
rm::Client clt("opc.tcp://127.0.0.1:4840");
rm::Client cli("opc.tcp://127.0.0.1:4840");

// 使用管道运算符 "|" 进行路径搜索,寻找待读取的变量
auto node = rm::nodeObjectsFolder | clt.find("number");
auto node = rm::nodeObjectsFolder | cli.find("number");
// 读取变量
rm::Variable target = clt.read(node);
rm::Variable target = cli.read(node);
// 判断是否为空
if (target.empty())
{
Expand Down Expand Up @@ -210,14 +210,14 @@ int main()

int main()
{
rm::Client clt("opc.tcp://127.0.0.1:4840");
rm::Client cli("opc.tcp://127.0.0.1:4840");

// 设置输入参数,1 和 2 是 Int32 类型的,因此可以直接隐式构造
std::vector<rm::Variable> input = {1, 2};
// 设置输出参数,用来存储结果
std::vector<rm::Variable> output;
// 调用方法,判断调用是否成功
if (!clt.call("add", input, output))
if (!cli.call("add", input, output))
{
ERROR_("Failed to call the method");
return 0;
Expand Down Expand Up @@ -287,17 +287,17 @@ int main()

int main()
{
rm::Client clt("opc.tcp://127.0.0.1:4840");
rm::Client cli("opc.tcp://127.0.0.1:4840");

// 路径搜索寻找 C2
auto node_c2 = rm::nodeObjectsFolder | clt.find("A") | clt.find("B1") | clt.find("C2");
auto node_c2 = rm::nodeObjectsFolder | cli.find("A") | cli.find("B1") | cli.find("C2");
rm::Variable c2;
clt.read(node_c2, c2);
cli.read(node_c2, c2);
std::cout << rm::Variable::cast<int>(c2) << std::endl;
// 路径搜索寻找 C3
auto node_c3 = rm::nodeObjectsFolder | clt.find("A") | clt.find("B2") | clt.find("C3");
auto node_c3 = rm::nodeObjectsFolder | cli.find("A") | cli.find("B2") | cli.find("C3");
rm::Variable c3;
clt.read(node_c3, c3);
cli.read(node_c3, c3);
std::cout << rm::Variable::cast<const char *>(c3) << std::endl;
}
```
Expand Down Expand Up @@ -371,13 +371,13 @@ using namespace std::chrono_literals;

int main()
{
rm::Client clt("opc.tcp://127.0.0.1:4840");
auto node = rm::nodeObjectsFolder | clt.find("number");
rm::Client cli("opc.tcp://127.0.0.1:4840");
auto node = rm::nodeObjectsFolder | cli.find("number");
for (int i = 0; i < 100; ++i)
{
std::this_thread::sleep_for(1s);
// 写入数据,i + 200 隐式构造成了 rm::Variable
bool success = clt.write(node, i + 200);
bool success = cli.write(node, i + 200);
if (!success)
ERROR_("Failed to write data to the variable.");
}
Expand All @@ -398,8 +398,8 @@ void onChange(UA_Client *, UA_UInt32, void *, UA_UInt32, void *, UA_DataValue *v
int main()
{
rm::Client clt("opc.tcp://127.0.0.1:4840");
auto node = rm::nodeObjectsFolder | clt.find("number");
rm::Client cli("opc.tcp://127.0.0.1:4840");
auto node = rm::nodeObjectsFolder | cli.find("number");
// 监视变量,这里的 onChange 同样可以写成无捕获列表的 lambda 表达式,因为存在隐式转换
client.monitor(node, onChange, 5);
// 线程阻塞
Expand Down
2 changes: 1 addition & 1 deletion modules/opcua/include/rmvl/opcua/method.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ namespace rm
struct Argument final
{
std::string name; //!< 参数名称
UA_TypeFlag data_type{}; //!< 参数数据类型 @note 形如 `UA_TYPES_<xxx>` 的类型标志位
DataType data_type{}; //!< 参数数据类型 @note 形如 `UA_TYPES_<xxx>` 的类型标志位
uint32_t dims{1U}; //!< 参数维数,单数据则是 `1`,数组则是数组长度 @warning 不能为 `0`
std::string description{}; //!< 参数描述
};
Expand Down
8 changes: 4 additions & 4 deletions modules/opcua/include/rmvl/opcua/subscriber.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ struct FieldMetaData final

/**
* @brief 字段类型
* @see UA_TypeFlag
* @see DataType
*/
UA_TypeFlag type;
DataType type;

//! 字段 ValueRank
int value_rank{};
Expand All @@ -45,11 +45,11 @@ struct FieldMetaData final
* @brief 创建字段元数据
*
* @param[in] name_ 字段名称
* @param[in] type_ 字段类型,可参考 @ref UA_TypeFlag
* @param[in] type_ 字段类型,可参考 @ref DataType
* @param[in] value_rank_ 字段 ValueRank
* @param[in] ns_ 命名空间索引,默认为 `1`
*/
FieldMetaData(const std::string &name_, UA_TypeFlag type_, int value_rank_, uint16_t ns_ = 1U) : ns(ns_), name(name_), type(type_), value_rank(value_rank_) {}
FieldMetaData(const std::string &name_, DataType type_, int value_rank_, uint16_t ns_ = 1U) : ns(ns_), name(name_), type(type_), value_rank(value_rank_) {}

/**
* @brief 从变量创建字段元数据
Expand Down
53 changes: 31 additions & 22 deletions modules/opcua/include/rmvl/opcua/utilities.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
#pragma once

#include <string>
#include <typeindex>
#include <string_view>
#include <typeindex>
#include <unordered_map>

#include <open62541/types_generated_handling.h>
Expand Down Expand Up @@ -66,13 +66,12 @@ struct NodeId final
inline UA_NodeId *operator&() { return &nid; }
inline const UA_NodeId *operator&() const { return &nid; }


//! 到 `UA_NodeId` 的转换
constexpr operator UA_NodeId() const { return nid; }

/**
* @brief 判断节点 ID 是否为空
*
*
* @return 是否为空
*/
inline bool empty() const { return UA_NodeId_isNull(&nid); }
Expand All @@ -81,6 +80,34 @@ struct NodeId final
inline void clear() { UA_NodeId_clear(&nid); }
};

//! OPC UA 数据类型
class DataType
{
//! 形如 `UA_TYPES_<xxx>` 的类型标志位
static const std::unordered_map<std::type_index, UA_UInt32> _map;
//! 数据类型 ID
UA_UInt32 id{};

public:
DataType() = default;

/**
* @brief 从 `UA_TYPES_<xxx>` 枚举值构造数据类型
*
* @param[in] id `UA_TYPES_<xxx>` 枚举值
*/
constexpr DataType(UA_UInt32 id) : id(id) {}

/**
* @brief 从 `std::type_info` 构造数据类型
*
* @param[in] tp `std::type_info` 类型,可用 `typeid()` 获取
*/
DataType(const std::type_info &tp) : id(_map.at(std::type_index(tp))) {}

operator UA_UInt32() const { return id; }
};

/**
* @brief 用户信息
* @brief
Expand All @@ -94,24 +121,6 @@ struct UserConfig final
std::string passwd; //!< 密码
};

//! 类型标志位,可通过 `typeflag.at(xxx)` 进行获取
using UA_TypeFlag = UA_UInt32;

//! 获取形如 `UA_TYPES_<xxx>` 的类型标志位
inline const std::unordered_map<std::type_index, UA_TypeFlag> typeflag =
{{std::type_index(typeid(bool)), UA_TYPES_BOOLEAN},
{std::type_index(typeid(int8_t)), UA_TYPES_SBYTE},
{std::type_index(typeid(uint8_t)), UA_TYPES_BYTE},
{std::type_index(typeid(UA_Int16)), UA_TYPES_INT16},
{std::type_index(typeid(UA_UInt16)), UA_TYPES_UINT16},
{std::type_index(typeid(UA_Int32)), UA_TYPES_INT32},
{std::type_index(typeid(UA_UInt32)), UA_TYPES_UINT32},
{std::type_index(typeid(UA_Int64)), UA_TYPES_INT64},
{std::type_index(typeid(UA_UInt64)), UA_TYPES_UINT64},
{std::type_index(typeid(UA_Float)), UA_TYPES_FLOAT},
{std::type_index(typeid(UA_Double)), UA_TYPES_DOUBLE},
{std::type_index(typeid(const char *)), UA_TYPES_STRING}};

//! 传输协议
enum class TransportID : uint8_t
{
Expand Down Expand Up @@ -168,7 +177,7 @@ using FindNodeInServer = ::std::tuple<UA_Server *, ::std::string_view, uint16_t>
using FindNodeInClient = ::std::tuple<UA_Client *, ::std::string_view, uint16_t>;

/**
* @brief `UA_TypeFlag` 到对应 `NS0` 下的类型名的映射
* @brief `DataType` 到对应 `NS0` 下的类型名的映射
* @brief
* - 例如:`typeflag_ns0[UA_TYPES_INT16]` 为 `UA_NS0ID_INT16`
*/
Expand Down
20 changes: 10 additions & 10 deletions modules/opcua/include/rmvl/opcua/variable.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class VariableType final
//! 默认数据
std::any _value;
//! 数据类型
UA_TypeFlag _data_type{};
DataType _data_type{};
//! 数据大小
UA_UInt32 _size{};

Expand All @@ -74,7 +74,7 @@ class VariableType final
* @param[in] str 字面量字符串
*/
template <size_t N>
VariableType(const char (&str)[N]) : _value(str), _data_type(typeflag.at(typeid(const char *))), _size(1) {}
VariableType(const char (&str)[N]) : _value(str), _data_type(DataType(typeid(const char *))), _size(1) {}

/**
* @brief 单值构造,设置默认值
Expand All @@ -83,7 +83,7 @@ class VariableType final
* @param[in] val 标量、数量值
*/
template <typename Tp, typename Enable = std::enable_if_t<std::is_fundamental_v<Tp> || std::is_same_v<Tp, const char *>>>
VariableType(Tp &&val) : _value(val), _data_type(typeflag.at(typeid(Tp))), _size(1) {}
VariableType(Tp &&val) : _value(val), _data_type(DataType(typeid(Tp))), _size(1) {}

/**
* @brief 列表构造,设置默认值
Expand All @@ -92,7 +92,7 @@ class VariableType final
* @param[in] arr 列表、数组
*/
template <typename Tp, typename Enable = std::enable_if_t<std::is_fundamental_v<Tp> && !std::is_same_v<bool, Tp>>>
VariableType(const std::vector<Tp> &arr) : _value(arr), _data_type(typeflag.at(typeid(Tp))), _size(arr.size()) {}
VariableType(const std::vector<Tp> &arr) : _value(arr), _data_type(DataType(typeid(Tp))), _size(arr.size()) {}

/**
* @brief 将变量类型节点转化为指定类型的数据
Expand All @@ -108,7 +108,7 @@ class VariableType final
inline const auto &data() const { return _value; }

//! 获取数据类型
inline UA_TypeFlag getDataType() const { return _data_type; }
inline DataType getDataType() const { return _data_type; }

//! 获取大小 @note 未初始化则返回 `0`
inline UA_UInt32 size() const { return _size; }
Expand Down Expand Up @@ -155,7 +155,7 @@ class Variable final
//! 数据
std::any _value;
//! 数据类型
UA_TypeFlag _data_type{};
DataType _data_type{};
//! 数据大小
UA_UInt32 _size{};

Expand All @@ -168,7 +168,7 @@ class Variable final
* @param[in] str 字面量字符串
*/
template <unsigned int N>
Variable(const char (&str)[N]) : access_level(3U), _value(str), _data_type(typeflag.at(typeid(const char *))), _size(1) {}
Variable(const char (&str)[N]) : access_level(3U), _value(str), _data_type(DataType(typeid(const char *))), _size(1) {}

/**
* @brief 单值构造
Expand All @@ -177,7 +177,7 @@ class Variable final
* @param[in] val 标量、数量值
*/
template <typename Tp, typename Enable = std::enable_if_t<std::is_fundamental_v<Tp> || std::is_same_v<Tp, const char *>>>
Variable(const Tp &val) : access_level(3U), _value(val), _data_type(typeflag.at(typeid(Tp))), _size(1) {}
Variable(const Tp &val) : access_level(3U), _value(val), _data_type(DataType(typeid(Tp))), _size(1) {}

/**
* @brief 列表构造
Expand All @@ -186,7 +186,7 @@ class Variable final
* @param[in] arr 列表、数组
*/
template <typename Tp, typename Enable = std::enable_if_t<std::is_fundamental_v<Tp> && !std::is_same_v<bool, Tp>>>
Variable(const std::vector<Tp> &arr) : access_level(3U), _value(arr), _data_type(typeflag.at(typeid(Tp))), _size(arr.size()) {}
Variable(const std::vector<Tp> &arr) : access_level(3U), _value(arr), _data_type(DataType(typeid(Tp))), _size(arr.size()) {}

/**
* @brief 从变量类型构造新的变量节点
Expand Down Expand Up @@ -247,7 +247,7 @@ class Variable final
inline const auto &data() const { return _value; }

//! 获取形如 `UA_TYPES_<xxx>` 的数据类型
inline UA_TypeFlag getDataType() const { return _data_type; }
inline DataType getDataType() const { return _data_type; }

//! 获取大小 @note 未初始化则返回 `0`
inline UA_UInt32 size() const { return _size; }
Expand Down
16 changes: 15 additions & 1 deletion modules/opcua/src/helper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,20 @@
namespace rm
{

const std::unordered_map<std::type_index, UA_UInt32> DataType::_map =
{{std::type_index(typeid(bool)), UA_TYPES_BOOLEAN},
{std::type_index(typeid(int8_t)), UA_TYPES_SBYTE},
{std::type_index(typeid(uint8_t)), UA_TYPES_BYTE},
{std::type_index(typeid(UA_Int16)), UA_TYPES_INT16},
{std::type_index(typeid(UA_UInt16)), UA_TYPES_UINT16},
{std::type_index(typeid(UA_Int32)), UA_TYPES_INT32},
{std::type_index(typeid(UA_UInt32)), UA_TYPES_UINT32},
{std::type_index(typeid(UA_Int64)), UA_TYPES_INT64},
{std::type_index(typeid(UA_UInt64)), UA_TYPES_UINT64},
{std::type_index(typeid(UA_Float)), UA_TYPES_FLOAT},
{std::type_index(typeid(UA_Double)), UA_TYPES_DOUBLE},
{std::type_index(typeid(const char *)), UA_TYPES_STRING}};

NodeId operator|(NodeId origin, FindNodeInServer &&fnis)
{
if (origin.empty())
Expand Down Expand Up @@ -278,7 +292,7 @@ UA_Variant cvtVariable(const Variable &val)
Variable cvtVariable(const UA_Variant &p_val)
{
UA_UInt32 dims = (p_val.arrayLength == 0 ? 1 : p_val.arrayLength);
UA_TypeFlag type_flag = p_val.type->typeKind;
DataType type_flag = p_val.type->typeKind;
void *data = p_val.data;
if (dims == 1)
{
Expand Down
Loading

0 comments on commit ea52a51

Please sign in to comment.