use crate::NamespaceMap;
use opcua_types::{
event_field::EventField, AttributeId, ByteString, DateTime, LocalizedText, NodeId,
NumericRange, ObjectTypeId, QualifiedName, TimeZoneDataType, UAString, Variant,
};
pub trait Event: EventField {
fn get_field(
&self,
type_definition_id: &NodeId,
attribute_id: AttributeId,
index_range: &NumericRange,
browse_path: &[QualifiedName],
) -> Variant;
fn time(&self) -> &DateTime;
fn event_type_id(&self) -> &NodeId;
}
#[derive(Debug, Default)]
pub struct BaseEventType {
pub event_id: ByteString,
pub event_type: NodeId,
pub source_node: NodeId,
pub source_name: UAString,
pub time: DateTime,
pub receive_time: DateTime,
pub local_time: Option<TimeZoneDataType>,
pub message: LocalizedText,
pub severity: u16,
pub condition_class_id: Option<NodeId>,
pub condition_class_name: Option<LocalizedText>,
pub condition_sub_class_id: Option<Vec<NodeId>>,
pub condition_sub_class_name: Option<Vec<LocalizedText>>,
}
impl Event for BaseEventType {
fn time(&self) -> &DateTime {
&self.time
}
fn get_field(
&self,
type_definition_id: &NodeId,
attribute_id: AttributeId,
index_range: &NumericRange,
browse_path: &[QualifiedName],
) -> Variant {
if type_definition_id == &ObjectTypeId::BaseEventType {
self.get_value(attribute_id, index_range, browse_path)
} else {
Variant::Empty
}
}
fn event_type_id(&self) -> &NodeId {
&self.event_type
}
}
impl EventField for BaseEventType {
fn get_value(
&self,
attribute_id: AttributeId,
index_range: &NumericRange,
remaining_path: &[QualifiedName],
) -> Variant {
if remaining_path.len() != 1 || attribute_id != AttributeId::Value {
return Variant::Empty;
}
let field = &remaining_path[0];
if field.namespace_index != 0 {
return Variant::Empty;
}
match field.name.as_ref() {
"EventId" => self.event_id.get_value(attribute_id, index_range, &[]),
"EventType" => self.event_type.get_value(attribute_id, index_range, &[]),
"SourceNode" => self.source_node.get_value(attribute_id, index_range, &[]),
"SourceName" => self.source_name.get_value(attribute_id, index_range, &[]),
"Time" => self.time.get_value(attribute_id, index_range, &[]),
"ReceiveTime" => self.receive_time.get_value(attribute_id, index_range, &[]),
"LocalTime" => self.local_time.get_value(attribute_id, index_range, &[]),
"Message" => self.message.get_value(attribute_id, index_range, &[]),
"Severity" => self.severity.get_value(attribute_id, index_range, &[]),
"ConditionClassId" => self
.condition_class_id
.get_value(attribute_id, index_range, &[]),
"ConditionClassName" => {
self.condition_class_name
.get_value(attribute_id, index_range, &[])
}
"ConditionSubClassId" => {
self.condition_sub_class_id
.get_value(attribute_id, index_range, &[])
}
"ConditionSubClassName" => {
self.condition_sub_class_name
.get_value(attribute_id, index_range, &[])
}
_ => Variant::Empty,
}
}
}
impl BaseEventType {
pub fn new_now(
type_id: impl Into<NodeId>,
event_id: ByteString,
message: impl Into<LocalizedText>,
) -> Self {
let time = DateTime::now();
Self::new(type_id, event_id, message, time)
}
pub fn new(
type_id: impl Into<NodeId>,
event_id: ByteString,
message: impl Into<LocalizedText>,
time: DateTime,
) -> Self {
Self {
event_id,
event_type: type_id.into(),
message: message.into(),
time,
receive_time: time,
..Default::default()
}
}
pub fn new_event(
type_id: impl Into<NodeId>,
event_id: ByteString,
message: impl Into<LocalizedText>,
_namespace: &NamespaceMap,
time: DateTime,
) -> Self {
Self::new(type_id, event_id, message, time)
}
pub fn set_source_node(mut self, source_node: NodeId) -> Self {
self.source_node = source_node;
self
}
pub fn set_source_name(mut self, source_name: UAString) -> Self {
self.source_name = source_name;
self
}
pub fn set_receive_time(mut self, receive_time: DateTime) -> Self {
self.receive_time = receive_time;
self
}
pub fn set_severity(mut self, severity: u16) -> Self {
self.severity = severity;
self
}
}
pub use method_event_field::MethodEventField;
mod method_event_field {
use opcua_macros::EventField;
use opcua_types::NodeId;
mod opcua {
pub(super) use crate as nodes;
pub(super) use opcua_types as types;
}
#[derive(Default, EventField, Debug)]
pub struct MethodEventField {
pub node_id: NodeId,
}
}
#[cfg(test)]
mod tests {
use crate::NamespaceMap;
mod opcua {
pub(super) use crate as nodes;
pub(super) use opcua_types as types;
}
use crate::{BaseEventType, Event, EventField};
use opcua_types::event_field::PlaceholderEventField;
use opcua_types::{
AttributeId, ByteString, EUInformation, KeyValuePair, LocalizedText, NodeId, NumericRange,
ObjectTypeId, QualifiedName, StatusCode, UAString, Variant,
};
#[derive(Event)]
#[opcua(identifier = "s=myevent", namespace = "uri:my:namespace")]
struct BasicValueEvent {
base: BaseEventType,
own_namespace_index: u16,
float: f32,
double: f64,
string: String,
status: StatusCode,
int: Option<i64>,
int2: Option<u64>,
vec: Vec<i64>,
optvec: Option<Vec<i32>>,
kvp: KeyValuePair,
euinfo: EUInformation,
}
fn namespace_map() -> NamespaceMap {
let mut map = NamespaceMap::new();
map.add_namespace("uri:my:namespace");
map
}
fn get(id: &NodeId, evt: &dyn Event, field: &str) -> Variant {
evt.get_field(id, AttributeId::Value, &NumericRange::None, &[field.into()])
}
fn get_nested(id: &NodeId, evt: &dyn Event, fields: &[&str]) -> Variant {
let fields: Vec<QualifiedName> = fields.iter().map(|f| (*f).into()).collect();
evt.get_field(id, AttributeId::Value, &NumericRange::None, &fields)
}
#[test]
fn test_basic_values() {
let namespaces = namespace_map();
let mut evt = BasicValueEvent::new_event_now(
BasicValueEvent::event_type_id(&namespaces),
ByteString::from_base64("dGVzdA==").unwrap(),
"Some message",
&namespaces,
);
evt.float = 1.0;
evt.double = 2.0;
evt.string = "foo".to_owned();
evt.status = StatusCode::BadMaxAgeInvalid;
evt.kvp = KeyValuePair {
key: "Key".into(),
value: 123.into(),
};
evt.int = None;
evt.int2 = Some(5);
evt.vec = vec![1, 2, 3];
evt.optvec = Some(vec![3, 2, 1]);
evt.euinfo = EUInformation {
namespace_uri: "uri:my:namespace".into(),
unit_id: 15,
display_name: "Some unit".into(),
description: "Some unit desc".into(),
};
let id = BasicValueEvent::event_type_id(&namespaces);
assert_eq!(
evt.get_field(
&ObjectTypeId::ProgressEventType.into(),
AttributeId::Value,
&NumericRange::None,
&["Message".into()],
),
Variant::Empty
);
assert_eq!(
evt.get_field(
&id,
AttributeId::Value,
&NumericRange::None,
&["FooBar".into()],
),
Variant::Empty
);
assert_eq!(
evt.get_field(
&id,
AttributeId::Value,
&NumericRange::None,
&["Float".into(), "Child".into()],
),
Variant::Empty
);
assert_eq!(
evt.get_field(
&id,
AttributeId::NodeId,
&NumericRange::None,
&["Float".into()],
),
Variant::Empty
);
assert_eq!(get(&id, &evt, "Float"), Variant::from(1f32));
assert_eq!(get(&id, &evt, "Double"), Variant::from(2.0));
assert_eq!(get(&id, &evt, "String"), Variant::from("foo"));
assert_eq!(
get(&id, &evt, "Status"),
Variant::from(StatusCode::BadMaxAgeInvalid)
);
let kvp: KeyValuePair = match get(&id, &evt, "Kvp") {
Variant::ExtensionObject(o) => *o.into_inner_as().unwrap(),
_ => panic!("Wrong variant type"),
};
assert_eq!(kvp.key, "Key".into());
assert_eq!(kvp.value, 123.into());
assert_eq!(get(&id, &evt, "Int"), Variant::Empty);
assert_eq!(get(&id, &evt, "Int2"), Variant::from(5u64));
assert_eq!(get(&id, &evt, "Vec"), Variant::from(vec![1i64, 2i64, 3i64]));
assert_eq!(
get(&id, &evt, "Optvec"),
Variant::from(vec![3i32, 2i32, 1i32])
);
let euinfo: EUInformation = match get(&id, &evt, "Euinfo") {
Variant::ExtensionObject(o) => *o.into_inner_as().unwrap(),
_ => panic!("Wrong variant type"),
};
assert_eq!(euinfo.namespace_uri.as_ref(), "uri:my:namespace");
assert_eq!(euinfo.unit_id, 15);
assert_eq!(euinfo.display_name, "Some unit".into());
assert_eq!(euinfo.description, "Some unit desc".into());
}
#[derive(EventField, Default, Debug)]
struct ComplexEventField {
float: f32,
}
#[derive(EventField, Default, Debug)]
struct SubComplexEventField {
base: ComplexEventField,
node_id: NodeId,
#[opcua(rename = "gnirtS")]
string: UAString,
#[opcua(ignore)]
data: i32,
}
#[derive(EventField, Default, Debug)]
struct ComplexVariable {
node_id: NodeId,
value: i32,
id: u32,
#[opcua(placeholder)]
extra: PlaceholderEventField<i32>,
}
#[derive(Event)]
#[opcua(identifier = "s=mynestedevent", namespace = "uri:my:namespace")]
struct NestedEvent {
base: BasicValueEvent,
own_namespace_index: u16,
complex: ComplexEventField,
sub_complex: SubComplexEventField,
var: ComplexVariable,
#[opcua(ignore)]
ignored: i32,
#[opcua(rename = "Fancy Name")]
renamed: String,
#[opcua(placeholder)]
extra_fields: PlaceholderEventField<SubComplexEventField>,
}
#[test]
fn test_nested_values() {
let namespaces = namespace_map();
let mut evt = NestedEvent::new_event_now(
NestedEvent::event_type_id(&namespaces),
ByteString::from_base64("dGVzdA==").unwrap(),
"Some message",
&namespaces,
);
let id = NestedEvent::event_type_id(&namespaces);
evt.base.float = 2f32;
evt.complex.float = 3f32;
evt.sub_complex.base.float = 4f32;
evt.sub_complex.string = "foo".into();
evt.sub_complex.data = 15;
evt.ignored = 16;
evt.renamed = "bar".to_owned();
evt.sub_complex.node_id = NodeId::new(0, 15);
evt.var.node_id = NodeId::new(0, 16);
evt.var.value = 20;
assert_eq!(get(&id, &evt, "Float"), Variant::from(2f32));
assert_eq!(
get(&id, &evt, "Message"),
Variant::from(LocalizedText::from("Some message"))
);
assert_eq!(get(&id, &evt, "Ignored"), Variant::Empty);
assert_eq!(
get_nested(&id, &evt, &["SubComplex", "Data"]),
Variant::Empty
);
assert_eq!(get(&id, &evt, "Fancy Name"), Variant::from("bar"));
assert_eq!(
get_nested(&id, &evt, &["SubComplex", "gnirtS"]),
Variant::from("foo")
);
assert_eq!(
get_nested(&id, &evt, &["Complex", "Float"]),
Variant::from(3f32)
);
assert_eq!(
get_nested(&id, &evt, &["SubComplex", "Float"]),
Variant::from(4f32)
);
assert_eq!(
evt.get_field(
&id,
AttributeId::NodeId,
&NumericRange::None,
&["SubComplex".into()],
),
Variant::from(NodeId::new(0, 15))
);
assert_eq!(
evt.get_field(
&id,
AttributeId::NodeId,
&NumericRange::None,
&["Var".into()],
),
Variant::from(NodeId::new(0, 16))
);
assert_eq!(
evt.get_field(
&id,
AttributeId::Value,
&NumericRange::None,
&["Var".into()],
),
Variant::from(20i32)
);
let name = QualifiedName::new(1, "Extra1");
evt.extra_fields
.insert_field(name.clone(), SubComplexEventField::default());
evt.extra_fields.get_field_mut(&name).unwrap().base.float = 20f32;
let name = QualifiedName::new(1, "Extra2");
evt.extra_fields
.insert_field(name.clone(), SubComplexEventField::default());
evt.extra_fields.get_field_mut(&name).unwrap().base.float = 21f32;
assert_eq!(
evt.get_field(
&id,
AttributeId::Value,
&NumericRange::None,
&[QualifiedName::new(1, "Extra1"), "Float".into()],
),
Variant::from(20f32)
);
assert_eq!(
evt.get_field(
&id,
AttributeId::Value,
&NumericRange::None,
&[QualifiedName::new(1, "Extra2"), "Float".into()],
),
Variant::from(21f32)
);
assert_eq!(
evt.get_field(
&id,
AttributeId::Value,
&NumericRange::None,
&[QualifiedName::new(1, "Extra3"), "Float".into()],
),
Variant::Empty
);
evt.var.extra.insert_field("Magic".into(), 15);
assert_eq!(
evt.get_field(
&id,
AttributeId::Value,
&NumericRange::None,
&["Var".into(), "Magic".into()],
),
Variant::from(15)
);
}
}