bacpypes3 Choice, _choice, and _elements: Source and Usage

Tools & Librariesbacpypes3Choiceconstructed dataASHRAE 135
May 31, 2026|8 min read

In bacpypes3, Choice is the Python base class that models the ASHRAE 135 CHOICE constructed data type — an alternative-of-named-elements, where exactly one named element is set at a time. The _elements class attribute declares the allowed alternatives (a name-to-type mapping), and _choice is the per-instance attribute that records which element name is currently set. The canonical source lives in the bacpypes3 constructed-data module; the easiest way to find the current location is to import it from the package and read its __module__ at runtime, or browse the GitHub repository directly. See JoelBender/BACpypes3 on GitHub for the authoritative implementation.

What You're Looking At

If you've been reading bacpypes3 source — or staring at a traceback from a Choice subclass — you've probably seen three things together: a class that inherits from Choice, a class-level _elements declaration listing alternative element names and their types, and an instance-level _choice that tells you which element is currently set. The search query “bacpypes3 choice _choice _elements source” almost always comes from one of three situations:

The rest of this entry covers the concept (what CHOICE is in BACnet), the pattern (how bacpypes3 models it), how to find the current source, and the encoding pitfalls that produce the most common errors.

CHOICE in ASHRAE 135 — the Concept

BACnet's data types are defined in ASHRAE Standard 135 using ASN.1 notation. CHOICE is one of the standard constructed types: it declares a set of named alternatives, and any given instance of the type carries exactly one of those alternatives. ASN.1 CHOICE is structurally similar to a tagged union in other languages — discriminated at runtime by which element is present, not by an explicit type code.

Every CHOICE production in 135 has the same shape:

On encode, exactly one element is selected and tagged. On decode, the receiver inspects the context tag to know which alternative arrived. If more than one element is present in memory at encode time, the type has been used incorrectly — that is the constraint bacpypes3 enforces with the “exactly one” check.

How bacpypes3 Models a CHOICE Type

bacpypes3 maps the ASN.1 CHOICE production onto a Python class hierarchy. A concrete BACnet CHOICE type is declared as a subclass of Choice, with a class-level _elements mapping the element names from the 135 production to the Python types they carry. Element types are themselves bacpypes3 classes — atomics like Real and Unsigned, primitive containers, or further constructed types like Sequence.

When you instantiate the subclass, the active alternative is recorded as _choice (the element name as declared in _elements), and the value itself is stored as an attribute by that same name. The class enforces the invariant that exactly one element name is set per instance — assigning two of them is what raises the runtime error referenced above.

For an end-to-end walkthrough of building custom services on top of these base classes, see BACpypes3: Custom BACnet Applications in Python. For background on how the primitive types fit into the same hierarchy (and why a class can inherit from both Atomic and a Python built-in), see bacpypes3 Real Class: Why It Inherits From Atomic and float.

Finding the Source for Choice, _choice, and _elements

Module layout in bacpypes3 has changed across early releases, and pinning a path in writing tends to age badly. Use one of these version-independent approaches instead:

# Locate the Choice base class in the installed version
import bacpypes3
from bacpypes3 import constructeddata  # may also be re-exported elsewhere
print(constructeddata.Choice.__module__)
print(constructeddata.Choice.__qualname__)

# Inspect a stock CHOICE type to see _elements in action
from bacpypes3 import basetypes
ts = basetypes.TimeStamp  # example: an ASHRAE 135 CHOICE
print(ts.__mro__)
print(ts._elements)

Avoid copy-pasting a module path from a Stack Overflow answer without verifying it against your installed version — the reorganization between bacpypes (the original) and bacpypes3 (the asyncio rewrite) moved several symbols, and not every blog post has kept up.

Construction and Common Pitfalls

Two patterns cover most CHOICE construction in user code:

Pitfalls to watch for:

When to Escalate

If you've confirmed the _elements declaration matches the ASHRAE 135 production, the active element is correctly set, and you still see an encoding or decoding failure, the issue is usually one of three things: a context-tag mismatch in a custom subclass, a library version that predates a fix you need, or a peer device that does not implement the CHOICE alternation correctly. File issues against bacpypes3 with a minimal reproducer at the project issue tracker. For peer-device problems, capture the exchange on the wire and verify the context tags on each alternative against the relevant 135 clause.

Source Attribution

The technical guidance in this entry is informed by the following sources:

Additional testing and field validation by SiteConduit.

bacpypes3Choiceconstructed dataASHRAE 135BACnet types

Was this article helpful?

Related Articles

Need to do this remotely? SiteConduit provides Layer 2 access that preserves BACnet broadcasts — no BBMD needed for remote sessions. Join the waitlist.

SC

SiteConduit Technical Team

Idea Networks Inc.

SiteConduit builds managed remote access for building automation. Our knowledge base is maintained by BAS professionals with hands-on experience deploying and troubleshooting BACnet, Niagara, Modbus, and Facility Explorer systems.