Expand description
§bacnet-client
Run a standalone asynchronous BACnet client with client::BACnetClient in
the Rusty BACnet workspace. It owns
a transport and request lifecycle, with property/service APIs, discovery,
transaction management and segmentation. Use client::ClientConfig for
request settings or a transport-specific builder for connection setup.
This hosted crate requires std, Tokio and allocating state. It has no
alloc-only or no-heap profile. For one device combining client and server roles
under one transport owner, see the separate bacnet-endpoint crate and its
narrower service contract.
§Send Who-Is using development source
Place an application directory beside a checkout named rusty-bacnet, then
add this exact dependency block to the application’s Cargo.toml. These paths
refer to development source; they do not imply the shown APIs are published.
Adjust paths for a different layout. Tokio uses its current-thread runtime,
macros and time support; the program needs no multi-thread runtime feature.
[dependencies]
bacnet-client = { path = "../rusty-bacnet/crates/bacnet-client", default-features = false }
bacnet-transport = { path = "../rusty-bacnet/crates/bacnet-transport" }
tokio = { version = "1", features = ["macros", "rt", "time"] }Save this complete program as src/main.rs. It starts a real client over
bounded in-process loopback channels and sends Who-Is without device-instance
limits. The paired raw transport receives the emitted frame. Sending and
receiving share a two-second timeout; the complete NPDU is checked against
independent expected bytes for this global Who-Is request.
use bacnet_client::client::{BACnetClient, ClientConfig};
use bacnet_transport::loopback::LoopbackTransport;
use bacnet_transport::port::TransportPort;
use tokio::time::{timeout, Duration};
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let (client_port, mut peer) = LoopbackTransport::pair(vec![1], vec![2]);
let mut frames = peer.start().await?;
let mut client = BACnetClient::start(ClientConfig::default(), client_port).await?;
let received = timeout(Duration::from_secs(2), async {
client.who_is(None).await?;
let frame = frames.recv().await.ok_or_else(|| {
std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "loopback peer closed")
})?;
Ok::<_, Box<dyn std::error::Error>>(frame)
})
.await;
let client_stopped = client.stop().await;
let peer_stopped = peer.stop().await;
let frame = received??;
client_stopped?;
peer_stopped?;
assert_eq!(
&frame.npdu[..],
&[0x01, 0x20, 0xFF, 0xFF, 0x00, 0xFF, 0x10, 0x08]
);
assert_eq!(&frame.source_mac[..], &[1]);
assert!(frame.link_layer_group);
Ok(())
}Both owners are explicitly stopped before the operation result is propagated. The operation timeout does not impose a shutdown deadline. This program observes an emitted Who-Is frame; it neither receives I-Am nor establishes that a device was discovered. For a complete client/server ReadProperty exchange, see the server guide.
Loopback sends to its paired peer regardless of the requested destination. This program establishes in-process transmission, receipt and cleanup, not destination filtering, UDP, TLS, serial, physical equipment, timing, firmware behavior or BACnet conformance. Enabling an optional feature while running it does not exercise that feature’s network transport.
§Features and builders
| Feature selection | Public entry points |
|---|---|
Default or --no-default-features | BACnetClient::start, BACnetClient::generic_builder and BACnetClient::bip_builder; hosted std/Tokio/allocation remain required. |
ipv6 | Adds Bip6ClientBuilder and BACnetClient::bip6_builder for BACnet/IPv6. |
sc-tls | Adds ScClientBuilder and BACnetClient::sc_builder for a constrained-TLS BACnet/SC hub connection. |
For the displayed application, select --features bacnet-client/ipv6,
--features bacnet-client/sc-tls, or
--features bacnet-client/ipv6,bacnet-client/sc-tls when running Cargo. Each
selection still runs the loopback program above. Real connections need the
appropriate transport addresses and, for SC, validated TLS policy and a
caller-provisioned nonzero device UUID. TLS peer authentication does not by
itself authorize BACnet services or mutations on a server.
The docs.rs profile enables ipv6 and sc-tls, disables defaults and targets
x86_64-unknown-linux-gnu. Optional builders and factories carry feature badges;
the always-present client module and generic builder stay available under
each profile. This README is the crate landing page and its program runs as a
doctest. A local render does not establish a docs.rs deployment.
§Request and lifecycle ownership
client::BACnetClient::read_property returns encoded property data and checks
that the acknowledgement matches the requested object, property and array
index. Decode it for your property’s expected type; the Who-Is byte literal
above is not a property-value decoder.
Keep the client owner until requests finish, then call
client::BACnetClient::stop. Stop aborts and joins its dispatch and local
network-number work, cancels transactions and stops the owned transport. A
cancelled stop waiter retains joins for a later stop; dropping the client is
not a substitute for awaiting cleanup. The API does not promise a universal
fixed shutdown deadline.
When using a server, its database exposes raw object state; use its read_local
method for executor-owned live properties. Release database guards before
network requests or shutdown. Server service/mutation/source policies remain
application decisions, including when a transport authenticates its peers.
Modules§
- client
- BACnetClient: high-level and low-level request APIs.
- discovery
- Device discovery table — collects IAm responses for WhoIs/WhoHas lookups.
- log_
reader - Paged reads of a log’s Log_Buffer over ReadRange (#1530).
- segmentation
- Segmentation re-exports from bacnet-encoding.
- tags
- Typed interpretation of the value bytes returned by a Tags read.
- tsm
- Transaction State Machine (TSM) per ASHRAE 135-2020 Clause 5.4.