Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 17 additions & 46 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Build the B-RTR example against the CAS BACnet Stack STATIC library and publish a GitHub
# Release (binaries + metrics.json) when a vX.Y.Z tag is pushed. PRs and pushes to main run
# the same build + smoke test without publishing.
# Build the B-RTR example and publish a GitHub Release (binaries + metrics.json) when a
# vX.Y.Z tag is pushed. PRs and pushes to main run the same build + smoke test without
# publishing. The build is exactly what README.md tells a customer to run: plain CMake in
# the adapter's default SOURCE mode, which compiles the stack into the executable.
#
# The CAS BACnet Stack is a PRIVATE git submodule: the repository secret CAS_STACK_PAT must
# hold a PAT with read access to chipkin/cas-bacnet-stack.
Expand All @@ -24,17 +25,15 @@ jobs:
fail-fast: false
matrix:
include:
# windows-2022, not windows-latest: the stack's vcxproj names the v143 (VS 2022)
# toolset; newer images may not carry it.
# windows-2022 pins the VS 2022 (v143) toolset the released binaries are built
# with, so the published metrics stay comparable release to release.
- os: windows-2022
artifact: BACnetExampleBRTR-windows-x64
exe: build/Release/BACnetExampleBRTR.exe
lib: submodules/cas-bacnet-stack/bin/CASBACnetStack_x64_Release.lib
metrics: metrics-windows.json
- os: ubuntu-latest
artifact: BACnetExampleBRTR-linux-x64
exe: build/BACnetExampleBRTR
lib: submodules/cas-bacnet-stack/bin/libCASBACnetStack_x64_Release.a
metrics: metrics-linux.json
steps:
- name: Enable git long paths (Windows)
Expand All @@ -47,52 +46,23 @@ jobs:
submodules: recursive
token: ${{ secrets.CAS_STACK_PAT || github.token }}

- name: Stack commit (cache key)
- name: Stack commit (recorded in metrics.json)
id: stack
shell: bash
run: echo "sha=$(git -C submodules/cas-bacnet-stack rev-parse HEAD)" >> "$GITHUB_OUTPUT"

# The static library depends only on the stack commit, so it is cached on that SHA.
# A moved pin therefore always rebuilds it - a stale library against a new header is
# an ABI mismatch that links and misbehaves.
- name: Restore the prebuilt static library
id: libcache
uses: actions/cache@v4
with:
path: submodules/cas-bacnet-stack/bin
key: cas-stack-static-${{ runner.os }}-${{ steps.stack.outputs.sha }}

- name: Build the CAS BACnet Stack static library (ReleaseLib, customer surface only)
if: steps.libcache.outputs.cache-hit != 'true'
shell: bash
run: |
set -e
if [ "$RUNNER_OS" = "Windows" ]; then
# microsoft/setup-msbuild's PATH update does not reach this Git-Bash step
# (msbuild: command not found even after that step ran), so resolve it
# directly the same way tools/build-stack-static.sh does locally.
vswhere='/c/Program Files (x86)/Microsoft Visual Studio/Installer/vswhere.exe'
MSBUILD=$("$vswhere" -latest -requires Microsoft.Component.MSBuild -find 'MSBuild\**\Bin\MSBuild.exe' | head -1)
# The vcxproj names PlatformToolset=v145; windows-2022 only carries the VS 2022
# v143 build tools, so force v143 (MSB8020 otherwise: "build tools for v145 ...
# cannot be found"). CMake's own "Visual Studio 17 2022" generator resolves to
# the same v143 toolset on this runner, so the two stay compiler-matched.
"$MSBUILD" submodules/cas-bacnet-stack/projects/msvs/CASBACnetStack/CASBACnetStack.vcxproj -p:Configuration=ReleaseLib -p:Platform=x64 -p:PlatformToolset=v143 -m -v:minimal
else
make -C submodules/cas-bacnet-stack/projects/linux/Release/x64 -j"$(nproc)" all
make -C submodules/cas-bacnet-stack/projects/linux/Release/x64 install
fi
ls -l "${{ matrix.lib }}"

- name: Configure (STATIC link mode)
run: cmake -B build -S . -DCMAKE_BUILD_TYPE=Release -DCAS_BACNET_STACK_LINK=STATIC
- name: Configure
run: cmake -B build -S . -DCMAKE_BUILD_TYPE=Release

# The README documents `cmake -B build -S .` with no link-mode flag, so assert the
# adapter's default really is SOURCE. If that default ever changes, the documented
# build silently starts needing a prebuilt library and fails for customers first.
- name: Confirm the link mode CMake actually used
shell: bash
run: |
mode=$(grep -E '^CAS_BACNET_STACK_LINK:' build/CMakeCache.txt | cut -d= -f2)
echo "CAS_BACNET_STACK_LINK=$mode"
[ "$mode" = "STATIC" ] || { echo "FAIL: expected STATIC, got '$mode'"; exit 1; }
[ "$mode" = "SOURCE" ] || { echo "FAIL: expected SOURCE, got '$mode'"; exit 1; }

- name: Build
run: cmake --build build --config Release
Expand All @@ -101,7 +71,7 @@ jobs:
shell: bash
run: |
start=$(date +%s%3N)
"${{ matrix.exe }}" --port 47821 > smoke.log 2>&1 &
"${{ matrix.exe }}" --port 47821 --port2 47822 > smoke.log 2>&1 &
PID=$!
ready_ms=""
for i in $(seq 1 100); do
Expand All @@ -128,7 +98,7 @@ jobs:
"example": "BACnetExampleBRTR",
"app_version": "$appver",
"os": "${{ matrix.os }}",
"link_mode": "STATIC",
"link_mode": "SOURCE",
"stack_commit": "${{ steps.stack.outputs.sha }}",
"stack_version": "$stackver",
"binary_bytes": $bytes,
Expand All @@ -147,7 +117,8 @@ jobs:
run: |
mkdir -p dist
cp "${{ matrix.exe }}" "${{ matrix.metrics }}" dist/
cp README.md LICENSE dist/ 2>/dev/null || true
cp README.md TUTORIAL.md LICENSE dist/ 2>/dev/null || true
cp docs/PICS.md dist/ 2>/dev/null || true

- name: Upload build artifact
uses: actions/upload-artifact@v4
Expand Down
66 changes: 44 additions & 22 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Guidance for AI coding agents working in this repository. See
<https://agents.md/> for the format. Human contributors should read
[README.md](README.md) first.
[README.md](README.md) first, then [TUTORIAL.md](TUTORIAL.md).

## What this project is

Expand All @@ -20,33 +20,47 @@ customer can learn from and copy-paste. Favour clarity over cleverness.
This repository is self-contained:

- `main.cpp` - the example device.
- `common/` - the shared helper, vendored in-repo (not referenced via a path
outside the repository).
- `common/` - the shared helper (UDP, callbacks, CLI, keyboard) vendored in.
- `README.md` - what this example is. Keep it short and about THIS example only.
- `TUTORIAL.md` - how to extend and review the example: adding objects, the
F-MULTIPORT/F-ROUTER patterns, who serves which property, and
troubleshooting. Long-form material that would bloat the README belongs
here.
- `docs/PICS.md` - the Protocol Implementation Conformance Statement. Its
objects-and-properties section is GENERATED from `docs/objects.json`; do not
hand-edit between the `OBJECTS-PROPERTIES` markers.
- `docs/objects.json` - the input to that generator. Update it in the same change
as any `main.cpp` change that adds an object or a `GetProperty*` branch. It
now includes the Device object as its first entry.
- `TODO.md` - the two filed stack gaps this example works around (DM-LM-B not
compiled into the customer build; inter-network forwarding not implemented
at this pin) and the third (`Routing_Table` read aborts). Read before
touching router behaviour.
- `submodules/cas-bacnet-stack/` - the **CAS BACnet Stack** as a git submodule
(private). After cloning, run `git submodule update --init --recursive`.
(private; compiled from source). After cloning, run
`git submodule update --init --recursive`.

The `PROFILE-TABLE` block in README.md is also generated, from the example-series
repository's `docs/profile-table.md`. Edit it there, not here.

## Build

This example links the CAS BACnet Stack as a prebuilt **STATIC** library - build
the library once from the pinned submodule commit, then configure and build:
Plain CMake, identical on every platform, in the adapter's default SOURCE mode
(the stack's sources are compiled into the executable - no prebuilt library, no
DLL, no per-platform pre-step):

```bash
git submodule update --init --recursive # once, if not cloned with --recursive
tools/build-stack-static.sh BACnetProfileExample-B-RTR-CPP # from the series root
cmake -B build -S . -DCAS_BACNET_STACK_LINK=STATIC
cmake -B build -S .
cmake --build build --config Release
```

The stack library build compiles the whole stack (~600 files) once and takes a
few minutes; the example itself then builds in seconds, and later incremental
rebuilds are fast. Use `-D CAS_STACK_DIR=...` only if your stack lives outside
the bundled submodule. The adapter also offers a SOURCE mode (compiles the
stack straight into the executable, no library build); this example builds and
ships STATIC only.

On Windows, if `msbuild`/CMake pick a toolset the stack's `.vcxproj` lacks,
force `v143` on both: `TOOLSET=v143 tools/build-stack-static.sh ...` and
`cmake -B build -S . -T v143 ...`.
The first build compiles the whole stack (~600 files) and takes a few minutes;
rebuilds after that are incremental and fast. Use `-D CAS_STACK_DIR=...` only if
your stack lives outside the bundled submodule. Do not reintroduce a link-mode
flag or a series-root build script into the documented build: a customer
downloads this repository on its own and must be able to build it with the two
commands above.

## Run

Expand Down Expand Up @@ -83,13 +97,19 @@ Interactive keys while running: `h` help, `q` quit, up/down nudge Analog Input 1
- **Do not claim cross-network forwarding works.** The pinned stack cannot
route between two ports of the same network type (stack issue #2037) - only
router *configuration* is verified. If a future stack pin fixes this,
update README's gap notice, `TODO.md`, and the "Verify" section together.
update README's gap notice, `TUTORIAL.md`, `docs/PICS.md` section 9,
`TODO.md`, and the README's "Verify" section together.
- Every `GetProperty*` callback ends with `uint32_t* errorCode`. Leave it alone
on a catch-all decline (the stack's decline-and-fabricate default answers
required properties this app does not serve); set it only where this device
knows the read is wrong (`State_Text` out of range is the one case here).
- Match the surrounding code style: `const`-correct parameters, check every stack
return value, keep `main.cpp` linear and well-commented.
- The `CHANGE ALL OF THIS BEFORE YOU SHIP` block in `main.cpp` carries a
per-constant comment on what to change it to (vendor ID, device name/
uniqueness, model, description, versions, device instance, and the two
network numbers). Keep those comments in sync with `docs/PICS.md` section 1
if the placeholder values change.
- **Never edit `common/` in this repo alone** - it is a vendored copy shared by
every example in the series, with its own version (`COMMON_VERSION`) and
changelog (`common/CHANGELOG.md`). To change it: edit it in
Expand All @@ -115,6 +135,9 @@ There are no unit tests; verification is behavioural:
6. **Do NOT assume cross-network forwarding works** - it currently does not
(see Conventions above); do not write a test that assumes it does without
first re-checking the stack gap is still open.
7. If you changed the objects or their properties, regenerate `docs/PICS.md`
(`python tools/gen-objects-properties.py BACnetProfileExample-B-RTR-CPP` from
the series root) and confirm no row comes out flagged with ⚠.

## Releasing

Expand All @@ -123,6 +146,5 @@ then tag `vX.Y.Z`. The GitHub Actions workflow builds and publishes the release.

## License

The example source code is dedicated to the public domain under
[CC0-1.0](LICENSE). The CAS BACnet Stack is a separate, commercially licensed
product and is not covered by that dedication.
See [LICENSE](LICENSE). The CAS BACnet Stack is a separate, commercially
licensed product and is not covered by it.
61 changes: 61 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,67 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Changed — documentation restructure, single-command build

- **README.md cut down to what this example is.** It now covers only the
B-RTR profile, the device this example creates, the BIBBs/services/objects
it supports, how to build and run it, and how to verify it. It links to the
releases page and to the new TUTORIAL.md/docs/PICS.md near the top. Removed:
the "part of the series" / "you can start here" framing, the generic
explanation of what a device profile is, the detailed F-MULTIPORT/F-ROUTER
narrative (moved to TUTORIAL.md), the "Before you ship" table (moved into
`main.cpp` comments), the "Get the code" and "Link mode" sections, the
Troubleshooting table (moved to TUTORIAL.md), and the "Objects and
properties" section (moved to `docs/PICS.md`).
- **New [TUTORIAL.md](TUTORIAL.md)** holds the long-form material the README
used to carry: extending the example (including the silent-failure warning
for adding a second object), the F-MULTIPORT and F-ROUTER walkthroughs
(including the router-forwarding limitation, carried over verbatim), what
each object type needs the application to serve, who serves what for a
representative commandable object, how to review a change against the
conformance statement, and troubleshooting.
- **New [docs/PICS.md](docs/PICS.md)**, a Protocol Implementation Conformance
Statement in the ANSI/ASHRAE 135 Annex A shape (product description, profile
claimed, BIBBs, services, segmentation, object types, data link layer,
address binding, networking options, character sets), with the generated
objects-and-properties tables as its penultimate section. Section 9
(networking options) spells out precisely what router configuration does
and does not do at this stack pin - it does not claim cross-network
forwarding works.
- **`docs/objects.json`'s Device entry now separates `stack` from `accepted`**,
matching the shape used across the series: device-wide facts the stack
computes (`Object_List`, `Protocol_Version`/`Revision`,
`Protocol_Services/Object_Types_Supported`, `Device_Address_Binding`) are
now labelled plain `stack` in the generated PICS instead of `stack default,
accepted`; the actual stack-configured defaults (APDU limits, segmentation,
system status, database revision) stay under `accepted`. Regenerated with
`tools/gen-objects-properties.py` - zero ⚠ rows.
- **Build is `cmake -B build -S .` and nothing else, on every platform.** The
documented build no longer calls `tools/build-stack-static.sh`, which lives
in the example-series repository and is therefore not available to a
customer who downloads this repository on its own, and no longer links a
prebuilt STATIC library. The example now builds in the adapter's default
SOURCE mode: the stack's sources are compiled into the executable, so there
is no library to build first. `CMakeLists.txt`, `AGENTS.md` and the release
workflow were updated to match; the workflow no longer builds/caches the
STATIC library or carries matrix `lib:` entries, configures without a
link-mode flag, asserts `CAS_BACNET_STACK_LINK=SOURCE` (was `STATIC`), sets
`"link_mode": "SOURCE"` in the published metrics JSON, and packages
`TUTORIAL.md`/`docs/PICS.md` alongside the binary.
- **The "Before you ship" guidance moved into `main.cpp`**, as a comment next
to each constant in the `CHANGE ALL OF THIS BEFORE YOU SHIP` block,
including the warning that `DEVICE_NAME` is a compile-time constant here and
must be made per-unit configurable (serial number, DIP switches, config
file, or a `--deviceName` argument) in a real product, and that the two
network numbers must be unique across the whole internetwork like the
device instance.
- **Footprint table now documents the SOURCE-mode build.** The v1.0.0 numbers
were measured from a STATIC-linked build; the table now notes that the next
release refreshes them under the documented SOURCE build, matching the
series convention.

## [1.0.0] - unreleased

### Added
Expand Down
15 changes: 7 additions & 8 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,15 @@
#
# This example links the CAS BACnet Stack through the CASBACnetStack::Adapter CMake
# target (submodules/cas-bacnet-stack/adapters/cpp) and calls the stack's C API
# (BACnetStack_*, via CASBACnetStackAdapter.h) directly. This example builds and
# ships in STATIC mode: CAS_BACNET_STACK_LINK=STATIC links a prebuilt
# CASBACnetStack_x64_Release.lib / libCASBACnetStack_x64_Release.a, built first by
# tools/build-stack-static.sh from the stack's own project files. (The adapter also
# offers a SOURCE mode that compiles the stack's source/ straight into the
# executable with no separate library build; this example is not built that way.)
# (BACnetStack_*, via CASBACnetStackAdapter.h) directly. It builds and ships in the
# adapter's default SOURCE mode: the stack's source/*.cpp are compiled straight into
# the executable, so there is no library or DLL to build, ship, or install first and
# the build is the same two commands on every platform. (The adapter also offers
# STATIC and DLL modes - see its CMakeLists.txt - but they need a prebuilt library
# and this example does not use them.)
#
# Build:
# tools/build-stack-static.sh BACnetProfileExample-B-RTR-CPP (from the series root)
# cmake -B build -S . -DCAS_BACNET_STACK_LINK=STATIC
# cmake -B build -S .
# cmake --build build --config Release --parallel
# Run (from the example folder):
# ./build/BACnetExampleBRTR (Linux/macOS)
Expand Down
Loading
Loading