pub struct BACnetServer<T: TransportPort> { /* private fields */ }Expand description
BACnet server with APDU dispatch and service handling.
Implementations§
Source§impl<T: TransportPort + 'static> BACnetServer<T>
impl<T: TransportPort + 'static> BACnetServer<T>
Sourcepub async fn start(
config: ServerConfig,
db: ObjectDatabase,
transport: T,
) -> Result<Self, Error>
pub async fn start( config: ServerConfig, db: ObjectDatabase, transport: T, ) -> Result<Self, Error>
Start with the default system-UTC Device clock.
Sourcepub async fn start_with_clock(
config: ServerConfig,
db: ObjectDatabase,
transport: T,
clock_config: ClockConfig,
) -> Result<Self, Error>
pub async fn start_with_clock( config: ServerConfig, db: ObjectDatabase, transport: T, clock_config: ClockConfig, ) -> Result<Self, Error>
Start with a validated Device clock configuration.
Sourcepub async fn start_clockless(
config: ServerConfig,
db: ObjectDatabase,
transport: T,
) -> Result<Self, Error>
pub async fn start_clockless( config: ServerConfig, db: ObjectDatabase, transport: T, ) -> Result<Self, Error>
Start without a Device wall clock or time-synchronization execution.
Source§impl<T: TransportPort + 'static> BACnetServer<T>
impl<T: TransportPort + 'static> BACnetServer<T>
Sourcepub fn dcc_outcome_counters(&self) -> DccOutcomeCounters
pub fn dcc_outcome_counters(&self) -> DccOutcomeCounters
Sample completed admitted DCC outcomes, including after stop().
Excludes pre-handler rejections, duplicates, incomplete cancelled handlers,
timer expiry and response delivery outcomes. No history is retained.
Source§impl<T: TransportPort + 'static> BACnetServer<T>
impl<T: TransportPort + 'static> BACnetServer<T>
Sourcepub async fn broadcast_i_am(&self) -> Result<(), Error>
pub async fn broadcast_i_am(&self) -> Result<(), Error>
Broadcast I-Am through this server’s bounded local-send owner.
Cancelling the waiter leaves admitted work owned until completion or stop.
While DeviceCommunicationControl restricts initiation nothing is sent
and this fails with SERVICES / COMMUNICATION_DISABLED (Clause 16.1).
Source§impl<T: TransportPort + 'static> BACnetServer<T>
impl<T: TransportPort + 'static> BACnetServer<T>
Sourcepub async fn purge_audit_log(&self, oid: &ObjectIdentifier) -> Result<(), Error>
pub async fn purge_audit_log(&self, oid: &ObjectIdentifier) -> Result<(), Error>
Purge an Audit Log: clear its records and append a BUFFER_PURGED
status record, as AuditLogObject::purge describes.
Peers have no way to do this, so it is the application’s to call. The log builds the purged state and queues its commit; the server waits for the commit with the object database guard dropped, so other requests carry on, and the log serves the purged state only once it is durable. Changes to one log land one at a time, in the order they were staged: a notification batch whose commit is running lands first and is purged with the rest, and one that arrives while the purge commits waits for it and follows the purge record.
§Errors
- OBJECT / UNKNOWN_OBJECT when
oidnames no object, and OBJECT / OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED when the object cannot be purged (it is not a built-in Audit Log). - DEVICE / OPERATIONAL_PROBLEM without a valid local clock, or when the commit fails; the log’s writer logs the storage error.
- The stopped-server error once
stophas run.
On any error the log is left as it was. A purge whose future is dropped before it finishes is handled as an abandoned write request is: the log keeps serving its state, drops the staged purge once it has waited long enough, and sets storage back to that state.
Source§impl<T: TransportPort + 'static> BACnetServer<T>
impl<T: TransportPort + 'static> BACnetServer<T>
Sourcepub fn event_notification_counters(&self) -> EventNotificationCounters
pub fn event_notification_counters(&self) -> EventNotificationCounters
Sample the lifetime totals of undelivered event notifications,
including after stop(). See EventNotificationCounters for what
each field counts and what is left out.
Source§impl<T: TransportPort + 'static> BACnetServer<T>
impl<T: TransportPort + 'static> BACnetServer<T>
Sourcepub fn local_mac(&self) -> &[u8] ⓘ
pub fn local_mac(&self) -> &[u8] ⓘ
Return the last bound local MAC snapshot, including after stop. This retained address does not assert that the transport is still active.
Sourcepub fn database(&self) -> &Arc<RwLock<ObjectDatabase>> ⓘ
pub fn database(&self) -> &Arc<RwLock<ObjectDatabase>> ⓘ
Get a reference to the shared object database.
Objects read through this handle return raw standalone data. A built-in
Device follows its declared service profile, with absent or empty COV lists.
Served Device definitions and live lists belong to the executor: use
read_local for that view. Normal Device mutation and
replacement cannot change the served execution profile.
Install Device objects before startup: changing their membership through
this handle does not rebind the discovery limiter’s startup identity.
Sourcepub async fn read_local(
&self,
oid: &ObjectIdentifier,
property: PropertyIdentifier,
array_index: Option<u32>,
) -> Result<PropertyValue, Error>
pub async fn read_local( &self, oid: &ObjectIdentifier, property: PropertyIdentifier, array_index: Option<u32>, ) -> Result<PropertyValue, Error>
Read one local property through the server’s ReadProperty evaluator.
With multiple Device objects, the lowest instance is selected for Device wildcard reads and the live COV lists, independent of insertion order. Other Device objects receive empty COV lists. Every served Device uses the executor’s services, COV presence and property definitions, even after public profile mutation, replacement or custom object installation.
This is the live local read boundary: it applies the same Device
wildcard resolution, UNKNOWN_OBJECT and PROPERTY_IS_NOT_AN_ARRAY
checks, and value source as network ReadProperty. The selected Device’s
Active_COV_Subscriptions (ordinary and single-property subscriptions)
and Active_COV_Multiple_Subscriptions (SubscribeCOVPropertyMultiple
contexts) are projected from the COV subscription table at one sampled
instant, returned as their encoded BACnetLIST of BACnetCOVSubscription
and BACnetLIST of BACnetCOVMultipleSubscription
(PropertyValue::ApplicationData). After stop the
server services no subscriptions, so both properties read as empty lists.
The selected Device’s Device_Address_Binding lists the server’s device
bindings at the time of the read (#1369): each configured
DeviceBinding and each device whose I-Am was heard in the last ten
minutes, in Device instance order, as a PropertyValue::List whose
items are encoded BACnetAddressBindings
(PropertyValue::ApplicationData). A device on this network has
network number 0. The bindings outlast stop, so they still read then.
A Group’s Present_Value is rebuilt from its members, whose rows count
against the ReadPropertyMultiple work limit
(ReadPropertyMultipleBudget::max_result_elements)
as for network ReadProperty; past it the read fails with
Error::Abort carrying OUT_OF_RESOURCES.
Sourcepub async fn write_local_encoded(
&self,
oid: &ObjectIdentifier,
property: PropertyIdentifier,
array_index: Option<u32>,
value: &[u8],
priority: Option<u8>,
source: LocalCommandSource,
) -> Result<(), Error>
pub async fn write_local_encoded( &self, oid: &ObjectIdentifier, property: PropertyIdentifier, array_index: Option<u32>, value: &[u8], priority: Option<u8>, source: LocalCommandSource, ) -> Result<(), Error>
Write one local property from its encoded value: the octets a network WriteProperty would carry for it.
The octets take the steps the WriteProperty handler takes before an
object sees a value. An array index is refused as it is over the
network (UNKNOWN_PROPERTY for a property the object doesn’t hold,
PROPERTY_IS_NOT_AN_ARRAY for one that isn’t an array), and the octets
are decoded with that handler’s per-property framing, which hands a
Schedule’s Effective_Period or a Staging’s Stages to the object in the
form it decodes. The value then takes the write_local
path, with its COV, event and audit work. A value read with
read_local and encoded therefore writes back, and
any value a network client could write is accepted here.
Like write_local, it must be awaited inside a Tokio runtime, failing
before anything is written outside one, and a caller dropped once the
write has committed skips none of the work the write owes (#1367).
Sourcepub fn i_am_broadcaster(&self) -> IAmBroadcaster<T>
pub fn i_am_broadcaster(&self) -> IAmBroadcaster<T>
Create a cloneable handle for unsolicited I-Am announcements.
Sourcepub fn comm_state(&self) -> DccState
pub fn comm_state(&self) -> DccState
Get the communication state per DeviceCommunicationControl.
The server refuses the deprecated DISABLE, so this is
DccState::Enable or DccState::DisableInitiation.
Sourcepub async fn generate_pics(&self, pics_config: &PicsConfig) -> Pics
pub async fn generate_pics(&self, pics_config: &PicsConfig) -> Pics
Generate PICS from the database and the server’s effective Device execution view.
Standalone PicsGenerator keeps raw-object semantics.
The caller must supply a PicsConfig for fields not available from the server
(vendor name, model, firmware revision, etc.).
Source§impl<T: TransportPort + 'static> BACnetServer<T>
impl<T: TransportPort + 'static> BACnetServer<T>
Sourcepub async fn set_life_safety_operation_expected_local(
&self,
oid: &ObjectIdentifier,
operation: LifeSafetyOperation,
) -> Result<(), Error>
pub async fn set_life_safety_operation_expected_local( &self, oid: &ObjectIdentifier, operation: LifeSafetyOperation, ) -> Result<(), Error>
Arm or rearm a Life Safety object from trusted local application logic.
This uses the object-internal state channel under the database write
lock. Network WriteProperty and WritePropertyMultiple remain unable to
forge Operation_Expected. After the lock is released, an actual
Operation_Expected readback change uses the exact Life Safety COV path;
rearming to the current value emits no notification.
Sourcepub async fn write_local(
&self,
oid: &ObjectIdentifier,
property: PropertyIdentifier,
array_index: Option<u32>,
value: PropertyValue,
priority: Option<u8>,
source: LocalCommandSource,
) -> Result<(), Error>
pub async fn write_local( &self, oid: &ObjectIdentifier, property: PropertyIdentifier, array_index: Option<u32>, value: PropertyValue, priority: Option<u8>, source: LocalCommandSource, ) -> Result<(), Error>
Write a property on a local object and fire the same post-write COV
and event notifications that a network WriteProperty does. When target
Audit reporting is configured, the same observer records eligible local
writes with local Device provenance and no invoke ID; Device recipient
changes retain their sole old/new pair owner. Eligible actual AV/BV Audit
policy changes reserve immediate delivery before assignment; unavailable
resources return SERVICES/SERVICE_REQUEST_DENIED without a policy change.
This is the server-owned local-mutation entry point: it performs the
write under the database lock — routing OBJECT_NAME through the name
uniqueness check and index refresh, exactly like the network handler —
then releases the lock and runs the COV/event trigger path so a
subscription observes a local mutation just as it would a network one.
source explicitly selects the Device or an existing initiating object.
Tracked commands require a concrete selected Device; the selected Device
retains correction ownership regardless of the local initiator. Unrelated
properties preserve their behavior without a usable command origin.
Low-level object setters deliberately bypass this notification owner.
A NULL to a property that isn’t commandable and has no NULL in its datatype succeeds and changes nothing, as over the network (#1396): Audit records it, and no COV, event or Schedule work follows it.
An array index is checked first, as the WriteProperty handler checks it (#1426): UNKNOWN_PROPERTY for a property the object doesn’t hold, PROPERTY_IS_NOT_AN_ARRAY for one that isn’t an array. Neither the object nor Audit sees such a write. The writes a Command or Channel run makes here get the same check.
§Cancellation
Dropped before the write commits, the future makes no change. Once it
has committed, the work the write owes (the event pass, the COV
fanout, a re-evaluated Schedule’s target fanout, a Staging plan, and
the Command or Channel runs it started) runs as a task of its own in
the server’s request task set, which this future only waits for, so
dropping the future skips none of it (#1367): subscribers hear of the
change, and a run ends as it would have, reported to its property
subscribers. stop() aborts that task with the other request tasks,
as it does a request handler: what it hadn’t sent by then is not
sent, and a run it hadn’t started ends as if none of its writes were
made (#1324), In_Process FALSE with each command unsuccessful, or
Write_Status FAILED. The call still returns Ok(()) then: the write
was made. A panic in that work is raised again in the caller, as it
would be were the work done in place.
The future must be awaited inside a Tokio runtime, which the task is
spawned onto: outside one it fails with Error::Encoding before
anything is written.
Sourcepub async fn set_present_value_local(
&self,
oid: &ObjectIdentifier,
value: PropertyValue,
) -> Result<(), Error>
pub async fn set_present_value_local( &self, oid: &ObjectIdentifier, value: PropertyValue, ) -> Result<(), Error>
Supply a supported object’s logical Present_Value from Rust application code.
Analog, Binary and Multi-state Inputs, noncommandable Values, Loop (the control algorithm’s output) and Life Safety Point and Zone opt in. Binary Input values are logical INACTIVE/ACTIVE states after Polarity, not raw physical states. The update runs the existing post-write intrinsic-event and COV processing after the database lock is released; configured delays and distribution policy still control delivery, and a COV_Increment still filters Present_Value reports.
Applications are denied while Out_Of_Service is TRUE to protect a
client’s simulation value. This is local policy for Inputs and required
by the object clauses for the supported Values and Loop. NULL is an
invalid application value. Other object families fail closed; use
BACnetServer::write_local for network-equivalent writes and sourced
commands on commandable objects. The Python binding exposes this as
BACnetServer.set_present_value_local.
A Life Safety Point or Zone takes an Enumerated BACnetLifeSafetyState,
a standard value or one from 256 to 65535, in or out of service, since
clients never write its Present_Value. The update leaves
Tracking_Value, Silenced and Operation_Expected alone, so any latching
until reset is the application’s (see
BACnetServer::set_tracking_value_local). A change notifies
SubscribeCOV and Present_Value property subscribers through the Life
Safety COV path. The built-in objects run no intrinsic reporting, so
the post-write event pass raises nothing for them.
Like write_local, it must be awaited inside a
Tokio runtime, failing before anything is written outside one, and a
caller dropped once the write has committed skips none of the work
the write owes (#1367).
Sourcepub async fn set_controlled_variable_value_local(
&self,
oid: &ObjectIdentifier,
value: PropertyValue,
) -> Result<(), Error>
pub async fn set_controlled_variable_value_local( &self, oid: &ObjectIdentifier, value: PropertyValue, ) -> Result<(), Error>
Supply a Loop’s measured Controlled_Variable_Value from the
application that runs its control algorithm.
The value is a finite REAL: another datatype fails with PROPERTY /
INVALID_DATA_TYPE and NaN or an infinity with PROPERTY /
VALUE_OUT_OF_RANGE. An unknown object fails with OBJECT /
UNKNOWN_OBJECT and any object other than a Loop with OBJECT /
OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED, the same errors as
BACnetServer::set_present_value_local. Unlike Present_Value it is
accepted while Out_Of_Service is TRUE, because Out_Of_Service
decouples only the output and Reliability from the algorithm.
The update runs the same post-write COV processing as
set_present_value_local once the database lock is released. A
SubscribeCOVProperty on Controlled_Variable_Value is notified of the
change; a SubscribeCOV on the Loop is not, since the Loop’s COV report
carries this value without being triggered by it, so its next report
carries the new value. The property stays read-only over the network.
Like write_local, it must be awaited inside a
Tokio runtime, failing before anything is written outside one, and a
caller dropped once the write has committed skips none of the work
the write owes (#1367).
Sourcepub async fn add_averaging_sample_local(
&self,
oid: &ObjectIdentifier,
sample: Option<PropertyValue>,
) -> Result<(), Error>
pub async fn add_averaging_sample_local( &self, oid: &ObjectIdentifier, sample: Option<PropertyValue>, ) -> Result<(), Error>
Record one sample for an Averaging object, taken by the application.
The server samples an object that holds an Object_Property_Reference
itself, every Window_Interval / Window_Samples seconds (#1144). For an
object without one, the application samples whatever it averages,
spacing its readings that far apart, and passes each result here. On an
object the server samples, a call is one more attempt in the window
and doesn’t move the server’s schedule. A value may be
a BOOLEAN (FALSE and TRUE count as 0 and 1), Signed, Unsigned,
Enumerated or finite REAL; the object keeps its statistics in REAL
(Clause 12.5). None records an attempt that produced no value, such
as a failed read: it counts toward Attempted_Samples but not
Valid_Samples. Another datatype, Double included, fails with PROPERTY /
INVALID_DATA_TYPE and NaN or an infinity with PROPERTY /
VALUE_OUT_OF_RANGE, and a refused sample counts as neither attempted
nor valid. An unknown object fails with OBJECT / UNKNOWN_OBJECT and any
object other than an Averaging object with OBJECT /
OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED, as with
BACnetServer::set_present_value_local.
Each call fills the next slot of the object’s window, dropping the
oldest once Window_Samples are held, and Minimum_Value, Maximum_Value,
Average_Value, Attempted_Samples and Valid_Samples change together
under the database lock; the server’s COV processing runs once the
lock is released. Averaging has no Table 13-1 row, so SubscribeCOV on
it is refused, but SubscribeCOVProperty and
SubscribeCOVPropertyMultiple are admitted: a numeric property is
reported when it moves by the subscription’s COV increment, or on any
change when the subscription gives none (Table 13-1a). A move to or
from the NaN or infinite value of an empty window is always reported,
and staying at it never is. The report has no Status_Flags because the
object has none. The Python binding exposes this as
BACnetServer.add_averaging_sample_local.
Like write_local, it must be awaited inside a
Tokio runtime, failing before anything is written outside one, and a
caller dropped once the write has committed skips none of the work
the write owes (#1367).
Sourcepub async fn set_tracking_value_local(
&self,
oid: &ObjectIdentifier,
value: PropertyValue,
) -> Result<(), Error>
pub async fn set_tracking_value_local( &self, oid: &ObjectIdentifier, value: PropertyValue, ) -> Result<(), Error>
Supply a Life Safety Point’s or Zone’s Tracking_Value, the live state
the application derived, while the server holds the object.
The value is an Enumerated naming a standard BACnetLifeSafetyState or
one from the proprietary range 256..=65535. A reserved or larger number
fails with PROPERTY / VALUE_OUT_OF_RANGE and another datatype with
PROPERTY / INVALID_DATA_TYPE. An unknown object fails with OBJECT /
UNKNOWN_OBJECT and any object other than a Life Safety Point or Zone
with OBJECT / OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED, as with
BACnetServer::set_present_value_local. Present_Value, Silenced and
Operation_Expected are left as they are: the object derives nothing
from Tracking_Value.
While Out_Of_Service is TRUE the value replaces the one set aside for
the return to service, and a client’s simulated Tracking_Value keeps
being served (#1108); the return to service then serves the latest
application value and notifies its subscribers. In service, a change
reaches SubscribeCOVProperty and SubscribeCOVPropertyMultiple
subscribers of Tracking_Value through the Life Safety COV path once the
database lock is released; whole-object SubscribeCOV reports don’t
carry it. The property stays read-only over the network in service.
The Python binding exposes this as BACnetServer.set_tracking_value_local.
Like write_local, it must be awaited inside a
Tokio runtime, failing before anything is written outside one, and a
caller dropped once the write has committed skips none of the work
the write owes (#1367).
Source§impl<T: TransportPort + 'static> BACnetServer<T>
impl<T: TransportPort + 'static> BACnetServer<T>
Sourcepub fn request_admission_counters(&self) -> RequestAdmissionCounters
pub fn request_admission_counters(&self) -> RequestAdmissionCounters
Sample admission counters. Available after stop; active counts then are zero.
Source§impl<T: TransportPort + 'static> BACnetServer<T>
impl<T: TransportPort + 'static> BACnetServer<T>
Sourcepub async fn stop(&mut self) -> Result<(), Error>
pub async fn stop(&mut self) -> Result<(), Error>
Seal egress, join admitted work, and stop the owned transport.
Call it before dropping the server. A server dropped without it in
async code aborts its tasks and lets the object database go on Tokio’s
blocking pool once they have let go of it (#1409), so the drop returns
at once, but nothing waits for the objects’ last saves. Storage may
still change after such a drop returns, as those saves and the
put-back of a staged write (#1363) land, so stop().await before
building another server on the same storage.
Once its requests are joined, a write a Notification Forwarder,
Notification Class or Audit Log still holds staged for one of them is
dropped, and stop waits until every save those objects have queued
has run, so storage holds the state each object serves (#1363); see
DurableWrites::settle_forgotten_writes. That wait has no limit:
storage that stalls holds stop up, and a warning naming the objects
still saving is logged after 5 s and every 30 s after that. Stop does
not wait while the application holds the database: the objects then
settle once it lets go, and put storage back when they are dropped.
Cancelling this waiter leaves cleanup owned by the server. A later stop joins it; after transport cleanup begins, dropping the server lets that cleanup finish. Local mutation and broadcasts are rejected from the first stop poll; local reads and database inspection remain available.
Source§impl BACnetServer<BipTransport>
impl BACnetServer<BipTransport>
Sourcepub fn bip_builder() -> BipServerBuilder
pub fn bip_builder() -> BipServerBuilder
Create a BIP-specific builder with interface/port/broadcast fields.
Source§impl<T: TransportPort + 'static> BACnetServer<T>
impl<T: TransportPort + 'static> BACnetServer<T>
Sourcepub fn generic_builder() -> ServerBuilder<T>
pub fn generic_builder() -> ServerBuilder<T>
Start a server builder for a caller-supplied transport type, with default configuration and an empty object database.
Sourcepub fn discovery_counters(&self) -> DiscoveryCounters
pub fn discovery_counters(&self) -> DiscoveryCounters
Get a snapshot of discovery rate-limiting counters.
Sourcepub fn cov_counters(&self) -> CovCounters
pub fn cov_counters(&self) -> CovCounters
Get a snapshot of COV operational and telemetry counters.
Sourcepub fn mutation_decision_counters(&self) -> MutationDecisionCounters
pub fn mutation_decision_counters(&self) -> MutationDecisionCounters
Sample lifetime mutation authorization decisions, including after stop().
Counts decisions, not completed handlers or response delivery; see
MutationDecisionCounters.
Sourcepub async fn remove_peer_subscriptions(
&self,
mac: &[u8],
network: Option<&NpduAddress>,
) -> usize
pub async fn remove_peer_subscriptions( &self, mac: &[u8], network: Option<&NpduAddress>, ) -> usize
Remove COV subscriptions using this current immediate MAC and routed source. Cleanup of an obsolete router leaves subscriptions renewed onto another route. Only removed entries release their contribution to the shared quota group.