@@ -152,6 +152,79 @@ impl BipClientBuilder {
152152 }
153153}
154154
155+ // ---------------------------------------------------------------------------
156+ // Multi-device batch operation types
157+ // ---------------------------------------------------------------------------
158+
159+ /// Default concurrency limit for multi-device batch operations.
160+ const DEFAULT_BATCH_CONCURRENCY : usize = 32 ;
161+
162+ /// A request to read a single property from a discovered device.
163+ #[ derive( Debug , Clone ) ]
164+ pub struct DeviceReadRequest {
165+ /// Device instance number (must be in the device table).
166+ pub device_instance : u32 ,
167+ /// Object to read from.
168+ pub object_identifier : bacnet_types:: primitives:: ObjectIdentifier ,
169+ /// Property to read.
170+ pub property_identifier : bacnet_types:: enums:: PropertyIdentifier ,
171+ /// Optional array index.
172+ pub property_array_index : Option < u32 > ,
173+ }
174+
175+ /// Result of a single-property read from a device within a batch.
176+ #[ derive( Debug ) ]
177+ pub struct DeviceReadResult {
178+ /// The device instance this result corresponds to.
179+ pub device_instance : u32 ,
180+ /// The read result (Ok = decoded ACK, Err = protocol/timeout error).
181+ pub result : Result < bacnet_services:: read_property:: ReadPropertyACK , Error > ,
182+ }
183+
184+ /// A request to read multiple properties from a discovered device (RPM).
185+ #[ derive( Debug , Clone ) ]
186+ pub struct DeviceRpmRequest {
187+ /// Device instance number (must be in the device table).
188+ pub device_instance : u32 ,
189+ /// ReadAccessSpecifications to send in a single RPM.
190+ pub specs : Vec < bacnet_services:: rpm:: ReadAccessSpecification > ,
191+ }
192+
193+ /// Result of an RPM to a single device within a batch.
194+ #[ derive( Debug ) ]
195+ pub struct DeviceRpmResult {
196+ /// The device instance this result corresponds to.
197+ pub device_instance : u32 ,
198+ /// The RPM result.
199+ pub result : Result < bacnet_services:: rpm:: ReadPropertyMultipleACK , Error > ,
200+ }
201+
202+ /// A request to write a single property on a discovered device.
203+ #[ derive( Debug , Clone ) ]
204+ pub struct DeviceWriteRequest {
205+ /// Device instance number (must be in the device table).
206+ pub device_instance : u32 ,
207+ /// Object to write to.
208+ pub object_identifier : bacnet_types:: primitives:: ObjectIdentifier ,
209+ /// Property to write.
210+ pub property_identifier : bacnet_types:: enums:: PropertyIdentifier ,
211+ /// Optional array index.
212+ pub property_array_index : Option < u32 > ,
213+ /// Encoded property value bytes.
214+ pub property_value : Vec < u8 > ,
215+ /// Optional write priority (1-16).
216+ pub priority : Option < u8 > ,
217+ }
218+
219+ /// Result of a single-property write to a device within a batch.
220+ #[ derive( Debug ) ]
221+ pub struct DeviceWriteResult {
222+ /// The device instance this result corresponds to.
223+ pub device_instance : u32 ,
224+ /// The write result (Ok = success, Err = protocol/timeout error).
225+ pub result : Result < ( ) , Error > ,
226+ }
227+
155228/// In-progress segmented receive state.
156229struct SegmentedReceiveState {
157230 receiver : SegmentReceiver ,
@@ -1430,6 +1503,241 @@ impl<T: TransportPort + 'static> BACnetClient<T> {
14301503 Ok ( ( ) )
14311504 }
14321505
1506+ // -----------------------------------------------------------------------
1507+ // Auto-routing _from_device variants (RPM, WP, WPM)
1508+ // -----------------------------------------------------------------------
1509+
1510+ /// Read multiple properties from a discovered device, auto-routing if needed.
1511+ pub async fn read_property_multiple_from_device (
1512+ & self ,
1513+ device_instance : u32 ,
1514+ specs : Vec < bacnet_services:: rpm:: ReadAccessSpecification > ,
1515+ ) -> Result < bacnet_services:: rpm:: ReadPropertyMultipleACK , Error > {
1516+ let ( mac, routing) = self . resolve_device ( device_instance) . await ?;
1517+
1518+ if let Some ( ( dnet, dadr) ) = routing {
1519+ use bacnet_services:: rpm:: { ReadPropertyMultipleACK , ReadPropertyMultipleRequest } ;
1520+
1521+ let request = ReadPropertyMultipleRequest {
1522+ list_of_read_access_specs : specs,
1523+ } ;
1524+ let mut buf = BytesMut :: new ( ) ;
1525+ request. encode ( & mut buf) ;
1526+
1527+ let response_data = self
1528+ . confirmed_request_routed (
1529+ & mac,
1530+ dnet,
1531+ & dadr,
1532+ ConfirmedServiceChoice :: READ_PROPERTY_MULTIPLE ,
1533+ & buf,
1534+ )
1535+ . await ?;
1536+
1537+ ReadPropertyMultipleACK :: decode ( & response_data)
1538+ } else {
1539+ self . read_property_multiple ( & mac, specs) . await
1540+ }
1541+ }
1542+
1543+ /// Write a property on a discovered device, auto-routing if needed.
1544+ pub async fn write_property_to_device (
1545+ & self ,
1546+ device_instance : u32 ,
1547+ object_identifier : bacnet_types:: primitives:: ObjectIdentifier ,
1548+ property_identifier : bacnet_types:: enums:: PropertyIdentifier ,
1549+ property_array_index : Option < u32 > ,
1550+ property_value : Vec < u8 > ,
1551+ priority : Option < u8 > ,
1552+ ) -> Result < ( ) , Error > {
1553+ let ( mac, routing) = self . resolve_device ( device_instance) . await ?;
1554+
1555+ if let Some ( ( dnet, dadr) ) = routing {
1556+ use bacnet_services:: write_property:: WritePropertyRequest ;
1557+
1558+ let request = WritePropertyRequest {
1559+ object_identifier,
1560+ property_identifier,
1561+ property_array_index,
1562+ property_value,
1563+ priority,
1564+ } ;
1565+ let mut buf = BytesMut :: new ( ) ;
1566+ request. encode ( & mut buf) ;
1567+
1568+ let _ = self
1569+ . confirmed_request_routed (
1570+ & mac,
1571+ dnet,
1572+ & dadr,
1573+ ConfirmedServiceChoice :: WRITE_PROPERTY ,
1574+ & buf,
1575+ )
1576+ . await ?;
1577+ Ok ( ( ) )
1578+ } else {
1579+ self . write_property (
1580+ & mac,
1581+ object_identifier,
1582+ property_identifier,
1583+ property_array_index,
1584+ property_value,
1585+ priority,
1586+ )
1587+ . await
1588+ }
1589+ }
1590+
1591+ /// Write multiple properties on a discovered device, auto-routing if needed.
1592+ pub async fn write_property_multiple_to_device (
1593+ & self ,
1594+ device_instance : u32 ,
1595+ specs : Vec < bacnet_services:: wpm:: WriteAccessSpecification > ,
1596+ ) -> Result < ( ) , Error > {
1597+ let ( mac, routing) = self . resolve_device ( device_instance) . await ?;
1598+
1599+ if let Some ( ( dnet, dadr) ) = routing {
1600+ use bacnet_services:: wpm:: WritePropertyMultipleRequest ;
1601+
1602+ let request = WritePropertyMultipleRequest {
1603+ list_of_write_access_specs : specs,
1604+ } ;
1605+ let mut buf = BytesMut :: new ( ) ;
1606+ request. encode ( & mut buf) ;
1607+
1608+ let _ = self
1609+ . confirmed_request_routed (
1610+ & mac,
1611+ dnet,
1612+ & dadr,
1613+ ConfirmedServiceChoice :: WRITE_PROPERTY_MULTIPLE ,
1614+ & buf,
1615+ )
1616+ . await ?;
1617+ Ok ( ( ) )
1618+ } else {
1619+ self . write_property_multiple ( & mac, specs) . await
1620+ }
1621+ }
1622+
1623+ /// Resolve a device instance to its MAC address and optional routing info.
1624+ async fn resolve_device (
1625+ & self ,
1626+ device_instance : u32 ,
1627+ ) -> Result < ( Vec < u8 > , Option < ( u16 , Vec < u8 > ) > ) , Error > {
1628+ let dt = self . device_table . lock ( ) . await ;
1629+ let device = dt. get ( device_instance) . ok_or_else ( || {
1630+ Error :: Encoding ( format ! ( "device {device_instance} not in device table" ) )
1631+ } ) ?;
1632+ let routing = match ( & device. source_network , & device. source_address ) {
1633+ ( Some ( snet) , Some ( sadr) ) => Some ( ( * snet, sadr. to_vec ( ) ) ) ,
1634+ _ => None ,
1635+ } ;
1636+ Ok ( ( device. mac_address . to_vec ( ) , routing) )
1637+ }
1638+
1639+ // -----------------------------------------------------------------------
1640+ // Multi-device batch operations
1641+ // -----------------------------------------------------------------------
1642+
1643+ /// Read a property from multiple discovered devices concurrently.
1644+ ///
1645+ /// All requests are dispatched concurrently (up to `max_concurrent`,
1646+ /// default 32) and results are returned in completion order. Each device
1647+ /// is resolved from the device table and auto-routed if behind a router.
1648+ pub async fn read_property_from_devices (
1649+ & self ,
1650+ requests : Vec < DeviceReadRequest > ,
1651+ max_concurrent : Option < usize > ,
1652+ ) -> Vec < DeviceReadResult > {
1653+ use futures_util:: stream:: { self , StreamExt } ;
1654+
1655+ let concurrency = max_concurrent. unwrap_or ( DEFAULT_BATCH_CONCURRENCY ) ;
1656+
1657+ stream:: iter ( requests)
1658+ . map ( |req| async move {
1659+ let result = self
1660+ . read_property_from_device (
1661+ req. device_instance ,
1662+ req. object_identifier ,
1663+ req. property_identifier ,
1664+ req. property_array_index ,
1665+ )
1666+ . await ;
1667+ DeviceReadResult {
1668+ device_instance : req. device_instance ,
1669+ result,
1670+ }
1671+ } )
1672+ . buffer_unordered ( concurrency)
1673+ . collect ( )
1674+ . await
1675+ }
1676+
1677+ /// Read multiple properties from multiple devices concurrently (RPM batch).
1678+ ///
1679+ /// Sends an RPM to each device concurrently. This is the most efficient
1680+ /// way to poll many properties across many devices — RPM batches within
1681+ /// a single device, and this method batches across devices.
1682+ pub async fn read_property_multiple_from_devices (
1683+ & self ,
1684+ requests : Vec < DeviceRpmRequest > ,
1685+ max_concurrent : Option < usize > ,
1686+ ) -> Vec < DeviceRpmResult > {
1687+ use futures_util:: stream:: { self , StreamExt } ;
1688+
1689+ let concurrency = max_concurrent. unwrap_or ( DEFAULT_BATCH_CONCURRENCY ) ;
1690+
1691+ stream:: iter ( requests)
1692+ . map ( |req| async move {
1693+ let result = self
1694+ . read_property_multiple_from_device ( req. device_instance , req. specs )
1695+ . await ;
1696+ DeviceRpmResult {
1697+ device_instance : req. device_instance ,
1698+ result,
1699+ }
1700+ } )
1701+ . buffer_unordered ( concurrency)
1702+ . collect ( )
1703+ . await
1704+ }
1705+
1706+ /// Write a property on multiple devices concurrently.
1707+ ///
1708+ /// All writes are dispatched concurrently (up to `max_concurrent`,
1709+ /// default 32). Results are returned in completion order.
1710+ pub async fn write_property_to_devices (
1711+ & self ,
1712+ requests : Vec < DeviceWriteRequest > ,
1713+ max_concurrent : Option < usize > ,
1714+ ) -> Vec < DeviceWriteResult > {
1715+ use futures_util:: stream:: { self , StreamExt } ;
1716+
1717+ let concurrency = max_concurrent. unwrap_or ( DEFAULT_BATCH_CONCURRENCY ) ;
1718+
1719+ stream:: iter ( requests)
1720+ . map ( |req| async move {
1721+ let result = self
1722+ . write_property_to_device (
1723+ req. device_instance ,
1724+ req. object_identifier ,
1725+ req. property_identifier ,
1726+ req. property_array_index ,
1727+ req. property_value ,
1728+ req. priority ,
1729+ )
1730+ . await ;
1731+ DeviceWriteResult {
1732+ device_instance : req. device_instance ,
1733+ result,
1734+ }
1735+ } )
1736+ . buffer_unordered ( concurrency)
1737+ . collect ( )
1738+ . await
1739+ }
1740+
14331741 /// Send a WhoIs broadcast to discover devices.
14341742 pub async fn who_is (
14351743 & self ,
0 commit comments