Skip to main content

BACnetClient

Struct BACnetClient 

Source
pub struct BACnetClient<T: TransportPort = AnyTransport> { /* private fields */ }
Expand description

BACnet client with low-level and high-level request APIs.

T is the transport the client runs over. It defaults to AnyTransport, the type the builders’ build_any() methods return, so BACnetClient alone names a client over any built-in transport; BACnetClient<BipTransport> and the like name one over a concrete transport.

Implementations§

Source§

impl<T: TransportPort + 'static> BACnetClient<T>

Source

pub async fn confirmed_audit_notification( &self, destination_mac: &[u8], request: &AuditNotificationRequest, ) -> Result<(), Error>

Send a typed ConfirmedAuditNotification to a directly addressed MAC.

The request uses the existing qualified Clause 21 Audit wire model.

Source

pub async fn unconfirmed_audit_notification( &self, destination_mac: &[u8], request: &AuditNotificationRequest, ) -> Result<(), Error>

Send a typed UnconfirmedAuditNotification to a directly addressed MAC.

The request uses the existing qualified Clause 21 Audit wire model and completes after transport send without waiting for a response.

Source

pub async fn audit_log_query( &self, destination_mac: &[u8], request: &AuditLogQueryRequest, ) -> Result<AuditLogQueryAck, Error>

Query an Audit Log at a directly addressed MAC and decode its typed ACK.

Request and response use the existing qualified Clause 21 Audit wire model; this helper does not reinterpret query fields or execution.

Source§

impl BACnetClient<Bip6Transport>

Source

pub fn bip6_builder() -> Bip6ClientBuilder

Available on crate feature ipv6 only.

Create a BIP6-specific builder for BACnet/IPv6 transport.

Source§

impl<T: TransportPort + 'static> BACnetClient<T>

Source

pub async fn subscribe_cov( &self, destination_mac: &[u8], subscriber_process_identifier: u32, monitored_object_identifier: ObjectIdentifier, confirmed: bool, lifetime: Option<u32>, ) -> Result<(), Error>

Subscribe to COV notifications for an object at a directly reachable MAC address.

Source

pub async fn subscribe_cov_to_device( &self, device_instance: u32, subscriber_process_identifier: u32, monitored_object_identifier: ObjectIdentifier, confirmed: bool, lifetime: Option<u32>, ) -> Result<(), Error>

Subscribe to COV notifications for a discovered device, auto-routing if needed.

Source

pub async fn unsubscribe_cov( &self, destination_mac: &[u8], subscriber_process_identifier: u32, monitored_object_identifier: ObjectIdentifier, ) -> Result<(), Error>

Cancel a COV subscription on a remote device.

Source

pub async fn unsubscribe_cov_to_device( &self, device_instance: u32, subscriber_process_identifier: u32, monitored_object_identifier: ObjectIdentifier, ) -> Result<(), Error>

Cancel a COV subscription for a discovered device, auto-routing if needed.

Source

pub async fn subscribe_cov_property( &self, destination_mac: &[u8], subscription: CovPropertySubscription, ) -> Result<(), Error>

Subscribe to COV notifications for a single property at a directly reachable MAC address. Lifetime must be positive; use unsubscribe_cov_property for cancellation.

Source

pub async fn subscribe_cov_property_to_device( &self, device_instance: u32, subscription: CovPropertySubscription, ) -> Result<(), Error>

Subscribe to COV notifications for a single property on a discovered device, auto-routing if needed. Lifetime must be positive; cancellation has a separate method.

Source

pub async fn unsubscribe_cov_property( &self, destination_mac: &[u8], subscriber_process_identifier: u32, monitored_object_identifier: ObjectIdentifier, monitored_property_identifier: PropertyIdentifier, monitored_property_array_index: Option<u32>, ) -> Result<(), Error>

Cancel a single-property COV subscription at a directly reachable MAC address.

Source

pub async fn unsubscribe_cov_property_to_device( &self, device_instance: u32, subscriber_process_identifier: u32, monitored_object_identifier: ObjectIdentifier, monitored_property_identifier: PropertyIdentifier, monitored_property_array_index: Option<u32>, ) -> Result<(), Error>

Cancel a single-property COV subscription for a discovered device, auto-routing if needed.

Source

pub async fn unsubscribe_cov_target( &self, target: &CovTarget, subscriber_process_identifier: u32, monitored_object_identifier: ObjectIdentifier, ) -> Result<(), Error>

Cancel a COV subscription at target, the device a manage_cov_subscription went to. The cancel goes the way the subscription did, with no device-table lookup, so it reaches the same device even if the table has since dropped or moved its entry.

Source

pub async fn unsubscribe_cov_property_target( &self, target: &CovTarget, subscriber_process_identifier: u32, monitored_object_identifier: ObjectIdentifier, monitored_property_identifier: PropertyIdentifier, monitored_property_array_index: Option<u32>, ) -> Result<(), Error>

Cancel a single-property COV subscription at target: unsubscribe_cov_target for one property.

Source

pub fn cov_notifications(&self) -> Receiver<ReceivedCOVNotification>

Get a receiver for incoming COV notifications. Each call returns a new independent receiver.

Source§

impl<T: TransportPort + 'static> BACnetClient<T>

Source

pub async fn manage_cov_subscription( self: Arc<Self>, target: CovTarget, subscriber_process_identifier: u32, monitored_object_identifier: ObjectIdentifier, confirmed: bool, lifetime: u32, options: ManagedCOVSubscriptionOptions, ) -> Result<ManagedCOVSubscription, Error>

Start a managed finite SubscribeCOV renewal task for the device at target, on this network or behind a router.

The helper sends the initial SubscribeCOV request, then renews the same subscription before expiry. It also listens to Self::cov_notifications and moves the renewal earlier when matching notifications report a lower time_remaining than the requested lifetime-based schedule. A notification matches when it comes from target’s address (see CovTarget) and names this subscription’s process and object. The helper only renews; after ManagedCOVSubscription::stop, cancel with Self::unsubscribe_cov_target.

This method takes Arc<Self> so the renewal task can keep the client alive. Call it as Arc::clone(&client).manage_cov_subscription(...).

Source

pub async fn manage_cov_property_subscription( self: Arc<Self>, target: CovTarget, subscription: CovPropertySubscription, options: ManagedCOVSubscriptionOptions, ) -> Result<ManagedCOVSubscription, Error>

Start a managed finite SubscribeCOVProperty renewal task for the device at target: Self::manage_cov_subscription for one property.

Every renewal repeats subscription as given, its lifetime included. The helper only renews; after ManagedCOVSubscription::stop, cancel with Self::unsubscribe_cov_property_target.

Source§

impl<T: TransportPort + 'static> BACnetClient<T>

Source

pub fn device_events(&self) -> Receiver<DeviceEvent>

Get a receiver for device discovery events. Each call returns a new independent receiver.

Events are notification-only; discovered_devices() remains the authoritative snapshot of the current discovery table.

Source

pub fn device_collision_events(&self) -> Receiver<DeviceCollisionEvent>

Get a receiver for Device-instance collision notifications. Each call returns a new independent receiver.

Delivery is notification-only and best-effort: receivers can lag and missed notifications do not affect collision handling. The retained snapshot identifies the discovery-table row that remains authoritative; the conflicting incoming snapshot is not installed in the table.

Source§

impl<T: TransportPort + 'static> BACnetClient<T>

Source

pub async fn device_communication_control( &self, destination_mac: &[u8], enable_disable: EnableDisable, time_duration: Option<u16>, password: Option<String>, ) -> Result<(), Error>

Send DeviceCommunicationControl to a remote device.

Source

pub async fn reinitialize_device( &self, destination_mac: &[u8], reinitialized_state: ReinitializedState, password: Option<String>, ) -> Result<(), Error>

Send ReinitializeDevice to a remote device.

Source

pub async fn time_synchronization( &self, destination_mac: &[u8], date: Date, time: Time, ) -> Result<(), Error>

Send a TimeSynchronization request (unconfirmed, no response expected).

Source

pub async fn utc_time_synchronization( &self, destination_mac: &[u8], date: Date, time: Time, ) -> Result<(), Error>

Send a UTCTimeSynchronization request (unconfirmed, no response expected).

Source

pub async fn device_communication_control_from_device( &self, device_instance: u32, enable_disable: EnableDisable, time_duration: Option<u16>, password: Option<String>, ) -> Result<(), Error>

Send DeviceCommunicationControl to a discovered device, routing when needed.

Preserves the request fields and confirmation semantics of Self::device_communication_control. A missing device fails before sending.

Source

pub async fn reinitialize_device_from_device( &self, device_instance: u32, reinitialized_state: ReinitializedState, password: Option<String>, ) -> Result<(), Error>

Send ReinitializeDevice to a discovered device, routing when needed.

Preserves the request fields and confirmation semantics of Self::reinitialize_device. A missing device fails before sending.

Source

pub async fn time_synchronization_from_device( &self, device_instance: u32, date: Date, time: Time, ) -> Result<(), Error>

Send local time synchronization to a discovered device, routing when needed.

This unconfirmed service sends once without waiting for a response. Success means local send completion, not acceptance by the remote clock.

Source

pub async fn utc_time_synchronization_from_device( &self, device_instance: u32, date: Date, time: Time, ) -> Result<(), Error>

Send UTC time synchronization to a discovered device, routing when needed.

This unconfirmed service sends once without waiting for a response. Success means local send completion, not acceptance by the remote clock.

Source§

impl<T: TransportPort + 'static> BACnetClient<T>

Source

pub async fn who_is( &self, range: Option<DeviceInstanceRange>, ) -> Result<(), Error>

Send a WhoIs broadcast to discover devices.

range limits the devices that answer to those whose instance lies in it; None asks every device. A DeviceInstanceRange holds both limits, low no greater than high, so the request can’t carry one alone (Clause 16.10, #1483).

Source

pub async fn who_is_directed( &self, destination_mac: &[u8], range: Option<DeviceInstanceRange>, ) -> Result<(), Error>

Send a directed (unicast) WhoIs to a specific device, with the same range as Self::who_is.

Source

pub async fn who_is_network( &self, dest_network: u16, range: Option<DeviceInstanceRange>, ) -> Result<(), Error>

Send a WhoIs broadcast to a specific remote network, with the same range as Self::who_is.

Source

pub async fn who_has( &self, object: WhoHasObject, range: Option<DeviceInstanceRange>, ) -> Result<(), Error>

Send a WhoHas as a global broadcast, as Self::who_is does, so that devices behind routers hear it too, to find an object by identifier or name, asking the devices in range, or every device for None (Clause 16.9, #1483).

Source

pub async fn discovered_devices(&self) -> Vec<DiscoveredDevice>

Get a snapshot of all discovered devices.

Source

pub async fn get_device(&self, instance: u32) -> Option<DiscoveredDevice>

Look up a discovered device by instance number.

Source

pub async fn clear_devices(&self)

Clear the discovered devices table.

Source

pub async fn add_device(&self, instance: u32, mac: &[u8]) -> Result<(), Error>

Manually register a local device in the device table.

Useful for adding known devices without requiring WhoIs/IAm exchange. The metadata defaults below (max_apdu_length 1476, segmentation NONE, vendor_id 0) are local/manual registration defaults, not values learned from an I-Am; requests to this device are bounded by them until a real I-Am refreshes the row. The row is unambiguously local: routed lookups by SNET/SADR will not match it.

Source

pub async fn add_routed_device( &self, config: RoutedDeviceConfig, ) -> Result<(), Error>

Manually register a routed peer whose address and capabilities are already known, without fabricating an I-Am exchange.

router_mac is only the immediate transport next hop (Clause 6.2.2); the peer’s identity is the (remote_network, remote_mac) SNET/SADR pair, which is what BACnetClient::confirmed_request_routed and the device-table routed lookup match against. The supplied Max APDU Length Accepted, segmentation capability, and optional Max Segments Accepted are treated as advertised peer limits for request sizing.

Vendor ID is recorded as 0 when unknown; this does not affect routing or request-size decisions.

Malformed routing metadata is rejected deliberately: an empty router_mac or remote_mac cannot identify a next hop or a peer, so such a registration returns Error::Encoding and the table is left unchanged (an empty SADR could never satisfy a routed lookup). A remote_network outside 1..=65534 or a remote_mac longer than MAX_MAC_LEN is refused the same way, since BACnetClient::confirmed_request_routed refuses that destination.

Source§

impl<T: TransportPort + 'static> BACnetClient<T>

Source

pub fn event_notifications(&self) -> Receiver<ReceivedEventNotification>

Subscribe to incoming event notifications with a new independent receiver.

Only subsequent notifications are delivered. Slow receivers observe broadcast lag; no receivers or lag never prevent a valid confirmed request from being acknowledged. The wire acknowledgment confirms receipt, not operator acknowledgment or durable application processing. Stopping the client ends dispatch; dropping it closes the channel after queued messages.

Source§

impl<T: TransportPort + 'static> BACnetClient<T>

Source

pub async fn acknowledge_alarm_request( &self, destination_mac: &[u8], request: &AcknowledgeAlarmRequest, ) -> Result<(), Error>

Send a caller-supplied AcknowledgeAlarm request without fabricating fields.

Source

pub async fn acknowledge_alarm_request_from_device( &self, device_instance: u32, request: &AcknowledgeAlarmRequest, ) -> Result<(), Error>

Send exact caller-supplied alarm acknowledgment fields to a discovered device.

Routes when needed and preserves both timestamps, confirmation and errors from Self::acknowledge_alarm_request. A missing device fails before sending.

Source

pub async fn acknowledge_alarm( &self, destination_mac: &[u8], acknowledging_process_identifier: u32, event_object_identifier: ObjectIdentifier, event_state_acknowledged: EventState, acknowledgment_source: &str, ) -> Result<(), Error>

👎Deprecated:

use acknowledge_alarm_request with caller-supplied correlation timestamps

Acknowledge an alarm on a remote device.

This compatibility helper fabricates sequence-zero timestamps, which are not a valid general correlation mechanism. Use Self::acknowledge_alarm_request with the original notification’s exact timestamp and a caller-selected acknowledgment time.

Source

pub async fn atomic_read_file( &self, destination_mac: &[u8], file_identifier: ObjectIdentifier, access: FileAccessMethod, ) -> Result<Bytes, Error>

Read file data from a remote device (stream or record access).

Source

pub async fn atomic_read_file_decoded( &self, destination_mac: &[u8], file_identifier: ObjectIdentifier, access: FileAccessMethod, ) -> Result<AtomicReadFileAck, Error>

Read and strictly decode one AtomicReadFile ACK window.

This additive typed boundary validates that the ACK uses the requested stream/record access arm and does not return more octets or records than requested. Use Self::atomic_read_file when the raw encoded service payload is required for compatibility.

Source

pub async fn atomic_read_file_from_device( &self, device_instance: u32, file_identifier: ObjectIdentifier, access: FileAccessMethod, ) -> Result<Bytes, Error>

Read raw file acknowledgement bytes from a discovered device.

Routes stream and record access when needed. The device address is resolved once for this request; a missing device fails before sending. Use Self::atomic_read_file_decoded_from_device for a checked ACK.

Source

pub async fn atomic_read_file_decoded_from_device( &self, device_instance: u32, file_identifier: ObjectIdentifier, access: FileAccessMethod, ) -> Result<AtomicReadFileAck, Error>

Read one checked file window from a discovered device, routing if needed.

Preserves the access-arm and window checks of Self::atomic_read_file_decoded. Successive calls resolve separately; a sequence of windows is not an atomic snapshot of the remote file.

Source

pub async fn atomic_write_file( &self, destination_mac: &[u8], file_identifier: ObjectIdentifier, access: FileWriteAccessMethod, ) -> Result<Bytes, Error>

Write file data to a remote device (stream or record access).

Source

pub async fn atomic_write_file_from_device( &self, device_instance: u32, file_identifier: ObjectIdentifier, access: FileWriteAccessMethod, ) -> Result<Bytes, Error>

Write stream or record data to a discovered device, routing if needed.

Returns the raw acknowledgement, as in Self::atomic_write_file. A missing device fails before sending.

Source

pub async fn add_list_element( &self, destination_mac: &[u8], object_identifier: ObjectIdentifier, property_identifier: PropertyIdentifier, property_array_index: Option<u32>, list_of_elements: Vec<u8>, ) -> Result<(), Error>

Add elements to a list property on a remote device.

Rejects index zero, empty elements and malformed tag framing before transaction admission or traffic. Element datatype remains target-owned.

Source

pub async fn remove_list_element( &self, destination_mac: &[u8], object_identifier: ObjectIdentifier, property_identifier: PropertyIdentifier, property_array_index: Option<u32>, list_of_elements: Vec<u8>, ) -> Result<(), Error>

Remove elements from a list property on a remote device.

Rejects index zero, empty elements and malformed tag framing before transaction admission or traffic. Element datatype remains target-owned.

Source§

impl<T: TransportPort + 'static> BACnetClient<T>

Source

pub fn i_have_events(&self) -> Receiver<ReceivedIHave>

Get a receiver for every I-Have this client receives, an answer to its own Who-Has or not (a device may send one at any time). Each call returns a new independent receiver. A receiver that falls I_HAVE_CHANNEL_CAPACITY behind loses the oldest.

Source

pub async fn who_has_replies( &self, object: WhoHasObject, range: Option<DeviceInstanceRange>, wait: Duration, ) -> Result<Vec<ReceivedIHave>, Error>

Send a Who-Has as Self::who_has does, then collect the I-Haves that answer it (ReceivedIHave::answers) for wait, one per device and object, in the order they arrived. No answer is an empty list, not an error.

Source§

impl<T: TransportPort + 'static> BACnetClient<T>

Source

pub async fn start(config: ClientConfig, transport: T) -> Result<Self, Error>

Start the client: bind transport, start network layer, spawn dispatch.

Source

pub async fn start_with_options( config: ClientConfig, transport: T, options: ClientOptions, ) -> Result<Self, Error>

Start the client with additional startup options.

Source

pub fn group_source_request_drops(&self) -> u64

Confirmed requests ignored since start because the link-layer address they came from is a group destination of the transport (TransportPort::is_group_destination), such as a B/IP broadcast or multicast address (#1504). The answer, such as the acknowledgment of a confirmed COV notification, would go back to that address and reach every node in the group, so the request is neither handled nor answered. No built-in transport hands one up; a custom one can.

Source

pub fn local_mac(&self) -> &[u8] ⓘ

Get the client’s local MAC address.

Source

pub fn max_apdu_length(&self) -> u16

Current BACnet max-APDU bucket advertised by this client.

Returns 0 if the live transport budget is below the smallest BACnet max-APDU bucket and a confirmed request would currently fail before encoding.

Source

pub fn transport_max_apdu_length(&self) -> u16

Current APDU payload budget reported by the underlying transport.

For BACnet/SC this reflects the negotiated hub envelope after connect and may be lower than the encoded BACnet max-APDU bucket advertised by the client.

Source

pub async fn stop(&mut self) -> Result<(), Error>

Stop the client, aborting and joining dispatch and local Number work. Canceling this waiter retains task joins for a subsequent stop.

Source§

impl<T: TransportPort + 'static> BACnetClient<T>

Source

pub fn network_reports(&self) -> Receiver<ReceivedNetworkReport>

Get a receiver for the network reports this client receives: the routers that announce themselves, Network-Number-Is broadcasts and the rejects of messages it sent. Each call returns a new independent receiver. A receiver that falls NETWORK_REPORT_CHANNEL_CAPACITY behind loses the oldest.

Source

pub async fn who_is_router_to_network( &self, network: Option<u16>, destination_mac: Option<&[u8]>, ) -> Result<(), Error>

Send a Who-Is-Router-To-Network (Clause 6.4.1), asking which router reaches network, or, for None, every router for all the networks it reaches. It is broadcast on the local network when destination_mac is None, else sent to that router alone. Answers come as NetworkReport::IAmRouterToNetwork through Self::network_reports. network 0 and 0xFFFF are refused.

Source

pub async fn what_is_network_number( &self, destination_mac: Option<&[u8]>, ) -> Result<(), Error>

Send a What-Is-Network-Number (Clause 6.4.19), broadcast when destination_mac is None. Any node holding this network’s number, a router in particular, broadcasts a Network-Number-Is in reply, which comes as NetworkReport::NetworkNumberIs through Self::network_reports. Ask once and keep the answer: the standard doesn’t want the question repeated.

Source

pub async fn discover_routers( &self, network: Option<u16>, wait: Duration, ) -> Result<Vec<DiscoveredRouter>, Error>

Broadcast a Who-Is-Router-To-Network for network (or every network), then collect the I-Am-Router-To-Network messages heard for wait: one entry per router MAC, in the order first heard. For a network, only the routers that list it are kept. No answer is an empty list, not an error.

Source§

impl<T: TransportPort + 'static> BACnetClient<T>

Source

pub async fn delete_object( &self, destination_mac: &[u8], object_identifier: ObjectIdentifier, ) -> Result<(), Error>

Delete an object on a remote device (DeleteObject, Clause 15.4).

Source

pub async fn create_object( &self, destination_mac: &[u8], object_specifier: ObjectSpecifier, initial_values: Vec<BACnetPropertyValue>, ) -> Result<ObjectIdentifier, Error>

Create an object on a remote device (CreateObject, Clause 15.3).

Returns the identifier the device’s CreateObject-ACK names, which tells a create by type the instance the device chose. An ACK that doesn’t decode is an error, though the device may have created the object.

Source

pub async fn delete_object_from_device( &self, device_instance: u32, object_identifier: ObjectIdentifier, ) -> Result<(), Error>

Delete an object on a discovered device, routing when needed.

Uses the same confirmation and error semantics as Self::delete_object. A missing device fails before sending.

Source

pub async fn create_object_from_device( &self, device_instance: u32, object_specifier: ObjectSpecifier, initial_values: Vec<BACnetPropertyValue>, ) -> Result<ObjectIdentifier, Error>

Create an object on a discovered device, routing when needed.

Retains the initial values and decoded acknowledgement of Self::create_object. A missing device fails before sending.

Source§

impl<T: TransportPort + 'static> BACnetClient<T>

Source

pub async fn read_property( &self, destination_mac: &[u8], object_identifier: ObjectIdentifier, property_identifier: PropertyIdentifier, property_array_index: Option<u32>, ) -> Result<ReadPropertyACK, Error>

Read a property from a remote device.

The ACK must match the requested object, property and array index. Device/Network Port wildcard requests accept only a same-type concrete peer-reported object; malformed or mismatched ACKs return a decoding error.

Source

pub async fn read_property_from_device( &self, device_instance: u32, object_identifier: ObjectIdentifier, property_identifier: PropertyIdentifier, property_array_index: Option<u32>, ) -> Result<ReadPropertyACK, Error>

Read a property from a discovered device, auto-routing if needed.

Source

pub async fn read_property_routed( &self, router_mac: &[u8], dest_network: u16, dest_mac: &[u8], object_identifier: ObjectIdentifier, property_identifier: PropertyIdentifier, property_array_index: Option<u32>, ) -> Result<ReadPropertyACK, Error>

Read a property from a device on a remote BACnet network via a router.

Source

pub async fn read_property_multiple( &self, destination_mac: &[u8], specs: Vec<ReadAccessSpecification>, ) -> Result<ReadPropertyMultipleACK, Error>

Read multiple properties from one or more objects on a remote device.

Source

pub async fn read_property_multiple_from_device( &self, device_instance: u32, specs: Vec<ReadAccessSpecification>, ) -> Result<ReadPropertyMultipleACK, Error>

Read multiple properties from a discovered device, auto-routing if needed.

Source

pub async fn read_property_from_devices( &self, requests: Vec<DeviceReadRequest>, max_concurrent: Option<NonZeroUsize>, ) -> Vec<DeviceReadResult>

Read one property from multiple discovered devices concurrently.

All reads are dispatched concurrently (up to max_concurrent, default 32). The nonzero limit prevents a stalled batch. Results are returned in completion order; request_index identifies the original input occurrence, including duplicates. Dropping the future cancels pending requests and returns no partial vector.

Source

pub async fn read_property_multiple_from_devices( &self, requests: Vec<DeviceRpmRequest>, max_concurrent: Option<NonZeroUsize>, ) -> Vec<DeviceRpmResult>

Read multiple properties from multiple devices concurrently (RPM batch).

Sends an RPM to each device concurrently. This is the most efficient way to poll many properties across many devices — RPM batches within a single device, and this method batches across devices. A nonzero limit bounds concurrency (None uses 32). Results use completion order and carry the original input occurrence in request_index. Dropping the future cancels pending requests and returns no partial vector.

Source

pub async fn write_property( &self, destination_mac: &[u8], object_identifier: ObjectIdentifier, property_identifier: PropertyIdentifier, property_array_index: Option<u32>, property_value: Vec<u8>, priority: Option<u8>, ) -> Result<(), Error>

Write a property on a remote device.

Source

pub async fn write_property_multiple( &self, destination_mac: &[u8], specs: Vec<WriteAccessSpecification>, ) -> Result<(), Error>

Write multiple properties on one or more objects on a remote device.

Source

pub async fn write_property_to_device( &self, device_instance: u32, object_identifier: ObjectIdentifier, property_identifier: PropertyIdentifier, property_array_index: Option<u32>, property_value: Vec<u8>, priority: Option<u8>, ) -> Result<(), Error>

Write a property on a discovered device, auto-routing if needed.

Source

pub async fn write_property_multiple_to_device( &self, device_instance: u32, specs: Vec<WriteAccessSpecification>, ) -> Result<(), Error>

Write multiple properties on a discovered device, auto-routing if needed.

Source

pub async fn write_property_to_devices( &self, requests: Vec<DeviceWriteRequest>, max_concurrent: Option<NonZeroUsize>, ) -> Vec<DeviceWriteResult>

Write one property on multiple discovered devices concurrently.

All writes are dispatched concurrently (up to max_concurrent, default 32). The nonzero limit prevents a stalled batch. Results are returned in completion order; request_index identifies the original input occurrence, including duplicates. Dropping the future cancels pending requests and returns no partial vector.

Source§

impl<T: TransportPort + 'static> BACnetClient<T>

Source

pub async fn read_range( &self, destination_mac: &[u8], object_identifier: ObjectIdentifier, property_identifier: PropertyIdentifier, property_array_index: Option<u32>, range: Option<RangeSpec>, ) -> Result<ReadRangeAck, Error>

Read a range of items from a list or log-buffer property.

The acknowledgement must keep every rule ReadRangeAck::violations checks against the request; one that breaks a rule fails with Error::ReadRangeViolation. Use read_range_with and ReadRangeValidation::Lenient to keep such an answer.

Source

pub async fn read_range_with( &self, destination_mac: &[u8], request: &ReadRangeRequest, validation: ReadRangeValidation, ) -> Result<ReadRangeReply, Error>

Read a range of items, choosing how to treat an acknowledgement that breaks a rule: Strict refuses it, Lenient returns it with the list of rules it broke, so a device that numbers the record after its sequence wrap 0 doesn’t cost the page.

An acknowledgement that doesn’t decode fails under either mode.

Source

pub async fn read_range_from_device( &self, device_instance: u32, object_identifier: ObjectIdentifier, property_identifier: PropertyIdentifier, property_array_index: Option<u32>, range: Option<RangeSpec>, ) -> Result<ReadRangeAck, Error>

Read a range from a discovered device, routing when needed.

The acknowledgement is checked strictly, as in Self::read_range. A missing device fails before sending.

Source

pub async fn read_range_from_device_with( &self, device_instance: u32, request: &ReadRangeRequest, validation: ReadRangeValidation, ) -> Result<ReadRangeReply, Error>

Read a range from a discovered device with strict or lenient validation.

This resolves the device once and preserves the acknowledgement checks of Self::read_range_with, including decode failures in either mode.

Source

pub async fn read_log_page_from_device( &self, device_instance: u32, log: ObjectIdentifier, cursor: LogCursor, page_size: u16, ) -> Result<LogPage, Error>

Read one log page from a discovered device, routing every subrequest.

The router, remote network and station are resolved once for this page; metadata property reads and range requests all use that address snapshot. A later page resolves the device again. Cursor, page-size and reply rules are those of Self::read_log_page; a missing device fails before sending.

Source§

impl<T: TransportPort + 'static> BACnetClient<T>

Source

pub async fn confirmed_request( &self, destination_mac: &[u8], service_choice: ConfirmedServiceChoice, service_data: &[u8], ) -> Result<Bytes, Error>

Send a confirmed request and wait for the response.

Returns the service response data (empty for SimpleAck). Automatically uses segmented transfer when the payload exceeds the remote device’s max APDU length.

A confirmed request goes to one device, so a destination_mac that reaches a group of nodes (TransportPort::is_group_destination: the link’s broadcast MAC, or on B/IP the limited broadcast, the configured broadcast IP or a multicast address at any port), which would make it a local broadcast (Clause 6.3), fails with Error::Encoding before any transaction state is taken (#1479). So does a routed target that names this client’s own network with such a MAC, since it goes local.

Source

pub async fn confirmed_request_routed( &self, router_mac: &[u8], dest_network: u16, dest_mac: &[u8], service_choice: ConfirmedServiceChoice, service_data: &[u8], ) -> Result<Bytes, Error>

Send a confirmed request routed through a BACnet router.

The NPDU is sent as a unicast to router_mac with DNET/DADR set so the router forwards it to dest_network/dest_mac.

A confirmed request goes to one device, so dest_network must be in 1..=65534 and dest_mac must hold 1 to MAX_MAC_LEN octets. DNET 0 names no network, DNET 65535 and an empty DADR address a broadcast, and any of them fails with Error::Encoding before path or transaction state is taken. Every routed confirmed entry point, such as the _to_device and _from_device methods, applies the same rule.

Once the client has learned its own network’s number, a dest_network naming it is this network. After those checks the request goes to dest_mac as a local request, with no DNET and not through router_mac (#1358). An answer from dest_mac with no SNET completes it, and so does one a router relays back with dest_network as its SNET and dest_mac as its SADR (#1465), since both name the same station.

Source

pub async fn unconfirmed_request( &self, destination_mac: &[u8], service_choice: UnconfirmedServiceChoice, service_data: &[u8], ) -> Result<(), Error>

Send an unconfirmed request (fire-and-forget) to a specific destination.

Source

pub async fn broadcast_unconfirmed( &self, service_choice: UnconfirmedServiceChoice, service_data: &[u8], ) -> Result<(), Error>

Broadcast an unconfirmed request on the local network.

Source

pub async fn broadcast_global_unconfirmed( &self, service_choice: UnconfirmedServiceChoice, service_data: &[u8], ) -> Result<(), Error>

Broadcast an unconfirmed request globally (DNET=0xFFFF).

Source

pub async fn broadcast_network_unconfirmed( &self, service_choice: UnconfirmedServiceChoice, service_data: &[u8], dest_network: u16, ) -> Result<(), Error>

Broadcast an unconfirmed request to a specific remote network.

Once the client has learned its own network’s number, a dest_network naming it is this network, and the request goes as a local broadcast with no DNET (#1358).

Source§

impl<T: TransportPort + 'static> BACnetClient<T>

Source

pub async fn configure_routed_path_max_npdu( &self, router_mac: &[u8], dnet: u16, max_npdu: u16, ) -> Result<(), Error>

Configure the maximum NPDU for one immediate-router/DNET path.

The update waits for any request already active on the same path. It replaces the previous configured value and deliberately clears learned reason-4 evidence for that path. A router MAC outside 1 to MAX_MAC_LEN octets or a DNET outside 1..=65534 is refused with Error::Encoding.

Source

pub async fn clear_routed_path_limit( &self, router_mac: &[u8], dnet: u16, ) -> Result<(), Error>

Restore one immediate-router/DNET path to the conservative default.

The update waits for an active request and clears both configured and learned reason-4 evidence. Later requests therefore use the 228-octet conservative routed NPDU envelope until new evidence is supplied. The router MAC and DNET are checked as in configure_routed_path_max_npdu.

Source§

impl<T: TransportPort + 'static> BACnetClient<T>

Source

pub async fn get_event_information( &self, destination_mac: &[u8], last_received_object_identifier: Option<ObjectIdentifier>, ) -> Result<GetEventInformationAck, Error>

GetEventInformation to a device on this network, its answer decoded: the objects after last_received_object_identifier (from the first when None) with an active event or an unacknowledged transition. While the answer’s more_events is set, ask again from its last object.

Source

pub async fn get_event_information_from_device( &self, device_instance: u32, last_received_object_identifier: Option<ObjectIdentifier>, ) -> Result<GetEventInformationAck, Error>

Self::get_event_information to a discovered device, routed when it is behind a router. A device not in the table fails before sending.

Source

pub async fn get_alarm_summary( &self, destination_mac: &[u8], ) -> Result<GetAlarmSummaryAck, Error>

GetAlarmSummary to a device on this network, its answer decoded.

Source

pub async fn get_alarm_summary_from_device( &self, device_instance: u32, ) -> Result<GetAlarmSummaryAck, Error>

Self::get_alarm_summary to a discovered device, routed when it is behind a router. A device not in the table fails before sending.

Source

pub async fn get_enrollment_summary( &self, destination_mac: &[u8], request: &GetEnrollmentSummaryRequest, ) -> Result<GetEnrollmentSummaryAck, Error>

GetEnrollmentSummary to a device on this network, its answer decoded. A filter out of its range fails before sending.

Source

pub async fn get_enrollment_summary_from_device( &self, device_instance: u32, request: &GetEnrollmentSummaryRequest, ) -> Result<GetEnrollmentSummaryAck, Error>

Self::get_enrollment_summary to a discovered device, routed when it is behind a router. A device not in the table fails before sending.

Source§

impl<T: TransportPort + 'static> BACnetClient<T>

Source

pub fn transport(&self) -> &T

The transport this client was built with.

The client keeps its transport inside the network layer that its dispatch task shares, and lends it out only as a shared borrow. Methods that take &mut self, such as start, stop and the configuration setters, stay out of reach; stop stops the transport in place.

§Supported through this borrow

Reading transport state and diagnostics while the client runs:

  • BACnet/SC: the connection_state_changes() watch, the connection_events() broadcast (reconnects, failover, primary restore, disconnect causes), the connection_status() snapshot (active hub, hub VMAC, heartbeat age and round-trip time), hub_peer_info() and npdu_drop_counts().
  • B/IP: management_counters(), fdt_counters(), fanout_counters(), receive_counters(), send_counters(), and bbmd_state() for a brief read of the BBMD tables.
  • MS/TP: the diagnostics() handle.
  • B/IP, B/IPv6, BACnet/SC and MS/TP: frame_tap(), to subscribe to every link-layer frame the transport sends and receives (#957).
  • Any transport: local_mac() and the APDU size limits.

The watch receiver, the diagnostics handle and the BBMD state Arc are owned and outlive the borrow, so a long-lived UI takes them once and moves them into its own tasks, as MS/TP callers already do with diagnostics() before handing a transport over. The counters are cheap snapshots: poll them through the borrow as often as the UI redraws.

§Not supported while the client runs

These go around the client, which does not guard against them:

  • Sending through the transport (send_unicast, send_broadcast and their variants). The client’s transaction state machine never sees the frame, so a hand-built confirmed request can reuse an invoke ID that is already in flight. Send through the client’s request methods, and use its BBMD helpers for BBMD management.
  • Holding the bbmd_state() lock across an await or a client call. It is a synchronous mutex the receive loop and a broadcast from the client take too, so holding it stalls the transport or deadlocks. Lock, copy what you need and release.

The live MS/TP master node and the BACnet/SC connection, whose locks the token loop and the hub link take, are not public at all.

§Why a borrow

This is one accessor for every transport, rather than a bundle of handles returned by each builder. Each transport already offers its diagnostics on &self, so nothing new is needed per transport, and clients from generic_builder get the same access. Sharing the transport through an Arc was ruled out too: stop needs sole ownership of the network layer to stop the transport, which an outstanding clone would block.

use std::net::Ipv4Addr;
use bacnet_client::client::BACnetClient;

let runtime = tokio::runtime::Builder::new_current_thread().enable_all().build()?;
runtime.block_on(Box::pin(async {
    let mut client = BACnetClient::bip_builder()
        .interface(Ipv4Addr::LOCALHOST)
        .port(0)
        .build()
        .await?;
    // Counter snapshots: poll them whenever the UI redraws.
    let counters = client.transport().management_counters();
    assert_eq!(counters.read_bdt_responses, 0);
    assert_eq!(client.transport().fanout_counters().packets_forwarded, 0);
    // This client is not a BBMD, so it has no BBMD state or FDT.
    assert!(client.transport().bbmd_state().is_none());
    assert!(client.transport().fdt_counters().is_none());
    client.stop().await?;
    Ok::<_, bacnet_types::error::Error>(())
}))?;
Source§

impl<T: TransportPort + AsBip + 'static> BACnetClient<T>

BBMD management helpers (Annex J), for a client whose transport is B/IP.

They work on a client over BipTransport, and on one over AnyTransport when its variant is Bip. Each request fails with a BvlcExchangeError: a NAK, no reply, or a local failure. On any other variant each returns Error::UnsupportedTransport, as BvlcExchangeError::Transport, before sending anything.

use bacnet_client::client::BACnetClient;
use bacnet_transport::any::AnyTransport;
use bacnet_transport::bip::BvlcExchangeError;
use bacnet_transport::loopback::LoopbackTransport;
use bacnet_types::data_link::DataLink;
use bacnet_types::error::Error;

let runtime = tokio::runtime::Builder::new_current_thread().enable_all().build()?;
runtime.block_on(Box::pin(async {
    let (ours, _peer) = LoopbackTransport::pair(vec![1], vec![2]);
    let mut client = BACnetClient::generic_builder()
        .transport(AnyTransport::Loopback(ours))
        .build()
        .await?;
    let bbmd = [127, 0, 0, 1, 0xBA, 0xC0];
    assert!(matches!(
        client.read_bdt(&bbmd).await,
        Err(BvlcExchangeError::Transport(Error::UnsupportedTransport {
            required: DataLink::Bip,
            actual: DataLink::Loopback,
        }))
    ));
    client.stop().await?;
    Ok::<_, Error>(())
}))?;
Source

pub fn foreign_registration(&self) -> Result<ForeignRegistrationSnapshot, Error>

How this client’s foreign-device registration is going (#938): whether it holds a live lease, the time left on it, and the last attempt’s result and outcome, a BBMD’s refusal included. Outside BipMode::Foreign only manual registrations show. It reads BipTransport::bvlc_client_snapshot and sends nothing.

Source

pub async fn read_bdt( &self, target: &[u8], ) -> Result<Vec<BdtEntry>, BvlcExchangeError>

Read the Broadcast Distribution Table from a BBMD.

Source

pub async fn write_bdt( &self, target: &[u8], entries: &[BdtEntry], ) -> Result<(), BvlcExchangeError>

Write the Broadcast Distribution Table to a BBMD.

A BBMD that follows the 2020 standard answers with the not-supported result, BvlcExchangeError::Nak, and keeps its table.

Source

pub async fn read_fdt( &self, target: &[u8], ) -> Result<Vec<FdtEntryWire>, BvlcExchangeError>

Read the Foreign Device Table from a BBMD.

Source

pub async fn delete_fdt_entry( &self, target: &[u8], ip: [u8; 4], port: u16, ) -> Result<(), BvlcExchangeError>

Delete a Foreign Device Table entry on a BBMD.

Source

pub async fn register_foreign_device_bvlc( &self, target: &[u8], ttl: u16, ) -> Result<(), BvlcExchangeError>

Register as a foreign device with a BBMD; Ok when it accepts, BvlcExchangeError::Nak when it refuses.

This sends one registration. It does not make the transport a foreign device for broadcasts or renew the registration.

Source§

impl<T: TransportPort + 'static> BACnetClient<T>

Source

pub async fn write_group( &self, destination: &WriteGroupDestination, request: &WriteGroupRequest, ) -> Result<(), Error>

Send a WriteGroup request to destination.

The service is unconfirmed: this returns once the request is sent, and no device answers it, so delivery isn’t confirmed either. Fails before anything is sent when request doesn’t encode (see WriteGroupRequest::encode) or a remote network number is 0 or 65535; use WriteGroupDestination::GlobalBroadcast for every network.

Source§

impl BACnetClient<BipTransport>

Source

pub fn bip_builder() -> BipClientBuilder

Create a BIP-specific builder with interface/port/broadcast fields.

Source§

impl BACnetClient<ScTransport<TlsWebSocket>>

Source

pub fn sc_builder() -> ScClientBuilder

Available on crate feature sc-tls only.

Create an SC-specific builder that connects to a BACnet/SC hub.

Source§

impl<T: TransportPort + 'static> BACnetClient<T>

Source

pub fn generic_builder() -> ClientBuilder<T>

Create a generic builder that accepts a pre-built transport, such as an MS/TP port. A serial port opened with TokioSerialPort::open (the serial feature) takes the place of the in-memory line here; give the MstpConfig the port’s baud rate, which its timers follow.

use bacnet_client::client::BACnetClient;
use bacnet_transport::mstp::{LoopbackSerial, MstpConfig, MstpTransport};

let (serial, _line) = LoopbackSerial::pair();
let config = MstpConfig {
    this_station: 1,
    max_master: 127,
    baud_rate: 76_800,
    ..MstpConfig::default()
};
let mut client = BACnetClient::generic_builder()
    .transport(MstpTransport::new(serial, config))
    .build()
    .await?;
client.stop().await?;
Source§

impl<T: TransportPort + 'static> BACnetClient<T>

Source

pub async fn read_log_page( &self, mac: &[u8], log: ObjectIdentifier, cursor: LogCursor, page_size: u16, ) -> Result<LogPage, Error>

Read one page of log’s Log_Buffer from cursor, asking for up to page_size records (1..=32767); see read_log_page.

let mut cursor = LogCursor::Oldest;
loop {
    let page = client.read_log_page(mac, log, cursor, 100).await?;
    // ... store page.records ...
    cursor = page.next;
    if page.done {
        return Ok(cursor); // persist as the checkpoint
    }
}

Trait Implementations§

Source§

impl<T: TransportPort> Drop for BACnetClient<T>

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more
Source§

fn pin_drop(self: Pin<&mut Self>)

🔬This is a nightly-only experimental API. (pin_ergonomics)
Execute the destructor for this type, but different to Drop::drop, it requires self to be pinned. Read more
Source§

impl<T: TransportPort + 'static> LogRequester for BACnetClient<T>

Source§

async fn read_unsigned( &self, mac: &[u8], log: ObjectIdentifier, property: PropertyIdentifier, ) -> Result<u64, Error>

Read property of log at mac as an Unsigned.
Source§

async fn read_range_lenient( &self, mac: &[u8], request: &ReadRangeRequest, ) -> Result<ReadRangeReply, Error>

Send one ReadRange to mac and check its answer leniently.

Auto Trait Implementations§

§

impl<T = AnyTransport> !RefUnwindSafe for BACnetClient<T>

§

impl<T = AnyTransport> !UnwindSafe for BACnetClient<T>

§

impl<T> Freeze for BACnetClient<T>
where Arc<NetworkLayer<T>>: Freeze,

§

impl<T> Send for BACnetClient<T>
where Arc<NetworkLayer<T>>: Send,

§

impl<T> Sync for BACnetClient<T>
where Arc<NetworkLayer<T>>: Sync,

§

impl<T> Unpin for BACnetClient<T>
where Arc<NetworkLayer<T>>: Unpin,

§

impl<T> UnsafeUnpin for BACnetClient<T>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<'a, T, E> AsTaggedExplicit<'a, E> for T
where T: 'a,

Source§

fn explicit(self, class: Class, tag: u32) -> TaggedParser<'a, Explicit, Self, E>

Source§

impl<'a, T, E> AsTaggedImplicit<'a, E> for T
where T: 'a,

Source§

fn implicit( self, class: Class, constructed: bool, tag: u32, ) -> TaggedParser<'a, Implicit, Self, E>

Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more