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;