Skip to main content

opcua_nodes/events/
event.rs

1use crate::NamespaceMap;
2use opcua_types::{
3    event_field::EventField, AttributeId, ByteString, DateTime, LocalizedText, NodeId,
4    NumericRange, ObjectTypeId, QualifiedName, TimeZoneDataType, UAString, Variant,
5};
6
7/// Trait implemented by all events.
8///
9/// This is used repeatedly when publishing event notifications to
10/// clients.
11pub trait Event: EventField {
12    /// Get a field from the event. Should return [`Variant::Empty`]
13    /// if the field is not valid for the event.
14    fn get_field(
15        &self,
16        type_definition_id: &NodeId,
17        attribute_id: AttributeId,
18        index_range: &NumericRange,
19        browse_path: &[QualifiedName],
20    ) -> Variant;
21
22    /// Get the `Time` of this event.
23    fn time(&self) -> &DateTime;
24
25    /// Get the event type ID of this event.
26    fn event_type_id(&self) -> &NodeId;
27}
28
29#[derive(Debug, Default)]
30/// This corresponds to BaseEventType definition in OPC UA Part 5
31pub struct BaseEventType {
32    /// A unique identifier for an event, e.g. a GUID in a byte string
33    pub event_id: ByteString,
34    /// Event type describes the type of event
35    pub event_type: NodeId,
36    /// Source node identifies the node that the event originated from or null.
37    pub source_node: NodeId,
38    /// Source name provides the description of the source of the event,
39    /// e.g. the display of the event source
40    pub source_name: UAString,
41    /// Time provides the time the event occurred. As close
42    /// to the event generator as possible.
43    pub time: DateTime,
44    /// Receive time provides the time the OPC UA server received
45    /// the event from the underlying device of another server.
46    pub receive_time: DateTime,
47    /// Local time (optional) is a structure containing
48    /// the offset and daylightsaving flag.
49    pub local_time: Option<TimeZoneDataType>,
50    /// Message provides a human readable localizable text description
51    /// of the event.
52    pub message: LocalizedText,
53    /// Severity is an indication of the urgency of the event. Values from 1 to 1000, with 1 as the lowest
54    /// severity and 1000 being the highest. A value of 1000 would indicate an event of catastrophic nature.
55    ///
56    /// Guidance:
57    ///
58    /// * 801-1000 - High
59    /// * 601-800 - Medium High
60    /// * 401-600 - Medium
61    /// * 201-400 - Medium Low
62    /// * 1-200 - Low
63    pub severity: u16,
64    /// Condition Class Id specifies in which domain this Event is used.
65    pub condition_class_id: Option<NodeId>,
66    /// Condition class name specifies the name of the condition class of this event, if set.
67    pub condition_class_name: Option<LocalizedText>,
68    /// ConditionSubClassId specifies additional classes that apply to the Event.
69    /// It is the NodeId of the corresponding subtype of BaseConditionClassType.
70    pub condition_sub_class_id: Option<Vec<NodeId>>,
71    /// Condition sub class name specifies the names of additional classes that apply to the event.
72    pub condition_sub_class_name: Option<Vec<LocalizedText>>,
73}
74
75impl Event for BaseEventType {
76    fn time(&self) -> &DateTime {
77        &self.time
78    }
79
80    fn get_field(
81        &self,
82        type_definition_id: &NodeId,
83        attribute_id: AttributeId,
84        index_range: &NumericRange,
85        browse_path: &[QualifiedName],
86    ) -> Variant {
87        if type_definition_id == &ObjectTypeId::BaseEventType {
88            self.get_value(attribute_id, index_range, browse_path)
89        } else {
90            Variant::Empty
91        }
92    }
93
94    fn event_type_id(&self) -> &NodeId {
95        &self.event_type
96    }
97}
98
99impl EventField for BaseEventType {
100    fn get_value(
101        &self,
102        attribute_id: AttributeId,
103        index_range: &NumericRange,
104        remaining_path: &[QualifiedName],
105    ) -> Variant {
106        if remaining_path.len() != 1 || attribute_id != AttributeId::Value {
107            // Field is not from base event type.
108            return Variant::Empty;
109        }
110        let field = &remaining_path[0];
111        if field.namespace_index != 0 {
112            return Variant::Empty;
113        }
114        match field.name.as_ref() {
115            "EventId" => self.event_id.get_value(attribute_id, index_range, &[]),
116            "EventType" => self.event_type.get_value(attribute_id, index_range, &[]),
117            "SourceNode" => self.source_node.get_value(attribute_id, index_range, &[]),
118            "SourceName" => self.source_name.get_value(attribute_id, index_range, &[]),
119            "Time" => self.time.get_value(attribute_id, index_range, &[]),
120            "ReceiveTime" => self.receive_time.get_value(attribute_id, index_range, &[]),
121            "LocalTime" => self.local_time.get_value(attribute_id, index_range, &[]),
122            "Message" => self.message.get_value(attribute_id, index_range, &[]),
123            "Severity" => self.severity.get_value(attribute_id, index_range, &[]),
124            "ConditionClassId" => self
125                .condition_class_id
126                .get_value(attribute_id, index_range, &[]),
127            "ConditionClassName" => {
128                self.condition_class_name
129                    .get_value(attribute_id, index_range, &[])
130            }
131            "ConditionSubClassId" => {
132                self.condition_sub_class_id
133                    .get_value(attribute_id, index_range, &[])
134            }
135            "ConditionSubClassName" => {
136                self.condition_sub_class_name
137                    .get_value(attribute_id, index_range, &[])
138            }
139            _ => Variant::Empty,
140        }
141    }
142}
143
144impl BaseEventType {
145    /// Create a new event with `Time` set to current time.
146    pub fn new_now(
147        type_id: impl Into<NodeId>,
148        event_id: ByteString,
149        message: impl Into<LocalizedText>,
150    ) -> Self {
151        let time = DateTime::now();
152        Self::new(type_id, event_id, message, time)
153    }
154
155    /// Create a new event.
156    pub fn new(
157        type_id: impl Into<NodeId>,
158        event_id: ByteString,
159        message: impl Into<LocalizedText>,
160        time: DateTime,
161    ) -> Self {
162        Self {
163            event_id,
164            event_type: type_id.into(),
165            message: message.into(),
166            time,
167            receive_time: time,
168            ..Default::default()
169        }
170    }
171
172    /// Create a new event, resolving the event type ID.
173    pub fn new_event(
174        type_id: impl Into<NodeId>,
175        event_id: ByteString,
176        message: impl Into<LocalizedText>,
177        _namespace: &NamespaceMap,
178        time: DateTime,
179    ) -> Self {
180        Self::new(type_id, event_id, message, time)
181    }
182
183    /// Set the event source node.
184    pub fn set_source_node(mut self, source_node: NodeId) -> Self {
185        self.source_node = source_node;
186        self
187    }
188
189    /// Set the event source name.
190    pub fn set_source_name(mut self, source_name: UAString) -> Self {
191        self.source_name = source_name;
192        self
193    }
194
195    /// Set the event receive time.
196    pub fn set_receive_time(mut self, receive_time: DateTime) -> Self {
197        self.receive_time = receive_time;
198        self
199    }
200
201    /// Set the event severity.
202    pub fn set_severity(mut self, severity: u16) -> Self {
203        self.severity = severity;
204        self
205    }
206}
207
208pub use method_event_field::MethodEventField;
209
210mod method_event_field {
211    use opcua_macros::EventField;
212    use opcua_types::NodeId;
213
214    mod opcua {
215        pub(super) use crate as nodes;
216        pub(super) use opcua_types as types;
217    }
218    #[derive(Default, EventField, Debug)]
219    /// A field of an event that references a method.
220    pub struct MethodEventField {
221        /// Method node ID.
222        pub node_id: NodeId,
223    }
224}
225
226#[cfg(test)]
227mod tests {
228    use crate::NamespaceMap;
229
230    mod opcua {
231        pub(super) use crate as nodes;
232        pub(super) use opcua_types as types;
233    }
234
235    use crate::{BaseEventType, Event, EventField};
236    use opcua_types::event_field::PlaceholderEventField;
237    use opcua_types::{
238        AttributeId, ByteString, EUInformation, KeyValuePair, LocalizedText, NodeId, NumericRange,
239        ObjectTypeId, QualifiedName, StatusCode, UAString, Variant,
240    };
241    #[derive(Event)]
242    #[opcua(identifier = "s=myevent", namespace = "uri:my:namespace")]
243    struct BasicValueEvent {
244        base: BaseEventType,
245        own_namespace_index: u16,
246        // Some primitives
247        float: f32,
248        double: f64,
249        string: String,
250        status: StatusCode,
251        // Option
252        int: Option<i64>,
253        int2: Option<u64>,
254        // Vec
255        vec: Vec<i64>,
256        // OptVec
257        optvec: Option<Vec<i32>>,
258        // Complex type with message info
259        kvp: KeyValuePair,
260        euinfo: EUInformation,
261    }
262
263    fn namespace_map() -> NamespaceMap {
264        let mut map = NamespaceMap::new();
265        map.add_namespace("uri:my:namespace");
266        map
267    }
268
269    fn get(id: &NodeId, evt: &dyn Event, field: &str) -> Variant {
270        evt.get_field(id, AttributeId::Value, &NumericRange::None, &[field.into()])
271    }
272
273    fn get_nested(id: &NodeId, evt: &dyn Event, fields: &[&str]) -> Variant {
274        let fields: Vec<QualifiedName> = fields.iter().map(|f| (*f).into()).collect();
275        evt.get_field(id, AttributeId::Value, &NumericRange::None, &fields)
276    }
277
278    #[test]
279    fn test_basic_values() {
280        let namespaces = namespace_map();
281        let mut evt = BasicValueEvent::new_event_now(
282            BasicValueEvent::event_type_id(&namespaces),
283            ByteString::from_base64("dGVzdA==").unwrap(),
284            "Some message",
285            &namespaces,
286        );
287        evt.float = 1.0;
288        evt.double = 2.0;
289        evt.string = "foo".to_owned();
290        evt.status = StatusCode::BadMaxAgeInvalid;
291        evt.kvp = KeyValuePair {
292            key: "Key".into(),
293            value: 123.into(),
294        };
295        evt.int = None;
296        evt.int2 = Some(5);
297        evt.vec = vec![1, 2, 3];
298        evt.optvec = Some(vec![3, 2, 1]);
299        evt.euinfo = EUInformation {
300            namespace_uri: "uri:my:namespace".into(),
301            unit_id: 15,
302            display_name: "Some unit".into(),
303            description: "Some unit desc".into(),
304        };
305        let id = BasicValueEvent::event_type_id(&namespaces);
306
307        // Get for some other event
308        assert_eq!(
309            evt.get_field(
310                &ObjectTypeId::ProgressEventType.into(),
311                AttributeId::Value,
312                &NumericRange::None,
313                &["Message".into()],
314            ),
315            Variant::Empty
316        );
317        // Get a field that doesn't exist
318        assert_eq!(
319            evt.get_field(
320                &id,
321                AttributeId::Value,
322                &NumericRange::None,
323                &["FooBar".into()],
324            ),
325            Variant::Empty
326        );
327        // Get a child of a field without children
328        assert_eq!(
329            evt.get_field(
330                &id,
331                AttributeId::Value,
332                &NumericRange::None,
333                &["Float".into(), "Child".into()],
334            ),
335            Variant::Empty
336        );
337        // Get a non-value attribute
338        assert_eq!(
339            evt.get_field(
340                &id,
341                AttributeId::NodeId,
342                &NumericRange::None,
343                &["Float".into()],
344            ),
345            Variant::Empty
346        );
347
348        // Test equality for each field
349        assert_eq!(get(&id, &evt, "Float"), Variant::from(1f32));
350        assert_eq!(get(&id, &evt, "Double"), Variant::from(2.0));
351        assert_eq!(get(&id, &evt, "String"), Variant::from("foo"));
352        assert_eq!(
353            get(&id, &evt, "Status"),
354            Variant::from(StatusCode::BadMaxAgeInvalid)
355        );
356        let kvp: KeyValuePair = match get(&id, &evt, "Kvp") {
357            Variant::ExtensionObject(o) => *o.into_inner_as().unwrap(),
358            _ => panic!("Wrong variant type"),
359        };
360        assert_eq!(kvp.key, "Key".into());
361        assert_eq!(kvp.value, 123.into());
362
363        assert_eq!(get(&id, &evt, "Int"), Variant::Empty);
364        assert_eq!(get(&id, &evt, "Int2"), Variant::from(5u64));
365        assert_eq!(get(&id, &evt, "Vec"), Variant::from(vec![1i64, 2i64, 3i64]));
366        assert_eq!(
367            get(&id, &evt, "Optvec"),
368            Variant::from(vec![3i32, 2i32, 1i32])
369        );
370        let euinfo: EUInformation = match get(&id, &evt, "Euinfo") {
371            Variant::ExtensionObject(o) => *o.into_inner_as().unwrap(),
372            _ => panic!("Wrong variant type"),
373        };
374        assert_eq!(euinfo.namespace_uri.as_ref(), "uri:my:namespace");
375        assert_eq!(euinfo.unit_id, 15);
376        assert_eq!(euinfo.display_name, "Some unit".into());
377        assert_eq!(euinfo.description, "Some unit desc".into());
378    }
379
380    #[derive(EventField, Default, Debug)]
381    struct ComplexEventField {
382        float: f32,
383    }
384
385    #[derive(EventField, Default, Debug)]
386    struct SubComplexEventField {
387        base: ComplexEventField,
388        node_id: NodeId,
389        #[opcua(rename = "gnirtS")]
390        string: UAString,
391        #[opcua(ignore)]
392        data: i32,
393    }
394
395    #[derive(EventField, Default, Debug)]
396    struct ComplexVariable {
397        node_id: NodeId,
398        value: i32,
399        id: u32,
400        #[opcua(placeholder)]
401        extra: PlaceholderEventField<i32>,
402    }
403
404    #[derive(Event)]
405    #[opcua(identifier = "s=mynestedevent", namespace = "uri:my:namespace")]
406    struct NestedEvent {
407        base: BasicValueEvent,
408        own_namespace_index: u16,
409        complex: ComplexEventField,
410        sub_complex: SubComplexEventField,
411        var: ComplexVariable,
412        #[opcua(ignore)]
413        ignored: i32,
414        #[opcua(rename = "Fancy Name")]
415        renamed: String,
416        #[opcua(placeholder)]
417        extra_fields: PlaceholderEventField<SubComplexEventField>,
418    }
419
420    #[test]
421    fn test_nested_values() {
422        let namespaces = namespace_map();
423        let mut evt = NestedEvent::new_event_now(
424            NestedEvent::event_type_id(&namespaces),
425            ByteString::from_base64("dGVzdA==").unwrap(),
426            "Some message",
427            &namespaces,
428        );
429        let id = NestedEvent::event_type_id(&namespaces);
430        evt.base.float = 2f32;
431        evt.complex.float = 3f32;
432        evt.sub_complex.base.float = 4f32;
433        evt.sub_complex.string = "foo".into();
434        evt.sub_complex.data = 15;
435        evt.ignored = 16;
436        evt.renamed = "bar".to_owned();
437        evt.sub_complex.node_id = NodeId::new(0, 15);
438        evt.var.node_id = NodeId::new(0, 16);
439        evt.var.value = 20;
440
441        // Get field from middle event type
442        assert_eq!(get(&id, &evt, "Float"), Variant::from(2f32));
443        // Get from grandparent
444        assert_eq!(
445            get(&id, &evt, "Message"),
446            Variant::from(LocalizedText::from("Some message"))
447        );
448        // Ignored fields should be skipped
449        assert_eq!(get(&id, &evt, "Ignored"), Variant::Empty);
450        assert_eq!(
451            get_nested(&id, &evt, &["SubComplex", "Data"]),
452            Variant::Empty
453        );
454        // Get renamed
455        assert_eq!(get(&id, &evt, "Fancy Name"), Variant::from("bar"));
456        assert_eq!(
457            get_nested(&id, &evt, &["SubComplex", "gnirtS"]),
458            Variant::from("foo")
459        );
460        // Get complex
461        assert_eq!(
462            get_nested(&id, &evt, &["Complex", "Float"]),
463            Variant::from(3f32)
464        );
465        assert_eq!(
466            get_nested(&id, &evt, &["SubComplex", "Float"]),
467            Variant::from(4f32)
468        );
469
470        // Get node IDs
471        assert_eq!(
472            evt.get_field(
473                &id,
474                AttributeId::NodeId,
475                &NumericRange::None,
476                &["SubComplex".into()],
477            ),
478            Variant::from(NodeId::new(0, 15))
479        );
480        assert_eq!(
481            evt.get_field(
482                &id,
483                AttributeId::NodeId,
484                &NumericRange::None,
485                &["Var".into()],
486            ),
487            Variant::from(NodeId::new(0, 16))
488        );
489        assert_eq!(
490            evt.get_field(
491                &id,
492                AttributeId::Value,
493                &NumericRange::None,
494                &["Var".into()],
495            ),
496            Variant::from(20i32)
497        );
498
499        let name = QualifiedName::new(1, "Extra1");
500        // Get from placeholders
501        evt.extra_fields
502            .insert_field(name.clone(), SubComplexEventField::default());
503        evt.extra_fields.get_field_mut(&name).unwrap().base.float = 20f32;
504        let name = QualifiedName::new(1, "Extra2");
505        evt.extra_fields
506            .insert_field(name.clone(), SubComplexEventField::default());
507        evt.extra_fields.get_field_mut(&name).unwrap().base.float = 21f32;
508
509        assert_eq!(
510            evt.get_field(
511                &id,
512                AttributeId::Value,
513                &NumericRange::None,
514                &[QualifiedName::new(1, "Extra1"), "Float".into()],
515            ),
516            Variant::from(20f32)
517        );
518        assert_eq!(
519            evt.get_field(
520                &id,
521                AttributeId::Value,
522                &NumericRange::None,
523                &[QualifiedName::new(1, "Extra2"), "Float".into()],
524            ),
525            Variant::from(21f32)
526        );
527        assert_eq!(
528            evt.get_field(
529                &id,
530                AttributeId::Value,
531                &NumericRange::None,
532                &[QualifiedName::new(1, "Extra3"), "Float".into()],
533            ),
534            Variant::Empty
535        );
536
537        evt.var.extra.insert_field("Magic".into(), 15);
538        assert_eq!(
539            evt.get_field(
540                &id,
541                AttributeId::Value,
542                &NumericRange::None,
543                &["Var".into(), "Magic".into()],
544            ),
545            Variant::from(15)
546        );
547    }
548}