Guidance for AI coding agents working in this repository. See https://agents.md/ for the format. Human contributors should read README.md first, then TUTORIAL.md.
A tutorial Python example that implements the BACnet B-SS (Smart
Sensor) profile using the CAS BACnet Stack through ctypes: a minimal,
read-only BACnet/IP device (DS-RP-B, DM-DDB-B, DM-DOB-B) whose readings
follow a sensor Simulation and which reports a broken sensor as a fault.
It is one of a series - one git repo per BACnet profile and language - and the
faithful Python translation of BACnetProfileExample-B-SS-CPP (v1.3.0):
same Device, objects, properties, Simulation, Failure Modes, test-only hook,
command line and Wire Tests. A test cannot tell the two apart on the wire; keep
it that way. The top priority is that the code reads like a tutorial a customer
can learn from and copy-paste. Favour clarity over cleverness.
main.py- the whole application, four numbered sections likemain.cpp(1 configuration, 2 the sensor Simulation, 3 theget_property_*callbacks, 4main()).common/- the shared Python helper (simple_udp.py,cas_example_helper.py,cas_bacnet_stack_example_constants.py), vendored in, with its own version (COMMON_VERSION) andcommon/CHANGELOG.md. It is the Python edition of the C++common/; port C++common/conventions into it, never the other way round.tests/- the Wire Tests, copied from the C++ reference. The only local change istests/bss_setup.py'sdefault_command()/launch_command()/start()(so--execan bemain.pyor a command line) and the file names in comments. Keep it that way: a new check goes into the reference first.wiretest/- the series' shared Wiretest Harness (0.7.0), byte-identical toBACnetProfileExample-B-SS-CPP/wiretest/. Never edit it; if it needs a change, report it.README.md- what this example is. Keep it short and about THIS example.TUTORIAL.md- how to extend and review the example.docs/PICS.md- the PICS. Its objects-and-properties section is GENERATED fromdocs/objects.json; do not hand-edit between theOBJECTS-PROPERTIESmarkers (the one exception: the generator's intro sentence namesmain.cpp'sGetProperty*; this repository saysmain.py'sget_property_*).submodules/cas-bacnet-stack/- the CAS BACnet Stack as a git submodule (private; Stack Pin6.x@64b2cfac5, 6.0.23). After cloning, rungit submodule update --init --recursive. Two things in it are used in place:adapters/python/- the stack's Python adapter.main.pyputs it onsys.path.CASBACnetStackAdapterBindings.pyis generated by the stack'sci_scripts/generate-python-adapter.js. Never copy, vendor, regenerate by hand or edit any adapter file into this repository - it is part of the licensed stack. If it is missing something, put the smallest marked workaround in this repository's own code and report it as an adapter defect.bin/- where the stack library is built (CASBACnetStack_x64_Release.dll/libCASBACnetStack_x64_Release.so) and wheremain.pyloads it from.
The PROFILE-TABLE block in README.md is generated from the example-series
repository's docs/profile-table.md. Edit it there, not here.
No build step for Python. The stack library is built once from the submodule
into its bin/ (README.md "Build the CAS BACnet Stack library"):
git submodule update --init --recursive
# Windows: MSBuild submodules/cas-bacnet-stack/projects/msvs/CASBACnetStack/CASBACnetStack.vcxproj -p:Configuration=ReleaseDll -p:Platform=x64
# Linux: g++ -shared -fPIC -std=c++17 -O2 -I submodules/cas-bacnet-stack/source submodules/cas-bacnet-stack/source/*.cpp -o submodules/cas-bacnet-stack/bin/libCASBACnetStack_x64_Release.so
python -m py_compile main.py common/*.py tests/*.pyThe stack binary is never committed and never released. .gitignore
covers every library name; .github/workflows/release.yml builds the library to
test, and refuses to upload any .dll / .so / .lib / .a / .node.
git ls-files | grep -iE '\.(dll|so|node|lib|a)$' must print nothing. Never
copy the library to the repository root.
No dependencies beyond the Python standard library - do not add a
requirements.txt.
python main.py [--port 47808] [--deviceID 389001] # python3 on Linux
python main.py --versionInteractive keys: h help, q quit, up/down nudge the simulated Analog Input
1 reading. On a redirected stdin only h / q (that is how the Wire Tests stop
it).
- Device "Chipkin Example B-SS"; objects use the series' colour names; vendor id 389; Protocol_Revision 30 (the stack's).
- Implement only what the B-SS profile requires - but expose every required property of each object.
- The sensor readings come from the Simulation in
main.pysection 2 (sensor_simulation_tick()); it is outside the BACnet Surface and labelled so in the README. Its broken-wire hook is test-only (CAS_EXAMPLE_TEST_SENSOR_FAULT_FILE, an environment variable naming a file)- never a command-line option or a menu key.
- Read-only. WriteProperty is never enabled and there is no
BACnetStack_RegisterCallbackSetProperty*call. - Explicit library load, like the C++
LoadBACnetFunctions():main()'sload_cas_bacnet_stack()looks in the run directory (the folder ofmain.py), thensubmodules/cas-bacnet-stack/bin/, doesctypes.CDLL+bind()before anyBACnetStack_*call. Nothing imports or loads the stack at module import time. Without it (or ifctypescannot load it) the example prints the series' missing-library message (helper.print_missing_stack_library(), owner ruling 2b - do not reword it) to stderr and exits 1, never a traceback;--help/--versionstill work. - Every
BACnetStack_*setup call's return value is checked; a failure prints which call failed andmain()returns 1. - Every
Get*Propertycallback ends with anerror_codepointer. Leave it alone on a catch-allreturn False; set it only where this device knows the read is wrong (theState_Textbranch). - Callbacks must be rooted. Every
ctypes.CFUNCTYPEobject passed to aBACnetStack_RegisterCallback*call is appended tocas_example_helper._callback_refsfirst - never held only by a local. CPython frees an unrooted one at once, and the next call into it crashes. - The stack is single-threaded. Nothing calls a
BACnetStack_*function or touches the UDP socket from another thread. (common/'s piped-stdin reader thread on Windows only queues key presses.) - The 6-byte IPv4 connection string (4 octets + big-endian port) is packed in
common/cas_example_helper.pyonly. - Links are identified by Network Port object instance
(
NETWORK_PORT_INSTANCE). - Present tense only: no comment or doc references a previous version.
- Never edit
common/in this repo alone once a sibling Python example exists: bumpCOMMON_VERSION, add acommon/CHANGELOG.mdentry, sweep it.
python -m py_compile main.py common/*.py tests/*.py.- The Wire Tests (README "Wire Tests"):
python tests/run_wire_tests.py --port <n>- discovery, the readings following the Simulation, and the Failure Modes. It must endALL WIRE TESTS PASSED. A new check must fail with its feature removed (tests/README.md has the mutation tables). - If you changed the objects or their properties, update
docs/objects.jsonand regeneratedocs/PICS.md(python tools/gen-objects-properties.py BACnetProfileExample-B-SS-Pythonfrom the series root, then restore themain.pywording of its intro sentence); no row may be flagged with ⚠.
Bump APP_VERSION in main.py and add an entry to
CHANGELOG.md, then tag vX.Y.Z. The GitHub Actions workflow
builds the stack library, smoke-tests and Wire-Tests the example, checks the
packaged app prints the missing-library message without the stack, and
publishes the example's own files (never the stack library or adapter).
See LICENSE. The CAS BACnet Stack, its adapters included, is a separate, commercially licensed product and is not covered by it.