Changelog¶
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[1.5.7] - 2026-02-24¶
Fixed¶
Client decode bugs: Fixed five methods in
app/client.pythat performedisinstancechecks on raw application-tagged bytes without first callingdecode_and_unwrap()ordecode_all_application_values():_traverse_hierarchy_recursive,discover_extendedenrichment,_discover_device_oid,_poll_backup_restore_state, andbackup_deviceconfig file decoding. These could cause silent data misinterpretation orTypeErrorat runtime.WritePropertyMultiple server decode:
handle_write_property_multipleinapp/server.pynow callsdecode_and_unwrap()on property values before writing, matching the pattern used elsewhere.Segmentation double-counting:
SegmentationManager._total_bytesno longer double-counts when a duplicate segment is received, which could cause premature reassembly completion.Audit log query unbound variable:
AuditLogQueryByTargetACK.decode()andAuditLogQueryBySourceACK.decode()inservices/audit.pynow initializeinner_endbefore the while loop, fixing anUnboundLocalErroron empty inner sequences.extract_context_value()closing tag validation: The function now verifies that the closing tag number matches the expected opening tag number, raisingValueErroron mismatch instead of silently accepting malformed packets.TSM falsy-value bug: Three places in
app/tsm.pyusedx or defaultwhich incorrectly replaced valid falsy values (e.g.,0,0.0) with defaults. Changed to explicitif x is not Nonechecks.Schedule engine midnight race:
ScheduleEngine._evaluate_allnow readsdatetime.now()once per evaluation instead of callingdate.today()anddatetime.now().time()separately, preventing a rare midnight-crossing inconsistency.
Changed¶
LifeSafetyOperationRequest.decodetag class check: Added explicitTagClass.CONTEXTvalidation before decoding context tag 0, raisingValueErrorwith a clear message if an application tag is encountered.Decode assertions → ValueError: Replaced 10 bare
assertstatements inservices/alarm_summary.pyandservices/event_notification.pydecode paths withraise ValueError(...), ensuring decode errors are never silenced by-Ooptimization.VT session hardening: Added source-address validation on
VT-Close/VT-Datarequests and a 64-session cap to prevent resource exhaustion via unbounded VT session creation.Routing table cap:
RoutingTable.update_routenow enforces a 2048-entry limit, rejecting new entries when the table is full to prevent memory exhaustion from malicious route advertisements.Address decode bounds checks:
BIPAddress.decodeandBIP6Address.decodenow validate minimum buffer lengths (6 and 18 bytes respectively) before parsing, raisingValueErrorinstead of producing truncated addresses.BIBB conformance public API:
ServiceRegistrynow exposeshas_confirmed_handler()andhas_unconfirmed_handler()methods;conformance/bibb.pyuses these instead of accessing private_confirmed/_unconfirmeddictionaries.
Removed¶
Dead code: Removed unused
_min_unsigned_bytes()fromencoding/primitives.py, superseded_MAX_SEGMENTS_DECODE/_MAX_APDU_DECODEdicts fromencoding/apdu.py, and 84-lineencode_npdu_with_source()fromnetwork/npdu.py.Redundant branches: Eliminated no-op ternary and identical if/else branches in
app/event_engine.py, and a redundant conditional insegmentation/manager.pyhandle_segment_ack.Unused loggers: Removed 8 unused
_loggerdefinitions and theirimport loggingstatements from service modules (alarm_summary,object_mgmt,cov,virtual_terminal,read_property_multiple,write_property_multiple,write_group,audit).
[1.5.6] - 2026-02-23¶
Fixed¶
extract_context_value()Boolean True decode failure: Fixed a bug whereextract_context_value()inencoding/tags.pyfailed with"Missing closing tag"when the context-wrapped data contained an application-tagged Boolean with valueTrue. Per Clause 20.2.3, application-tagged Booleans encode the value in the tag’s L/V/T bits with zero content octets, but the function was treating the L/V/T value as a content byte count, advancing 1 byte too far and skipping past the closing tag. BooleanFalsewas unaffected (LVT=0 happened to produce correct advancement). This caused anyReadPropertyorReadPropertyMultipleof a Boolean property set toTrue(e.g.log-enable,out-of-service,stop-when-full) to fail on the client side when decoding the response.
[1.5.5] - 2026-02-23¶
Changed¶
Flexible input types for protocol-level
Clientmethods:read_property(),write_property(), andread_range()now acceptstr | BACnetAddressfor addresses,str | tuple | ObjectIdentifierfor object identifiers, andstr | int | PropertyIdentifierfor property identifiers – matching the convenience API (read(),write()).read_property_multiple()andwrite_property_multiple()now acceptstr | BACnetAddressfor addresses. All parse functions use fastisinstancepass-through for already-typed objects, so existing code is fully backward-compatible with no performance overhead. The underlyingBACnetClientretains strict-typed signatures as the intentional low-level API.
[1.5.4] - 2026-02-20¶
Added¶
json_default()function: Public default handler for serializing BACnet types with stdlibjson.dumps()ororjson.dumps(). Handles objects withto_dict(),bytes/memoryview(→ hex string), andIntEnumsubclasses (→int). Importable frombac_pyandbac_py.serialization.write_multiple()priority parameter: BothBACnetClient.write_multiple()andClient.write_multiple()now accept an optionalpriority(1–16) that is applied uniformly to every property in the request.
[1.5.3] - 2026-02-18¶
Added¶
SC server example (
examples/sc_server.py): BACnet/SC server usingBACnetApplicationandDefaultServerHandlerswith an SC hub function for full APDU dispatch (ReadProperty, WriteProperty, Who-Is, etc.). This is the high-level approach to running an SC server.IPv6 server example (
examples/ipv6_server.py): BACnet/IPv6 server usingBACnetApplicationandDefaultServerHandlers.Ethernet server example (
examples/ethernet_server.py): BACnet Ethernet (Clause 7) server usingBACnetApplicationandDefaultServerHandlerswith platform notes for Linux and macOS.Docker SC server role (
docker/entrypoint.py): Newrun_sc_server()function and"sc-server"role dispatch usingBACnetApplicationwith SC transport, matching the pattern ofrun_server()andrun_server_ipv6().Clienttransport parameters:Clientconstructor now acceptssc_config,ethernet_interface, andethernet_macparameters for selecting BACnet/SC and Ethernet transports directly.Top-level SC exports:
SCHubConfigandSCTLSConfigare now importable directly frombac_py(lazy-loaded likeSCTransport).Top-level Ethernet export:
EthernetTransportis now importable directly frombac_py.
Changed¶
Renamed
ipv6_client_server.py→ipv6_client.py: The example was client-only with no server component. Updated docstring to reference the newipv6_server.pyfor the server counterpart.Updated SC example docstrings:
secure_connect.pyandsecure_connect_hub.pynow clarify they are low-level examples and point tosc_server.pyfor the high-levelBACnetApplicationapproach.
Documentation¶
User guide: transport-setup.rst — Added high-level
DeviceConfig-based server examples for IPv6, Ethernet, and SC transports. Existing low-level transport examples retained with clarified headings.User guide: secure-connect.rst — Added “High-Level Integration” section showing
DeviceConfig(sc_config=...)andClient(sc_config=...). Clarified “Quick Start” and “Hub Function” sections as low-level API.User guide: client-guide.rst — Added “Transport Options” section documenting
Clientconstructor parameters for IPv6, SC, and Ethernet.User guide: server-mode.rst — Added
ipv6,sc_config,ethernet_interface, andethernet_macto the DeviceConfig Options section with mutual exclusion note.User guide: getting-started.rst — Added transport selection examples to the Configuration section (IPv6, SC, Ethernet).
User guide: examples.rst — Updated example count (23 → 26), added documentation entries for
sc_server.py,ipv6_server.py, andethernet_server.py. Added Ethernet section.README.md — Updated example table entry for renamed
ipv6_client.py.Test count:
test_example_countupdated to>= 25(26 total examples).
[1.5.2] - 2026-02-16¶
Fixed¶
Docker entrypoint
sys.path— Added project root tosys.pathsodocker.libimports work when the entrypoint is run as a script.SC PKI
generate_test_pki()— Clear directory contents instead ofshutil.rmtree()on Docker volume mount points (which cannot be removed).SC certificate SANs — Use specific
IPv4Addressentries instead ofIPv4Networkfor Docker bridge IPs in certificate Subject Alternative Names.IPv4Networkdoes not work for SSL hostname verification.
Added¶
Mixed-environment SC profiling —
bench_sc.pysupports--mode huband--mode clientfor split Docker/local benchmarks, enabling isolated pyinstrument profiling of hub-side or client-side TLS overhead.--generate-certs DIRcreates shared TLS certificates with broad SANs (localhost, host.docker.internal, Docker bridge IPs). New Docker Compose profilessc-bench-hubandsc-bench-clientand Makefile targetsbench-sc-profile-clientandbench-sc-profile-huborchestrate the mixed-environment runs.Docker scenario 14: Mixed BIP↔IPv6 routing — A BACnet/IP client on network 1 communicates with a BACnet/IPv6 server on network 2 through a dual-stack
NetworkRouter. Tests read, write, RPM, WPM, and object-list operations through the cross-transport router (6 tests).make docker-test-mixed-bip-ipv6.Docker scenario 15: Mixed BIP↔SC routing — A BACnet/IP client sends NPDUs through a BIP↔SC
NetworkRouterto SC echo nodes connected via an SC hub with mutual TLS 1.3 on network 2. SC echo nodes parse incoming NPDUs and swap SNET/SADR→DNET/DADR headers for proper routed responses. TLS certificates are generated locally and bind-mounted into containers (4 tests).make docker-test-mixed-bip-sc.Entrypoint roles:
router-bip-sc,sc-npdu-echo— New Docker entrypoint roles for BIP↔SC gateway routing and NPDU-level SC echo with proper routing header manipulation.
Changed¶
Docker images tagged with version — All services share a version-tagged image (
bac-py:<version>) for reproducible builds.docker-buildusesdocker builddirectly — Replaceddocker compose build(which built nothing since all services have profiles) with a directdocker buildcommand.docker-cleannow removes allbac-py:*images.Performance: APDU dispatch optimization — Replaced
match/caseonPduTypeenum with directisinstancechecks in_on_apdu_received(), eliminating a redundantPduTypeextraction from the raw byte afterdecode_apdu()already determines the type. BIP throughput improved ~4%, Router throughput improved ~36% (cumulative with loop caching).Performance: Event loop caching — Cache the running
asyncioevent loop inBACnetApplication.start()and useloop.create_task()instead ofasyncio.create_task()in_spawn_task(), skipping theget_running_loop()lookup on every request dispatch.Performance: SC WebSocket pending events deque — Changed
SCWebSocket._pending_eventsfromlistwith O(n)pop(0)tocollections.dequewith O(1)popleft()and built-inmaxlen=64cap.Performance: SC hub payload skip — Hub connections now decode BVLC-SC messages with
skip_payload=True, avoiding abytes()copy of the NPDU payload that the hub never inspects (it forwards raw bytes directly).
[1.5.1] - 2026-02-15¶
Added¶
SC TLS stress testing: All BACnet/SC stress tests and benchmarks now use mutual TLS 1.3 with a mock CA (EC P-256) by default, matching production requirements (Annex AB.7.4). A shared
docker/lib/sc_pki.pymodule generates the test PKI. Docker SC scenarios use init containers to generate certificates into shared volumes. The localbench_sc.pybenchmark accepts--no-tlsto fall back to plaintext for comparison.add_route()API: Addedadd_route(network, router_address)toClient,BACnetApplication, andNetworkLayerfor pre-populating the router cache. Enables communication with devices on remote networks without broadcast-based router discovery — required in Docker bridge networks where ephemeral-port clients cannot receive broadcast responses on the standard BACnet port.
Fixed¶
Router per-port broadcast address: Added
broadcast_addressfield toRouterPortConfigand pass it toBIPTransportin router mode. Previously, all router ports used the default global broadcast (255.255.255.255), which fails in Docker bridge networks where directed subnet broadcasts are required. Docker router services now useBROADCAST_ADDRESS_1/BROADCAST_ADDRESS_2environment variables for per-port configuration.BIPTransport ephemeral port broadcast: Fixed
BIPTransport.send_broadcast()sending to port 0 when the transport was created withport=0(ephemeral). The bound port is now stored after socket binding so broadcasts and BBMD advertisements use the correct port.Docker router stress test: Fixed the pre-existing router stress test failure caused by three compounding issues: (1) per-port broadcast addresses not being passed to the router’s BIPTransport, (2) ephemeral port clients unable to receive broadcast I-Am responses forwarded by the router, and (3) missing router cache pre-population. The test now uses
add_route()and a direct server address (SERVER_ADDRESS) to bypass all broadcast-dependent discovery.
Security¶
SC hub VMAC collision race fix: Added
_pending_vmacsreservation set toSCHubFunctionto prevent a TOCTOU race between VMAC collision check and connection registration during the handshake window (Annex AB.6.2).SC URI scheme validation:
SCNodeSwitch.establish_direct()now validates that hub-provided peer URIs usews://orwss://schemes before connecting, preventing SSRF-like redirection to non-WebSocket endpoints.SC header options count cap: BVLC-SC header option decoding now limits lists to 32 options per message (defense-in-depth against malformed payloads).
SC pending resolution cache cap:
SCNodeSwitch.resolve_address()now rejects new resolution requests when the cache reachesmax_connections, preventing unbounded memory growth from address resolution flooding.BBMD max BDT entries: Added
max_bdt_entriesparameter (default 128) toBBMDManager. Write-BDT requests exceeding the limit are NAKed, preventing oversized BDT payloads from consuming unbounded memory.IPv6 VMAC cache size limit:
VMACCachenow accepts amax_entriesparameter (default 4096). When full, stale entries are evicted first; if still full, the oldest entry is dropped.IPv6 pending resolution cap:
BIP6Transport.send_unicast()now limits the pending VMAC resolution cache to 1024 entries, preventing unbounded growth from resolution requests to many unknown VMACs.H1:
decode_real/decode_doublebuffer validation: Added explicit length checks beforestruct.unpack_frominencoding/primitives.py, raisingValueErrorinstead of the opaquestruct.erroron truncated input.H2: ErrorPDU bounds check: Added bounds checks after each
decode_tag()in_decode_error()(encoding/apdu.py) to reject truncated error class/code fields before slicing.H3:
extract_context_valueoverflow check: Added bounds validation inencoding/tags.pyto reject primitive tags whose length extends past the buffer end, preventing silent reads of stale/adjacent memory.H4: Ethernet 802.3 minimum length:
_decode_frame()intransport/ethernet.pynow rejects frames with length field < LLC header size (3 bytes), preventing underflow in NPDU extraction.C1: Service decoder list caps: Added
_MAX_DECODED_ITEMS = 10,000cap to all unbounded decode loops across 8 service files (19 loops total):read_property_multiple,write_property_multiple,alarm_summary,cov,write_group,virtual_terminal,object_mgmt, andaudit.C2:
ObjectTypevendor cache cap:ObjectType._missing_()now clears the vendor cache at 4096 entries, matching thePropertyIdentifierpattern and preventing unbounded growth from vendor-proprietary object types.C3: Segmentation reassembly size cap:
SegmentReceivernow tracks total reassembly bytes and returnsABORTwhen the cumulative size exceeds 1 MiB. Addedcreated_attimestamp field for stale receiver detection.C4: Audit nesting depth enforcement: Added depth checks (max 32) to all manual tag-nesting loops in
services/audit.py(4 loops), preventing stack exhaustion from deeply nested opening tags in audit decode paths.S1: Hub pending VMAC TTL and cap: Converted
_pending_vmacsfromsettodict[SCVMAC, float]with 30-second TTL purge andmax_connectionscap intransport/sc/hub_function.py, preventing unbounded growth from slow or abandoned handshakes.S2: SC header option data size cap: Added
_MAX_OPTION_DATA_SIZE = 512limit toSCHeaderOption.decode_list()intransport/sc/bvlc.py, rejecting oversized option data (up to 65535 per option) early in the decode path.S3: SC WebSocket oversized frame rate limit:
SCWebSocket._process_frame()now tracks consecutive oversized frames and raisesConnectionClosedErrorafter 3 in a row, preventing log flooding from misbehaving peers.S4: SC WebSocket pending events cap: Capped
_pending_eventsbuffer at 64 entries intransport/sc/websocket.py, silently dropping excess frames when a single TCP segment delivers many WebSocket frames.S5: SC address resolution URI cap:
AddressResolutionAckPayload.decode()now truncates the URI list to 16 entries, preventing unbounded allocations from malformed address resolution responses.B1: FDT TTL upper bound: Foreign device registration TTL is now capped at 3600 seconds (1 hour) in
transport/bbmd.py, preventing unreasonably long registration durations.A1: Change callback cap:
ObjectDatabase.register_change_callback()now raisesValueErrorwhen a single property exceeds 100 registered callbacks, preventing unbounded list growth.COV nesting depth enforcement: Added depth check (max 32) to the manual tag-nesting loop in
COVPropertyValue.decode()(services/cov.py), preventing stack exhaustion from deeply nested opening tags in COV value decode paths.decode_booleanbuffer validation: Added explicit length check before accessingdata[0]indecode_boolean()(encoding/primitives.py), raisingValueErroron empty input for consistency withdecode_realanddecode_double.
[1.5.0] - 2026-02-15¶
Added¶
CONTRIBUTING.md: Contributing guidelines covering development setup, code standards, quality gates, and pull request process.
SECURITY.md: Security policy with vulnerability reporting instructions, supported versions, and security considerations for BACnet deployments.
CODE_OF_CONDUCT.md: Contributor Covenant 3.0 Code of Conduct.
GitHub issue templates: Structured bug report and feature request forms (
.github/ISSUE_TEMPLATE/bug_report.yml,feature_request.yml).GitHub PR template: Pull request template with quality gate checklist (
.github/PULL_REQUEST_TEMPLATE.md).README badges: Added PyPI version, Python version, license, and CI status badges. Added Contributing section linking to CONTRIBUTING.md and SECURITY.md.
Changelog project URL: Added
Changeloglink to[project.urls]inpyproject.tomlfor display on PyPI.
Changed¶
Release workflow: Restructured
.github/workflows/release.ymlinto three separate jobs (release, build, publish) per the official Python packaging guide. The publish job uses a dedicatedpypiGitHub Environment withid-token: writeandattestations: writepermissions for trusted publishing with PEP 740 attestations. Build artifacts pass between jobs viaactions/upload-artifact.sdist exclusions: Configured
[tool.hatch.build.targets.sdist]to excludedocker/,scripts/,docs/,.github/,Makefile,ruff.toml,uv.lock, and.python-versionfrom source distributions. Reduces sdist from 397 to 314 files. Wheel contents unchanged (onlybac_py/package).
Fixed¶
README test count: Reconciled inconsistent test counts (“6,380+” vs “6,300+”) in the Testing section.
[1.4.9] - 2026-02-14¶
Added¶
Interactive CLI example: New
examples/interactive_cli.pyproviding a menu-driven interactive CLI for testing Client API features against a real BACnet device. Supports read/write (single and multiple), device discovery (Who-Is, Who-Has, object list), COV subscriptions with live notifications, and time synchronization. Uses non-blocking input to keep the event loop responsive for COV callbacks between actions.
[1.4.8] - 2026-02-14¶
Added¶
Expanded object type aliases: Added 48 short aliases (up from 10) for common object types in
parse_object_identifier(). New aliases includefile,nc,sched,tl,el,ch,lp,lo,sv,np,ee,tmr,iv,csv,acc,lc,avg,al,ar, and more. Full hyphenated names (e.g."analog-input") continue to work without needing aliases.Expanded property identifier aliases: Added 45 short aliases (up from 8) for common property identifiers in
parse_property_identifier(). New aliases includetype,list,priority,relinquish,min,max,polarity,event-state,high-limit,low-limit,deadband,notify-class,vendor-name,model-name,max-apdu,log-buffer,enable, and more.
Changed¶
traverse_hierarchy()string support: Now accepts string addresses and object identifiers (e.g."sv,1") in addition to typed objects.who_has()string support:object_identifierparameter now accepts string formats (e.g."ai,1") in addition toObjectIdentifier.read_multiple()timeout parameter: Added missingtimeoutparameter for consistency withwrite_multiple()and other convenience methods.
Docs¶
Alias reference tables: Updated the string aliases tables in
getting-started.rst,features.rst, andREADME.mdto reflect the expanded alias sets. Fixed incorrectsfalias reference in README (correct alias isstatus).
[1.4.7] - 2026-02-14¶
Changed¶
Router local-delivery fast path: Added
encode_npdu_local_delivery()that combines NPDU construction and encoding into a single operation, eliminating intermediateNPDUandBACnetAddressobject creation on every routed packet in_deliver_to_directly_connected().Router forwarding fast path: Added
encode_npdu_with_source()combined encode for source-injected NPDUs with destination preserved.Debug log guards (BIP transport): Added
if __debug__ and logger.isEnabledFor()guards tosend_unicast()and_on_datagram_received()hot paths, preventing string formatting and enum.nameattribute access on every UDP datagram when DEBUG logging is disabled.Debug log guards (network layer): Guarded
logger.debugin_on_npdu_received()(every APDU dispatch) and_send_remote()(every remote send, avoids.hex()call).Debug log guards (router): Guarded
logger.debugin_forward_to_network()hot path (every directly-connected and next-hop forwarding decision).
Docs¶
Docstring cleanup: Added
:param:documentation to all server handler methods (handle_read_property,handle_write_property,handle_subscribe_cov, etc.),AuditManager.record_operation(), COV manager methods, andBACnetApplicationrequest methods.Removed spec page/table references: Replaced all page number references (e.g.
pp. 821-822), table references (e.g.Table 19-4,Table 6-1,Table 20.2.9.1), and figure references (e.g.Figure 6-11,Figure 6-12) with Clause references throughout the codebase. Clause references are retained.
[1.4.6] - 2026-02-14¶
Changed¶
SC BVLC decode performance: Replaced
SCControlFlagIntFlag bitwise operations with raw integer constants andBvlcSCFunctionenum construction with pre-built tuple lookup in the decode hot path, eliminating ~9% CPU overhead per message.SCVMAC fast construction: Added
SCVMAC._from_trusted()classmethod that bypasses length validation on internal decode paths where the caller guarantees 6 bytes.NPDU decode performance: Added
_make_npdu()fast constructor bypassing frozen dataclass__init__overhead, andNetworkPrioritytuple lookup replacing enum construction.BVLL decode performance: Replaced
BvlcFunctionenum construction with pre-built tuple lookup indexed by byte value.APDU decode performance: Replaced
PduTypeenum construction with pre-built tuple lookup, added_make_confirmed_request()fast constructor forConfirmedRequestPDU, and replaceddict.get()in max-segments/max-APDU decoding with direct tuple indexing.TSM event loop caching: Cached
asyncio.get_running_loop()result in bothClientTSMandServerTSMto avoid repeated lookups on every timeout start.Debug log guards: Added
if __debug__ and logger.isEnabledFor()guards around debug logging in SC BVLC encode/decode, APDU encode/decode, SC transport send/receive, and SC hub function routing hot paths to avoid string formatting and attribute access when DEBUG is disabled.NPDU encode performance:
encode_npdu()now pre-calculates total buffer size and fills a single pre-sizedbytearraywith slice assignment andstruct.pack_into, replacing repeatedappend()/extend()calls.SCMessage fast construction: Added
_make_sc_message()fast constructor bypassing frozen-dataclass__init__overhead, used inSCMessage.decode().BvllMessage fast construction: Added
_make_bvll_message()fast constructor bypassing frozen-dataclass__init__overhead, used indecode_bvll().Router hot-path NPDU construction:
_deliver_to_directly_connected()and_prepare_forwarded_npdu()now use_make_npdu()fast constructor instead ofNPDU(...), avoiding frozen-dataclass overhead on every routed packet.
[1.4.5] - 2026-02-14¶
Added¶
Benchmark profiling: All local benchmark scripts (
bench_bip,bench_router,bench_bbmd,bench_sc) accept--profileand--profile-htmlflags for pyinstrument profiling of async hot paths.pyinstrumentadded as a dev dependency. Makefile targets:make bench-{bip,router,bbmd,sc}-profile.
Changed¶
Docs dependency group: Added
websockets,cryptography, andorjsonto thedocsdependency group so Sphinx autodoc builds with real imports instead of mock stubs, fixing SC transport API documentation warnings.Transport setup guide: Removed redundant
.. contents::TOC directive (Furo theme provides sidebar navigation).
[1.4.4] - 2026-02-14¶
Changed¶
TLS X.509 strict verification: SSL contexts for BACnet/SC now enable
VERIFY_X509_STRICTflag for stricter certificate validation per RFC 5280.System CA store blocked: BACnet/SC SSL contexts no longer fall back to the system certificate store. Only explicitly configured CA certificates are trusted, preventing accidental trust of arbitrary CAs on the host.
Deprecated asyncio APIs replaced: All
asyncio.ensure_future()calls replaced withasyncio.create_task()and allasyncio.get_event_loop()calls replaced withasyncio.get_running_loop()across the entire codebase (source, tests, examples, docker).Fire-and-forget task error logging: Background tasks in
BACnetApplication._spawn_task()and SC transport task schedulers now log exceptions via done callbacks instead of silently dropping them.WebSocket client handshake timeout:
SCWebSocket.connect()now accepts ahandshake_timeoutparameter (default 10s), matching the server-sideaccept()pattern. Prevents indefinite hangs on unresponsive peers.
Added¶
Private key passphrase support:
SCTLSConfig.key_passwordparameter allows passphrase-protected PEM private keys (acceptsbytesorstr). Passphrase values are redacted inrepr()alongsideprivate_key_path.Transport setup guide: New comprehensive documentation page covering all five transport types (BACnet/IP, IPv6, BBMD, Router, Ethernet, SC) with setup examples for common deployment topologies.
Documentation restructure: Moved changelog to its own sidebar section, added introductory text to all API reference pages, documented the IPv6 example script, and fixed the example count.
[1.4.3] - 2026-02-14¶
Changed¶
WebSocket write buffer tuning: SC WebSocket connections now set write buffer high/low water marks (32 KiB / 8 KiB) to trigger backpressure earlier for slow peers, and enable TCP_NODELAY for low-latency frame delivery.
WebSocket max_size enforcement:
SCWebSocket.connect()and.accept()now accept amax_sizeparameter forwarded to the websockets protocol layer for early oversized-frame rejection. All callers (hub connector, hub function, node switch) passmax_bvlc_lengthas the limit.Hub broadcast batched writes:
SCHubFunction._broadcast()now buffers WebSocket frames to all connections synchronously, then drains them concurrently viaasyncio.gather(), reducing broadcast latency.WebSocket recv() event buffering: Fixed a bug where multiple WebSocket frames arriving in a single TCP segment could cause lost frames.
recv()now buffers unconsumed events fromevents_received()for subsequent calls.
Added¶
Local SC benchmark:
scripts/bench_sc.pyruns a complete hub, echo nodes, and stress workers in a single process for Docker-free benchmarking. Supports human-readable and--jsonoutput modes. Makefile targets:make bench-scandmake bench-sc-json.Local BIP benchmark:
scripts/bench_bip.pyruns an in-process BACnet/IP server with 40 objects and configurable stress client pools on localhost. Mixed workloads: read, write, RPM, WPM, object-list, COV. Makefile targets:make bench-bipandmake bench-bip-json.Local router benchmark:
scripts/bench_router.pycreates a two-network router on localhost with a stress server on network 2 and client pools on network 1. Measures cross-network routing overhead. Makefile targets:make bench-routerandmake bench-router-json.Local BBMD benchmark:
scripts/bench_bbmd.pyruns a server with BBMD attached and foreign-device client pools. Includes FDT/BDT read workers. Makefile targets:make bench-bbmdandmake bench-bbmd-json.
Documentation¶
Benchmarks guide overhaul (
docs/guide/benchmarks.rst): Restructured into Local Benchmarks and Docker Benchmarks sections with reference results for all four transport types. Added results comparison table, key observations (local vs Docker performance characteristics), local CLI tuning parameters, and testing conditions.
[1.4.2] - 2026-02-14¶
Fixed¶
Receive callback crash protection: Wrapped 13
_receive_callbackand application callback call sites intry/exceptacrossbip.py,bip6.py,sc/__init__.py,router.py, andlayer.py. An exception from the callback no longer crashes the transport’s datagram handler or router receive loop. The ethernet transport already had this pattern; now all transports are consistent.Assert → TypeError in encode paths: Replaced 8
assert isinstance()calls inBACnetTimeStamp.encode(),BACnetCalendarEntry.encode(), andBACnetValueSource.encode()with explicitTypeErrorraises. These validations now work correctly underpython -O(optimized builds).Router cache cap:
NetworkLayer._router_cacheis now capped at 1024 entries. When full, stale entries are evicted first, then the oldest entry. Prevents unbounded memory growth from I-Am-Router-To-Network floods.Network list decode cap:
_decode_network_list()now rejects messages containing more than 512 network numbers, preventing allocation of oversized tuples from malformed packets.COV subscription cap:
COVManagernow acceptsmax_subscriptionsandmax_property_subscriptionsparameters (default 1000). New subscriptions beyond the limit are rejected withBACnetError(RESOURCES).Time series import cap:
TimeSeriesImporter.from_json()andfrom_csv()now reject imports exceeding 100,000 records with aValueError, preventing memory exhaustion from oversized files.
[1.4.1] - 2026-02-14¶
Changed¶
Encoding hot-path optimization:
encode_unsigned()andencode_unsigned64()now use a pre-computed 256-element lookup table for values 0-255, eliminatingto_bytes()allocation on the most common code path.encode_boolean()uses pre-allocatedb"\x01"/b"\x00"constants.Address encode caching:
BIPAddress.encode()andBIP6Address.encode()now cache their result on the frozen dataclass instance, avoiding repeatedinet_aton()/inet_pton()+struct.pack()calls.BIPAddress.decode()uses an LRU-cached factory to deduplicate instances for the same remote device.Transport receive-path optimization: BIP and BIP6 transports now perform a fast self-echo check using raw tuple/VMAC comparison before allocating address objects, reducing per-packet overhead on broadcast-heavy networks.
BBMD forwarding optimization: BDT unicast-mask lookup is now O(1) via a pre-computed dict. Peer list excludes self, eliminating per-forward self-skip checks. Foreign device registration messages are pre-computed once in
__init__for both IPv4 and IPv6 managers.ObjectType vendor member caching:
ObjectType._missing_()now caches vendor-proprietary pseudo-members (128-1023), matching the existingPropertyIdentifierpattern. Repeated lookups return the same object.StatusFlags singleton: A shared
_NORMAL_STATUS_FLAGSinstance is returned for the common all-normal case, avoiding per-read allocation.standard_properties()caching: The 5-entry base property dict is now computed once and reused across all 40+ object class definitions.
Fixed¶
PropertyIdentifier vendor cache unbounded growth: The
_PROPERTY_ID_VENDOR_CACHEdict is now capped at 4096 entries. A misbehaving device sending millions of unique vendor property IDs can no longer cause unbounded memory growth.
[1.4.0] - 2026-02-14¶
Added¶
Full BACnet/IPv6 (Annex U) integration — IPv6 transport is now fully wired into
ClientandBACnetApplication. Setipv6=Trueto use IPv6 multicast discovery and communication:Client(ipv6=True)creates aBIP6Transportwithff02::bac0multicastDeviceConfig(ipv6=True)andRouterPortConfig(ipv6=True)for app-level configMixed IPv4/IPv6 router ports in a single router configuration
IPv6 foreign device registration via
bbmd_address="[fd00::1]:47808"
IPv6 BBMD manager (
transport/bbmd6.py): BDT/FDT management, broadcast forwarding to peers and foreign devices, FDT expiry cleanup. Mirrors the IPv4 BBMD architecture adapted for Annex U (no broadcast mask, multicast callbacks, source VMAC on all messages).IPv6 foreign device manager (
transport/foreign_device6.py): Registration, re-registration loop at TTL/2, deregistration on stop, distribute-broadcast.BVLL6 spec compliance fix: All 13 BVLL6 function codes now include
source_vmacper Annex U (previously 4 codes were missing it).NetworkLayer generalized to accept any
TransportPort(not justBIPTransport), enabling IPv6 and future transport types.IPv6 example script (
examples/ipv6_client_server.py): Demonstrates IPv6 client with multicast discovery and property reads.IPv6 Docker integration test (Scenario 13, profile
ipv6):server-ipv6andtest-ipv6containers on afd00:bac:1::/64network. Newmake docker-test-ipv6target.
[1.3.11] - 2026-02-14¶
Added¶
Router stress test scenario (
docker/scenarios/test_router_stress.py): Sustained cross-network routing throughput test. Discovers a server on a remote BACnet network through a router and runs mixed-workload stress workers (read, write, RPM, WPM, object-list) with all traffic traversing the router. Includes periodic route health-check workers. Newmake docker-test-router-stressandmake docker-router-stresstargets.BBMD stress test scenario (
docker/scenarios/test_bbmd_stress.py): Sustained foreign-device management throughput test. Registers test clients as foreign devices with a BBMD and runs mixed BACnet service workloads alongside BBMD-specific operations (FDT reads, BDT reads). Measures BBMD overhead under concurrent foreign device activity. Newmake docker-test-bbmd-stressandmake docker-bbmd-stresstargets.Shared stress modules (
docker/lib/router_stress.py,docker/lib/bbmd_stress.py): Reusable worker libraries extendingbip_stress.Statswith routing-specific (RouterStats) and BBMD-specific (BBMDStats) metrics. Both modules reuse the core BIP stress workers for read/write/RPM/WPM operations.Standalone stress runners (
docker/lib/router_stress_runner.py,docker/lib/bbmd_stress_runner.py): JSON-reporting standalone runners for router and BBMD stress tests, following the same warmup/sustain/report pattern as the existing BIP and SC stress runners.
Changed¶
BACnet/SC WebSocket performance optimizations (
transport/sc/): Multiple improvements to the SC transport hot path, collectively improving sustained throughput by ~25% and reducing p99 latency from 0.6ms to 0.4ms:TCP_NODELAY on all SC connections (
websocket.py): Disables Nagle’s algorithm on both client and server WebSocket connections. Prevents 40-200ms stalls when sending small BACnet frames (100-1500 bytes) due to Nagle + delayed-ACK interaction.Raw bytes forwarding in hub (
hub_function.py,connection.py): The hub now forwards pre-encoded message bytes directly to destination connections, skipping the decode-then-re-encode cycle for routed messages. Theon_messagecallback chain passes raw wire bytes alongside the parsedSCMessage(optionalraw: bytes | Noneparameter, backward-compatible).BVLC header caching (
__init__.py):SCTransport.send_unicast()andsend_broadcast()use pre-computed BVLC-SC headers (per-destination for unicast, fixed for broadcast) concatenated with the NPDU payload, bypassingSCMessageobject creation andencode()on every send.Direct chunk writes (
websocket.py): New_write_pending()helper writes protocol output chunks directly to theStreamWriterinstead of collecting them withb"".join(), eliminating an allocation and copy per send.Concurrent hub broadcasts (
hub_function.py): Broadcast messages are forwarded to all connected nodes concurrently viaasyncio.gather()instead of sequentially.Pre-sized encode buffer (
bvlc.py):SCMessage.encode()pre-calculates the total message size and allocates a singlebytearray, eliminating incremental growth and reallocation. Addedencode_encapsulated_npdu()fast-path function for the common case.
Cross-transport encoding optimizations: Applied the pre-sized bytearray pattern from BACnet/SC to all transport and encoding layers, reducing per-message memory allocations across the entire stack:
BVLL encode (
bvll.py):encode_bvll()now uses a single pre-sizedbytearraywithstruct.pack_into()instead ofbytes([...]) + contentconcatenation. Eliminates 2 intermediate allocations per send for BACnet/IP, BBMD forwarding, and foreign device registration.BVLL6 encode (
bvll_ipv6.py):encode_bvll6()rewritten to calculate total message size upfront and fill a singlebytearray, replacing thelist.append()+b"".join()+header + contentpattern that created 3+ intermediate allocations per send.BIP send_unicast (
bip.py): Inline MAC-to-host:port parsing avoids creating aBIPAddressobject on every unicast send.Ethernet frame encoding (
ethernet.py):_encode_frame()uses a single pre-sizedbytearray(zero-initialized for implicit padding) instead of concatenation + conditional padding allocation. LLC header validation simplified to a single 3-byte slice comparison. MAC address logging uses.hex(':')instead of generator + join.NPDU logging guards (
npdu.py): Debug log statements that call.hex()on MAC addresses are now guarded bylogger.isEnabledFor(DEBUG), avoiding string formatting overhead on every encode/decode when debug logging is disabled.Tag encoding lookup table (
tags.py): Pre-computed 150-entry lookup table for the most common single-byte tag encodings (tag 0-14, length 0-4, both APPLICATION and CONTEXT classes). Eliminatesbytes([...])list allocation on ~95% ofencode_tag()calls.
Benchmarks guide (
docs/guide/benchmarks.rst): Added documentation for router stress and BBMD stress test configurations, architecture, and worker descriptions.Docker infrastructure (
docker-compose.yml,entrypoint.py,Makefile): Added Scenario 11 (Router Stress) and Scenario 12 (BBMD Stress) with dedicated server, router/BBMD, test, and runner containers. Addedrouter-stressandbbmd-stressrole dispatches in the entrypoint. Added 4 new Makefile targets.
[1.3.10] - 2026-02-14¶
Fixed¶
Missing
docker/lib/in repository (.gitignore): Thelib/gitignore pattern was excludingdocker/lib/, which contains shared stress test worker modules (bip_stress.py,sc_stress.py,stress_runner.py,sc_stress_runner.py). Added!docker/lib/negation so the directory is tracked. This caused mypy CI failures sincedocker.lib.*imports could not resolve.
[1.3.9] - 2026-02-14¶
Added¶
SC stress test scenario – New Docker-based sustained WebSocket throughput test (
docker/scenarios/test_sc_stress.py) with unicast and broadcast workers through an SC hub, measuring latency with echo correlation. Newmake docker-test-sc-stressandmake docker-sc-stresstargets.Benchmarks guide – New
docs/guide/benchmarks.rstdocumenting both BIP and SC stress test configurations, server object inventories, workload profiles, latency targets, and tuning guidance.
Changed¶
DeviceConfig version defaults (
application.py):firmware_revisionandapplication_software_versionnow default tobac_py.__version__instead of a hardcoded"0.1.0". Docker entrypoint and thermostat demo updated to usebac_py.__version__instead of hardcoded version strings.Docker build caching (
Makefile):docker-buildtarget now uses--no-cacheto ensure clean builds.BIP stress test refactored (
docker/scenarios/test_stress.py,docker/entrypoint.py): Dedicatedstress-serverrole with 40 diverse objects (analog, binary, multi-state, schedule, calendar, notification class). Configurable worker pools (readers, writers, RPM, WPM, object-list, COV) with environment variables. Warmup/sustain phase architecture replaces ramp schedule. Shared worker logic extracted todocker/lib/modules;docker/__init__.pyadded to enable package imports from test scenarios.Docker Compose reorganized (
docker/docker-compose.yml): Added Scenario 10 (SC Stress) with hub, two echo nodes, test container, and stress runner. Stress runner container moved next to its server. Header comment with scenario index and usage guide.
Documentation¶
Server mode guide expansion (
docs/guide/server-mode.rst): Expanded from 188 to 954 lines. Added sections for DeviceConfig options (password, broadcast_address, APDU settings), Object Database management (add/remove/query, change callbacks), supported object types (categorized list of 40+ types), commandable objects and priority arrays, COV subscriptions (server side), custom service handlers (signatures, registration, validation example, error responses), event engine (18 algorithms, intrinsic/algorithmic reporting), audit logging (server side), error handling (error table, password validation, DCC states), application lifecycle (context manager, manual, combined client+server), and registered services reference.Client guide (
docs/guide/client-guide.rst): New consolidated client reference page covering API level comparison, capabilities-at-a-glance table with cross-references, and previously undocumented features: file access (AtomicReadFile/AtomicWriteFile with stream and record examples), private transfer (confirmed/unconfirmed vendor-specific), WriteGroup (channel group writes), virtual terminal sessions (VT-Open/Data/Close), list element operations (AddListElement/RemoveListElement), hierarchy traversal (StructuredView walking), and protocol-level API examples.Added
guide/client-guideandguide/benchmarksto User Guide toctree indocs/index.rst.Updated
docs/getting-started.rstwith cross-references to client guide and protocol-level API section.Updated
docs/features.rstwith cross-references to client guide, commandable objects, supported object types, and new service documentation. Added SC Stress scenario, updated Docker scenario count from nine to ten, added benchmark cross-references.
[1.3.8] - 2026-02-14¶
Fixed¶
BIP future race condition (
bip.py): Explicitly cancel pending BVLC request futures on timeout in_bvlc_request()to prevent late responses from setting results on garbage-collected futures. Also cancel all pending futures duringstop()to prevent dangling references.BIP6 memory leak (
bip6.py): Capped pending address resolution queues at 16 entries per VMAC to prevent unbounded growth when resolution never completes. Added 30-second TTL eviction to discard stale queued NPDUs in_flush_pending().BIP6 dangling futures (
bip6.py): Cancel all pending BVLC futures and clear pending resolution queues duringstop().Ethernet exception handler (
ethernet.py): Broadened_on_readable()exception catch fromOSErrortoExceptionto prevent unexpected parsing errors or callback exceptions from crashing the event loop reader.Router assertion safety (
router.py): Replaced threeassertstatements in_deliver_to_directly_connected(),_forward_via_next_hop(), and_send_reject_toward_source()with explicit guard checks that degrade gracefully when Python runs with-Ooptimization flag.NPDU decode bounds checking (
npdu.py): Added explicit bounds checks before reading DNET/DLEN, SNET/SLEN, hop count, and message type fields indecode_npdu()to raiseValueErrorinstead ofIndexErroron truncated data.Tag decode bounds checking (
tags.py): Added bounds validation before reading extended tag numbers, 1/2/4-byte extended length fields. Truncated packets now raiseValueErrorwith descriptive messages instead ofIndexError.Tag length allocation cap (
tags.py): Reject tag lengths exceeding 1 MB (1,048,576 bytes) to prevent memory exhaustion from malformed or malicious packets.Context nesting depth limit (
tags.py): Cap context tag nesting at 32 levels inextract_context_value()to prevent stack exhaustion from crafted payloads.Application value content bounds check (
primitives.py): Added bounds validation indecode_application_value()after decoding tag metadata but before reading content bytes.Decoded value count cap (
primitives.py):decode_all_application_values()now limits to 10,000 values to prevent memory exhaustion from crafted payloads.Constant-time password comparison (
server.py):_validate_password()now useshmac.compare_digest()instead of==to prevent timing-based password extraction attacks.SC VMAC origin validation (
hub_function.py): Hub function now validates that the originating VMAC in received messages matches the authenticated peer’s VMAC to prevent VMAC spoofing in hub-routed traffic (Annex AB.6.2).SC TLS credential redaction (
tls.py):SCTLSConfig.__repr__()now redactsprivate_key_pathas'<REDACTED>'to prevent credential leaks in logs and tracebacks.SC TLS configuration validation (
tls.py): Added warnings for mismatchedcertificate_path/private_key_pathconfiguration and missing CA certificates.SC WebSocket frame size limit (
websocket.py): Addedmax_frame_sizeparameter propagated from connection setup; oversized frames are logged at WARNING and dropped to prevent memory exhaustion.SC plaintext warnings: Upgraded plaintext-mode log messages from DEBUG to WARNING across
tls.py,__init__.py,hub_connector.py,hub_function.py,node_switch.py, andwebsocket.pywith references to ASHRAE 135-2020 Annex AB.7.4.
Changed¶
Lazy logging across entire codebase: Converted all f-string
logger.debug()/logger.warning()/logger.info()calls to lazy%s/%dformatting across the full stack:app/(client, server, application, tsm, event_engine, cov, audit, schedule_engine, trendlog_engine),encoding/(apdu),objects/(base, device),segmentation/(manager),serialization/,types/(enums),transport/(bip, bip6, bbmd, ethernet, npdu, layer, router, address), andtransport/sc/(all modules). This avoids string interpolation overhead when the log level is disabled.Device info cache eviction (
application.py): Capped_device_info_cacheat 1,000 entries with FIFO eviction (removes oldest 100 when limit reached) to prevent unbounded growth from I-Am responses.Application stop cleanup (
application.py): Clear unconfirmed listeners and device info cache duringstop()to release references.Event engine stop cleanup (
event_engine.py): Cancel pending confirmed notification tasks duringstop()to release memory.Server TSM buffer release (
tsm.py): Clearcached_responseon server transaction abort and timeout to release large byte buffers promptly.SC transport stop cleanup (
__init__.py): Cancel and clean up pending send tasks duringstop()to prevent dangling references.SC connection callback cleanup (
connection.py): Clearon_connected,on_disconnected,on_message, andon_vmac_collisioncallbacks in_go_idle()to break reference cycles between connection, hub function, and node switch objects.SC hub connector resource cleanup (
hub_connector.py): Call_go_idle()on failed connections (VMAC collision or non-connected state) to clean up resources.BBMD byte operations (
bbmd.py): Replacedbytearray+extend()loops in_handle_read_bdt()and_handle_read_fdt()withb"".join()for fewer intermediate allocations.Router cache optimization (
layer.py): In_learn_router_from_source(), updatelast_seentimestamp in-place on fresh cache entries instead of creating a newRouterCacheEntryobject.Foreign device exception narrowing (
foreign_device.py): Narrowed_registration_loop()exception catch from bareExceptiontoOSError.Ethernet stop() cleanup (
ethernet.py): Narrowedcontextlib.suppress(Exception)tocontextlib.suppress(OSError, ValueError)instop().Docker firmware/application version strings updated from
"1.2.0"to"1.3.8".
Added¶
Security and memory safety documentation – New
docs/guide/security.rstcovering protocol safety (ASN.1/BER bounds checking, allocation caps, nesting depth limits), transport security (TLS 1.3, VMAC validation, frame size limits, credential redaction), logging safety (lazy formatting), memory safety (frozen dataclasses, bounded buffers, constant-time comparisons), dependency posture, and a production checklist.~1,000 new test lines covering security hardening: tag decode bounds, allocation caps, nesting depth, application value truncation, decoded value count limit, constant-time password comparison, SC VMAC origin validation, SC frame size limits, SC TLS credential redaction, SC plaintext warnings, NPDU truncation, Ethernet exception handling, and device info cache eviction.
Documentation¶
Added
guide/securityto the User Guide toctree indocs/index.rst.Trimmed
docs/guide/device-management.rst– removed JSON Serialization, Docker Integration Testing, and Protocol-Level API sections that were duplicated infeatures.rstandgetting-started.rst.Updated
docs/features.rstDocker section: added SC scenario, updated count from eight to nine, addeddocker-test-sctarget reference.Fixed cross-references after removing
protocol-level-apilabel.
[1.3.7] - 2026-02-14¶
Fixed¶
SC connection state machine (
connection.py): Added re-entry guards toinitiate()andaccept()to prevent use from non-IDLE states. Fixed_go_idle()double-cancellation race when called from bothdisconnect()and background tasks. Wrappedon_messagecallback dispatch in try/except to prevent callback exceptions from killing the receive loop.SC exception handling: Narrowed broad
(TimeoutError, Exception)catches to specific(TimeoutError, OSError, ConnectionError)across connection.py, hub_connector.py, and node_switch.py to avoid catchingSystemExit,KeyboardInterrupt, and other non-network exceptions.SC node switch shutdown ordering: Reordered
stop()to close direct connections beforeserver.close()+wait_closed(), preventing Python 3.13 hang wherewait_closed()blocks until active connection handlers finish.
Changed¶
SC BVLC-SC message encoding (
bvlc.py): Replaced list-of-bytes + join pattern with pre-sizedbytearray+struct.pack_intoacross all encode methods (SCMessage.encode(),SCHeaderOption.encode(),_encode_options(),BvlcResultPayload.encode()). Reduces allocations on the hot path.SC payload consolidation: Merged identical
ConnectRequestPayloadandConnectAcceptPayloadinto single_ConnectPayloadwith public aliases.SC TLS context caching: Cached SSL contexts in
SCHubConnector.__init__()andSCNodeSwitch.__init__()instead of rebuilding per connection attempt.SC WebSocket accept timeout: Added
handshake_timeoutparameter (default 10s) toSCWebSocket.accept()to prevent slow-client denial of service.Removed dead code (
decode_listunreachable break) and moved lazy_WSStateimport to module level in websocket.py.
Added¶
Example test suite (
tests/test_examples.py): 110 tests covering all 21 example scripts – syntax validation, module-level docstring checks,__main__guard verification,async def main()presence, import validation (core and SC examples separately), and functional tests forcreate_object_database()andgenerate_test_pki()helpers.Expanded CI quality gates: mypy type checking now covers
src/,examples/, anddocker/(previouslysrc/only). Ruff lint and format checks now coverexamples/. Added full type annotations to all 21 example scripts, 8 docker test scenarios, 2 demo scripts, the docker entrypoint, and the stress runner.
Fixed¶
secure_connect_hub.pyexample: FixedAnalogInputObjectpresent-value writes that failed because present-value is read-only per the spec unless out-of-service is True. Now setsOUT_OF_SERVICE = Truebefore writing values.
[1.3.6] - 2026-02-14¶
Added¶
Comprehensive structured logging across the entire stack – Every module now uses
logging.getLogger(__name__)for hierarchical logger namespaces underbac_py.*. Users can enable granular debugging by configuring any logger in the hierarchy (e.g.,logging.getLogger("bac_py.app.client").setLevel(logging.DEBUG)).app/client.py: 56 log statements across all public methods (DEBUG for request/response, INFO for lifecycle/discovery operations)
app/application.py: Lifecycle (INFO start/stop), APDU dispatch (DEBUG), device info cache updates, handler errors (ERROR with exc_info)
App engines (tsm, event, cov, audit, schedule, trendlog): Transaction lifecycle, event state transitions, COV subscription management, audit record creation, schedule/trend evaluation cycles
app/server.py: Handler dispatch (DEBUG), registration events, all error paths now log WARNING before raising BACnetError
Network layer (npdu, layer, router, address): NPDU encode/decode routing info, APDU dispatch, router cache updates, address parsing
Transports (bip, bbmd, ethernet, bip6): Send/receive (DEBUG), BBMD lifecycle (INFO start/stop), broadcast forwarding
Encoding/types (apdu, tags, enums, constructed): APDU encode/decode type identification, tag validation warnings, vendor-proprietary PropertyIdentifier creation, CHOICE decode failures
SC transport (all 8 files): Connection state machines, hub routing, failover events, TLS context creation, BVLC message codec, WebSocket connect/accept/close
Objects (base, device): ObjectDatabase add/remove (INFO), property read/write (DEBUG), not-found warnings
Segmentation: Segment send/receive progress, window management, transfer completion (INFO), duplicate/out-of-window warnings
Serialization: Serialize/deserialize operations (DEBUG), type errors (WARNING)
Debugging and logging documentation – New
docs/guide/debugging-logging.rstguide with logger hierarchy table, log level descriptions, practical debugging recipes (failed reads, discovery, server handlers, segmentation, SC connections), file logging configuration, and performance notes. Added “Structured Logging” section todocs/features.rstand “Debugging and Logging” subsection todocs/getting-started.rst.Logging in example scripts – Added
logging.basicConfig()to 5 core examples (read_value.py,write_value.py,discover_devices.py,monitor_cov.py,object_management.py), bringing the total to 9 of 21 examples with logging setup.
[1.3.5] - 2026-02-14¶
Changed¶
ObjectDatabase Device object caching –
_increment_database_revision()now uses a cached reference to the Device object instead of scanning all objects on every add/remove/rename operation (O(1) instead of O(n)).encode_property_value()dispatch table – Replaced 20-deepisinstancecascade for constructed BACnet types with an O(1) type-keyed dispatch table. The table is built lazily on first call to avoid circular imports. Primitive type dispatch retains theisinstancechain due to subclass ordering requirements (bool < int, IntEnum < int, BACnetDouble < float).decode_real()/decode_double()usestruct.unpack_from()– Avoids a memoryview slice copy on every float decode by usingunpack_frominstead ofunpackwith a slice. This is a hot path in APDU decoding.client.pytop-level imports – Moved 14 repeated local imports (parse_object_identifier,parse_property_identifier,_resolve_object_type,GLOBAL_BROADCAST,MessagePriority,EnableDisable,ReinitializedState) to module-level to eliminate per-call import lookup overhead.Dispatch table encoders use
b"".join()– Converted multi-part constructed type encoders (BACnetDestination,BACnetRecipientProcess,BACnetDeviceObjectPropertyReference,BACnetObjectPropertyReference,BACnetLogRecord) from O(n²)bytes +=concatenation to O(n)b"".join().encode_tag()fast path – Added a single-byte fast path for the common case (tag_number <= 14 and length <= 4), avoiding bytearray allocation entirely. This function is called 100+ times per APDU encode.encode_npdu()removed redundantbytearray.clear()– The buffer was pre-allocated with an estimated size and then immediately cleared, defeating the pre-allocation. Now starts with an empty bytearray.EventEngine deduplicated
_sync_state_machine()calls – New enrollment contexts were synced twice on first evaluation (once in creation, once in the per-cycle sync). Removed the redundant initial sync.Server
_read_object_property()uses identity check – ReplacedObjectIdentifier.__eq__comparison withobj is self._deviceidentity check for the Device object special-case path.Server
_expand_property_references()uses short-circuit scan – Replaced set comprehension withany()generator forPROPERTY_LISTmembership check, avoiding full set construction on every ReadPropertyMultiple ALL request.Pre-computed opening/closing tag lookup tables –
encode_opening_tag()andencode_closing_tag()now return pre-computedbytesobjects for tag numbers 0–14, eliminating abytes([...])allocation on every call. These functions are called for every constructed type in every APDU.Constructed type
encode()methods useb"".join()– Converted all remainingbytes +=concatenation inconstructed.pyencode methods (BACnetTimeStamp,BACnetCalendarEntry,BACnetSpecialEvent,BACnetObjectPropertyReference,BACnetRecipient,BACnetDestination,BACnetLogRecord,BACnetRecipientProcess,BACnetValueSource) and primitives.py helper functions (_encode_calendar_entry,_encode_special_event,_encode_recipient,_encode_cov_subscription) from O(n²) concatenation to O(n)b"".join().PropertyIdentifier._missing_()vendor cache – Vendor-proprietary property IDs (512–4194303) are now cached so repeated lookups return the same pseudo-member instance instead of creating a new one each time.encode_bit_string()/encode_character_string()pre-sized buffers – Replacedbytes([x]) + datatwo-object concatenation with pre-sizedbytearraywrites.decode_character_string()avoids an unnecessarybytes()wrapper when input is alreadybytes.README broadened to cover all transports – Updated description, installation, examples table, Docker scenarios, and requirements to reflect BACnet/IP, IPv6, Ethernet, and Secure Connect support.
pyproject.tomlcleanup – Broadened description, addedbacnet-sckeyword, removed unusedclioptional dependency, removed stale duplicate[tool.ruff]section (authoritative config is inruff.toml).
[1.3.4] - 2026-02-14¶
Added¶
Self-signed certificate generation example (
examples/sc_generate_certs.py) – Generates a test PKI (EC P-256 CA + hub and two node device certificates) and demonstrates TLS-secured BACnet/SC communication with mutual authentication by routing an NPDU between two nodes through a hub. Provides the missing guidance for users who need to test SC transport with real TLS instead ofallow_plaintext=True.Certificate generation guide – New “Generating Test Certificates” section in the BACnet Secure Connect documentation (
docs/guide/secure-connect.rst) with step-by-step instructions for creating a self-signed CA and device certificates using EC P-256 and thecryptographylibrary, including SAN configuration notes for IP address vs DNS hostname verification.
[1.3.3] - 2026-02-13¶
Added¶
BACnet/IP-to-SC gateway router example (
examples/ip_to_sc_router.py) – Demonstrates bridging a BACnet/IP network and a BACnet Secure Connect network usingNetworkRouterwith dual transports (BIPTransport+SCTransport). Shows the real-world building modernisation pattern where existing IP controllers communicate transparently with new SC devices through a pure-forwarding gateway.
Fixed¶
audit_log.pyexample used invalid alias"al,1"– The short alias"al"is not registered inOBJECT_TYPE_ALIASES. Changed to"audit-log,1"which resolves correctly via hyphen-to-underscore conversion. Also fixed the matching code snippet indocs/guide/examples.rst.
[1.3.1] - 2026-02-13¶
Added¶
Docker integration tests for BACnet Secure Connect – New Scenario 9 (
secure-connectprofile) with real cross-container WebSocket communication between separate hub and node containers on Docker bridge networking. Three new container roles:sc-hub(SCHubFunction WebSocket server),sc-node1andsc-node2(SCTransport nodes with echo handlers). 9 test cases covering hub connection, unicast routing to each node, broadcast delivery to all nodes, bidirectional exchange, large NPDU transfer (~1400 bytes), rapid sequential messages (50 messages), and concurrent multi-node traffic.make docker-test-scMakefile target for running the SC Docker scenario independently; also added to themake docker-testchain.5 in-process SC integration tests moved from
docker/scenarios/totests/transport/sc/test_sc_integration.pywhere they belong alongside the 224 existing SC unit tests. Total test count increased from 5,925 to 5,930.
Changed¶
Docker image now includes SC dependencies – Added
--extra secureto bothuv synccommands indocker/Dockerfilesowebsocketsandcryptographyare available inside containers.
[1.3.0] - 2026-02-13¶
Added¶
BACnet Secure Connect (Annex AB) – Full implementation of BACnet/SC per ASHRAE 135-2020 Annex AB, providing encrypted, authenticated BACnet communication over WebSocket/TLS with a hub-and-spoke topology and optional direct peer-to-peer connections. This is the largest feature addition since the initial release.
New transport layer in
src/bac_py/transport/sc/(10 modules):BVLC-SC codec (
bvlc.py) – Encode/decode for all 13 BVLC-SC message types (BVLC-Result, Encapsulated-NPDU, Address-Resolution, Address-Resolution-ACK, Advertisement, Advertisement-Solicitation, Connect-Request, Connect-Accept, Disconnect-Request, Disconnect-ACK, Heartbeat-Request, Heartbeat-ACK, Proprietary-Message) with typed payload dataclasses, header option chaining, and control flag handling.VMAC addressing (
vmac.py) – 6-byte virtual MAC addresses with locally-administered unicast bit management and 16-byte RFC 4122 Device UUIDs for collision detection.WebSocket I/O layer (
websocket.py) – Sans-I/Owebsocketslibrary integration with asyncio TCP/TLS streams for both client and server connections, binary frame send/receive, and subprotocol negotiation (hub.bsc.bacnet.org,dc.bsc.bacnet.org).TLS context builder (
tls.py) – TLS 1.3 client and server SSL context creation with mutual authentication, certificate chain loading, and plaintext mode for testing.Connection state machine (
connection.py) – Initiating peer (Figure AB-11) and accepting peer (Figure AB-12) state machines with Connect-Request/Accept handshake, periodic heartbeat (AB.6.3), graceful Disconnect-Request/ACK exchange, VMAC collision detection, and configurable timeouts.Hub Function (
hub_function.py) – WebSocket server (AB.5.3) that accepts hub connections from SC nodes, maintains a connection table indexed by VMAC, routes unicast messages to destination VMAC, replicates broadcasts to all connected nodes except the source, and detects VMAC/UUID collisions.Hub Connector (
hub_connector.py) – WebSocket client (AB.5.2) with persistent connection to a primary hub, automatic reconnection with exponential backoff (configurable min/max delay), and failover to a secondary hub when the primary is unavailable.Node Switch (
node_switch.py) – Direct peer-to-peer connection manager (AB.4) that listens for inbound direct connections, initiates outbound connections via address resolution through the hub, and maintains a connection pool with configurable limits.SC Transport (
__init__.py) –SCTransportclass implementing theTransportPortprotocol, wiring together the hub connector, optional hub function, and optional node switch. Integrates with the existingNetworkLayertransparently – the network layer sees standard 6-byte MAC addresses (VMACs) and uses the samesend_unicast/send_broadcastAPI.Types and constants (
types.py) –BvlcSCFunction(13 message types),SCControlFlag,SCResultCode,SCHubConnectionStatus, header option types, WebSocket subprotocol names, and VMAC constants.
Install with optional dependencies:
pip install bac-py[secure](websockets>=14.0, cryptography>=42.0).Top-level lazy exports –
SCTransportandSCTransportConfigare available frombac_pyvia__getattr__lazy loading, so importing the package incurs no cost when SC is not used.Docker SC integration scenario –
docker/scenarios/test_secure_connect.pywith 5 tests: two-node unicast via hub, broadcast to all nodes, hub failover, direct P2P connection, and concurrent message stress test.224 new unit tests across 10 test files covering BVLC-SC codec round-trips, VMAC generation and parsing, WebSocket client/server handshake, TLS context creation, connection state machine lifecycle, hub function routing, hub connector failover, node switch direct connections, address resolution, SC transport protocol compliance, and end-to-end NPDU exchange. Total test count increased from 5,701 to 5,925.
Documentation¶
BACnet/SC user guide – New
docs/guide/secure-connect.rstcovering hub connection, hub function setup, direct connections via Node Switch, TLS certificate configuration, failover, and VMAC address resolution.BACnet/SC features page – Added comprehensive SC section to
docs/features.rstwith feature bullet list and code example.API reference – Added all 8 SC sub-modules to
docs/api/transport.rst(BVLC codec, VMAC, Connection, Hub Function, Hub Connector, Node Switch, WebSocket, TLS, Types).Getting started – Added
pip install bac-py[secure]installation instructions and optional dependency note.Example scripts – Added
examples/secure_connect.py(SC client with manual NPDU/APDU construction) andexamples/secure_connect_hub.py(SC hub server with ObjectDatabase, Node Switch, and signal-based shutdown).Examples guide – Added BACnet Secure Connect section to
docs/guide/examples.rstdocumenting both new example scripts.README – Added SC feature bullet,
pip install bac-py[secure]installation section, updated architecture and test count.
[1.2.2] - 2026-02-13¶
Added¶
Test coverage improvements – Added ~640 new unit tests covering encoding edge cases, network layer validation, event engine branches, application lifecycle, server handler error paths, object model operations, transport frame validation, conformance PICS generation, segmentation accounting, service decode branch partials, and type system optional-field paths. Coverage improved from 95% (472 uncovered lines) to 99% (15 uncovered lines). Total test count increased from 5,061 to 5,701.
Fixed¶
Protocol stub coverage exclusions – Added
# pragma: no covertoTransportPort,Serializer, andNetworkSenderprotocol class stubs (transport/port.py,serialization/__init__.py,network/__init__.py) since abstract method bodies (...) are untestable by design.
[1.2.1] - 2026-02-12¶
Fixed¶
read_bdt()/read_fdt()NAK handling – These client methods now convert the internalBvlcNakErrortoRuntimeErrorwhen the target device rejects the request (not a BBMD), matching the pattern already used bywrite_bdt(). Previously the internal exception type was not exported, making it impossible for callers to catch cleanly.
Changed¶
Targeted unicast discovery early return –
who_is()anddiscover()now auto-inferexpected_count=1whenlow_limit == high_limitand the destination is a unicast address. This avoids waiting the full broadcast timeout (typically 3s) when only one response is expected, reducing targeted discovery from ~3s to RTT (~50ms).who_is_router_to_network()early return – Addedexpected_countparameter towho_is_router_to_network()(bothBACnetClientandClientwrapper). When set, the method returns as soon as the expected number of distinct routers have responded instead of waiting the full timeout.
[1.2.0] - 2026-02-12¶
Breaking Changes¶
PropertyIdentifier enum values corrected – ~40+ numerical values realigned to match ASHRAE 135-2020 Clause 21 pp. 933-942. Key changes:
TIME_DELAY_NORMAL204→356,TIME_SYNCHRONIZATION_INTERVAL205→204, lift/escalator properties renumbered (CAR_ASSIGNED_DIRECTION500→448, etc.), staging properties renumbered (PRESENT_STAGEat 493, etc.), audit properties renumbered (AUDIT_LEVEL550→498, etc.). New properties added:ISSUE_CONFIRMED_NOTIFICATIONS(51),INTERFACE_VALUE(387),LOW_DIFF_LIMIT(390),STRIKE_COUNT(391), and others.EngineeringUnits enum expanded from 62 to 269 members – Complete per ASHRAE 135-2020 Clause 21. Many existing values corrected:
WATTS48→47,KILOWATTS49→48,MEGAWATTS50→49,LITERS_PER_SECOND85→87,CUBIC_METERS46→80,KILOGRAMS28→39, and others.StagingObject property names corrected – Renamed to match Clause 12.62:
STAGING_STATE→PRESENT_STAGE,TARGET_OBJECT→STAGES,TARGET_PROPERTY→STAGE_NAMES,STAGING_TIMEOUT→TARGET_REFERENCES.AccessEvent enum corrected – Renamed
NO_ENTRY_AFTER_GRANT→NO_ENTRY_AFTER_GRANTED,DENIED_INCORRECT_AUTHENTICATION→DENIED_INCORRECT_AUTHENTICATION_FACTOR,DENIED_OTHER133→164, and 30+ new denied event members added.
Added¶
5 new enums –
AccessCredentialDisableReason(10 members),LiftCarDoorCommand(3),LiftCarDriveStatus(10),LiftCarMode(14),LiftFault(17) per ASHRAE 135-2020.Bvlc6ResultCode fix –
VIRTUAL_ADDRESS_RESOLUTION_NAKcorrected from 0x0060 to 0x0040 per Clause 7.Examples guide – New
docs/guide/examples.rstcovering all 17 example scripts organized into 5 categories with code snippets and cross-references.Device management guide sections – Added Device Communication Control, Reinitialization, Time Synchronization, and Object Management sections to
docs/guide/device-management.rst.120+ new enum tests covering
LiftFault,LiftCarMode,LiftCarDoorCommand,LiftCarDriveStatus,AccessCredentialDisableReason,AccessEvent,StagingState,EscalatorMode,EscalatorFault,LiftCarDirection,LiftGroupMode,LiftCarDoorStatus,AccessCredentialDisable.
Fixed¶
Docker infrastructure – Pinned uv from
latestto0.9in Dockerfile for deterministic builds. Updated firmware/application version strings from"0.1.0"to"1.2.0"across all 4 server roles. AddedBROADCAST_ADDRESSenv var support to thermostat demo. Added.dockerignoreexclusions (.github/,.env*,tests/,*.md).Makefile CI alignment – Added
docker/tolint,fix, andformattargets to match CI. Added--profile demoand--profile stress-runnertodocker-cleantarget.
Changed¶
Documentation completeness – Added missing services (
SubscribeCOVProperty,SubscribeCOVPropertyMultiple,ConfirmedCOVNotification,ConfirmedCOVNotificationMultiple,GetEventInformation,UnconfirmedCOVNotificationMultiple) tofeatures.rstandREADME.md. Updated test count from 4,920+ to 5,050+. Fixed stale architecture link infeatures.rst.
[1.1.1] - 2026-02-12¶
Added¶
5 new example scripts –
device_control.py(communication control, reinitialization, time synchronization),object_management.py(create, list, and delete objects),advanced_discovery.py(Who-Has, unconfigured device discovery, hierarchy traversal),cov_property.py(property-level COV subscriptions with increment),audit_log.py(audit log queries with pagination). Total example count is now 17.get_enrollment_summary()example inalarm_management.py– demonstrates querying enrollment summaries with acknowledgment filtering.
Fixed¶
extended_discovery.py– changeddev.addresstodev.address_strfor consistency with all other example scripts.
Changed¶
Updated README examples table and documentation to reflect the 5 new example scripts.
Added
get_enrollment_summary()to the events-alarms guide and features convenience API list.
[1.1.0] - 2026-02-12¶
Added¶
4 new
Clientwrapper methods –traverse_hierarchy()walks Structured View object hierarchies with string addressing;subscribe_cov_property_multiple()batches property-level COV subscriptions in a single request;write_group()sends unconfirmed WriteGroup channel writes;discover_unconfigured()finds unconfigured devices via Who-Am-I (Clause 19.7).UnconfiguredDevicetop-level export –UnconfiguredDeviceis now importable directly frombac_py.~40 new
Clientunit tests covering string address parsing,BACnetAddresspass-through, broadcast destination defaults, enum string parsing, and new wrapper delegation.
Changed¶
Consistent string support across all
Clientmethods – 11 methods that previously required typed parameters now accept strings:time_synchronizationandutc_time_synchronization(address),atomic_read_fileandatomic_write_file(address, file identifier),confirmed_private_transferandunconfirmed_private_transfer(address),add_list_elementandremove_list_element(address, object identifier, property identifier),subscribe_covandunsubscribe_cov(address, object identifier),who_has(object identifier). All parsing functions handle typed pass-through, so existing code usingBACnetAddress/ObjectIdentifier/PropertyIdentifiercontinues to work unchanged.Internal deduplication – Extracted
_resolve_broadcast_destination()and_parse_enum()helpers to replace ~20 copy-pasted address parsing, broadcast resolution, and enum parsing blocks across theClientclass. Movedparse_addressto a top-level import.Documentation updates – Added
discover_unconfigured()andwho_has()string usage to the Discovery and Networking guide. Addedsubscribe_cov_property_multiple()example to the COV section. Updatedtraverse_hierarchyexample in Features to use string-friendly syntax. Expanded the Convenience API feature list with new methods and consistent string support note.
[1.0.2] - 2026-02-12¶
Fixed¶
MS/TP and non-IP address parsing –
parse_address()now supports"NETWORK:HEXMAC"format (e.g."4352:01"for MS/TP devices behind routers), enabling the high-level Client to communicate with devices on remote non-IP data links discovered via BBMD or router forwarding.Router path learning from routed APDUs – The network layer now learns router paths from the SNET/SADR fields of incoming routed APDUs. When a response arrives from a remote network, the transport-level sender is cached as the router for that network, enabling efficient unicast routing for subsequent requests instead of broadcasting.
BACnetAddress.__str__()round-trip completeness – Address string output for non-IP MACs (1-byte MS/TP, 2-byte ARCNET, etc.) now round-trips correctly throughparse_address().
Changed¶
Documentation sidebar restructure – Split monolithic
examples.rst(907 lines) into 5 topical guide pages underdocs/guide/(Reading and Writing Properties, Discovery and Networking, Events and Alarms, Server Mode, Device Management and Tools). Reorganized the Sphinx sidebar from 4 flat entries into 3 captioned sections (Getting Started, User Guide, API Reference) with 17 navigable entries. API modules are now listed directly in the top-level toctree instead of behind an intermediate landing page.API reference sidebar overhaul – Split 3 monolithic API pages (Application, Services, Objects) into 12 focused sub-pages grouped by category. Application is now split into Client, Server, and Engines; Services into Property, Discovery, Events, and Management; Objects into Base, I/O, Scheduling, Monitoring, and Infrastructure. Removed
:undoc-members:from all API documentation to reduce noise. Each sub-page now has a manageable right-hand table of contents instead of listing 20-30+ sections on a single page.Hot-path performance optimizations for typical building monitoring scenarios (100+ devices, 25-40 points each):
parse_address()string inputs now cached vialru_cache(O(1) repeated lookups in polling loops)._resolve_object_type()and_resolve_property_identifier()alias resolution now cached vialru_cache.BIPAddress.encode()usessocket.inet_aton()+struct.pack()instead of string splitting.encode_npdu()pre-allocates bytearray to estimated packet size.COVManager.check_and_notify()andcheck_and_notify_property()use secondary dict indices for O(k) dispatch (k = subscriptions on the changed object) instead of O(N) full scan of all subscriptions.ObjectDatabase.get_objects_of_type()uses a type index for O(1) lookup instead of O(N) full scan.
Added¶
137 new unit tests covering Ethernet/MS/TP support and performance optimizations:
NPDU variable MAC length encode/decode (1–8 byte SADR/DADR)
Mixed data link router forwarding (BIP↔MS/TP, 2-byte MAC, broadcasts)
Address
str()↔parse_address()round-trips for all MAC formatsNetwork layer remote send with variable-length MACs and router cache learning
COV secondary index maintenance (subscribe/unsubscribe/expiry/shutdown)
ObjectDatabase type index (add/remove/query by type)
[1.0.0] - 2026-02-12¶
Added¶
Core protocol stack – Full BACnet/IP (Annex J) over UDP, ASN.1/BER encoding for all 13 primitive application tags, context-tagged constructed types, NPDU network layer with all 12 network messages, automatic segmented request/response handling (Clause 5.2), and Transaction State Machine with retry/timeout management.
All confirmed and unconfirmed services – ReadProperty, WriteProperty, ReadPropertyMultiple, WritePropertyMultiple, ReadRange, CreateObject, DeleteObject, AddListElement, RemoveListElement, AtomicReadFile, AtomicWriteFile, SubscribeCOV, SubscribeCOVProperty, EventNotification, AcknowledgeAlarm, GetAlarmSummary, GetEnrollmentSummary, GetEventInformation, ConfirmedTextMessage, DeviceCommunicationControl, ReinitializeDevice, ConfirmedPrivateTransfer, ConfirmedAuditNotification, AuditLogQuery, VT-Open/Close/Data, Who-Is/I-Am, Who-Has/I-Have, Who-Am-I/You-Are, TimeSynchronization, UnconfirmedCOVNotification, UnconfirmedEventNotification, UnconfirmedTextMessage, UnconfirmedAuditNotification, WriteGroup, and UnconfirmedPrivateTransfer.
62 object types – Device, Analog/Binary/MultiState I/O/Value, Accumulator, Averaging, Calendar, Channel, Command, Event Enrollment, Event Log, File, Global Group, Group, Life Safety Point/Zone, Load Control, Loop, Network Port, Notification Class, Notification Forwarder, Program, Pulse Converter, Schedule, Staging, Structured View, Timer, Trend Log, Trend Log Multiple, Audit Reporter, Audit Log, Alert Enrollment, Access Door/Point/Zone/User/Rights/Credential, Credential Data Input, Elevator Group, Lift, Escalator, Lighting Output, Binary Lighting Output, and 12 generic value types.
Event engine – All 18 standard event algorithms (change-of-bitstring, change-of-state, change-of-value, out-of-range, floating-limit, change-of-life-safety, change-of-discrete-value, etc.), intrinsic reporting for 9 object types, NotificationClass recipient list routing with day/time filtering and per-recipient confirmed/unconfirmed delivery.
Schedule engine – Weekly and exception schedules with calendar-aware evaluation, wildcard dates, week-n-day patterns, and priority-based resolution writing to target object property references.
Trend log engine – Polled, COV-based (Clause 12.25.13), and triggered acquisition modes with configurable circular buffer management and property-change callbacks.
Audit logging – AuditManager with automatic audit records for write/create/delete operations, AuditReporter and AuditLog objects with buffer management, ConfirmedAuditNotification/UnconfirmedAuditNotification services, and AuditLogQuery for retrieval.
COV manager – Subscription lifecycle management, increment threshold enforcement, property-level and object-level subscriptions, confirmed and unconfirmed notification delivery.
High-level Client API – Simplified async context manager with string-based addressing, short aliases (ai, ao, av, bi, bo, bv, msv, pv, name, desc, etc.), auto-encoding/decoding, and convenience wrappers for discover, read, write, read_multiple, write_multiple, subscribe_cov, get_alarm_summary, get_event_information, acknowledge_alarm, send_text_message, backup, restore, query_audit_log, subscribe_cov_property, create_object, delete_object, device_communication_control, and reinitialize_device.
Server handlers – DefaultServerHandlers for ReadProperty, WriteProperty, ReadPropertyMultiple, WritePropertyMultiple, ReadRange, Who-Is, COV subscriptions, CreateObject, DeleteObject, device management, file access, and audit instrumentation.
Network routing – Multi-port BACnet router with dynamic routing tables (Clause 6), Who-Is-Router-To-Network discovery, and cross-network message forwarding.
BBMD – Broadcast Management Device with foreign device registration, BDT/FDT table management, cross-subnet forwarding, and IPv4 multicast (Annex J.8).
BACnet Ethernet – Raw IEEE 802.3 transport with 802.2 LLC headers (DSAP/SSAP=0x82) per Clause 7, Linux AF_PACKET and macOS BPF support.
BACnet/IPv6 – Full Annex U transport with 3-byte VMAC addressing, IPv6 multicast, address resolution with TTL caching, and foreign device registration.
Device info caching – Automatic caching of peer device capabilities from I-Am responses (Clause 19.4) for correct APDU size negotiation.
JSON serialization –
to_dict()/from_dict()on all data types with optionalorjsonbackend, time series export/import (Annex AA) in JSON and CSV formats.Conformance and PICS generation – PICSGenerator with full object introspection, BIBBMatrix with 40+ BIBB definitions and auto-detection.
Docker integration tests – 8 scenarios (Client/Server, BBMD, Router, Stress, Device Management, COV Advanced, Events, Demo) with real UDP communication between containers using Docker Compose with isolated bridge networks.
12 example scripts – read_value, write_value, read_multiple, write_multiple, discover_devices, extended_discovery, monitor_cov, alarm_management, text_message, backup_restore, router_discovery, and foreign_device.