use super::OnvifClient;
use crate::error::OnvifError;
use crate::soap::{find_response, parse_soap_body};
use crate::types::{
Capabilities, DeviceInfo, DnsInformation, Hostname, NetworkGateway, NetworkInterface,
NetworkProtocol, NtpInfo, OnvifService, RelayOutput, StorageConfiguration, SystemDateTime,
SystemLog, SystemUris, User, xml_escape,
};
impl OnvifClient {
pub async fn get_capabilities(&self) -> Result<Capabilities, OnvifError> {
const ACTION: &str = "http://www.onvif.org/ver10/device/wsdl/GetCapabilities";
const BODY: &str =
"<tds:GetCapabilities><tds:Category>All</tds:Category></tds:GetCapabilities>";
let xml = self.call(&self.device_url, ACTION, BODY).await?;
let body = parse_soap_body(&xml)?;
let resp = find_response(&body, "GetCapabilitiesResponse")?;
Capabilities::from_xml(resp)
}
pub async fn get_services(&self) -> Result<Vec<OnvifService>, OnvifError> {
const ACTION: &str = "http://www.onvif.org/ver10/device/wsdl/GetServices";
const BODY: &str = "<tds:GetServices><tds:IncludeCapability>false</tds:IncludeCapability></tds:GetServices>";
let xml = self.call(&self.device_url, ACTION, BODY).await?;
let body = parse_soap_body(&xml)?;
let resp = find_response(&body, "GetServicesResponse")?;
OnvifService::vec_from_xml(resp)
}
pub async fn get_system_date_and_time(&self) -> Result<SystemDateTime, OnvifError> {
const ACTION: &str = "http://www.onvif.org/ver10/device/wsdl/GetSystemDateAndTime";
const BODY: &str = "<tds:GetSystemDateAndTime/>";
let xml = self.call(&self.device_url, ACTION, BODY).await?;
let body = parse_soap_body(&xml)?;
let resp = find_response(&body, "GetSystemDateAndTimeResponse")?;
SystemDateTime::from_xml(resp)
}
pub async fn get_device_info(&self) -> Result<DeviceInfo, OnvifError> {
const ACTION: &str = "http://www.onvif.org/ver10/device/wsdl/GetDeviceInformation";
const BODY: &str = "<tds:GetDeviceInformation/>";
let xml = self.call(&self.device_url, ACTION, BODY).await?;
let body = parse_soap_body(&xml)?;
let resp = find_response(&body, "GetDeviceInformationResponse")?;
DeviceInfo::from_xml(resp)
}
pub async fn get_hostname(&self) -> Result<Hostname, OnvifError> {
const ACTION: &str = "http://www.onvif.org/ver10/device/wsdl/GetHostname";
const BODY: &str = "<tds:GetHostname/>";
let xml = self.call(&self.device_url, ACTION, BODY).await?;
let body_node = parse_soap_body(&xml)?;
let resp = find_response(&body_node, "GetHostnameResponse")?;
Hostname::from_xml(resp)
}
pub async fn set_hostname(&self, name: &str) -> Result<(), OnvifError> {
const ACTION: &str = "http://www.onvif.org/ver10/device/wsdl/SetHostname";
let name = xml_escape(name);
let body = format!("<tds:SetHostname><tds:Name>{name}</tds:Name></tds:SetHostname>");
let xml = self.call(&self.device_url, ACTION, &body).await?;
let body_node = parse_soap_body(&xml)?;
find_response(&body_node, "SetHostnameResponse")?;
Ok(())
}
pub async fn get_ntp(&self) -> Result<NtpInfo, OnvifError> {
const ACTION: &str = "http://www.onvif.org/ver10/device/wsdl/GetNTP";
const BODY: &str = "<tds:GetNTP/>";
let xml = self.call(&self.device_url, ACTION, BODY).await?;
let body_node = parse_soap_body(&xml)?;
let resp = find_response(&body_node, "GetNTPResponse")?;
NtpInfo::from_xml(resp)
}
pub async fn set_ntp(&self, from_dhcp: bool, servers: &[&str]) -> Result<(), OnvifError> {
const ACTION: &str = "http://www.onvif.org/ver10/device/wsdl/SetNTP";
let from_dhcp_str = if from_dhcp { "true" } else { "false" };
let server_els: String = servers
.iter()
.map(|s| {
format!(
"<tds:NTPManual>\
<tt:Type>DNS</tt:Type>\
<tt:DNSname>{}</tt:DNSname>\
</tds:NTPManual>",
xml_escape(s)
)
})
.collect();
let body = format!(
"<tds:SetNTP>\
<tds:FromDHCP>{from_dhcp_str}</tds:FromDHCP>\
{server_els}\
</tds:SetNTP>"
);
let xml = self.call(&self.device_url, ACTION, &body).await?;
let body_node = parse_soap_body(&xml)?;
find_response(&body_node, "SetNTPResponse")?;
Ok(())
}
pub async fn system_reboot(&self) -> Result<String, OnvifError> {
const ACTION: &str = "http://www.onvif.org/ver10/device/wsdl/SystemReboot";
const BODY: &str = "<tds:SystemReboot/>";
let xml = self.call(&self.device_url, ACTION, BODY).await?;
let body_node = parse_soap_body(&xml)?;
let resp = find_response(&body_node, "SystemRebootResponse")?;
Ok(resp
.child("Message")
.map(|n| n.text().to_string())
.unwrap_or_default())
}
pub async fn get_scopes(&self) -> Result<Vec<String>, OnvifError> {
const ACTION: &str = "http://www.onvif.org/ver10/device/wsdl/GetScopes";
const BODY: &str = "<tds:GetScopes/>";
let xml = self.call(&self.device_url, ACTION, BODY).await?;
let body_node = parse_soap_body(&xml)?;
let resp = find_response(&body_node, "GetScopesResponse")?;
Ok(resp
.children_named("Scopes")
.filter_map(|n| n.child("ScopeItem").map(|s| s.text().to_string()))
.collect())
}
pub async fn get_users(&self) -> Result<Vec<User>, OnvifError> {
const ACTION: &str = "http://www.onvif.org/ver10/device/wsdl/GetUsers";
const BODY: &str = "<tds:GetUsers/>";
let xml = self.call(&self.device_url, ACTION, BODY).await?;
let body_node = parse_soap_body(&xml)?;
let resp = find_response(&body_node, "GetUsersResponse")?;
User::vec_from_xml(resp)
}
pub async fn create_users(&self, users: &[(&str, &str, &str)]) -> Result<(), OnvifError> {
const ACTION: &str = "http://www.onvif.org/ver10/device/wsdl/CreateUsers";
let user_els: String = users
.iter()
.map(|(u, p, l)| {
format!(
"<tds:User>\
<tt:Username>{}</tt:Username>\
<tt:Password>{}</tt:Password>\
<tt:UserLevel>{}</tt:UserLevel>\
</tds:User>",
xml_escape(u),
xml_escape(p),
xml_escape(l)
)
})
.collect();
let body = format!("<tds:CreateUsers>{user_els}</tds:CreateUsers>");
let xml = self.call(&self.device_url, ACTION, &body).await?;
let body_node = parse_soap_body(&xml)?;
find_response(&body_node, "CreateUsersResponse")?;
Ok(())
}
pub async fn delete_users(&self, usernames: &[&str]) -> Result<(), OnvifError> {
const ACTION: &str = "http://www.onvif.org/ver10/device/wsdl/DeleteUsers";
let user_els: String = usernames
.iter()
.map(|u| format!("<tds:Username>{}</tds:Username>", xml_escape(u)))
.collect();
let body = format!("<tds:DeleteUsers>{user_els}</tds:DeleteUsers>");
let xml = self.call(&self.device_url, ACTION, &body).await?;
let body_node = parse_soap_body(&xml)?;
find_response(&body_node, "DeleteUsersResponse")?;
Ok(())
}
pub async fn set_user(
&self,
username: &str,
password: Option<&str>,
user_level: &str,
) -> Result<(), OnvifError> {
const ACTION: &str = "http://www.onvif.org/ver10/device/wsdl/SetUser";
let pass_el = password
.map(|p| format!("<tt:Password>{}</tt:Password>", xml_escape(p)))
.unwrap_or_default();
let body = format!(
"<tds:SetUser>\
<tds:User>\
<tt:Username>{}</tt:Username>\
{pass_el}\
<tt:UserLevel>{}</tt:UserLevel>\
</tds:User>\
</tds:SetUser>",
xml_escape(username),
xml_escape(user_level)
);
let xml = self.call(&self.device_url, ACTION, &body).await?;
let body_node = parse_soap_body(&xml)?;
find_response(&body_node, "SetUserResponse")?;
Ok(())
}
pub async fn get_network_interfaces(&self) -> Result<Vec<NetworkInterface>, OnvifError> {
const ACTION: &str = "http://www.onvif.org/ver10/device/wsdl/GetNetworkInterfaces";
const BODY: &str = "<tds:GetNetworkInterfaces/>";
let xml = self.call(&self.device_url, ACTION, BODY).await?;
let body_node = parse_soap_body(&xml)?;
let resp = find_response(&body_node, "GetNetworkInterfacesResponse")?;
NetworkInterface::vec_from_xml(resp)
}
pub async fn set_network_interfaces(
&self,
token: &str,
enabled: bool,
ipv4_address: &str,
prefix_length: u32,
from_dhcp: bool,
) -> Result<bool, OnvifError> {
const ACTION: &str = "http://www.onvif.org/ver10/device/wsdl/SetNetworkInterfaces";
let enabled_str = if enabled { "true" } else { "false" };
let from_dhcp_str = if from_dhcp { "true" } else { "false" };
let body = format!(
"<tds:SetNetworkInterfaces>\
<tds:InterfaceToken>{}</tds:InterfaceToken>\
<tds:NetworkInterface>\
<tt:Enabled>{enabled_str}</tt:Enabled>\
<tt:IPv4>\
<tt:Enabled>true</tt:Enabled>\
<tt:DHCP>{from_dhcp_str}</tt:DHCP>\
<tt:Manual>\
<tt:Address>{}</tt:Address>\
<tt:PrefixLength>{prefix_length}</tt:PrefixLength>\
</tt:Manual>\
</tt:IPv4>\
</tds:NetworkInterface>\
</tds:SetNetworkInterfaces>",
xml_escape(token),
xml_escape(ipv4_address)
);
let xml = self.call(&self.device_url, ACTION, &body).await?;
let body_node = parse_soap_body(&xml)?;
let resp = find_response(&body_node, "SetNetworkInterfacesResponse")?;
let reboot = resp
.child("RebootNeeded")
.map(|n| n.text() == "true" || n.text() == "1")
.unwrap_or(false);
Ok(reboot)
}
pub async fn get_network_protocols(&self) -> Result<Vec<NetworkProtocol>, OnvifError> {
const ACTION: &str = "http://www.onvif.org/ver10/device/wsdl/GetNetworkProtocols";
const BODY: &str = "<tds:GetNetworkProtocols/>";
let xml = self.call(&self.device_url, ACTION, BODY).await?;
let body_node = parse_soap_body(&xml)?;
let resp = find_response(&body_node, "GetNetworkProtocolsResponse")?;
NetworkProtocol::vec_from_xml(resp)
}
pub async fn get_dns(&self) -> Result<DnsInformation, OnvifError> {
const ACTION: &str = "http://www.onvif.org/ver10/device/wsdl/GetDNS";
const BODY: &str = "<tds:GetDNS/>";
let xml = self.call(&self.device_url, ACTION, BODY).await?;
let body_node = parse_soap_body(&xml)?;
let resp = find_response(&body_node, "GetDNSResponse")?;
DnsInformation::from_xml(resp)
}
pub async fn set_dns(&self, from_dhcp: bool, servers: &[&str]) -> Result<(), OnvifError> {
const ACTION: &str = "http://www.onvif.org/ver10/device/wsdl/SetDNS";
let from_dhcp_str = if from_dhcp { "true" } else { "false" };
let server_els: String = servers
.iter()
.map(|s| {
format!(
"<tds:DNSManual>\
<tt:Type>IPv4</tt:Type>\
<tt:IPv4Address>{}</tt:IPv4Address>\
</tds:DNSManual>",
xml_escape(s)
)
})
.collect();
let body = format!(
"<tds:SetDNS>\
<tds:FromDHCP>{from_dhcp_str}</tds:FromDHCP>\
{server_els}\
</tds:SetDNS>"
);
let xml = self.call(&self.device_url, ACTION, &body).await?;
let body_node = parse_soap_body(&xml)?;
find_response(&body_node, "SetDNSResponse")?;
Ok(())
}
pub async fn get_network_default_gateway(&self) -> Result<NetworkGateway, OnvifError> {
const ACTION: &str = "http://www.onvif.org/ver10/device/wsdl/GetNetworkDefaultGateway";
const BODY: &str = "<tds:GetNetworkDefaultGateway/>";
let xml = self.call(&self.device_url, ACTION, BODY).await?;
let body_node = parse_soap_body(&xml)?;
let resp = find_response(&body_node, "GetNetworkDefaultGatewayResponse")?;
NetworkGateway::from_xml(resp)
}
pub async fn get_system_log(&self, log_type: &str) -> Result<SystemLog, OnvifError> {
const ACTION: &str = "http://www.onvif.org/ver10/device/wsdl/GetSystemLog";
let body = format!(
"<tds:GetSystemLog><tds:LogType>{}</tds:LogType></tds:GetSystemLog>",
xml_escape(log_type)
);
let xml = self.call(&self.device_url, ACTION, &body).await?;
let body_node = parse_soap_body(&xml)?;
let resp = find_response(&body_node, "GetSystemLogResponse")?;
SystemLog::from_xml(resp)
}
pub async fn get_relay_outputs(&self) -> Result<Vec<RelayOutput>, OnvifError> {
const ACTION: &str = "http://www.onvif.org/ver10/device/wsdl/GetRelayOutputs";
const BODY: &str = "<tds:GetRelayOutputs/>";
let xml = self.call(&self.device_url, ACTION, BODY).await?;
let body_node = parse_soap_body(&xml)?;
let resp = find_response(&body_node, "GetRelayOutputsResponse")?;
RelayOutput::vec_from_xml(resp)
}
pub async fn set_relay_output_settings(
&self,
relay_token: &str,
mode: &str,
delay_time: &str,
idle_state: &str,
) -> Result<(), OnvifError> {
const ACTION: &str = "http://www.onvif.org/ver10/device/wsdl/SetRelayOutputSettings";
let body = format!(
"<tds:SetRelayOutputSettings>\
<tds:RelayOutputToken>{}</tds:RelayOutputToken>\
<tds:Properties>\
<tt:Mode>{}</tt:Mode>\
<tt:DelayTime>{}</tt:DelayTime>\
<tt:IdleState>{}</tt:IdleState>\
</tds:Properties>\
</tds:SetRelayOutputSettings>",
xml_escape(relay_token),
xml_escape(mode),
xml_escape(delay_time),
xml_escape(idle_state)
);
let xml = self.call(&self.device_url, ACTION, &body).await?;
let body_node = parse_soap_body(&xml)?;
find_response(&body_node, "SetRelayOutputSettingsResponse")?;
Ok(())
}
pub async fn set_relay_output_state(
&self,
relay_token: &str,
state: &str,
) -> Result<(), OnvifError> {
const ACTION: &str = "http://www.onvif.org/ver10/device/wsdl/SetRelayOutputState";
let body = format!(
"<tds:SetRelayOutputState>\
<tds:RelayOutputToken>{}</tds:RelayOutputToken>\
<tds:LogicalState>{}</tds:LogicalState>\
</tds:SetRelayOutputState>",
xml_escape(relay_token),
xml_escape(state)
);
let xml = self.call(&self.device_url, ACTION, &body).await?;
let body_node = parse_soap_body(&xml)?;
find_response(&body_node, "SetRelayOutputStateResponse")?;
Ok(())
}
pub async fn set_network_protocols(
&self,
protocols: &[(&str, bool, &[u32])],
) -> Result<(), OnvifError> {
const ACTION: &str = "http://www.onvif.org/ver10/device/wsdl/SetNetworkProtocols";
let proto_els: String = protocols
.iter()
.map(|(name, enabled, ports)| {
let port_els: String = ports
.iter()
.map(|p| format!("<tt:Port>{p}</tt:Port>"))
.collect();
format!(
"<tds:NetworkProtocols>\
<tt:Name>{}</tt:Name>\
<tt:Enabled>{enabled}</tt:Enabled>\
{port_els}\
</tds:NetworkProtocols>",
xml_escape(name)
)
})
.collect();
let body = format!("<tds:SetNetworkProtocols>{proto_els}</tds:SetNetworkProtocols>");
let xml = self.call(&self.device_url, ACTION, &body).await?;
let body_node = parse_soap_body(&xml)?;
find_response(&body_node, "SetNetworkProtocolsResponse")?;
Ok(())
}
pub async fn set_system_factory_default(&self, default_type: &str) -> Result<(), OnvifError> {
const ACTION: &str = "http://www.onvif.org/ver10/device/wsdl/SetSystemFactoryDefault";
let body = format!(
"<tds:SetSystemFactoryDefault>\
<tds:FactoryDefault>{}</tds:FactoryDefault>\
</tds:SetSystemFactoryDefault>",
xml_escape(default_type)
);
let xml = self.call(&self.device_url, ACTION, &body).await?;
let body_node = parse_soap_body(&xml)?;
find_response(&body_node, "SetSystemFactoryDefaultResponse")?;
Ok(())
}
pub async fn get_storage_configurations(
&self,
) -> Result<Vec<StorageConfiguration>, OnvifError> {
const ACTION: &str = "http://www.onvif.org/ver10/device/wsdl/GetStorageConfigurations";
const BODY: &str = "<tds:GetStorageConfigurations/>";
let xml = self.call(&self.device_url, ACTION, BODY).await?;
let body_node = parse_soap_body(&xml)?;
let resp = find_response(&body_node, "GetStorageConfigurationsResponse")?;
StorageConfiguration::vec_from_xml(resp)
}
pub async fn set_storage_configuration(
&self,
token: &str,
storage_type: &str,
local_path: &str,
storage_uri: &str,
user: &str,
use_anonymous: bool,
) -> Result<(), OnvifError> {
const ACTION: &str = "http://www.onvif.org/ver10/device/wsdl/SetStorageConfiguration";
let token_attr = if token.is_empty() {
String::new()
} else {
format!(" token=\"{}\"", xml_escape(token))
};
let use_anon_str = if use_anonymous { "true" } else { "false" };
let body = format!(
"<tds:SetStorageConfiguration>\
<tds:StorageConfiguration{token_attr}>\
<tt:StorageType>{}</tt:StorageType>\
<tt:LocalPath>{}</tt:LocalPath>\
<tt:StorageUri>{}</tt:StorageUri>\
<tt:UserInfo>\
<tt:Username>{}</tt:Username>\
<tt:UseAnonymous>{use_anon_str}</tt:UseAnonymous>\
</tt:UserInfo>\
</tds:StorageConfiguration>\
</tds:SetStorageConfiguration>",
xml_escape(storage_type),
xml_escape(local_path),
xml_escape(storage_uri),
xml_escape(user)
);
let xml = self.call(&self.device_url, ACTION, &body).await?;
let body_node = parse_soap_body(&xml)?;
find_response(&body_node, "SetStorageConfigurationResponse")?;
Ok(())
}
pub async fn get_system_uris(&self) -> Result<SystemUris, OnvifError> {
const ACTION: &str = "http://www.onvif.org/ver10/device/wsdl/GetSystemUris";
const BODY: &str = "<tds:GetSystemUris/>";
let xml = self.call(&self.device_url, ACTION, BODY).await?;
let body_node = parse_soap_body(&xml)?;
let resp = find_response(&body_node, "GetSystemUrisResponse")?;
SystemUris::from_xml(resp)
}
pub async fn get_discovery_mode(&self) -> Result<String, OnvifError> {
const ACTION: &str = "http://www.onvif.org/ver10/device/wsdl/GetDiscoveryMode";
const BODY: &str = "<tds:GetDiscoveryMode/>";
let xml = self.call(&self.device_url, ACTION, BODY).await?;
let body_node = parse_soap_body(&xml)?;
let resp = find_response(&body_node, "GetDiscoveryModeResponse")?;
Ok(resp
.child("DiscoveryMode")
.map(|n| n.text().to_string())
.unwrap_or_default())
}
pub async fn set_discovery_mode(&self, mode: &str) -> Result<(), OnvifError> {
const ACTION: &str = "http://www.onvif.org/ver10/device/wsdl/SetDiscoveryMode";
let body = format!(
"<tds:SetDiscoveryMode>\
<tds:DiscoveryMode>{}</tds:DiscoveryMode>\
</tds:SetDiscoveryMode>",
xml_escape(mode)
);
let xml = self.call(&self.device_url, ACTION, &body).await?;
let body_node = parse_soap_body(&xml)?;
find_response(&body_node, "SetDiscoveryModeResponse")?;
Ok(())
}
}