1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263
//! Rust wrapper for the [open62541](https://www.open62541.org) library.
//!
//! # Examples
//!
//! ## Client: Connect to server
//!
//! Use [`AsyncClient`] to asynchronously connect to an OPC UA server:
//!
//! ```no_run
//! use open62541::AsyncClient;
//!
//! # #[tokio::main]
//! # async fn main() -> anyhow::Result<()> {
//! #
//! let client = AsyncClient::new("opc.tcp://opcuademo.sterfive.com:26543")?;
//! #
//! # Ok(())
//! # }
//! ```
//!
//! This requires an async runtime such as [`tokio`].
//!
//! ## Client: Read node's value attribute
//!
//! Read a variable node's value attribute with [`AsyncClient::read_value()`]:
//!
//! ```no_run
//! # use open62541::AsyncClient;
//! use open62541::ua;
//!
//! # #[tokio::main]
//! # async fn main() -> anyhow::Result<()> {
//! #
//! # let client = AsyncClient::new("opc.tcp://opcuademo.sterfive.com:26543")?;
//! #
//! let node_id = ua::NodeId::numeric(0, 2258); // Server/ServerStatus/CurrentTime
//!
//! let value = client.read_value(&node_id).await?;
//!
//! println!("Received value: {value:?}");
//! #
//! # Ok(())
//! # }
//! ```
//!
//! Use [`AsyncClient::read_attribute()`] and related methods to read other attributes other than
//! the value.
//!
//! ## Client: Watch node for changes in value attribute
//!
//! Subscribe to a node's value by creating a subscription with
//! [`AsyncClient::create_subscription()`] and adding monitored items to it with
//! [`AsyncSubscription::create_monitored_item()`]:
//!
//! ```no_run
//! # use open62541::{AsyncClient, ua};
//! #
//! # #[tokio::main]
//! # async fn main() -> anyhow::Result<()> {
//! #
//! # let client = AsyncClient::new("opc.tcp://opcuademo.sterfive.com:26543")?;
//! #
//! # let node_id = ua::NodeId::numeric(0, 2258); // Server/ServerStatus/CurrentTime
//! #
//! // Create subscription that receives the updates.
//! let subscription = client.create_subscription().await?;
//! // Create monitored item to receive node updates.
//! let mut monitored_item = subscription.create_monitored_item(&node_id).await?;
//!
//! while let Some(value) = monitored_item.next().await {
//! println!("Received value: {value:?}");
//! }
//! #
//! # Ok(())
//! # }
//! ```
//!
//! ## Server: Run server
//!
//! Create an OPC UA server with [`Server::new()`]. When instantiating a server, you receive a
//! [`ServerRunner`] along with the [`Server`]. Use it to run the server until interrupted:
//!
//! ```no_run
//! use open62541::Server;
//!
//! # #[tokio::main]
//! # async fn main() -> anyhow::Result<()> {
//! let (server, runner) = Server::new();
//!
//! // Define data nodes on `server`.
//!
//! runner.run()?;
//! #
//! # Ok(())
//! # }
//! ```
//!
//! By default, [`ServerRunner::run()`] runs on the current thread. Use
//! [`thread::spawn()`](std::thread::spawn) to run it in a different thread.
//!
//! ## Server: Define object and managed variable nodes
//!
//! Define nodes with [`Server::add_object_node()`] and related methods:
//!
//! ```
//! # use open62541::Server;
//! use open62541::{ObjectNode, ua, VariableNode};
//! use open62541_sys::{
//! UA_NS0ID_BASEDATAVARIABLETYPE, UA_NS0ID_FOLDERTYPE, UA_NS0ID_OBJECTSFOLDER,
//! UA_NS0ID_ORGANIZES, UA_NS0ID_STRING,
//! };
//!
//! # #[tokio::main]
//! # async fn main() -> anyhow::Result<()> {
//! # let (server, runner) = Server::new();
//! #
//! let object_node_id = server.add_object_node(ObjectNode {
//! requested_new_node_id: None,
//! parent_node_id: ua::NodeId::ns0(UA_NS0ID_OBJECTSFOLDER),
//! reference_type_id: ua::NodeId::ns0(UA_NS0ID_ORGANIZES),
//! browse_name: ua::QualifiedName::new(1, "SomeFolder"),
//! type_definition: ua::NodeId::ns0(UA_NS0ID_FOLDERTYPE),
//! attributes: ua::ObjectAttributes::default(),
//! })?;
//!
//! let variable_node_id = server.add_variable_node(VariableNode {
//! requested_new_node_id: None,
//! parent_node_id: object_node_id,
//! reference_type_id: ua::NodeId::ns0(UA_NS0ID_ORGANIZES),
//! browse_name: ua::QualifiedName::new(1, "SomeVariable"),
//! type_definition: ua::NodeId::ns0(UA_NS0ID_BASEDATAVARIABLETYPE),
//! attributes: ua::VariableAttributes::default()
//! .with_data_type(&ua::NodeId::ns0(UA_NS0ID_STRING)),
//! })?;
//!
//! server.write_value(
//! &variable_node_id,
//! &ua::Variant::scalar(ua::String::new("Lorem Ipsum")?),
//! )?;
//! #
//! # Ok(())
//! # }
//! ```
//!
//! Nodes may also be added (and removed) while the server is running.
//!
//! ## Server: Define data source variable nodes (callback-driven)
//!
//! Implement [`DataSource`] for custom types to enable callback-driven read and write access
//! through OPC UA variables.
//!
//! Use [`DataSourceReadContext`] and [`DataSourceWriteContext`] to set the value to return to the
//! client, or access the incoming value received from the client:
//!
//! ```
//! # use open62541::{ObjectNode, Server, ua, VariableNode};
//! use open62541::{DataSource, DataSourceReadContext, DataSourceResult, DataSourceWriteContext};
//! # use open62541_sys::{
//! # UA_NS0ID_BASEDATAVARIABLETYPE, UA_NS0ID_OBJECTSFOLDER, UA_NS0ID_ORGANIZES,
//! # UA_NS0ID_STRING,
//! # };
//!
//! struct SomeDataSource {
//! some_value: u32,
//! }
//!
//! impl DataSource for SomeDataSource {
//! fn read(&mut self, ctx: &mut DataSourceReadContext) -> DataSourceResult {
//! let value = format!("This is #{value}", value = self.some_value);
//! ctx.set_variant(ua::Variant::scalar(ua::String::new(&value)?));
//! Ok(())
//! }
//!
//! fn write(&mut self, ctx: &mut DataSourceWriteContext) -> DataSourceResult {
//! println!("Received value: {value:?}", value = ctx.value());
//! self.some_value += 1;
//! Ok(())
//! }
//! }
//!
//! # #[tokio::main]
//! # async fn main() -> anyhow::Result<()> {
//! # let (server, runner) = Server::new();
//! #
//! # let object_node_id = ua::NodeId::ns0(UA_NS0ID_OBJECTSFOLDER);
//! #
//! let variable_node = VariableNode {
//! requested_new_node_id: None,
//! parent_node_id: object_node_id,
//! reference_type_id: ua::NodeId::ns0(UA_NS0ID_ORGANIZES),
//! browse_name: ua::QualifiedName::new(1, "SomeVariable"),
//! type_definition: ua::NodeId::ns0(UA_NS0ID_BASEDATAVARIABLETYPE),
//! attributes: ua::VariableAttributes::default()
//! .with_data_type(&ua::NodeId::ns0(UA_NS0ID_STRING))
//! .with_access_level(
//! &ua::AccessLevel::NONE
//! .with_current_read(true)
//! .with_current_write(true),
//! ),
//! };
//!
//! let variable_node_id = server.add_data_source_variable_node(
//! variable_node,
//! SomeDataSource { some_value: 0 },
//! )?;
//! #
//! # Ok(())
//! # }
//! ```
mod client;
mod data_type;
mod error;
mod server;
mod service;
pub mod ua;
#[cfg(feature = "tokio")]
mod async_client;
#[cfg(feature = "tokio")]
mod async_monitored_item;
#[cfg(feature = "tokio")]
mod async_subscription;
mod attributes;
mod browse_result;
#[cfg(feature = "tokio")]
mod callback;
mod data_value;
mod logger;
mod traits;
mod userdata;
mod value;
pub use self::{
browse_result::BrowseResult,
client::{Client, ClientBuilder},
data_type::DataType,
data_value::DataValue,
error::{Error, Result},
server::{
DataSource, DataSourceError, DataSourceReadContext, DataSourceResult,
DataSourceWriteContext, MethodCallback, MethodCallbackContext, MethodCallbackError,
MethodCallbackResult, MethodNode, Node, ObjectNode, Server, ServerBuilder, ServerRunner,
VariableNode,
},
traits::{Attribute, Attributes},
userdata::Userdata,
value::{ScalarValue, ValueType, VariantValue},
};
pub(crate) use self::{
data_type::{bitmask_ops, data_type, enum_variants},
logger::logger,
service::{ServiceRequest, ServiceResponse},
value::{ArrayValue, NonScalarValue},
};
#[cfg(feature = "tokio")]
pub use self::{
async_client::AsyncClient,
async_monitored_item::AsyncMonitoredItem,
async_subscription::AsyncSubscription,
callback::{CallbackOnce, CallbackStream},
};