Skip to main content

bacnet_client/client/
discovery.rs

1use super::*;
2
3impl<T: TransportPort + 'static> BACnetClient<T> {
4    /// Resolve a device instance to its MAC address and optional routing info.
5    pub(super) async fn resolve_device(
6        &self,
7        device_instance: u32,
8    ) -> Result<(Vec<u8>, Option<(u16, Vec<u8>)>), Error> {
9        let dt = self.device_table.lock().await;
10        let device = dt.get(device_instance).ok_or_else(|| {
11            Error::Encoding(format!("device {device_instance} not in device table"))
12        })?;
13        let routing = match (&device.source_network, &device.source_address) {
14            (Some(snet), Some(sadr)) => Some((*snet, sadr.to_vec())),
15            _ => None,
16        };
17        Ok((device.mac_address.to_vec(), routing))
18    }
19
20    // -----------------------------------------------------------------------
21    // Multi-device batch operations
22    // -----------------------------------------------------------------------
23
24    /// Read a property from multiple discovered devices concurrently.
25    ///
26    /// All requests are dispatched concurrently (up to `max_concurrent`,
27    /// default 32) and results are returned in completion order. Each device
28    /// is resolved from the device table and auto-routed if behind a router.
29    /// Send a WhoIs broadcast to discover devices.
30    pub async fn who_is(
31        &self,
32        low_limit: Option<u32>,
33        high_limit: Option<u32>,
34    ) -> Result<(), Error> {
35        use bacnet_services::who_is::WhoIsRequest;
36
37        let request = WhoIsRequest {
38            low_limit,
39            high_limit,
40        };
41        let mut buf = BytesMut::new();
42        request.encode(&mut buf);
43
44        self.broadcast_global_unconfirmed(UnconfirmedServiceChoice::WHO_IS, &buf)
45            .await
46    }
47
48    /// Send a directed (unicast) WhoIs to a specific device.
49    pub async fn who_is_directed(
50        &self,
51        destination_mac: &[u8],
52        low_limit: Option<u32>,
53        high_limit: Option<u32>,
54    ) -> Result<(), Error> {
55        use bacnet_services::who_is::WhoIsRequest;
56
57        let request = WhoIsRequest {
58            low_limit,
59            high_limit,
60        };
61        let mut buf = BytesMut::new();
62        request.encode(&mut buf);
63
64        self.unconfirmed_request(destination_mac, UnconfirmedServiceChoice::WHO_IS, &buf)
65            .await
66    }
67
68    /// Send a WhoIs broadcast to a specific remote network.
69    pub async fn who_is_network(
70        &self,
71        dest_network: u16,
72        low_limit: Option<u32>,
73        high_limit: Option<u32>,
74    ) -> Result<(), Error> {
75        use bacnet_services::who_is::WhoIsRequest;
76
77        let request = WhoIsRequest {
78            low_limit,
79            high_limit,
80        };
81        let mut buf = BytesMut::new();
82        request.encode(&mut buf);
83
84        self.broadcast_network_unconfirmed(UnconfirmedServiceChoice::WHO_IS, &buf, dest_network)
85            .await
86    }
87
88    /// Send a WhoHas broadcast to find an object by identifier or name.
89    pub async fn who_has(
90        &self,
91        object: bacnet_services::who_has::WhoHasObject,
92        low_limit: Option<u32>,
93        high_limit: Option<u32>,
94    ) -> Result<(), Error> {
95        use bacnet_services::who_has::WhoHasRequest;
96
97        let request = WhoHasRequest {
98            low_limit,
99            high_limit,
100            object,
101        };
102        let mut buf = BytesMut::new();
103        request.encode(&mut buf)?;
104
105        self.broadcast_unconfirmed(UnconfirmedServiceChoice::WHO_HAS, &buf)
106            .await
107    }
108
109    /// Subscribe to COV notifications for an object on a remote device.
110    /// Get a snapshot of all discovered devices.
111    pub async fn discovered_devices(&self) -> Vec<DiscoveredDevice> {
112        self.device_table.lock().await.all()
113    }
114
115    /// Look up a discovered device by instance number.
116    pub async fn get_device(&self, instance: u32) -> Option<DiscoveredDevice> {
117        self.device_table.lock().await.get(instance).cloned()
118    }
119
120    /// Clear the discovered devices table.
121    pub async fn clear_devices(&self) {
122        self.device_table.lock().await.clear();
123    }
124
125    /// Manually register a device in the device table.
126    ///
127    /// Useful for adding known devices without requiring WhoIs/IAm exchange.
128    /// Sets default values for max_apdu_length (1476), segmentation (NONE),
129    /// and vendor_id (0) since these are unknown without IAm.
130    pub async fn add_device(&self, instance: u32, mac: &[u8]) -> Result<(), Error> {
131        let oid = bacnet_types::primitives::ObjectIdentifier::new(
132            bacnet_types::enums::ObjectType::DEVICE,
133            instance,
134        )?;
135        let device = DiscoveredDevice {
136            object_identifier: oid,
137            mac_address: MacAddr::from_slice(mac),
138            max_apdu_length: 1476,
139            segmentation_supported: bacnet_types::enums::Segmentation::NONE,
140            max_segments_accepted: None,
141            vendor_id: 0,
142            last_seen: std::time::Instant::now(),
143            source_network: None,
144            source_address: None,
145        };
146        self.device_table.lock().await.upsert(device);
147        Ok(())
148    }
149}