跳到主要内容

Call

前置 / 配套
  • 数据类型 Variant 是入参 / 出参的容器.
  • 找方法 NodeId 用 Browsefilter: NodeClass::Method.

签名

pub fn Session::call(
&self,
object_node_id: &str,
method_node_id: &str,
inputs: &[Variant],
) -> Result<Vec<Variant>, OpcUaError>;
参数说明
object_node_id方法所在 Object 节点
method_node_id方法本身 NodeId
inputs入参 Variant 切片 (可空 &[])

返回 Vec<Variant> 输出参数列表.


内存所有权

返回列表里每个 Variant 都持有 native handle, 由 Rust Drop trait 自动释放. 不需要手动 dispose, 也不会泄漏 native Variant 内存:

{
let outputs = s.call("ns=2;s=Obj", "ns=2;s=Obj.Method", &[])?;
for v in &outputs {
println!("{}", v.try_get_i32().unwrap_or(0));
}
} // <- outputs Vec drop, 每个 Variant native 内存全释放

例子

调 Calculator.Add(3, 4)

use darra_opcua::Variant;

let mut a = Variant::new(); a.set_i32(3);
let mut b = Variant::new(); b.set_i32(4);

let outputs = s.call(
"ns=2;s=Calculator",
"ns=2;s=Calculator.Add",
&[a, b],
)?;

for v in &outputs {
println!("sum = {}", v.try_get_i32().unwrap_or(0));
}

调无入参方法

let outputs = s.call(
"ns=2;s=Boiler1",
"ns=2;s=Boiler1.Reset",
&[], // 空切片
)?;

调多个不同类型入参

let mut name = Variant::new(); name.set_string("Boiler1");
let mut sp = Variant::new(); sp.set_f64(85.5);

let outputs = s.call(
"ns=2;s=Plant",
"ns=2;s=Plant.SetTemperature",
&[name, sp],
)?;

错误

StatusCode含义
BAD_NODE_ID_INVALIDNodeId 解析失败
BAD_COMMUNICATION_ERRORTransport 错
BAD_METHOD_INVALID该 NodeId 不是 Method
BAD_INVALID_ARGUMENT入参类型 / 个数不符
BAD_USER_ACCESS_DENIED无权限调
BAD_*方法返回的业务级错误

业务级失败 (方法返回非 Good) 也作为 Err 返回, e.status 即业务码:

match s.call("ns=2;s=O", "ns=2;s=O.M", &[]) {
Ok(outs) => println!("ok, {} outputs", outs.len()),
Err(e) if e.status == darra_opcua::StatusCode::BAD_USER_ACCESS_DENIED
=> eprintln!("无权限"),
Err(e) => eprintln!("call failed: {}", e),
}

找方法的 NodeId

use darra_opcua::NodeClass;

// 1. Browse Object 子节点过滤 Method
let methods = s.browse("ns=2;s=Calculator", NodeClass::Method)?;
for m in &methods {
println!(" {} -> {}", m.browse_name, m.node_id);
}

// 2. 看入参 / 出参签名 (Browse 该方法节点的 Property)
let props = s.browse(&methods[0].node_id, NodeClass::Variable)?;
// 含 InputArguments / OutputArguments

输入参数构造

按方法签名构造 Variant:

use darra_opcua::Variant;
use std::time::SystemTime;

let mut a = Variant::new(); a.set_i32(3); // Int32
let mut b = Variant::new(); b.set_i32(4); // Int32
let mut name = Variant::new(); name.set_string("Boiler1");
let mut sp = Variant::new(); sp.set_f64(85.5);
let mut t = Variant::new(); t.set_datetime(SystemTime::now());
let mut flag = Variant::new(); flag.set_bool(true);

输出 Variant 解码

use darra_opcua::BuiltinType;

let outputs = s.call(/* ... */, /* ... */, &[])?;
for v in &outputs {
match v.data_type() {
BuiltinType::Int32 => println!("i32: {}", v.try_get_i32().unwrap_or(0)),
BuiltinType::Double => println!("f64: {}", v.try_get_f64().unwrap_or(0.0)),
BuiltinType::String => println!("str: {:?}", v.try_get_string()),
t => println!("other type: {:?}", t),
}
}

异步

当前 Call 是同步阻塞 (内部走 RPC + 等响应). Session::call 借用 &self, 内部 Stack 加锁, 跨线程并发调用安全; tokio 用户用 tokio::task::spawn_blocking 包装即可.

下一步