Upgrade from v0.13.0 to v0.14.0
Do not treat this pre-1.0 update as a drop-in replacement merely because the package name stayed the same. v0.14.0 makes the terminal UI the CLI’s default entry and removes the shell, puts every server authorization decision behind one policy, gives codec errors static labels, moves the network-layer vocabulary into a new bacnet-npdu crate, and changes Rust, Python and CLI APIs and some on-the-wire behavior. This page groups what changes for code and deployments built on v0.13.0.
Before updating
Section titled “Before updating”Record your current package/binary version, enabled features, platform, transport, and important application workflows. List the scripts that drive the bacnet CLI: the shell is gone, and every command that changes a device now needs a flag. Keep a known-working deployment or environment available according to your operational policy.
Update the selected interface
Section titled “Update the selected interface”In a Python virtual environment:
python -m pip install --upgrade rusty-bacnet==0.14.0For Rust, move the bacnet-* dependencies to 0.14.0 together. The exception is bacnet-time, which now versions on its own (#1768): this release ships it as 0.13.1, and later releases may leave it there, so a direct dependency on it names 0.13.1. Eighteen crates publish, one of them, bacnet-npdu, for the first time. For the CLI, download the v0.14.0 executable, check it against SHA256SUMS, and confirm its version and features.
The published CLIs and Python wheels are built with whole-program LTO, so they are about a third smaller; the platforms, glibc floor and CPython versions are those of v0.13.0 (#973).
Rust API
Section titled “Rust API”- Errors.
Error::Decodingholds aDecodeErrorinstead of{ offset, kind, message }; read the offset and kind withError::decode_fault().Error::decoding,decoding_kind,invalid_tag,missing,trailing,out_of_range,overflowandtagged::misplaced_tagtake a&'static str; build aDecodeErrorfor a label with numbers in it. Codec encoders refuse withError::Encode(EncodeError)in place ofError::Encoding, and range checks (the APDU and NPDU encoders,ObjectIdentifier::new,DeviceInstanceRange, oversized lighting, color and elevator command members) withError::Rangein place ofError::EncodingorError::OutOfRange.Error::Segmentationholds aSegmentationErrorenum. Some messages read differently, so don’t match on error text (#1646, #1776, #1777, #1802, #1803). - Typed protocol errors.
Error::Protocol,Error::StructuredandTsmResponse::ErrorholdErrorClassandErrorCodeinstead ofu32s, andRejectandAbortholdRejectReasonandAbortReasoninstead ofu8s;from_rawandto_rawconvert. A log record’sLogDatum::FailureandLogValue::Failurehold the same typed pair (#915, #1814). - Engineering units. The analog, accumulator, pulse converter and loop constructors and
StagingConfig::unitstakeEngineeringUnitsinstead of a number (#915). - Device identity. One
DeviceIdentity, now inbacnet-objects(still re-exported bybacnet-endpoint), holds what the server, the endpoints and the Device share.ServerConfig.devicereplacesServerConfig’svendor_id,max_apdu_length,segmentation_supportedandapdu_segment_timeout_ms, andDeviceConfig.identityreplacesDeviceConfig’sinstance,name,vendor_id,max_apdu_length,segmentation_supportedandapdu_segment_timeout. On the endpoint builders,.device(...)replaces.identity(...)and.with_identity(...); an endpoint now needs an explicit Device, and a Device you supply must match the resolved identity.DeviceConfigalso gainsbackup_and_restoreandbackup_failure_timeout: end a literal with..DeviceConfig::default()(#1720). - Exhaustive matches.
AnyTransportandDataLinkSupportare#[non_exhaustive]: add a_arm to amatchon either (#940, #1725). - Network layer.
NetworkLayer::startreturns anApduReceiverinstead of anmpsc::ReceiverofReceivedApdu. It hasrecv,poll_recv,try_recv,closeandis_closed;recv_many,blocking_recv,len,capacityand wrapping it inReceiverStreamare gone. It decodes NPDUs as it is polled, so network controls flow only while you poll it. The transport’s queue is the only buffer now (256 NPDUs on most links, 64 on MS/TP). AReceivedApdubuilt by hand setsrouter_response: None, and the*_on_issuancecallbacks must own their captures (Send + 'static) (#1699, #1713). - NPDU types.
NetworkMessageType,RejectMessageReason,NetworkPriority,NpduLimitsandMAX_MAC_LENcome frombacnet_npdu.NetworkPriorityis a closed enum (Normal,Urgent,CriticalEquipment,LifeSafety) instead of au8newtype.BACNET_PROTOCOL_VERSIONisbacnet_npdu::VERSIONandNpduAddressFieldisbacnet_npdu::AddressField. The ownedbacnet_encoding::npdu::Npdudrops itsis_network_messagefield for theis_network_message()method, and itsmessage_typeis an optionalNetworkMessageType.NpduDecodeErrorno longer converts frombacnet_types::Error.RejectMessageToNetworkanddecode_reject_message_to_networkleavebacnet_encoding::npdu: usebacnet_npdu::RejectMessageToNetwork, whosedecodereturns aRejectLengthErrorand whosednetfield isnetwork(#1770, #1794). - BBMD helpers.
read_bdt,read_fdt,write_bdt,delete_fdt_entryandregister_foreign_device_bvlconBipTransportandBACnetClientreturnResult<_, BvlcExchangeError>: matchNak(code)instead of the"BVLC-Result: …"error text andNoReplyinstead ofError::Timeout. The last three returnOk(())on success, and a NAK is anErr(#1822). - Lighting outputs.
write_propertyrefuses Present_Value and Value_Source on Binary Lighting Output and Lighting Output, and Lighting_Command on Lighting Output: callwrite_property_fromwith aCommandOrigin, orBACnetServer::write_localwith aLocalCommandSourcein a server (#1866). - Event Enrollment. Event_Type follows Event_Parameters. The
event_typeargument ofEventEnrollmentObject::newnow only names the algorithm of legacy octet-string parameters, and an enrollment whose parameters are never set reads Event_Type NONE (#1868). - The new
bacnet_types::map_hash,bacnet_transport::warn_throttled!and the enumerations’name()need no change to existing code (#915, #1713, #1950).
Python API
Section titled “Python API”- Shed levels, Event_Parameters, authentication factors, Access_Event_Time, Last_Restore_Time, Subordinate_List and Event_Algorithm_Inhibit_Ref read as typed values instead of
application_data: compare them by.tagand.value, and read the octets with the new.octets(#1499, #1888). See typed constructed values. add_event_enrollment(event_type=)only names the algorithm of legacy octet-string parameters; set Event_Parameters to choose it (#1868).CovNotificationIteratorcounts notifications lost to lag inmissedinstead of printing to stderr, and ends with “COV notifications ended: the client stopped” instead of “channel closed” (#1858).- An SC hub host that does not resolve raises
BacnetTransportErrorreading “WebSocket host lookup for HOST failed” rather than a TCP dial failure, with errno ENOENT for a name with no addresses (#967). - An SC node queues up to 256 received NPDUs, 32 from one peer (was 64 and 4), and drops a source’s network-layer messages beyond 256 at once or 32 a second;
sc_npdu_drop_counts()reports those drops incontrol_rate_drops(#518, #1712, #1718). - The stack’s log events reach Python’s
loggingunderrusty_bacnet.<crate>loggers, with aNullHandler, so nothing prints until the application configures logging (#1724).
The new keywords and methods need no change to existing code: multicast=, Bbmd(nat_global_address=, nat_private_ranges=, alone_on_subnet=), on_reinitialize and after_reinitialize, storage_path on the logs and colour objects, the BBMD table helpers, who_has_replies(), discover_routers(), the SC connection_events() and connection_status(), and add_bip6_network_port (#940, #941, #1541, #1566, #1579, #1668, #1829, #1841, #1850).
CLI and TUI
Section titled “CLI and TUI”- The TUI is the default. Plain
bacneton a terminal opens the full-screen TUI, now a default feature and in the release binaries. Off a terminal, or with--no-tuiorBACNET_NO_TUI,bacnetwith no command prints its usage and exits with status 2.--no-default-featuresbuilds the CLI without the TUI (#974). - The shell is gone.
bacnet shell, its line editor and thedevicescommand are removed. Open the TUI or call one one-shot command per line, with explicit targets and--jsonfor scripts and screen readers; usediscover(ordiscover --json) for the device list.writemis a one-shot command (#974). - Write guard.
write,writem,create-object,delete-object,file-write,ack-alarm,time-syncandunregisterneed--allow-write;control(dcc) andreinitneed--allow-write --yesor a typed confirmation. A refused command sends nothing and exits with status 4.subscribedefaults to a 300-second lifetime, renewed while it runs and cancelled on exit; an indefinite one needs--lifetime 0 --allow-write(#972). See the write guard. - Routed stations.
discoverandsubscribeprint a routed device’s station as a target,2:0x05. In JSON,discovernames itstation(wasremote_mac) andsubscribenames itrouted_source(wassource_address); thediscovertable’sRemote MACcolumn isStation(#1916). registerandunregisterprint “… accepted” without the result code, in JSON mode too, and a NAK fails the command with exit status 1 (#1822).- On BACnet/SC, an expired or not-yet-valid certificate, or a key that doesn’t match it, is refused before the dial with “SC credential check refused the dial”; renew or fix the files.
bacnet sc-checkinspects the files offline (#968). findand:findsend Who-Has as a global broadcast and print the objects that answer, andfindalso takes the name as a plain word;whois-routertakes--dnetand--waitand lists the routers that answer (#960, #1872).bacnet capturefilters on the-pport instead of always 47808 (#971).
New in the CLI: --mstp PORT joins an MS/TP trunk, in the Linux release binary (elsewhere build with serial); bacnet mstp-ports lists serial ports; --bip-multicast joins a B/IP multicast group; and --sc-failover-url, --sc-reconnect and --sc-connect-timeout set the SC hub link (#941, #969, #1831). The TUI keeps B/IP, BACnet/SC and MS/TP profiles, and adds a device browser, guarded write sessions, a watch list, and Network, SC, MS/TP and Capture screens; the website’s Terminal UI section covers each one (#962–#971, #975).
Wire behavior
Section titled “Wire behavior”BACnetClient::who_hassends Who-Has as a global broadcast, aswho_isdoes, so it reaches devices behind routers (#960).- A non-router
NetworkLayerdrops a network message whose DNET names a single network instead of handing it to its control receiver, as it already did for APDUs (#1795). - An Event Enrollment’s Event_Type follows its Event_Parameters, and one whose parameters were never set serves the
nonealternative; any enrollment with a monitored reference reports and acknowledges its faults (#1868). Event Enrollment and Alert Enrollment serve Event_Message_Texts, Event_Message_Texts_Config and the Event_Algorithm_Inhibit pair (#1507, #1865). - Binary Lighting Output and Lighting Output serve Value_Source, Value_Source_Array and Last_Command_Time (#1866). Color and Color Temperature outputs go to their default at server start, and objects run without a server read In_Progress NOT_CONTROLLED until started or written; a Value_Source COV subscription on them reports Present_Value changes too (#1526, #1582).
- A Trend Log can log by COV a property of its own device (#1480).
- A server or endpoint resolves its link’s receive capacity before it builds a Device, so the Device declares what the link can take: 480 octets on a default MS/TP port (#1720).
MstpExecutionMode::DedicatedThreadcarries anMstpThreadConfig, and the enum isClonebut no longerCopy: writeMstpExecutionMode::dedicated_thread()for the v0.13.0 behaviour. On Linux the thread can run underSCHED_FIFOand be pinned to CPUs;startfails if either can’t be applied, andexecution_status()reports the outcome (#501).MstpCounters::deadline_missesislate_deadlines, inbacnet_mstp_coreandbacnet_transport::mstp, with a lateness histogram beside it.bacnet_mstp_core::LATE_DEADLINEis gone: setMstpCoreConfig::late_deadline(1 ms by default; the Tokio transport counts from 5 ms) (#1897).bacnet-mstp-core: buildMasterCore::new(config, &mut storage, now)over aStandardStorageorExtendedStorage(from_partskeeps the v0.13.0 arguments);HeaplessQueuesisFixedQueues; the line inputs,on_transmit_completeandon_transmit_abortedreturn();CoreErrorsplits intoConfigError,TransmitErrorandRequestError.Action::Transmitcarries{ id, header, length }: callon_transmit_started(id, now)right before driving the line and send the octets in itsOk(frame)(#1769).mstp::monitor::MstpMonitorlistens to a trunk without transmitting, andEndpointSession::mstp_diagnosticsandMstpDiagnostics::node_statereport a station’s counters and state; none of them changes existing code (#958, #969, #970).
B/IP: NAT, multicast and BBMDs
Section titled “B/IP: NAT, multicast and BBMDs”- A
BbmdOptionsliteral naming every field addsnat: Noneandalone_on_subnet: false, or ends with..BbmdOptions::default().BbmdState::local_address()returns the socket’sSocketAddrV4(was([u8; 4], u16)), even behind NAT; the newown_address()gives the BDT’s own address. Buildbacnet_bip_core::bbmd::TableswithTables::new(#940, #1668). bacnet_transport::port::BipPortis#[non_exhaustive]: build it withBipPort::new(endpoint, mode)and add a multicast group view with.with_multicast(...)(#941).BipPortMode::Foreignholds a liveForeignRegistrationinstead of a fixed BBMD and lifetime: build it withBipPortMode::fixed_foreign(bbmd, subscription_lifetime)and read the values through the registration (#1825).- On the wire: a registered BBMD’s Network Port takes writes to its BDT and its foreign-registration setting, and a foreign device’s to FD_BBMD_Address and FD_Subscription_Lifetime, applied by ReinitializeDevice ACTIVATE_CHANGES or WARMSTART when a handler is set; an activated BDT is saved to the persisted-BDT file (#1578, #1825, #1826). Every registered normal-mode port on an explicit interface lists BACnet_IP_Multicast_Address (#941). A Delete-FDT-Entry for a registration past its time-to-live and grace period gets a NAK (#1773).
- A port shared by address hands up broadcasts and unicast in the order the kernel received them on Linux, macOS and the BSDs (#1546).
- New, needing no change: a BBMD behind a NAT router at a configured global address (
BbmdOptions::nat), a BBMD alone on its subnet (alone_on_subnet), and nodes and BBMDs on an IPv4 multicast group (.multicast(...)) (#940, #941, #1668). bacnet-bip-core: the port driver moves frombacnet_router_core::bipintobacnet_bip_core::port(NodePort::new(node, held),BbmdPort::new(link, tables, targets, rows)), andBbmdPortruns the BBMD’sLinkinstead of aNode, answering management requests itself throughTables::serve.Ingress::ManagementandBbmdPort::on_deadlineare gone (#1766).
BACnet/SC: security, ServerPolicy and principals
Section titled “BACnet/SC: security, ServerPolicy and principals”- Server policy. One
ServerPolicymakes every authorization decision. Themutation_authorizer,life_safety_operation_authorizer,audit_notification_authorizerandunconfirmed_audit_notification_authorizerfields and setters, and theLifeSafetyOperationAuthorizer,AuditNotificationAuthorizerandUnconfirmedAuditNotificationAuthorizeraliases, are gone: implementServerPolicyor chainFnPolicyclosures..policy(..)replaces an earlier policy, so combine yours into one.MutationAuthorizerismutation::DeviceWriteAuthorizer(#524). See Server authorization policy. - Principals. Each policy context, and a hardened router’s control authorizer for unicast controls, carries the verified certificate principal (
ScPrincipal) of a direct BACnet/SC peer; hub-relayed and non-SC traffic carries none (#518, #524). - Admission.
ScNpduAdmissionPolicy,ScNpduDropCountsand the newScControlRateLimitare#[non_exhaustive]: start fromDefaultor the builders. A node queues up to 256 received NPDUs, 32 from one peer (was 64 and 4;with_npdu_per_origin_limit(4)restores the old guard), and drops a source’s network-layer messages beyond 256 at once or 32 a second; a site whose router fronts many networks raises that withwith_control_rate_limit()(#518, #1712). - Dial timeouts. A hub dial that runs out of time fails with
Error::TransportcarryingScConnectError::DialTimeout { timeout, stage }instead ofError::Timeout; read it withScConnectError::from_io_error. A missing Connect-Accept is stillError::Timeout, and Python raisesBacnetTimeoutErrorfor both (#1839). - BACnet/SC needs Rustls 0.23.46 or later (#1737). Before a dial, the CLI and TUI lint this node’s certificate, key and CA file, and hard failures stop the dial before any socket opens (#968).
- New, needing no change: hub-connector events and status (
connection_events(),build_with_events), a failover hub URL, the TLS failure reason on connect errors, and offline credential inspection (#959, #967, #968, #1735, #1890).
The router
Section titled “The router”BACnetRouter::startand the core’sRouter::newrefuse a port on network 0 or 0xFFFF: number everyRouterPortand corePortConfigfrom 1 to 65534.Router::newreturnsConfigError::ReservedNetwork(#1698).bacnet-router-core:PortConfigis built withPortConfig::new(network, mac, limits, groups)or the glue configurations instead of a literal;Host::groupimplementations move to aGroupClassifier, passed as theGparameter ofStorageandRouter, and the B/IP and MS/TP group helpers are gone (#1721).router::Countersholdsu32counts since the lastRouter::take_counters(),Options::route_capis anOption<u16>, andbacnet_network::router::Countersis its ownu64type (#1767).MstpPort::new(port, core)owns its master core and refuses one without extended frames (#1769).MessageType,RejectReason,Limits,Npdu::parseandwritebecomeNetworkMessageType,RejectMessageReason,NpduLimits,decodeandencode_into,Limits::MINisrouter::MIN_PORT_LIMIT, andmessage::Rejectis gone (#1770, #1794).- To return a length rejection for a send refused after queueing, a core adapter keeps each send’s
reject_originand passes it toRouter::refused_later; a custom transport returnsError::SendTooLongonly before it queues or writes any bytes (#1716). - On the wire: queued traffic leaves in network-priority order; at the 256-route cap an Initialize-Routing-Table-Ack refreshes the networks the table holds; a BACnet/SC port’s NPDU limit is followed through failover; router-local replies return through the port and peer they came from; and B/IP and MS/TP group forwarding works with the default host (#1696, #1697, #1698, #1699, #1721).
- New and off by default: Router-Busy flow control for a congested port, next-hop-only route rejects, duplicate global-broadcast suppression, and
BACnetRouter::originatefor the router’s own requests (#1697, #1698, #1699).
Server
Section titled “Server”- See Device identity for
ServerConfig.deviceand Server policy forServerPolicy. - The Device gains setters for Object_List (through
DeviceAuthority), System_Status and Database_Revision, and serves the backup and restore properties whenDeviceConfig::backup_and_restoreis set. - A ReinitializeDevice handler can run its restart after the SimpleACK leaves (
ReinitializeContext::after_reply), and an accepted request writes an Audit record (#1564, #1565). - Trend Log, Trend Log Multiple and Event Log can keep themselves in storage across restarts (
with_persistence,FileLogPersistence) (#1541). - Requests cost less: one B/IP ReadProperty client gets about 36% more requests a second than on v0.13.0 on the same machine. Warnings a peer can set off once per packet or connection log at most once per 10 seconds with a count (#1713, #1724, #1950).
New crate: bacnet-npdu
Section titled “New crate: bacnet-npdu”bacnet-npdu publishes for the first time. Like the other portable crates it is no_std and needs no allocator; it has no features. It holds the network-layer vocabulary once: the borrowed NPDU view and encoder, network message types, reject reasons, the network priority and NPDU limits. Every other crate speaks its types, and bacnet-encoding re-exports it as bacnet_encoding::npdu::view (#1770).
bacnet-types, bacnet-encoding and bacnet-services also build without an allocator now: Who-Is, I-Am and ReadProperty encode into caller storage and decode in place (#1614). bacnet-time 0.13.1 adds earliest and, behind a new std feature, HostClock (#1768).
Verify more than import success
Section titled “Verify more than import success”Exercise a known read, error handling, any routing or segmentation paths you depend on, subscription lifecycle, relevant server objects, and controlled cleanup. Rerun every script that drives the CLI: the shell is gone, writes need --allow-write, and routed stations print in a new form. Check any code that matched error text, since codec messages changed. On BACnet/SC, check that your certificates pass the CLI’s pre-dial lint (bacnet sc-check) and that a router fronting many networks stays under the control-message rate limit.
Retest your application’s output parsing where it depends on CLI JSON keys, typed Python values or Rust models. Separate regressions from corrected behavior that an older integration may have depended on accidentally. The integration guides explain shared endpoints, Network Port registration and SC setup.
Sources and release scope
Section titled “Sources and release scope”This guide covers the move from v0.13.0 to v0.14.0. Source review is not hardware qualification.
v0.14.0 release notes · Changelog · Rust API guide · Python API guide.
Release v0.15.0 ·Build and integrate. Pre-1.0 APIs; partial conformance.Support & limitations