Skip to content

Commit 1b1e6fd

Browse files
Add message pulling
1 parent 7718a04 commit 1b1e6fd

File tree

6 files changed

+282
-4
lines changed

6 files changed

+282
-4
lines changed

README.md

+8-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Cargo.toml:
2020
onvif = { git = "https://github.com/lumeohq/onvif-rs" }
2121
```
2222

23-
## Troubleshooting
23+
## Troubleshooting
2424

2525
If you have an issue with OpenSSL build under Ubuntu, perform the following actions:
2626

@@ -41,6 +41,7 @@ cargo run --example discovery
4141
```
4242

4343
To [inspect and control a camera](onvif/examples/camera.rs):
44+
4445
```shell script
4546
cargo run --example camera -- help
4647

@@ -53,6 +54,12 @@ cargo run --example camera -- set-hostname \
5354
cargo run --example camera -- get-stream-uris --uri=http://192.168.0.2:8000
5455
```
5556

57+
To [pull events](onvif/examples/event.rs) from a camera, adjust credentials in event.rs and run:
58+
59+
```shell script
60+
cargo run --example event
61+
```
62+
5663
## Dependencies
5764

5865
- XSD -> Rust code generation: [xsd-parser-rs](https://github.com/lumeohq/xsd-parser-rs)

onvif/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,4 @@ futures-util = "0.3.8"
4040
structopt = "0.3.21"
4141
tokio = { version = "1.0.1", features = ["full"] }
4242
tracing-subscriber = "0.2.20"
43+
b_2 = {path = "../wsdl_rs/b_2"}

onvif/examples/event.rs

+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// This example pulls messages related to the RuleEngine topic.
2+
// RuleEngine topic consists of events related to motion detection.
3+
// Tested on Dahua, uniview, reolink and axis ip cameras.
4+
// Don't forget to set the camera's IP address, username and password.
5+
6+
use onvif::soap::client::{ClientBuilder, Credentials};
7+
use schema::event::{self, CreatePullPointSubscription, PullMessages};
8+
use url::Url;
9+
10+
#[derive(Debug, Clone)]
11+
pub struct Camera {
12+
pub device_service_url: String,
13+
pub username: String,
14+
pub password: String,
15+
pub event_service_url: String,
16+
}
17+
18+
impl Default for Camera {
19+
fn default() -> Self {
20+
Camera {
21+
device_service_url: "http://192.168.1.100/onvif/device_service".to_string(),
22+
username: "admin".to_string(),
23+
password: "admin".to_string(),
24+
event_service_url: "http://192.168.1.100/onvif/event_service".to_string(),
25+
}
26+
}
27+
}
28+
29+
#[tokio::main]
30+
async fn main() {
31+
let camera_ip = "192.168.1.50";
32+
let username = "admin";
33+
let password = "admin";
34+
35+
let camera: Camera = Camera {
36+
device_service_url: format!("http://{}/onvif/device_service", camera_ip),
37+
username: username.to_string(),
38+
password: password.to_string(),
39+
event_service_url: format!("http://{}/onvif/event_service", camera_ip),
40+
};
41+
42+
let creds: Credentials = Credentials {
43+
username: camera.username.to_string(),
44+
password: camera.password.to_string(),
45+
};
46+
let event_client = ClientBuilder::new(&Url::parse(&camera.event_service_url).unwrap())
47+
.credentials(Some(creds))
48+
.build();
49+
let create_pull_sub_request = CreatePullPointSubscription {
50+
initial_termination_time: None,
51+
filter: Some(b_2::FilterType {
52+
topic_expression: Some(b_2::TopicExpressionType {
53+
dialect: "http://www.onvif.org/ver10/tev/topicExpression/ConcreteSet".to_string(),
54+
inner_text: "tns1:RuleEngine//.".to_string(),
55+
}),
56+
}),
57+
subscription_policy: None,
58+
};
59+
let create_pull_puint_sub_response =
60+
event::create_pull_point_subscription(&event_client, &create_pull_sub_request).await;
61+
let camera_sub = match create_pull_puint_sub_response {
62+
Ok(sub) => sub,
63+
Err(e) => {
64+
println!("Error: {:?}", e);
65+
return;
66+
}
67+
};
68+
69+
let uri: Url = Url::parse(&camera_sub.subscription_reference.address).unwrap();
70+
let creds: Credentials = Credentials {
71+
username: camera.username.to_string(),
72+
password: camera.password.to_string(),
73+
};
74+
let pull_msg_client = ClientBuilder::new(&uri)
75+
.credentials(Some(creds))
76+
.auth_type(onvif::soap::client::AuthType::Digest)
77+
.build();
78+
let pull_messages_request = PullMessages {
79+
message_limit: 256,
80+
timeout: xsd_types::types::Duration {
81+
seconds: 1.0,
82+
..Default::default()
83+
},
84+
};
85+
86+
// Main Loop
87+
loop {
88+
let pull_messages_response =
89+
event::pull_messages(&pull_msg_client, &pull_messages_request).await;
90+
let msg = match pull_messages_response {
91+
Ok(msg) => msg,
92+
Err(e) => {
93+
println!("Error: {:?}", e);
94+
continue;
95+
}
96+
};
97+
if !msg.notification_message.is_empty() {
98+
println!("Notification Message: {:?}", msg.notification_message[0]);
99+
} else {
100+
println!("No new notification message");
101+
}
102+
103+
tokio::time::sleep(tokio::time::Duration::from_secs(3)).await;
104+
}
105+
}

schema/src/tests.rs

+120
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ impl transport::Transport for FakeTransport {
2020
}
2121

2222
#[test]
23+
#[cfg(feature = "devicemgmt")]
2324
fn basic_deserialization() {
2425
let response = r#"
2526
<?xml version="1.0" encoding="UTF-8"?>
@@ -66,6 +67,7 @@ fn basic_deserialization() {
6667
assert_eq!(de.utc_date_time.as_ref().unwrap().time.second, 9);
6768
}
6869

70+
#[cfg(feature = "devicemgmt")]
6971
#[test]
7072
fn basic_serialization() {
7173
let expected = r#"
@@ -327,6 +329,7 @@ fn duration_deserialization() {
327329
}
328330

329331
#[tokio::test]
332+
#[cfg(feature = "devicemgmt")]
330333
async fn operation_get_system_date_and_time() {
331334
let req: devicemgmt::GetSystemDateAndTime = Default::default();
332335

@@ -369,6 +372,7 @@ async fn operation_get_system_date_and_time() {
369372
}
370373

371374
#[tokio::test]
375+
#[cfg(feature = "devicemgmt")]
372376
async fn operation_get_device_information() {
373377
let req: devicemgmt::GetDeviceInformation = Default::default();
374378

@@ -693,3 +697,119 @@ fn media2_configs_name_serialization() {
693697
type_of(&media2::GetAudioDecoderConfigurationOptions::default())
694698
);
695699
}
700+
701+
#[tokio::test]
702+
#[cfg(feature = "event")]
703+
async fn operation_pull_messages() {
704+
let req: event::PullMessages = Default::default();
705+
706+
let transport = FakeTransport {
707+
response: r#"
708+
<tev:PullMessagesResponse
709+
xmlns:tt="http://www.onvif.org/ver10/schema"
710+
xmlns:wsnt="http://docs.oasis-open.org/wsn/b-2"
711+
xmlns:tev="http://www.onvif.org/ver10/events/wsdl"
712+
xmlns:wsa5="http://www.w3.org/2005/08/addressing"
713+
xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing"
714+
xmlns:wstop="http://docs.oasis-open.org/wsn/t-1"
715+
xmlns:tns1="http://www.onvif.org/ver10/topics">
716+
<tev:CurrentTime>
717+
2023-09-28T16:01:15Z
718+
</tev:CurrentTime>
719+
<tev:TerminationTime>
720+
2023-09-28T16:11:15Z
721+
</tev:TerminationTime>
722+
<wsnt:NotificationMessage>
723+
<wsnt:Topic
724+
Dialect="http://www.onvif.org/ver10/tev/topicExpression/ConcreteSet">
725+
tns1:RuleEngine/CellMotionDetector/Motion
726+
</wsnt:Topic>
727+
<wsnt:Message>
728+
<tt:Message
729+
UtcTime="2023-09-28T16:01:15Z"
730+
PropertyOperation="Initialized">
731+
<tt:Source>
732+
<tt:SimpleItem
733+
Name="VideoSourceConfigurationToken"
734+
Value="00000"/>
735+
<tt:SimpleItem
736+
Name="VideoAnalyticsConfigurationToken"
737+
Value="00000"/>
738+
<tt:SimpleItem
739+
Name="Rule"
740+
Value="00000"/>
741+
</tt:Source>
742+
<tt:Data>
743+
<tt:SimpleItem
744+
Name="IsMotion"
745+
Value="false"/>
746+
</tt:Data>
747+
</tt:Message>
748+
</wsnt:Message>
749+
</wsnt:NotificationMessage>
750+
</tev:PullMessagesResponse>
751+
"#
752+
.into(),
753+
};
754+
755+
let response = event::pull_messages(&transport, &req).await;
756+
757+
let resp = match response {
758+
Ok(resp) => resp,
759+
Err(err) => panic!("Error: {:?}", err),
760+
};
761+
762+
assert_eq!(
763+
resp.notification_message[0].message.msg.source.simple_item[0].name,
764+
"VideoSourceConfigurationToken"
765+
);
766+
assert_eq!(
767+
resp.notification_message[0].message.msg.source.simple_item[0].value,
768+
"00000"
769+
);
770+
assert_eq!(
771+
resp.notification_message[0].message.msg.data.simple_item[0].name,
772+
"IsMotion"
773+
);
774+
assert_eq!(
775+
resp.notification_message[0].message.msg.data.simple_item[0].value,
776+
"false"
777+
);
778+
}
779+
780+
#[tokio::test]
781+
#[cfg(feature = "event")]
782+
async fn operation_create_pullpoint_subscription() {
783+
let req: event::CreatePullPointSubscription = Default::default();
784+
785+
let transport = FakeTransport {
786+
response: r#"
787+
<tev:CreatePullPointSubscriptionResponse
788+
xmlns:tev="http://www.onvif.org/ver10/events/wsdl"
789+
xmlns:wsnt="http://docs.oasis-open.org/wsn/b-2"
790+
xmlns:wsa5="http://www.w3.org/2005/08/addressing">
791+
<tev:SubscriptionReference>
792+
<wsa5:Address>
793+
http://192.168.88.108/onvif/Subscription?Idx=5
794+
</wsa5:Address>
795+
</tev:SubscriptionReference>
796+
<wsnt:CurrentTime>
797+
2023-09-28T16:01:15Z
798+
</wsnt:CurrentTime>
799+
<wsnt:TerminationTime>
800+
2023-09-28T16:11:15Z
801+
</wsnt:TerminationTime>
802+
</tev:CreatePullPointSubscriptionResponse>
803+
"#
804+
.into(),
805+
};
806+
807+
let resp = event::create_pull_point_subscription(&transport, &req)
808+
.await
809+
.unwrap();
810+
811+
assert_eq!(
812+
resp.subscription_reference.address,
813+
"http://192.168.88.108/onvif/Subscription?Idx=5"
814+
);
815+
}

wsdl_rs/b_2/src/lib.rs

+47-2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ impl Validate for QueryExpressionType {}
2727
pub struct TopicExpressionType {
2828
#[yaserde(attribute, rename = "Dialect")]
2929
pub dialect: String,
30+
31+
#[yaserde(text)]
32+
pub inner_text: String,
3033
}
3134

3235
impl Validate for TopicExpressionType {}
@@ -36,7 +39,10 @@ impl Validate for TopicExpressionType {}
3639
prefix = "wsnt",
3740
namespace = "wsnt: http://docs.oasis-open.org/wsn/b-2"
3841
)]
39-
pub struct FilterType {}
42+
pub struct FilterType {
43+
#[yaserde(prefix = "wsnt", rename = "TopicExpression")]
44+
pub topic_expression: Option<TopicExpressionType>,
45+
}
4046

4147
impl Validate for FilterType {}
4248

@@ -131,15 +137,54 @@ pub struct NotificationMessageHolderType {
131137

132138
impl Validate for NotificationMessageHolderType {}
133139

140+
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
141+
#[yaserde(prefix = "tt", namespace = "tt: http://www.onvif.org/ver10/schema")]
142+
pub struct SimpleItemType {
143+
// Item name.
144+
#[yaserde(attribute, rename = "Name")]
145+
pub name: String,
146+
147+
// Item value. The type is defined in the corresponding description.
148+
#[yaserde(attribute, rename = "Value")]
149+
pub value: String,
150+
}
151+
134152
pub mod notification_message_holder_type {
135153
use super::*;
136154

155+
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
156+
#[yaserde(prefix = "tt", namespace = "tt: http://www.onvif.org/ver10/schema")]
157+
pub struct DataType {
158+
#[yaserde(prefix = "tt", rename = "SimpleItem")]
159+
pub simple_item: Vec<SimpleItemType>,
160+
}
161+
162+
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
163+
#[yaserde(prefix = "tt", namespace = "tt: http://www.onvif.org/ver10/schema")]
164+
pub struct SourceType {
165+
#[yaserde(prefix = "tt", rename = "SimpleItem")]
166+
pub simple_item: Vec<SimpleItemType>,
167+
}
168+
169+
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
170+
#[yaserde(prefix = "tt", namespace = "tt: http://www.onvif.org/ver10/schema")]
171+
pub struct MessageTypeInner {
172+
#[yaserde(prefix = "tt", rename = "Source")]
173+
pub source: SourceType,
174+
175+
#[yaserde(prefix = "tt", rename = "Data")]
176+
pub data: DataType,
177+
}
178+
137179
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
138180
#[yaserde(
139181
prefix = "wsnt",
140182
namespace = "wsnt: http://docs.oasis-open.org/wsn/b-2"
141183
)]
142-
pub struct MessageType {}
184+
pub struct MessageType {
185+
#[yaserde(prefix = "tt", rename = "Message")]
186+
pub msg: MessageTypeInner,
187+
}
143188

144189
impl Validate for MessageType {}
145190
}

wsdl_rs/ws_addr/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ pub type EndpointReference = EndpointReferenceType;
1414
)]
1515
pub struct EndpointReferenceType {
1616
#[yaserde(prefix = "tns", rename = "Address")]
17-
pub address: AttributedURIType,
17+
pub address: String,
1818

1919
#[yaserde(prefix = "tns", rename = "ReferenceParameters")]
2020
pub reference_parameters: Option<ReferenceParameters>,

0 commit comments

Comments
 (0)