From dd67d9858dd8dc9a376debf2a794dc3199d6d206 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuzhan=20Emre=20=C3=96zdo=C4=9Fan?= Date: Tue, 26 Sep 2023 12:49:48 +0300 Subject: [PATCH 1/4] added necessary fields for event pulling edited readme added event example revision + added SourceType test fix --- README.md | 9 +++- onvif/Cargo.toml | 1 + onvif/examples/event.rs | 108 +++++++++++++++++++++++++++++++++++++ schema/src/tests.rs | 99 ++++++++++++++++++++++++++++++++++ wsdl_rs/b_2/src/lib.rs | 49 ++++++++++++++++- wsdl_rs/ws_addr/src/lib.rs | 2 +- 6 files changed, 264 insertions(+), 4 deletions(-) create mode 100644 onvif/examples/event.rs diff --git a/README.md b/README.md index d62244b..aac87f7 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Cargo.toml: onvif = { git = "https://github.com/lumeohq/onvif-rs" } ``` -## Troubleshooting +## Troubleshooting If you have an issue with OpenSSL build under Ubuntu, perform the following actions: @@ -41,6 +41,7 @@ cargo run --example discovery ``` To [inspect and control a camera](onvif/examples/camera.rs): + ```shell script cargo run --example camera -- help @@ -53,6 +54,12 @@ cargo run --example camera -- set-hostname \ cargo run --example camera -- get-stream-uris --uri=http://192.168.0.2:8000 ``` +To [pull events](onvif/examples/event.rs) from a camera: + +```shell script +cargo run --example event +``` + ## Dependencies - XSD -> Rust code generation: [xsd-parser-rs](https://github.com/lumeohq/xsd-parser-rs) diff --git a/onvif/Cargo.toml b/onvif/Cargo.toml index fbc742d..687ba73 100644 --- a/onvif/Cargo.toml +++ b/onvif/Cargo.toml @@ -43,3 +43,4 @@ futures-util = "0.3.8" structopt = "0.3.21" tokio = { version = "1.0.1", features = ["full"] } tracing-subscriber = "0.2.20" +b_2 = {path = "../wsdl_rs/b_2"} diff --git a/onvif/examples/event.rs b/onvif/examples/event.rs new file mode 100644 index 0000000..2c4bd2d --- /dev/null +++ b/onvif/examples/event.rs @@ -0,0 +1,108 @@ +// This example pulls messages related to the RuleEngine topic. +// RuleEngine topic consists of events related to motion detection. +// Tested with Dahua camera. +// Don't forget to set the camera's IP address, username and password. + +use onvif::soap::client::{ClientBuilder, Credentials}; +use schema::event::{ + self, CreatePullPointSubscription, CreatePullPointSubscriptionResponse, PullMessages, +}; +use url::Url; + +#[derive(Debug, Clone)] +pub struct Camera { + pub device_service_url: String, + pub username: String, + pub password: String, + pub event_service_url: String, +} + +impl Default for Camera { + fn default() -> Self { + Camera { + device_service_url: "http://192.168.1.100/onvif/device_service".to_string(), + username: "admin".to_string(), + password: "admin".to_string(), + event_service_url: "http://192.168.1.100/onvif/event_service".to_string(), + } + } +} + +#[tokio::main] +async fn main() { + let camera_ip = "192.168.1.50"; + let username = "admin"; + let password = "admin"; + + let camera: Camera = Camera { + device_service_url: format!("http://{}/onvif/device_service", camera_ip), + username: username.to_string(), + password: password.to_string(), + event_service_url: format!("http://{}/onvif/event_service", camera_ip), + }; + + let camera_sub: CreatePullPointSubscriptionResponse; + let creds: Credentials = Credentials { + username: camera.username.to_string(), + password: camera.password.to_string(), + }; + let event_client = ClientBuilder::new(&Url::parse(&camera.event_service_url).unwrap()) + .credentials(Some(creds)) + .build(); + let create_pull_sub_request = CreatePullPointSubscription { + initial_termination_time: None, + filter: Some(b_2::FilterType { + topic_expression: Some(b_2::TopicExpressionType { + dialect: "http://www.onvif.org/ver10/tev/topicExpression/ConcreteSet".to_string(), + inner_text: "tns1:RuleEngine//.".to_string(), + }), + }), + subscription_policy: None, + }; + let create_pull_puint_sub_response = + event::create_pull_point_subscription(&event_client, &create_pull_sub_request).await; + camera_sub = match create_pull_puint_sub_response { + Ok(sub) => sub, + Err(e) => { + println!("Error: {:?}", e); + return; + } + }; + + let uri: Url = Url::parse(&camera_sub.subscription_reference.address).unwrap(); + let creds: Credentials = Credentials { + username: camera.username.to_string(), + password: camera.password.to_string(), + }; + let pull_msg_client = ClientBuilder::new(&uri) + .credentials(Some(creds)) + .auth_type(onvif::soap::client::AuthType::Digest) + .build(); + let pull_messages_request = PullMessages { + message_limit: 256, + timeout: xsd_types::types::Duration { + seconds: 1.0, + ..Default::default() + }, + }; + + // Main Loop + loop { + let pull_messages_response = + event::pull_messages(&pull_msg_client, &pull_messages_request).await; + let msg = match pull_messages_response { + Ok(msg) => msg, + Err(e) => { + println!("Error: {:?}", e); + continue; + } + }; + if msg.notification_message.len() > 0 { + println!("Notification Message: {:?}", msg.notification_message[0]); + } else { + println!("No new notification message"); + } + + tokio::time::sleep(tokio::time::Duration::from_secs(3)).await; + } +} diff --git a/schema/src/tests.rs b/schema/src/tests.rs index 40da2da..391261b 100644 --- a/schema/src/tests.rs +++ b/schema/src/tests.rs @@ -620,3 +620,102 @@ fn extension_inside_extension() { let _ = yaserde::de::from_str::(ser).unwrap(); } + +#[tokio::test] +async fn operation_pull_messages() { + let req: event::PullMessages = Default::default(); + + let transport = FakeTransport { + response: r#" + + + 2023-09-26T07:55:11Z + + + 2023-09-26T07:56:05Z + + + + tns1:RuleEngine/CellMotionDetector/Motion + + + + + + + + + + + + + + + + "# + .into(), + }; + + let resp = event::pull_messages(&transport, &req).await.unwrap(); + + assert_eq!( + resp.notification_message[0].message.msg.source.simple_item[0].name, + "VideoSourceConfigurationToken" + ); + assert_eq!( + resp.notification_message[0].message.msg.source.simple_item[0].value, + "00000" + ); + assert_eq!( + resp.notification_message[0].message.msg.data.simple_item[0].name, + "IsMotion" + ); + assert_eq!( + resp.notification_message[0].message.msg.data.simple_item[0].value, + "true" + ); +} + +#[tokio::test] +async fn operation_create_pullpoint_subscription() { + let req: event::CreatePullPointSubscription = Default::default(); + + let transport = FakeTransport { + response: r#" + + + + http://192.168.88.108/onvif/Subscription?Idx=162 + + + + 2023-09-26T07:55:05Z + + + 2023-09-26T07:56:05Z + + + "# + .into(), + }; + + let resp = event::create_pull_point_subscription(&transport, &req) + .await + .unwrap(); + + assert_eq!( + resp.subscription_reference.address, + "http://192.168.88.108/onvif/Subscription?Idx=162" + ); +} diff --git a/wsdl_rs/b_2/src/lib.rs b/wsdl_rs/b_2/src/lib.rs index b312162..ba72cd8 100644 --- a/wsdl_rs/b_2/src/lib.rs +++ b/wsdl_rs/b_2/src/lib.rs @@ -27,6 +27,9 @@ impl Validate for QueryExpressionType {} pub struct TopicExpressionType { #[yaserde(attribute, rename = "Dialect")] pub dialect: String, + + #[yaserde(text)] + pub inner_text: String, } impl Validate for TopicExpressionType {} @@ -36,7 +39,10 @@ impl Validate for TopicExpressionType {} prefix = "wsnt", namespace = "wsnt: http://docs.oasis-open.org/wsn/b-2" )] -pub struct FilterType {} +pub struct FilterType { + #[yaserde(prefix = "wsnt", rename = "TopicExpression")] + pub topic_expression: Option, +} impl Validate for FilterType {} @@ -131,15 +137,54 @@ pub struct NotificationMessageHolderType { impl Validate for NotificationMessageHolderType {} +#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] +#[yaserde(prefix = "tt", namespace = "tt: http://www.onvif.org/ver10/schema")] +pub struct SimpleItemType { + // Item name. + #[yaserde(attribute, rename = "Name")] + pub name: String, + + // Item value. The type is defined in the corresponding description. + #[yaserde(attribute, rename = "Value")] + pub value: String, +} + pub mod notification_message_holder_type { use super::*; + #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] + #[yaserde(prefix = "tt", namespace = "tt: http://www.onvif.org/ver10/schema")] + pub struct DataType { + #[yaserde(prefix = "tt", rename = "SimpleItem")] + pub simple_item: Vec, + } + + #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] + #[yaserde(prefix = "tt", namespace = "tt: http://www.onvif.org/ver10/schema")] + pub struct SourceType { + #[yaserde(prefix = "tt", rename = "SimpleItem")] + pub simple_item: Vec, + } + + #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] + #[yaserde(prefix = "tt", namespace = "tt: http://www.onvif.org/ver10/schema")] + pub struct MessageTypeInner { + #[yaserde(prefix = "tt", rename = "Source")] + pub source: SourceType, + + #[yaserde(prefix = "tt", rename = "Data")] + pub data: DataType, + } + #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] #[yaserde( prefix = "wsnt", namespace = "wsnt: http://docs.oasis-open.org/wsn/b-2" )] - pub struct MessageType {} + pub struct MessageType { + #[yaserde(prefix = "tt", rename = "Message")] + pub msg: MessageTypeInner, + } impl Validate for MessageType {} } diff --git a/wsdl_rs/ws_addr/src/lib.rs b/wsdl_rs/ws_addr/src/lib.rs index 24546b8..074a901 100644 --- a/wsdl_rs/ws_addr/src/lib.rs +++ b/wsdl_rs/ws_addr/src/lib.rs @@ -14,7 +14,7 @@ pub type EndpointReference = EndpointReferenceType; )] pub struct EndpointReferenceType { #[yaserde(prefix = "tns", rename = "Address")] - pub address: AttributedURIType, + pub address: String, #[yaserde(prefix = "tns", rename = "ReferenceParameters")] pub reference_parameters: Option, From 1b5efbdd82ca71f91c8f207e53a2f9abf0082355 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuzhan=20Emre=20=C3=96zdo=C4=9Fan?= Date: Thu, 28 Sep 2023 18:57:41 +0300 Subject: [PATCH 2/4] !fixup test xml --- schema/src/tests.rs | 146 ++++++++++++++++++++++++++++---------------- 1 file changed, 95 insertions(+), 51 deletions(-) diff --git a/schema/src/tests.rs b/schema/src/tests.rs index 391261b..0c827db 100644 --- a/schema/src/tests.rs +++ b/schema/src/tests.rs @@ -627,42 +627,64 @@ async fn operation_pull_messages() { let transport = FakeTransport { response: r#" - - - 2023-09-26T07:55:11Z - - - 2023-09-26T07:56:05Z - - - - tns1:RuleEngine/CellMotionDetector/Motion - - - - - - - - - - - - - - - + + + + + http://www.onvif.org/ver10/events/wsdl/PullPointSubscription/PullMessagesResponse + + + http://192.168.88.108/onvif/Subscription?Idx=5 + + + + + + 2023-09-28T16:01:15Z + + + 2023-09-28T16:11:15Z + + + + tns1:RuleEngine/CellMotionDetector/Motion + + + + + + + + + + + + + + + + + "# .into(), }; @@ -683,7 +705,7 @@ async fn operation_pull_messages() { ); assert_eq!( resp.notification_message[0].message.msg.data.simple_item[0].value, - "true" + "false" ); } @@ -693,19 +715,41 @@ async fn operation_create_pullpoint_subscription() { let transport = FakeTransport { response: r#" - - - - http://192.168.88.108/onvif/Subscription?Idx=162 - - - - 2023-09-26T07:55:05Z - - - 2023-09-26T07:56:05Z - - + + + + + http://www.onvif.org/ver10/events/wsdl/EventPortType/CreatePullPointSubscriptionResponse + + + http://192.168.88.108/onvif/event_service + + + + + + + http://192.168.88.108/onvif/Subscription?Idx=5 + + + + 2023-09-28T16:01:15Z + + + 2023-09-28T16:11:15Z + + + + "# .into(), }; @@ -716,6 +760,6 @@ async fn operation_create_pullpoint_subscription() { assert_eq!( resp.subscription_reference.address, - "http://192.168.88.108/onvif/Subscription?Idx=162" + "http://192.168.88.108/onvif/Subscription?Idx=5" ); } From ae5abf2480ca21ef9bc65cabcf8987d6a3d3b0cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuzhan=20Emre=20=C3=96zdo=C4=9Fan?= Date: Wed, 3 Apr 2024 16:44:49 +0300 Subject: [PATCH 3/4] fix tests --- schema/src/tests.rs | 95 +++++++++++++++++---------------------------- 1 file changed, 36 insertions(+), 59 deletions(-) diff --git a/schema/src/tests.rs b/schema/src/tests.rs index 0c827db..e522789 100644 --- a/schema/src/tests.rs +++ b/schema/src/tests.rs @@ -20,6 +20,7 @@ impl transport::Transport for FakeTransport { } #[test] +#[cfg(feature = "devicemgmt")] fn basic_deserialization() { let response = r#" @@ -66,6 +67,7 @@ fn basic_deserialization() { assert_eq!(de.utc_date_time.as_ref().unwrap().time.second, 9); } +#[cfg(feature = "devicemgmt")] #[test] fn basic_serialization() { let expected = r#" @@ -327,6 +329,7 @@ fn duration_deserialization() { } #[tokio::test] +#[cfg(feature = "devicemgmt")] async fn operation_get_system_date_and_time() { let req: devicemgmt::GetSystemDateAndTime = Default::default(); @@ -369,6 +372,7 @@ async fn operation_get_system_date_and_time() { } #[tokio::test] +#[cfg(feature = "devicemgmt")] async fn operation_get_device_information() { let req: devicemgmt::GetDeviceInformation = Default::default(); @@ -622,32 +626,20 @@ fn extension_inside_extension() { } #[tokio::test] +#[cfg(feature = "event")] async fn operation_pull_messages() { let req: event::PullMessages = Default::default(); let transport = FakeTransport { response: r#" - - - - - http://www.onvif.org/ver10/events/wsdl/PullPointSubscription/PullMessagesResponse - - - http://192.168.88.108/onvif/Subscription?Idx=5 - - - - + 2023-09-28T16:01:15Z @@ -683,13 +675,16 @@ async fn operation_pull_messages() { - - "# .into(), }; - let resp = event::pull_messages(&transport, &req).await.unwrap(); + let response = event::pull_messages(&transport, &req).await; + + let resp = match response { + Ok(resp) => resp, + Err(err) => panic!("Error: {:?}", err), + }; assert_eq!( resp.notification_message[0].message.msg.source.simple_item[0].name, @@ -710,46 +705,28 @@ async fn operation_pull_messages() { } #[tokio::test] +#[cfg(feature = "event")] async fn operation_create_pullpoint_subscription() { let req: event::CreatePullPointSubscription = Default::default(); let transport = FakeTransport { response: r#" - - - - - http://www.onvif.org/ver10/events/wsdl/EventPortType/CreatePullPointSubscriptionResponse - - - http://192.168.88.108/onvif/event_service - - - - - - - http://192.168.88.108/onvif/Subscription?Idx=5 - - - - 2023-09-28T16:01:15Z - - - 2023-09-28T16:11:15Z - - - - + + + + http://192.168.88.108/onvif/Subscription?Idx=5 + + + + 2023-09-28T16:01:15Z + + + 2023-09-28T16:11:15Z + + "# .into(), }; From ff3e47535c0b139a9394d64c8bd039ba0ad44598 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuzhan=20Emre=20=C3=96zdo=C4=9Fan?= Date: Thu, 25 Apr 2024 14:36:50 +0300 Subject: [PATCH 4/4] fix clippy errors and warnings in examples/event.rs, add event example to readme --- README.md | 2 +- onvif/examples/event.rs | 11 ++++------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index aac87f7..cf1aece 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ cargo run --example camera -- set-hostname \ cargo run --example camera -- get-stream-uris --uri=http://192.168.0.2:8000 ``` -To [pull events](onvif/examples/event.rs) from a camera: +To [pull events](onvif/examples/event.rs) from a camera, adjust credentials in event.rs and run: ```shell script cargo run --example event diff --git a/onvif/examples/event.rs b/onvif/examples/event.rs index 2c4bd2d..2929df0 100644 --- a/onvif/examples/event.rs +++ b/onvif/examples/event.rs @@ -1,12 +1,10 @@ // This example pulls messages related to the RuleEngine topic. // RuleEngine topic consists of events related to motion detection. -// Tested with Dahua camera. +// Tested on Dahua, uniview, reolink and axis ip cameras. // Don't forget to set the camera's IP address, username and password. use onvif::soap::client::{ClientBuilder, Credentials}; -use schema::event::{ - self, CreatePullPointSubscription, CreatePullPointSubscriptionResponse, PullMessages, -}; +use schema::event::{self, CreatePullPointSubscription, PullMessages}; use url::Url; #[derive(Debug, Clone)] @@ -41,7 +39,6 @@ async fn main() { event_service_url: format!("http://{}/onvif/event_service", camera_ip), }; - let camera_sub: CreatePullPointSubscriptionResponse; let creds: Credentials = Credentials { username: camera.username.to_string(), password: camera.password.to_string(), @@ -61,7 +58,7 @@ async fn main() { }; let create_pull_puint_sub_response = event::create_pull_point_subscription(&event_client, &create_pull_sub_request).await; - camera_sub = match create_pull_puint_sub_response { + let camera_sub = match create_pull_puint_sub_response { Ok(sub) => sub, Err(e) => { println!("Error: {:?}", e); @@ -97,7 +94,7 @@ async fn main() { continue; } }; - if msg.notification_message.len() > 0 { + if !msg.notification_message.is_empty() { println!("Notification Message: {:?}", msg.notification_message[0]); } else { println!("No new notification message");