Skip to content

Commit

Permalink
add example code showing how to create custom enums and structures
Browse files Browse the repository at this point in the history
  • Loading branch information
Olivier authored and oroulet committed Jan 5, 2025
1 parent 65198b5 commit 1f27e9b
Show file tree
Hide file tree
Showing 4 changed files with 223 additions and 17 deletions.
5 changes: 5 additions & 0 deletions samples/demo-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,8 @@ tokio-util = { workspace = true }
[dependencies.opcua]
path = "../../lib"
features = ["all"]

[features]
default = ["json", "xml"]
json = ["opcua/json"]
xml = ["opcua/xml"]
32 changes: 16 additions & 16 deletions samples/demo-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ Use `simple-server` as reference for a very simple OPC UA server.

Use `demo-server` (this project) for a more full-featured server that demonstrates the following.

* Exposes static and dynamically changing variables
* Variables of every supported data type including arrays
* Events
* Http access to diagnostics and other info
* More sophisticated logging and data capture
* Be used for testing / verification purposes
- Exposes static and dynamically changing variables
- Variables of every supported data type including arrays
- Events
- Http access to diagnostics and other info
- More sophisticated logging and data capture
- Be used for testing / verification purposes

The demo-server enables the `http` feature in `opcua-server` so it can display metrics
from `http://localhost:8585`, however you must start it from the `demo-server` directory so it can find its html
from `http://localhost:8585`, however you must start it from the `demo-server` directory so it can find its html
and other resources.

```
Expand All @@ -20,24 +20,24 @@ cargo run

## Testing configuration

If you are using the demo server for testing a client you must do a few things depending on what you're testing
If you are using the demo server for testing a client you must do a few things depending on what you're testing
against.

1. Copy `sample.server.test.conf` to `../server.test.conf`. The `demo-server` will load this file
if it exists.
if it exists.
2. Edit `../server.test.conf`
3. Set `tcp_config.host` and `discovery_urls` to the IP address of the server host. Do not set it to localhost
5. Set `create_sample_keypair` to false
6. Generate a PKI keypair that is acceptable to your test environment and matches the IP address you set in the config. Copy
this to `pki/own/cert.der` and `pki/private/private.pem`.
4. Set `create_sample_keypair` to false
5. Generate a PKI keypair that is acceptable to your test environment and matches the IP address you set in the config. Copy
this to `pki/own/cert.der` and `pki/private/private.pem`.

### Troubleshooting

* It is best to try opening Project settings in test harness and browsing to server first to ensure trust is possible, and to troubleshoot any basic connection issues.
* Check logs if certs are rejected.
* If you get `BadCertificateTimeInvalid` returned to the test harness, try setting `check_time`
- It is best to try opening Project settings in test harness and browsing to server first to ensure trust is possible, and to troubleshoot any basic connection issues.
- Check logs if certs are rejected.
- If you get `BadCertificateTimeInvalid` returned to the test harness, try setting `check_time`
to `false` in the `server.test.conf`. For some reason test harness uses certs which can be out of date.
* If the network is IPv6, use `127.0.0.1` instead of the machine name or `localhost`
- If the network is IPv6, use `127.0.0.1` instead of the machine name or `localhost`

## Run using Docker

Expand Down
195 changes: 195 additions & 0 deletions samples/demo-server/src/customs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
use std::sync::Arc;

use opcua::{
nodes::{DataTypeBuilder, ObjectBuilder, ReferenceDirection, VariableBuilder},
server::node_manager::memory::SimpleNodeManager,
types::{
DataTypeDefinition, DataTypeId, EnumDefinition, EnumField, ExpandedNodeId, ExtensionObject,
NodeId, ObjectId, ObjectTypeId, ReferenceTypeId, StructureDefinition, StructureField,
StructureType,
},
};

use crate::NAMESPACE_URI;

const STRUCT_ENC_TYPE_ID: u32 = 3324;
const STRUCT_DATA_TYPE_ID: u32 = 3325;
const ENUM_DATA_TYPE_ID: u32 = 3326;

pub fn add_custom_types(nm: Arc<SimpleNodeManager>, ns: u16) {
let enum_id = add_enum_data_type(&nm, ns);
let struct_id = add_custom_data_type(&nm, ns, &enum_id);

let addr = nm.address_space();
let mut addr = addr.write();
let error_node = NodeId::next_numeric(ns);
let error_data = ErrorData::new("No Error", 98, AxisState::Idle);
VariableBuilder::new(&error_node, "ErrorData", "ErrorData")
.organized_by(ObjectId::ObjectsFolder)
.data_type(struct_id.clone())
.value(ExtensionObject::new(error_data))
.insert(&mut *addr);
}

fn enum_field(name: &str, value: i64) -> EnumField {
EnumField {
name: name.into(),
description: name.into(),
display_name: name.into(),
value,
}
}

fn add_enum_data_type(nm: &Arc<SimpleNodeManager>, ns: u16) -> NodeId {
let mut addr = nm.address_space().write();

let id = NodeId::new(ns, ENUM_DATA_TYPE_ID);

let type_def = DataTypeDefinition::Enum(EnumDefinition {
fields: Some(vec![
enum_field("Disabled", 1),
enum_field("Enabled", 2),
enum_field("Idle", 3),
enum_field("MoveAbs", 4),
enum_field("Error", 5),
]),
});
DataTypeBuilder::new(&id, "AxisState", "AxisState")
.subtype_of(DataTypeId::Enumeration)
.data_type_definition(type_def)
.insert(&mut *addr);

id
}

fn add_encoding(nm: &SimpleNodeManager, ns: u16, struct_id: &NodeId) -> NodeId {
let mut addr = nm.address_space().write();
let id = NodeId::new(ns, STRUCT_ENC_TYPE_ID);
ObjectBuilder::new(&id, "Default Binary", "Default Binary")
.reference(
struct_id,
ReferenceTypeId::HasEncoding,
ReferenceDirection::Inverse,
)
.has_type_definition(ObjectTypeId::DataTypeEncodingType)
.insert(&mut *addr);
id
}

fn add_custom_data_type(nm: &SimpleNodeManager, ns: u16, e_state_id: &NodeId) -> NodeId {
let struct_id = NodeId::new(ns, STRUCT_DATA_TYPE_ID);
let enc_id = add_encoding(nm, ns, &struct_id);

let type_def = DataTypeDefinition::Structure(StructureDefinition {
default_encoding_id: NodeId::new(ns, STRUCT_ENC_TYPE_ID),
base_data_type: DataTypeId::Structure.into(),
structure_type: StructureType::Structure,
fields: Some(vec![
StructureField {
name: "sErrorMessage".into(),
data_type: DataTypeId::String.into(),
value_rank: -1,
..Default::default()
},
StructureField {
name: "nErrorID".into(),
data_type: DataTypeId::UInt32.into(),
value_rank: -1,
..Default::default()
},
StructureField {
name: "eLastState".into(),
data_type: e_state_id.clone(),
value_rank: -1,
..Default::default()
},
]),
});
let mut addr = nm.address_space().write();
DataTypeBuilder::new(&struct_id, "ErrorData", "ErrorData")
.subtype_of(DataTypeId::Structure)
.data_type_definition(type_def)
.reference(
enc_id,
ReferenceTypeId::HasEncoding,
ReferenceDirection::Forward,
)
.insert(&mut *addr);

struct_id
}

#[derive(
Debug,
Copy,
Clone,
PartialEq,
Eq,
opcua::types::UaEnum,
opcua::types::BinaryEncodable,
opcua::types::BinaryDecodable,
)]
#[cfg_attr(
feature = "json",
derive(opcua::types::JsonEncodable, opcua::types::JsonDecodable)
)]
#[cfg_attr(feature = "xml", derive(opcua::types::FromXml))]
#[derive(Default)]
#[repr(i32)]
pub enum AxisState {
#[default]
Disabled = 1i32,
Enabled = 2i32,
Idle = 3i32,
MoveAbs = 4i32,
Error = 5i32,
}

#[derive(Debug, Clone, PartialEq, opcua::types::BinaryEncodable, opcua::types::BinaryDecodable)]
#[cfg_attr(
feature = "json",
derive(opcua::types::JsonEncodable, opcua::types::JsonDecodable)
)]
#[cfg_attr(feature = "xml", derive(opcua::types::FromXml))]
#[derive(Default)]
pub struct ErrorData {
message: opcua::types::UAString,
error_id: u32,
last_state: AxisState,
}

impl ErrorData {
pub fn new(msg: &str, error_id: u32, last_state: AxisState) -> Self {
Self {
message: msg.into(),
error_id,
last_state,
}
}
}

impl opcua::types::ExpandedMessageInfo for ErrorData {
fn full_type_id(&self) -> opcua::types::ExpandedNodeId {
ExpandedNodeId {
node_id: NodeId::new(0, STRUCT_ENC_TYPE_ID),
namespace_uri: NAMESPACE_URI.into(),
server_index: 0,
}
}

fn full_json_type_id(&self) -> opcua::types::ExpandedNodeId {
todo!()
}

fn full_xml_type_id(&self) -> opcua::types::ExpandedNodeId {
todo!()
}

fn full_data_type_id(&self) -> opcua::types::ExpandedNodeId {
ExpandedNodeId {
node_id: NodeId::new(0, STRUCT_DATA_TYPE_ID),
namespace_uri: NAMESPACE_URI.into(),
server_index: 0,
}
}
}
8 changes: 7 additions & 1 deletion samples/demo-server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,13 @@ use opcua::server::{
};

mod control;
mod customs;
mod machine;
mod methods;
mod scalar;

const NAMESPACE_URI: &str = "urn:DemoServer";

struct Args {
help: bool,
raise_events: bool,
Expand Down Expand Up @@ -130,10 +133,13 @@ async fn main() {
.node_managers()
.get_of_type::<SimpleNodeManager>()
.unwrap();
let ns = handle.get_namespace_index("urn:DemoServer").unwrap();
let ns = handle.get_namespace_index(NAMESPACE_URI).unwrap();

let token = handle.token();

// Define some custom types
customs::add_custom_types(node_manager.clone(), ns);

// Add some objects representing machinery
machine::add_machinery(
ns,
Expand Down

0 comments on commit 1f27e9b

Please sign in to comment.