/opcua
Description of the locka99/opcua project starts from the description of OPC UA. OPC UA is the industry standard for data monitoring. It’s used extensively for embedded devices, industrial control, IoT, etc. – just about anything that has data that something else wants to monitor, control, or visualize. The development team implemented this project in the Rust language. I really like the Rust language and fully support the development team in choosing the language. Rust is a system programming language and is, therefore, a natural choice for implementing OPC UA. This implementation supports the embedded, micro, and nano profiles but may grow to support features in time.
Rust has high performance, is fast and efficient with memory, without runtime or garbage collector. Rust is a super robust programming language, the ownership model guarantees memory safety and thread-safety. Rust is a productive programming language, Rust has great documentation, a great compiler that tells you how to do it most of the time, and provides links to the documentation so you can understand the problem and can solve it. Rust has cool tools, package manager, code formatting. Rust is getting better, and the Rust community is here to help you. Rust is a system-level programming language, but over time this definition has disappeared, more and more libraries for the Internet, graphical interfaces, games, etc. have appeared. The first thing you think when you read that Rust is a systems programming language is, “Oh, I need to deal with pointers! I need to deal with freeing and allocating memory! I need to work at a very low level without abstractions! What for? Do I need it? “If, of course, this is your usual life, because it’s scary to go back to C/C++, but the vast majority of these fears are very well resolved by the compiler. Rust is very easy to enter the world of systems programming. I already wrote about Rust:
- Rust embedded. Stopwatch.
- Rust embedded. Spi and embedded-graphics (in Russian).
- Rust embedded. Gpio. (in Russian)
- Rust embedded. Sequel. (in Russian)
- Rust embedded. (in Russian)
We continue to work with our basic project. Our task is to output the state of the variables and display them and change the value of variable 1, in our case we will turn on the relay on the PLC.
use std::sync::{Arc, RwLock}; use std::{ str::FromStr, }; use opcua_client::prelude::*; struct Args { help: bool, url: String, } impl Args { pub fn parse_args() -> Result<Args, Box<dyn std::error::Error>> { let mut args = pico_args::Arguments::from_env(); Ok(Args { help: args.contains(["-h", "--help"]), url: args.opt_value_from_str("--url")?.unwrap_or(String::from(DEFAULT_URL)), }) } pub fn usage() { println!(r#"Simple Client Usage: -h, --help Show help --url [url] Url to connect to (default: {})"#, DEFAULT_URL); } } const DEFAULT_URL: &str = "opc.tcp://192.168.0.1:4840"; fn main() -> Result<(), ()> { // Read command line arguments let args = Args::parse_args() .map_err(|_| Args::usage())?; if args.help { Args::usage(); } else { // Optional - enable OPC UA logging opcua_console_logging::init(); // Make the client configuration let mut client = ClientBuilder::new() .application_name("Simple Client") .application_uri("urn:SimpleClient") .trust_server_certs(true) .create_sample_keypair(true) .session_retry_limit(3) .client().unwrap(); if let Ok(session) = client.connect_to_endpoint((args.url.as_ref(), SecurityPolicy::None.to_str(), MessageSecurityMode::None, UserTokenPolicy::anonymous()), IdentityToken::Anonymous) { if let Err(result) = subscribe_to_variables(session.clone(), 2) { println!("ERROR: Got an error while subscribing to variables - {}", result); } else { // Loops forever. The publish thread will call the callback with changes on the variables println!("Loops forever. The publish thread will call the callback with changes on the variables"); let _ = Session::run(session); println!("Session::run(session)"); } } } println!("OK"); Ok(()) } fn subscribe_to_variables(session: Arc<RwLock<Session>>, _ns: u16) -> Result<(), StatusCode> { let mut session = session.write().unwrap(); // Creates a subscription with a data change callback let subscription_id = session.create_subscription(2000.0, 10, 30, 0, 0, true, DataChangeCallback::new(|changed_monitored_items| { println!("Data change from server:"); changed_monitored_items.iter().for_each(|item| print_value(item)); }))?; println!("Created a subscription with id = {}", subscription_id); let node_ids: Vec<String> = vec!["ns=4;i=3".to_string(), "ns=4;i=4".to_string(), "ns=4;i=5".to_string(), "ns=4;i=6".to_string()]; // Create some monitored items let items_to_create: Vec<MonitoredItemCreateRequest> = node_ids.iter().map(|node_id| { let node_id = NodeId::from_str(node_id).unwrap(); // Trust client to not break this node_id.into() }).collect(); let node_id = NodeId::from_str(&node_ids[0]).unwrap(); // Trust client to not break this // Read the existing value let results = session.read(&[node_id.clone().into()]).unwrap(); let value = &results[0]; println!("Item \"{}\", Value = {:?}\n", node_id, value); let results_write = session.write(&[WriteValue { node_id: node_id.clone(), attribute_id: AttributeId::Value as u32, index_range: UAString::null(), value: Variant::Boolean(true).into(), }]).unwrap().unwrap(); let _value_write = results_write[0]; let _ = session.create_monitored_items(subscription_id, TimestampsToReturn::Both, &items_to_create)?; println!("subscribe_to_variables"); Ok(()) } fn print_value(item: &MonitoredItem) { println!("print_value"); let node_id = &item.item_to_monitor().node_id; let data_value = item.value(); if let Some(ref value) = data_value.value { println!("Item \"{}\", Value = {:?}\n", node_id, value); } else { println!("Item \"{}\", Value not found, error: {}\n", node_id, data_value.status.as_ref().unwrap()); } }
I modified the base project “opcua/samples/simple-client/” to reveal and show the possibility of the locka99/opcua project. Our OPC UA server is located at opc.tcp://192.168.0.1: 4840. We need to add the nodes from the PLC project: let node_ids: Vec <String> = vec! [“Ns = 4; i = 3” .to_string (), “ns = 4; i = 4” .to_string (), “ns = 4; i = 5 “.to_string (),” ns = 4; i = 6 “.to_string ()]. Next, we create a new item to monitor the state of the variables let items_to_create: Vec<MonitoredItemCreateRequest> = node_ids.iter().map(|node_id| {let node_id = NodeId::from_str(node_id).unwrap(); node_id.into()}).collect(). After creating the state monitor, I add a node to be modified. This is the node “ns = 4; i = 3”. To read the state of a node, run the following command session.read (& [node_id.clone (). Into ()]). Unwrap () and to write a new state for the node, run the following command session.write (& [WriteValue {node_id : node_id.clone(), attribute_id: AttributeId::Value as u32, index_range: UAString :: null(), value: Variant::Boolean(true).into(),}]). unwrap (). unwrap ().
The locka99/opcua project offers us a very interesting example “opcua/samples/web-client”. This example creates a web server at http://127.0.0.1:8686/ and provides an interface for monitoring the status of nodes. To read nodes, you need to add them to the appropriate field (Nodes Ids), in my case, the line for reading the state of nodes looks like this “ns = 4; i = 3, ns = 4; i = 4, ns = 4; i = 5, ns = 4; i = 6 “.
Dear friends, thank you for reading this article to the end, you are great. I would be glad to hear and see your comments and suggestions for improving my articles. Although the OPC UA topic has not yet been fully disclosed, I still want to tell a lot more, in the future I will definitely add new articles to the OPC UA topic. Thank you very much for your attention.
- Industrial Network Protocols. OPC UA. Part 1.
- Industrial Network Protocols. OPC UA. Part 2.
- Industrial Network Protocols. OPC UA. Part 3.
- Industrial Network Protocols. OPC UA. Part 4.
- Industrial Network Protocols. OPC UA. Part 5.
- Industrial Network Protocols. OPC UA. Part 6.
- Industrial Network Protocols. OPC UA. Part 7.