opcua_client/lib.rs
1// OPCUA for Rust
2// SPDX-License-Identifier: MPL-2.0
3// Copyright (C) 2017-2022 Adam Lock
4
5//! The OPC UA Client module contains the functionality necessary for a client to connect to an OPC UA server,
6//! authenticate itself, send messages, receive responses, get values, browse the address space and
7//! provide callbacks for things to be propagated to the client.
8//!
9//! A client has to specify the endpoint description they wish to connect to, security policy and other
10//! configurable options, e.g. paths to PKI keys. All of this is encapsulated in a [`Client`] object.
11//!
12//! One of these may be made programatically using a [`ClientBuilder`] or from a preexisting [`ClientConfig`]
13//! which can be loaded fully or partially from disk. Use the way that suits you.
14//!
15//! Once the `Client` is created it can connect to a server by creating a [`Session`]. Multiple sessions
16//! can be created from the same client. Functions on the [`Session`] correspond to OPC UA services so
17//! it can be used to:
18//!
19//! * Discover endpoints
20//! * Activate a session
21//! * Create / modify / delete subscriptions
22//! * Create / modify / delete monitored items
23//! * Read and write values
24//! * Browse the address space
25//! * Add or remove nodes
26//!
27//! Functionality is synchronous and housekeeping such as renewing the active session and sending publish requests is
28//! handled automatically.
29//!
30//! Data change and event notifications are via asynchronous callbacks.
31//!
32//! # Example
33//!
34//! Here is a complete example of a client that connects to the `samples/simple-server`, subscribes
35//! to some values and prints out changes to those values. This example corresponds to the one
36//! described in the in docs/client.md tutorial.
37//!
38//! ```no_run
39//! use std::sync::{Arc, RwLock};
40//! use opcua_client::prelude::*;
41//!
42//! fn main() {
43//! let mut client = ClientBuilder::new()
44//! .application_name("My First Client")
45//! .application_uri("urn:MyFirstClient")
46//! .create_sample_keypair(true)
47//! .trust_server_certs(false)
48//! .session_retry_limit(3)
49//! .client().unwrap();
50//!
51//! // Create an endpoint. The EndpointDescription can be made from a tuple consisting of
52//! // the endpoint url, security policy, message security mode and user token policy.
53//! let endpoint: EndpointDescription = ("opc.tcp://localhost:4855/", "None", MessageSecurityMode::None, UserTokenPolicy::anonymous()).into();
54//!
55//! // Create the session
56//! let session = client.connect_to_endpoint(endpoint, IdentityToken::Anonymous).unwrap();
57//!
58//! // Create a subscription and monitored items
59//! if subscribe_to_values(session.clone()).is_ok() {
60//! let _ = Session::run(session);
61//! } else {
62//! println!("Error creating subscription");
63//! }
64//! }
65//!
66//! fn subscribe_to_values(session: Arc<RwLock<Session>>) -> Result<(), StatusCode> {
67//! let mut session = session.write().unwrap();
68//! // Create a subscription polling every 2s with a callback
69//! let subscription_id = session.create_subscription(2000.0, 10, 30, 0, 0, true, DataChangeCallback::new(|changed_monitored_items| {
70//! println!("Data change from server:");
71//! changed_monitored_items.iter().for_each(|item| print_value(item));
72//! }))?;
73//! // Create some monitored items
74//! let items_to_create: Vec<MonitoredItemCreateRequest> = ["v1", "v2", "v3", "v4"].iter()
75//! .map(|v| NodeId::new(2, *v).into()).collect();
76//! let _ = session.create_monitored_items(subscription_id, TimestampsToReturn::Both, &items_to_create)?;
77//! Ok(())
78//! }
79//!
80//! fn print_value(item: &MonitoredItem) {
81//! let node_id = &item.item_to_monitor().node_id;
82//! let data_value = item.last_value();
83//! if let Some(ref value) = data_value.value {
84//! println!("Item \"{}\", Value = {:?}", node_id, value);
85//! } else {
86//! println!("Item \"{}\", Value not found, error: {}", node_id, data_value.status.as_ref().unwrap());
87//! }
88//!}
89//! ```
90//!
91//! [`Client`]: ./client/struct.Client.html
92//! [`ClientConfig`]: ./config/struct.ClientConfig.html
93//! [`ClientBuilder`]: ./client_builder/struct.ClientBuilder.html
94//! [`Session`]: ./session/struct.Session.html
95#[macro_use]
96extern crate lazy_static;
97#[macro_use]
98extern crate log;
99#[macro_use]
100extern crate opcua_core;
101#[macro_use]
102extern crate serde_derive;
103
104use opcua_core::supported_message::SupportedMessage;
105use opcua_types::{response_header::ResponseHeader, status_code::StatusCode};
106
107mod comms;
108mod message_queue;
109mod subscription;
110mod subscription_state;
111
112// Use through prelude
113mod builder;
114mod callbacks;
115mod client;
116mod config;
117mod session;
118mod session_retry_policy;
119
120/// Process the service result, i.e. where the request "succeeded" but the response
121/// contains a failure status code.
122pub(crate) fn process_service_result(response_header: &ResponseHeader) -> Result<(), StatusCode> {
123 if response_header.service_result.is_bad() {
124 info!(
125 "Received a bad service result {} from the request",
126 response_header.service_result
127 );
128 Err(response_header.service_result)
129 } else {
130 Ok(())
131 }
132}
133
134pub(crate) fn process_unexpected_response(response: SupportedMessage) -> StatusCode {
135 match response {
136 SupportedMessage::ServiceFault(service_fault) => {
137 error!(
138 "Received a service fault of {} for the request",
139 service_fault.response_header.service_result
140 );
141 service_fault.response_header.service_result
142 }
143 _ => {
144 error!("Received an unexpected response to the request");
145 StatusCode::BadUnknownResponse
146 }
147 }
148}
149
150pub mod prelude {
151 pub use opcua_core::prelude::*;
152 pub use opcua_crypto::*;
153 pub use opcua_types::{service_types::*, status_code::StatusCode};
154
155 pub use crate::{
156 builder::*,
157 callbacks::*,
158 client::*,
159 config::*,
160 session::{services::*, session::*},
161 subscription::MonitoredItem,
162 };
163}
164
165#[cfg(test)]
166mod tests;