Why bacpypes3 Defines class AnyAtomic(Any)

Tools & Librariesbacpypes3AnyAtomicAnyconstructed data
June 16, 2026|8 min read

In bacpypes3, class AnyAtomic(Any) defines the type used for a BACnet property whose value can be any single atomic (primitive) datatype — Null, Boolean, Unsigned, Integer, Real, Double, Octet String, Character String, Bit String, Enumerated, Date, Time, or Object Identifier. It inherits from Any, bacpypes3's open "ABSTRACT-SYNTAX.&Type" container, because an AnyAtomic is an Any constrained to exactly one atomic element. You meet it most often as the value field of a TimeValue inside a Schedule's Weekly_Schedule, and the usual confusion is that reading one back hands you a wrapper object rather than a native Python value.

The Scenario

You are reading bacpypes3 source — or your IDE jumped to a definition — and you land on a line that reads class AnyAtomic(Any):. The name looks like a contradiction. "Atomic" in BACnet means a single primitive value, while Any sounds like an open, anything-goes container. Why would the atomic type inherit from the open one?

This usually surfaces in one of two ways. Either you are tracing how a Schedule object is built and want to know what type to hand bacpypes3, or you read a property back, expected a plain Python number or string, and got an AnyAtomic instance instead. Both lead to the same question: what is this class, and how do I work with it without fighting the encoder?

What AnyAtomic Represents

BACnet's abstract syntax (ASHRAE Standard 135) includes an open datatype written as ABSTRACT-SYNTAX.&Type. It is the BACnet equivalent of "a value whose datatype is not fixed by the schema — it is whatever the application tag on the wire says it is." A property declared with this datatype can carry a Real on Monday and an Enumerated on Tuesday, and the receiver decides what it is from the tag.

Standard 135 defines thirteen atomic (primitive, application-tagged) datatypes:

AnyAtomic is bacpypes3's model of an open value that is restricted to exactly one of those atomic types — no constructed types (no Sequence, no Array, no nested CHOICE). That restriction matters: several places in the standard allow "any primitive value" but explicitly forbid a constructed one, and AnyAtomic is how bacpypes3 enforces that boundary.

Why It Inherits From Any

The inheritance is the answer to the "contradiction." In bacpypes3, Any is the general open-type container. It holds the raw, application-tagged content and defers decoding until you cast it to a concrete type — that is what makes it able to carry "any" value. AnyAtomic subclasses Any to reuse that open encode/decode machinery, then narrows it: it accepts and validates a single atomic element and rejects constructed data.

This is the same pattern you see elsewhere in the library, where a type reuses a more general base and adds a constraint rather than reimplementing encoding from scratch. It mirrors the reasoning behind why bacpypes3 defines Real as a subclass of both Atomic and float — inheritance carries the protocol behavior, and the subclass pins down the specifics. Reading AnyAtomic(Any) the right way: "an open value (Any) that happens to be a single primitive."

Where You Actually Encounter It

The most common place is the Schedule object. In ASHRAE 135, a BACnetTimeValue is a SEQUENCE of a Time and a value, where that value is the open ABSTRACT-SYNTAX.&Type restricted to a primitive. A Schedule's Weekly_Schedule is an array of seven BACnetDailySchedule entries, each a list of those TimeValue pairs. So when you build or read a weekly schedule in bacpypes3, every setpoint value passes through AnyAtomic.

The Schedule object's Present_Value and Schedule_Default are also open primitive types for the same reason — a schedule might drive an analog setpoint, a binary state, or a multistate command, and the object cannot know which at design time. If you have hit type errors building a Weekly_Schedule, the related entry on bacpypes3 AnyAtomic and the get_value accessor walks through extracting the native value from what ReadProperty returns.

How to Find the Definition in Your Installed Release

Module paths and accessor names have moved between bacpypes3 releases, so rather than trust a path from a blog post, confirm where AnyAtomic lives in the exact version you have installed. Locate the package directory and grep for the class:

# Find where bacpypes3 is installed
python -c "import bacpypes3, os; print(os.path.dirname(bacpypes3.__file__))"

# Grep that directory for the class definition
grep -rn "class AnyAtomic" /path/printed/above

You will typically find the line reads class AnyAtomic(Any):, defined alongside Any and the other constructed/open types rather than next to the simple primitives. You can confirm the inheritance chain at runtime without reading the file:

import inspect
from bacpypes3.constructeddata import AnyAtomic  # confirm this import against your release

print(inspect.getsourcefile(AnyAtomic))
print(AnyAtomic.__mro__)

Treat the import line as something to verify, not copy blindly — if it raises ImportError, the grep above tells you the correct module for your version. The __mro__ output is the authoritative answer to "what does it inherit from" for the code actually running on your machine.

Common Pitfalls

When to Escalate

If you have confirmed the inheritance and the atomic constraint but encoding still fails on a real device, the issue is usually the specific atomic type you are supplying, not AnyAtomic itself — a device may accept a Real where you sent an Unsigned, or reject a Null relinquish where you expected one. Capture the exchange and compare the application tag on the wire against what the device's PICS says the property accepts.

When the class definition or accessor genuinely differs from what any guide describes, the source of truth is the bacpypes3 project itself. Open an issue or check the discussions on the bacpypes3 GitHub repository rather than guessing, since the maintainer documents behavior changes there. For building custom BACnet applications on top of these types, see the bacpypes3 custom applications tutorial.

Source Attribution

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

Additional testing and field validation by SiteConduit.

bacpypes3AnyAtomicAnyconstructed dataBACnet 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.