1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
use mdns_sd::{ServiceDaemon, ServiceEvent};
use tracing::{info, warn};
#[derive(Debug, Clone)]
/// Represents a VTN discovered via mDNS, including its connection URL and metadata
pub struct DiscoveredVtn {
/// The full URL to connect to the VTN (from local_url TXT property)
pub url: url::Url,
/// The mDNS instance name (e.g., "openleadr-vtn")
pub instance_name: String,
/// OpenADR version (e.g., "3.1.0")
pub version: String,
/// API base path (e.g., "" for root or "openadr3/3.1.0")
pub base_path: String,
}
/// Enables VENs to discover available VTNs via service type (i.e. "_openadr3._tcp.local.") without needing manual configuration of IP addresses or ports
pub async fn discover_local_vtns(
service_type: &str,
timeout: tokio::time::Duration,
vtn_limit: Option<usize>,
) -> Vec<DiscoveredVtn> {
let mdns = ServiceDaemon::new().unwrap();
let receiver = mdns.browse(service_type).expect("Failed to browse");
let mut found_vtns = Vec::new();
loop {
tokio::select! {
event = receiver.recv_async() => {
if let Ok(ServiceEvent::ServiceResolved(info)) = event {
// Get local_url from TXT properties (required by OpenADR 3.1 spec)
if let Some(local_url_str) = info.get_properties().get_property_val_str("local_url") {
match url::Url::parse(local_url_str) {
Ok(url) => {
found_vtns.push(DiscoveredVtn {
url,
instance_name: info.get_fullname().to_string(),
version: info.get_properties().get_property_val_str("version").unwrap_or("unknown").to_string(),
base_path: info.get_properties().get_property_val_str("base_path").unwrap_or("").to_string(),
});
// Check if we've hit the limit
if let Some(vtn_limit) = vtn_limit && found_vtns.len() >= vtn_limit {
return found_vtns;
}
}
Err(e) => {
warn!("Failed to parse local_url '{}': {}", local_url_str, e);
}
}
} else {
warn!("VTN {} missing required 'local_url' property, skipping", info.get_fullname());
}
}
}
_ = tokio::time::sleep_until(tokio::time::Instant::now() + timeout) => {
info!("Discovery timeout reached. Found {} VTN(s)", found_vtns.len());
break;
}
}
}
found_vtns
}