Network

The network layer handles NPDU encoding/decoding, address parsing, and multi-port routing. It sits between the application layer (APDUs) and the transport layer (raw datagrams).

For address format details, see Addressing. For router configuration, see BACnet/IP Router.

NPDU encoding/decoding, BACnet addressing, and network-layer management.

This package provides:

  • NetworkSender — protocol satisfied by both NetworkLayer (non-router) and NetworkRouter (router mode) for sending APDUs.

  • NetworkLayer — bridges transport and application layers in non-router mode, handling NPDU wrapping/unwrapping.

  • NetworkRouter — full BACnet router engine per Clause 6.6.

  • BACnetAddress / NPDU — addressing and PDU dataclasses.

Addressing

BACnet addressing types per ASHRAE 135-2020 Clause 6.

class bac_py.network.address.BIPAddress(host, port)[source]

Bases: object

A 6-octet BACnet/IP address composed of a 4-byte IPv4 address and a 2-byte UDP port.

Used as the MAC-layer address for BACnet/IP data links (Annex J).

Parameters:
host: str
port: int
encode()[source]

Encode this address to the 6-byte BACnet/IP wire format.

Return type:

bytes

Returns:

A 6-byte bytes object (4 octets IP + 2 octets port, big-endian).

classmethod decode(data)[source]

Decode a BIPAddress from the 6-byte BACnet/IP wire format.

Parameters:

data (bytes | memoryview) – At least 6 bytes of raw address data.

Return type:

BIPAddress

Returns:

The decoded BIPAddress.

Raises:

ValueError – If data is shorter than 6 bytes.

to_dict()[source]

Serialize this address to a JSON-friendly dictionary.

Return type:

dict[str, Any]

Returns:

A dict with "host" and "port" keys.

classmethod from_dict(data)[source]

Reconstruct a BIPAddress from a dictionary produced by to_dict().

Parameters:

data (dict[str, Any]) – Dictionary containing "host" and "port" keys.

Return type:

BIPAddress

Returns:

The reconstructed BIPAddress.

class bac_py.network.address.BIP6Address(host, port)[source]

Bases: object

An 18-octet BACnet/IPv6 address: 16-byte IPv6 + 2-byte UDP port.

Used as the MAC-layer address for BACnet/IPv6 data links (Annex U).

Parameters:
host: str
port: int
encode()[source]

Encode to 18-byte wire format (16 octets IPv6 + 2 octets port, big-endian).

Return type:

bytes

classmethod decode(data)[source]

Decode from 18-byte wire format.

Raises:

ValueError – If data is shorter than 18 bytes.

Return type:

BIP6Address

Parameters:

data (bytes | memoryview)

to_dict()[source]

Serialize this address to a JSON-friendly dictionary.

Return type:

dict[str, Any]

classmethod from_dict(data)[source]

Reconstruct a BIP6Address from a dictionary produced by to_dict().

Return type:

BIP6Address

Parameters:

data (dict[str, Any])

class bac_py.network.address.EthernetAddress(mac)[source]

Bases: object

A 6-octet IEEE 802 MAC address for BACnet Ethernet (Clause 7).

Used as the MAC-layer address for BACnet/Ethernet (ISO 8802-3) data links.

Parameters:

mac (bytes)

mac: bytes

6-byte IEEE MAC address.

encode()[source]

Encode to 6-byte wire format.

Return type:

bytes

Returns:

The 6-byte MAC address.

classmethod decode(data)[source]

Decode from 6 bytes of MAC address data.

Parameters:

data (bytes | memoryview) – At least 6 bytes of raw address data.

Return type:

EthernetAddress

Returns:

The decoded EthernetAddress.

to_dict()[source]

Serialize to a JSON-friendly dictionary.

Return type:

dict[str, Any]

Returns:

A dict with a "mac" key containing colon-separated hex.

classmethod from_dict(data)[source]

Reconstruct from a dictionary produced by to_dict().

Parameters:

data (dict[str, Any]) – Dictionary with a "mac" key.

Return type:

EthernetAddress

Returns:

The reconstructed EthernetAddress.

class bac_py.network.address.BACnetAddress(network=None, mac_address=b'')[source]

Bases: object

A full BACnet address: optional network number + MAC address.

Network numbers must be None (local), 0xFFFF (global broadcast), or 1-65534 (valid remote network per Clause 6.2.1).

Parameters:
  • network (int | None)

  • mac_address (bytes)

network: int | None
mac_address: bytes
property is_local: bool

True if this address targets the local network (no DNET specified).

property is_broadcast: bool

True if this is any type of broadcast address (global, remote, or local).

property is_global_broadcast: bool

True if this is a global broadcast (DNET = 0xFFFF).

property is_remote_broadcast: bool

True if this is a broadcast on a specific remote network.

A remote broadcast has a network number set (not global 0xFFFF) and an empty MAC address (DLEN=0).

to_dict()[source]

Serialize this address to a JSON-friendly dictionary.

Return type:

dict[str, Any]

Returns:

A dict with optional "network" and "mac_address" keys.

classmethod from_dict(data)[source]

Reconstruct a BACnetAddress from a dictionary produced by to_dict().

Parameters:

data (dict[str, Any]) – Dictionary with optional "network" and "mac_address" keys.

Return type:

BACnetAddress

Returns:

The reconstructed BACnetAddress.

bac_py.network.address.remote_broadcast(network)[source]

Create a remote broadcast address for a specific network.

A remote broadcast has the DNET set and an empty MAC address (DLEN=0).

Parameters:

network (int) – The target network number (1–65534).

Return type:

BACnetAddress

Returns:

A BACnetAddress representing a directed broadcast on network.

bac_py.network.address.remote_station(network, mac)[source]

Create a unicast address for a station on a remote network.

Parameters:
  • network (int) – The target network number (1–65534).

  • mac (bytes) – The MAC address of the station on that network.

Return type:

BACnetAddress

Returns:

A BACnetAddress with both DNET and DADR set.

bac_py.network.address.parse_address(addr)[source]

Parse a human-readable address string to a BACnetAddress.

Accepted formats:

"192.168.1.100"           -> local BACnet/IP, default port 0xBAC0
"192.168.1.100:47809"     -> local BACnet/IP, explicit port
"2:192.168.1.100"         -> remote network 2, default port
"2:192.168.1.100:47809"   -> remote network 2, explicit port
"[::1]"                   -> local BACnet/IPv6, default port 0xBAC0
"[::1]:47808"             -> local BACnet/IPv6, explicit port
"2:[::1]:47808"           -> remote network 2, IPv6
"AA:BB:CC:DD:EE:FF"       -> local Ethernet MAC
"2:AA:BB:CC:DD:EE:FF"     -> remote Ethernet MAC on network 2
"4352:01"                 -> remote station with hex MAC (e.g. MS/TP)
"*"                       -> global broadcast
"2:*"                     -> remote broadcast on network 2

If already a BACnetAddress, returns it unchanged (pass-through).

String results are cached so repeated lookups of the same address (common in building polling loops) are O(1) after the first call.

Parameters:

addr (str | BACnetAddress) – Address string or existing BACnetAddress.

Return type:

BACnetAddress

Returns:

The parsed BACnetAddress.

Raises:

ValueError – If the format is not recognised.

NPDU

NPDU encoding and decoding per ASHRAE 135-2016 Clause 6.

class bac_py.network.npdu.NPDU(version=1, is_network_message=False, expecting_reply=False, priority=NetworkPriority.NORMAL, destination=None, source=None, hop_count=255, message_type=None, vendor_id=None, apdu=b'', network_message_data=b'')[source]

Bases: object

Decoded Network Protocol Data Unit (Clause 6.2).

Represents the complete contents of a BACnet NPDU including the control octet fields, optional source/destination addressing, and either an application-layer APDU or a network-layer message payload.

Parameters:
version: int

BACnet protocol version (always 1).

is_network_message: bool

True for network-layer messages, False for application-layer APDUs.

expecting_reply: bool

True when the sender expects a reply.

priority: NetworkPriority

Message priority (NORMAL, URGENT, etc.).

destination: BACnetAddress | None

Remote destination address, or None for local.

source: BACnetAddress | None

Originating address (populated by routers).

hop_count: int

Remaining hop count for routed messages (0-255).

message_type: int | None

Network message type code when is_network_message is True.

vendor_id: int | None

Vendor identifier for proprietary network messages.

apdu: bytes

Application-layer APDU payload bytes.

network_message_data: bytes

Payload bytes for network-layer messages.

bac_py.network.npdu.encode_npdu(npdu)[source]

Encode an NPDU dataclass into on-the-wire bytes.

Builds the version octet, control octet, optional destination/source address fields, hop count, and either the network-message type + data or the application-layer APDU payload.

Pre-calculates the total buffer size upfront and fills with slice assignment / struct.pack_into to avoid repeated append/extend.

Parameters:

npdu (NPDU) – The NPDU dataclass to encode.

Return type:

bytes

Returns:

The fully encoded NPDU byte string.

Raises:

ValueError – If source address fields are invalid per the BACnet specification (e.g. SNET is 0xFFFF or SLEN is 0).

bac_py.network.npdu.decode_npdu(data)[source]

Decode raw bytes into an NPDU dataclass.

Parses the version octet, control octet, optional destination/source address fields, hop count, and the remaining payload (network message or application-layer APDU).

Parameters:

data (memoryview | bytes) – Raw NPDU bytes (at least 2 bytes required).

Return type:

NPDU

Returns:

The decoded NPDU.

Raises:

ValueError – If the data is too short or the protocol version is not 1.

bac_py.network.npdu.encode_npdu_local_delivery(npdu, source_network, source_mac)[source]

Fast-path encode for router local delivery (no destination).

Combines NPDU construction and encoding into a single operation, skipping intermediate NPDU object creation. Used by the router when delivering to a directly-connected network: strips the destination and injects the source address in one step.

Parameters:
  • npdu (NPDU) – The original routed NPDU (destination is stripped).

  • source_network (int) – SNET to inject (arrival port’s network number).

  • source_mac (bytes) – SADR to inject (data-link source MAC).

Return type:

bytes

Returns:

Encoded NPDU bytes ready for transport.

Network Messages

Network layer message encoding and decoding per ASHRAE 135-2024 Clause 6.4.

This module handles the variable-length data payloads of router-related network layer messages. The NPDU envelope (version, control, addresses, hop count, message type) is handled by npdu.py; this module handles only the data that follows the message type byte.

class bac_py.network.messages.WhoIsRouterToNetwork(network=None)[source]

Bases: object

Clause 6.4.1 – request routing info for a specific or all networks.

If network is None, the request is for all reachable networks.

Parameters:

network (int | None)

network: int | None
class bac_py.network.messages.IAmRouterToNetwork(networks)[source]

Bases: object

Clause 6.4.2 – list of reachable DNETs.

Parameters:

networks (tuple[int, ...])

networks: tuple[int, ...]
class bac_py.network.messages.ICouldBeRouterToNetwork(network, performance_index)[source]

Bases: object

Clause 6.4.3 – half-router advertisement.

Performance index: lower value = higher performance.

Parameters:
  • network (int)

  • performance_index (int)

network: int
performance_index: int
class bac_py.network.messages.RejectMessageToNetwork(reason, network)[source]

Bases: object

Clause 6.4.4 – routing rejection with reason code.

Parameters:
reason: RejectMessageReason
network: int
class bac_py.network.messages.RouterBusyToNetwork(networks)[source]

Bases: object

Clause 6.4.5 – congestion control, impose.

Empty networks list means all networks served by the router.

Parameters:

networks (tuple[int, ...])

networks: tuple[int, ...]
class bac_py.network.messages.RouterAvailableToNetwork(networks)[source]

Bases: object

Clause 6.4.6 – congestion control, lift.

Empty networks list means all previously curtailed networks.

Parameters:

networks (tuple[int, ...])

networks: tuple[int, ...]
class bac_py.network.messages.RoutingTablePort(network, port_id, port_info=b'')[source]

Bases: object

A single port entry within an Initialize-Routing-Table message (Clause 6.5.5).

Parameters:
network: int
port_id: int
port_info: bytes
class bac_py.network.messages.InitializeRoutingTable(ports)[source]

Bases: object

Clause 6.4.7 – routing table initialization or query.

Empty ports list (Number of Ports = 0) is a query requesting the complete routing table without modification.

Parameters:

ports (tuple[RoutingTablePort, ...])

ports: tuple[RoutingTablePort, ...]
class bac_py.network.messages.InitializeRoutingTableAck(ports)[source]

Bases: object

Clause 6.4.8 – routing table initialization response.

Contains routing table data when responding to a query. Empty ports list when acknowledging an update.

Parameters:

ports (tuple[RoutingTablePort, ...])

ports: tuple[RoutingTablePort, ...]
class bac_py.network.messages.EstablishConnectionToNetwork(network, termination_time)[source]

Bases: object

Clause 6.4.9 – instruct half-router to establish PTP connection.

Termination time of 0 means the connection is permanent.

Parameters:
  • network (int)

  • termination_time (int)

network: int
termination_time: int
class bac_py.network.messages.DisconnectConnectionToNetwork(network)[source]

Bases: object

Clause 6.4.10 – instruct half-router to disconnect PTP connection.

Parameters:

network (int)

network: int
class bac_py.network.messages.WhatIsNetworkNumber[source]

Bases: object

Clause 6.4.19 – request local network number. No payload.

class bac_py.network.messages.NetworkNumberIs(network, configured)[source]

Bases: object

Clause 6.4.20 – announce local network number.

configured=True means the number was manually configured. configured=False means it was learned from another device.

Parameters:
network: int
configured: bool
bac_py.network.messages.encode_network_message(msg)[source]

Encode the data payload of a network layer message.

This encodes only the variable-length data that follows the message type byte in the NPDU. The caller is responsible for constructing the full NPDU with the correct message type.

Some message types (e.g. WhatIsNetworkNumber) have no payload, so this function returns b"".

See decode_network_message() for the inverse operation.

Parameters:

msg (WhoIsRouterToNetwork | IAmRouterToNetwork | ICouldBeRouterToNetwork | RejectMessageToNetwork | RouterBusyToNetwork | RouterAvailableToNetwork | InitializeRoutingTable | InitializeRoutingTableAck | EstablishConnectionToNetwork | DisconnectConnectionToNetwork | WhatIsNetworkNumber | NetworkNumberIs) – A network message dataclass instance.

Return type:

bytes

Returns:

Encoded data bytes (may be empty for some message types).

Raises:

TypeError – If the message type is not recognized.

bac_py.network.messages.decode_network_message(message_type, data)[source]

Decode the data payload of a network layer message.

See encode_network_message() for the inverse operation.

Parameters:
  • message_type (int) – The NetworkMessageType value from the NPDU.

  • data (bytes | memoryview) – The raw data bytes following the message type byte (may be empty for message types with no payload).

Return type:

WhoIsRouterToNetwork | IAmRouterToNetwork | ICouldBeRouterToNetwork | RejectMessageToNetwork | RouterBusyToNetwork | RouterAvailableToNetwork | InitializeRoutingTable | InitializeRoutingTableAck | EstablishConnectionToNetwork | DisconnectConnectionToNetwork | WhatIsNetworkNumber | NetworkNumberIs

Returns:

A decoded network message dataclass instance.

Raises:

ValueError – If the message type is not supported or data is malformed.

Network Layer

Network layer manager wiring transport to application per Clause 6.

Provides NetworkLayer, the non-router network layer manager that bridges a single transport (any TransportPort implementation) with the application layer by handling NPDU wrapping/unwrapping, router cache management, and network-number learning.

class bac_py.network.layer.RouterCacheEntry(network, router_mac, last_seen)[source]

Bases: object

Cached router address for a remote network.

Non-router devices maintain a cache mapping remote DNETs to the local MAC address of a router that can reach that network.

Parameters:
network: int

The remote DNET this entry provides a route to.

router_mac: bytes

6-byte MAC of the local router serving this DNET.

last_seen: float

time.monotonic() timestamp of last I-Am-Router-To-Network update.

class bac_py.network.layer.NetworkLayer(transport, network_number=None, *, network_number_configured=False, cache_ttl=300.0)[source]

Bases: object

Network layer manager (non-router mode).

Bridges the transport layer and the application layer. Accepts any transport implementing TransportPort (BACnet/IP, BACnet/IPv6, Ethernet, etc.). Handles NPDU wrapping/unwrapping for application-layer APDUs.

Supports optional network number assignment and maintains a router cache for addressing remote networks via known routers.

Parameters:
property network_number: int | None

The local network number, or None if unknown.

on_receive(callback)[source]

Register a callback for received application-layer APDUs.

Parameters:

callback (Callable[[bytes, BACnetAddress], None]) – Called with (apdu_bytes, source_address) for each received NPDU containing an application-layer APDU.

Return type:

None

register_network_message_handler(message_type, handler)[source]

Register a handler for incoming network-layer messages.

Multiple handlers may be registered for the same message type; they are invoked in registration order.

Parameters:
  • message_type (int) – Network message type code to listen for.

  • handler (Callable[..., None]) – Called with (decoded_message, source_mac) when a matching network message is received.

Return type:

None

unregister_network_message_handler(message_type, handler)[source]

Remove a previously registered network-layer message handler.

Parameters:
  • message_type (int) – Network message type code.

  • handler (Callable[..., None]) – The handler to remove.

Return type:

None

send_network_message(message_type, data, destination=None)[source]

Send a network-layer message (non-APDU).

Parameters:
  • message_type (int) – Network message type code.

  • data (bytes) – Encoded message payload.

  • destination (BACnetAddress | None (default: None)) – Target address. If None, broadcasts locally. A local-network address (no DNET) sends a unicast without NPDU destination routing.

Return type:

None

send(apdu, destination, *, expecting_reply=True, priority=NetworkPriority.NORMAL)[source]

Send an APDU to a destination address.

Wraps the APDU in an NPDU and sends via the transport layer. For remote destinations (DNET set), uses the router cache to send via a known router, or broadcasts if no router is cached.

Parameters:
  • apdu (bytes) – Application-layer PDU bytes.

  • destination (BACnetAddress) – Target BACnetAddress.

  • expecting_reply (bool (default: True)) – Whether a reply is expected (affects routing).

  • priority (NetworkPriority (default: <NetworkPriority.NORMAL: 0>)) – Network priority level.

Return type:

None

property local_address: object

The local address of the underlying transport.

Returns the transport-specific address type (e.g. BIPAddress for BACnet/IP, BIP6Address for BACnet/IPv6).

add_route(network, router_mac)[source]

Pre-populate the router cache for a remote network.

Allows sending to a remote network without broadcast-based router discovery. Useful in environments (e.g. Docker bridge networks) where broadcast-based discovery is unreliable.

Parameters:
  • network (int) – The remote network number.

  • router_mac (bytes) – 6-byte MAC address of the router that reaches the target network.

Return type:

None

get_router_for_network(dnet)[source]

Look up the cached router MAC for a remote network.

Evicts the entry if it has exceeded the cache TTL.

Parameters:

dnet (int) – The destination network number to look up.

Return type:

bytes | None

Returns:

The 6-byte router MAC if cached and fresh, or None if unknown or stale.

Router

Network router per ASHRAE 135-2024 Clause 6.

This module provides the routing table data structures and the NetworkRouter engine that interconnects multiple BACnet networks, forwarding NPDUs, processing network layer messages, and maintaining the routing table dynamically.

class bac_py.network.router.RouterPort(port_id, network_number, transport, mac_address, max_npdu_length, network_number_configured=True)[source]

Bases: object

A single router port connecting to a BACnet network.

Each port represents one physical or logical attachment point of the router to a BACnet network. The port owns a TransportPort that handles the actual data-link send/receive.

Parameters:
port_id: int

Unique port identifier (1-255).

network_number: int

DNET of the directly connected network (1-65534).

transport: TransportPort

The data-link transport for this port.

mac_address: bytes

Local MAC address on this network.

max_npdu_length: int

Max NPDU size for this data link.

network_number_configured: bool

True if manually configured, False if learned via Network-Number-Is.

class bac_py.network.router.RoutingTableEntry(network_number, port_id, next_router_mac=None, reachability=NetworkReachability.REACHABLE, busy_timeout_handle=None)[source]

Bases: object

A single entry in the router’s routing table (Clause 6.6.1).

Parameters:
network_number: int

The reachable DNET (1-65534).

port_id: int

Which port this network is reachable through.

next_router_mac: bytes | None

MAC of the next-hop router, or None if directly connected.

reachability: NetworkReachability

Current reachability status.

busy_timeout_handle: TimerHandle | None

Handle for the 30-second congestion timer.

class bac_py.network.router.RoutingTable[source]

Bases: object

Router’s complete routing table (Clause 6.6.1).

Manages reachability information for all known networks. All mutating operations are synchronous and intended to be called from the asyncio event loop thread.

add_port(port)[source]

Register a router port and create a directly-connected routing entry.

The port’s network_number is automatically added to the routing table as a directly-connected entry (no next-hop router).

Parameters:

port (RouterPort) – The RouterPort to register.

Raises:

ValueError – If a port with the same port_id or a route for the same network_number already exists.

Return type:

None

get_port(port_id)[source]

Look up a RouterPort by its identifier.

Parameters:

port_id (int) – The port identifier to look up.

Return type:

RouterPort | None

Returns:

The RouterPort, or None if not found.

get_all_ports()[source]

Return all registered RouterPort instances.

Return type:

list[RouterPort]

Returns:

A list of all ports managed by this routing table.

get_port_for_network(dnet)[source]

Find the port and routing entry that can reach dnet.

Parameters:

dnet (int) – The destination network number to look up.

Return type:

tuple[RouterPort, RoutingTableEntry] | None

Returns:

A (RouterPort, RoutingTableEntry) tuple, or None if no route to dnet is known.

port_for_directly_connected(dnet)[source]

Return the port if dnet is directly connected, else None.

Parameters:

dnet (int) – The network number to check.

Return type:

RouterPort | None

Returns:

The RouterPort if the network is directly connected, or None if it is remote or unknown.

get_reachable_networks(*, exclude_port=None, include_busy=False)[source]

Return network numbers of reachable routing table entries.

Parameters:
  • exclude_port (int | None (default: None)) – If given, exclude networks reachable through this port. Used when responding to Who-Is-Router so we don’t advertise networks back to the port that asked.

  • include_busy (bool (default: False)) – If True, include networks marked BUSY (temporarily unreachable due to congestion). Per Clause 6.6.3.2, Who-Is-Router responses must include temporarily unreachable networks.

Return type:

list[int]

Returns:

A list of reachable DNET numbers.

get_all_entries()[source]

Return all routing table entries.

Return type:

list[RoutingTableEntry]

Returns:

A list of all RoutingTableEntry instances.

get_entry(dnet)[source]

Look up a routing table entry by network number.

Parameters:

dnet (int) – The destination network number to look up.

Return type:

RoutingTableEntry | None

Returns:

The RoutingTableEntry, or None if not found.

update_route(dnet, port_id, next_router_mac)[source]

Add or update a routing table entry.

If the entry already exists, its port, next-hop, and reachability are updated. If the entry is new, it is created as REACHABLE.

Parameters:
  • dnet (int) – Destination network number.

  • port_id (int) – The port through which dnet is reachable.

  • next_router_mac (bytes | None) – MAC of the next-hop router, or None if directly connected.

Raises:

ValueError – If port_id is not a registered port.

Return type:

None

remove_entry(dnet)[source]

Remove a routing table entry.

Any pending busy timer is cancelled. Silently does nothing if the entry does not exist.

Parameters:

dnet (int) – The network number to remove.

Return type:

None

update_port_network_number(port_id, new_network)[source]

Update a port’s network number and re-key its routing table entry.

Called when a Network-Number-Is message provides the actual network number for a port that was not statically configured. Updates both the port’s network_number and the routing table entry key so they remain consistent.

Parameters:
  • port_id (int) – The port whose network number changed.

  • new_network (int) – The new network number.

Raises:

ValueError – If a route for new_network already exists (other than the port’s own entry).

Return type:

None

mark_busy(dnet, timeout_callback=None, *, timeout_seconds=30.0)[source]

Mark a network as BUSY (congestion control).

Starts a timer that will call timeout_callback after timeout_seconds (default 30 s per the BACnet specification). The callback is typically used to automatically restore the entry to REACHABLE.

Does nothing if the entry does not exist.

Parameters:
  • dnet (int) – Network number to mark as busy.

  • timeout_callback (Callable[[], None] | None (default: None)) – Called when the busy timer expires.

  • timeout_seconds (float (default: 30.0)) – Timer duration in seconds.

Return type:

None

mark_available(dnet)[source]

Mark a network as REACHABLE (congestion lifted).

Cancels any pending busy timer. Does nothing if the entry does not exist.

Parameters:

dnet (int) – Network number to mark as available.

Return type:

None

mark_unreachable(dnet)[source]

Mark a network as UNREACHABLE (permanent failure).

Cancels any pending busy timer. Does nothing if the entry does not exist.

Parameters:

dnet (int) – Network number to mark as unreachable.

Return type:

None

class bac_py.network.router.NetworkRouter(ports, *, application_port_id=None, application_callback=None)[source]

Bases: object

BACnet router engine per Clause 6.6.

Interconnects multiple BACnet networks via RouterPort instances. Forwards NPDUs between ports, processes network layer messages, and maintains the routing table dynamically.

Optionally hosts a local application entity on one port, enabling the router device itself to participate in BACnet services.

Parameters:
async start()[source]

Start all port transports, wire receive callbacks, and perform startup broadcasts.

Per Clause 6.6.2, after starting transports, each port receives:

  1. A Network-Number-Is broadcast (if the port’s network number is configured).

  2. An I-Am-Router-To-Network broadcast listing all networks reachable through other ports.

Return type:

None

async stop()[source]

Stop all port transports and cancel active routing table timers.

Return type:

None

property routing_table: RoutingTable

The router’s routing table.

send(apdu, destination, *, expecting_reply=True, priority=NetworkPriority.NORMAL)[source]

Send an APDU from the router’s local application entity.

This is called by the application layer to send outbound messages. The router wraps the APDU in an NPDU and routes it to the appropriate port based on the destination address.

Parameters:
  • apdu (bytes) – Application-layer PDU bytes.

  • destination (BACnetAddress) – Target BACnetAddress.

  • expecting_reply (bool (default: True)) – Whether a reply is expected.

  • priority (NetworkPriority (default: <NetworkPriority.NORMAL: 0>)) – Network priority level.

Raises:

RuntimeError – If no application port is configured or the configured application port is not found.

Return type:

None