diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4fb81a1..498ac9d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -163,8 +163,26 @@ jobs: - uses: actions/download-artifact@v4 with: path: artifacts + # Every platform artifact carries the same docs (README.md, LICENSE, ...). + # Uploading two assets with one name makes the release action fail part-way + # and leaves a draft release behind (a real tagged release hit this). Flatten the + # artifacts into one folder with a single copy of each file name, and stop + # if two files share a name but differ. + - name: Collect release assets (one copy of each file name) + shell: bash + run: | + mkdir -p release + find artifacts -type f | sort | while read -r f; do + name=$(basename "$f") + if [ -e "release/$name" ]; then + cmp -s "$f" "release/$name" || { echo "FAIL: two different files are both named $name ($f)"; exit 1; } + else + cp "$f" "release/$name" + fi + done + ls -l release - uses: softprops/action-gh-release@v2 with: name: ${{ github.ref_name }} generate_release_notes: true - files: artifacts/**/* + files: release/* diff --git a/AGENTS.md b/AGENTS.md index a47461f..888a5bf 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -83,7 +83,7 @@ up/down nudge Analog Input 1. ## Conventions -- Device is named "Rainbow"; objects use the series' colour names; vendor id +- Device is named "Chipkin Example B-SS"; objects use the series' colour names; vendor id 389. - Implement **only** the services and objects the B-SS profile requires - but expose **every required property** of each object. diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f1880d..4d11aed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,40 @@ 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). +## [1.0.2] - unreleased + +### Changed + +- **Device renamed from the series' colour placeholder "Rainbow" to "Chipkin + Example B-SS"** so devices from different examples in the series are + distinguishable from each other on the same BACnet network - every example + previously announced the identical Object_Name "Rainbow", which made two + examples on one subnet indistinguishable by name. Sub-object names are + unchanged. `docs/colour-table.md` (series root) updated to match. + APP_VERSION bumped 1.0.1 -> 1.0.2. + +## [1.0.1] - unreleased + +### Fixed + +- **`Application_Software_Version` (12) and `Firmware_Revision` (44) were + hardcoded to `"1.0.0"` and never updated as the example's real version + advanced** - the same issue found and fixed in + [BACnetProfileExample-B-SCHUB-CPP](https://github.com/chipkin/BACnetProfileExample-B-SCHUB-CPP) + v1.1.13 via a real device read with CAS BACnet Explorer. + `Application_Software_Version` now returns `APP_VERSION` directly (one + source of truth, can't drift from `--version`'s own banner again). + `Firmware_Revision` is now built once at start-up, right after + `CASExampleHelper.PrintVersion()` makes the first successful native stack + call, from the CAS BACnet Stack's own + `BACnetStack_GetAPIMajorVersion()`/`GetAPIMinorVersion()`/ + `GetAPIPatchVersion()`/`GetAPIBuildVersion()` (the same 4 calls + `PrintVersion()` already uses for the start-up banner) - it names the + underlying platform, not this app. Verified with a real ReadProperty + (`bacpypes3`) against the running device: + `Application_Software_Version = "1.0.1"`, + `Firmware_Revision = "6.0.21.0"`. + ## [1.0.0] - unreleased ### Added diff --git a/Program.cs b/Program.cs index a8229d6..e5c1956 100644 --- a/Program.cs +++ b/Program.cs @@ -40,7 +40,7 @@ // object that every BACnet device must have. Each object is given a colour name // (a convention shared across this example series): // -// Device 389001 "Rainbow" (instance configurable with --deviceID) +// Device 389001 "Chipkin Example B-SS" (instance configurable with --deviceID) // Analog Input 1 "Bronze" (REAL, degrees Celsius; starts at 21.5) // Binary Input 1 "Emerald" (active / inactive) // Multi-State Input 1 "Hot Pink" (an enumerated state, 1..3) @@ -76,7 +76,7 @@ internal static unsafe class Program // 1. Example + device configuration // ------------------------------------------------------------------- private const string APP_NAME = "BACnet B-SS (Smart Sensor) Example - C#"; - private const string APP_VERSION = "1.0.0"; + private const string APP_VERSION = "1.0.2"; // The device instance. BACnet requires this to be configurable, so it // defaults to 389001 and can be overridden on the command line with @@ -105,11 +105,11 @@ internal static unsafe class Program // the whole BACnet internetwork, and here it is a COMPILE-TIME constant. // The device instance is runtime-configurable via --deviceID, so it is // easy to ship two units, configure their instances correctly, and still - // have BOTH announce Object_Name "Rainbow" - a spec violation, and a hard + // have BOTH announce Object_Name "Chipkin Example B-SS" - a spec violation, and a hard // BTL failure. In a real product Object_Name must be per-unit // configurable too: derive it from a serial number, DIP switches, a // config file, or add a --deviceName argument. - private const string DEVICE_NAME = "Rainbow"; + private const string DEVICE_NAME = "Chipkin Example B-SS"; // The Device object's Description. Change it to what YOUR device // actually is; this string describes this tutorial. @@ -121,13 +121,22 @@ internal static unsafe class Program // VENDOR_NAME - your company name; it must match VENDOR_IDENTIFIER above. // MODEL_NAME - your model designation. This is what a building operator // reads to identify your device in a discovery tool. - // FIRMWARE_REVISION / APPLICATION_SOFTWARE_VERSION - your real - // versions. Wire them to your build rather than - // hard-coding a number that will go stale. + // APPLICATION_SOFTWARE_VERSION - this example's own real version; + // sourced directly from APP_VERSION above, so it can't + // drift from what --version reports. + // g_firmwareRevision - the underlying CAS BACnet Stack's REAL + // version (it names the platform underneath this app, + // not the app itself). Built once at start-up, right + // after the native library is confirmed loaded - see + // Main(). Not a compile-time constant because it is + // read from the stack at runtime via the same + // BACnetStack_GetAPI*Version() calls + // CASExampleHelper.PrintVersion() uses for the + // start-up banner. private const string VENDOR_NAME = "Chipkin Automation Systems"; private const string MODEL_NAME = "CAS BACnet Stack Example - B-SS"; - private const string FIRMWARE_REVISION = "1.0.0"; - private const string APPLICATION_SOFTWARE_VERSION = "1.0.0"; + private const string APPLICATION_SOFTWARE_VERSION = APP_VERSION; + private static string g_firmwareRevision = "0.0.0.0"; // The sensor objects (all instance 1) and their colour names. private const uint ANALOG_INPUT_INSTANCE = 1; // "Bronze" @@ -489,7 +498,7 @@ private static bool GetPropertyCharacterString( } if (propertyIdentifier == CASBACnetStackAdapter.PROPERTY_IDENTIFIER_FIRMWAREREVISION) { - return ReturnCharacterString(FIRMWARE_REVISION, value, valueElementCount, maxElementCount, encodingType); + return ReturnCharacterString(g_firmwareRevision, value, valueElementCount, maxElementCount, encodingType); } if (propertyIdentifier == CASBACnetStackAdapter.PROPERTY_IDENTIFIER_APPLICATIONSOFTWAREVERSION) { @@ -552,6 +561,17 @@ private static int Main(string[] args) return 1; } + // The native library is confirmed loaded at this point (PrintVersion() + // above made the first successful stack call). Build the Device + // object's Firmware_Revision from the stack's own real version - it + // reports the underlying CAS BACnet Stack, not this app - using the + // same 4 getter calls PrintVersion() already used for the banner. + g_firmwareRevision = + CASBACnetStackAdapter.BACnetStack_GetAPIMajorVersion() + "." + + CASBACnetStackAdapter.BACnetStack_GetAPIMinorVersion() + "." + + CASBACnetStackAdapter.BACnetStack_GetAPIPatchVersion() + "." + + CASBACnetStackAdapter.BACnetStack_GetAPIBuildVersion(); + // --- Bind the BACnet/IP socket ------------------------------------- try { diff --git a/README.md b/README.md index 15f1328..eaaf43e 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ and who answers it. ## The device this example creates ``` -Device 389001 "Rainbow" (Vendor 389 - Chipkin Automation Systems) +Device 389001 "Chipkin Example B-SS" (Vendor 389 - Chipkin Automation Systems) │ ├── Analog Input 1 "Bronze" Present_Value 21.5 (REAL, degrees Celsius; read-only) ├── Binary Input 1 "Emerald" Present_Value inactive (read-only) @@ -82,7 +82,7 @@ which is the point of a profile example. | Object type | Instance | Name | |-------------|:--------:|------| -| Device | 389001 | Rainbow | +| Device | 389001 | Chipkin Example B-SS | | Analog Input | 1 | Bronze | | Binary Input | 1 | Emerald | | Multi-state Input | 1 | Hot Pink | @@ -223,7 +223,7 @@ Expected output: BACnet B-SS (Smart Sensor) Example - C# v1.0.0 CAS BACnet Stack v6.0.21.0 FYI: listening on :47808 (broadcast ) -FYI: Device 389001 ("Rainbow") ready. Vendor ID 389. Press 'h' for help. +FYI: Device 389001 ("Chipkin Example B-SS") ready. Vendor ID 389. Press 'h' for help. ``` The device listens on UDP **47808** (BACnet/IP) by default. Allow that port @@ -264,10 +264,10 @@ Use a BACnet client such as the instance **389001** (vendor **389**). It also broadcasts an I-Am at start-up. 2. **Browse the object model** - the device shows five objects: the Device - (`Rainbow`), the three sensors, and the Network Port (`Vermilion`). Reading + (`Chipkin Example B-SS`), the three sensors, and the Network Port (`Vermilion`). Reading the Device's `Object_List` returns all five. 3. **Read the Device** - ReadProperty `389001` -> `Object_Name` returns - `"Rainbow"`; `Protocol_Revision` returns `24`; `Description` returns the + `"Chipkin Example B-SS"`; `Protocol_Revision` returns `24`; `Description` returns the profile description string. 4. **Read a sensor** - ReadProperty Analog Input `1` -> `Present_Value` returns `21.5`; `Units` returns `degrees-Celsius`; `Out_Of_Service` returns diff --git a/docs/PICS.md b/docs/PICS.md index 7e66438..974841f 100644 --- a/docs/PICS.md +++ b/docs/PICS.md @@ -82,7 +82,7 @@ is writable. | Object type | Instance | Object_Name | Optional properties supported | |---|:---:|---|---| -| Device | 389001 | Rainbow | Description | +| Device | 389001 | Chipkin Example B-SS | Description | | Analog Input | 1 | Bronze | - | | Binary Input | 1 | Emerald | - | | Multi-State Input | 1 | Hot Pink | State_Text | @@ -119,7 +119,7 @@ handle data in all character sets. Every object this example creates, and every REQUIRED property of each (per ANSI/ASHRAE 135-2024 clause 12 and the stack's `docs/property-profile-reference.md`), plus the optional properties the example turns on. **Served by** says who answers a ReadProperty: the **stack** generates it, or the **app** serves it from a `GetProperty*` callback in `Program.cs`. A ⚠ row is a required property the app does not serve and the stack would fill with a default - that is a defect, not a feature. -### Device 389001 "Rainbow" - the device itself; the instance is configurable with --deviceID. The stack rows are device-wide facts only the stack knows - the protocol version and revision it implements, the services and object types it was configured with, the live object list and address-binding table. The accepted rows are the stack's configured defaults for APDU limits, segmentation, system status and database revision; an application that answered them from its own constants could contradict the stack, so this example does not +### Device 389001 "Chipkin Example B-SS" - the device itself; the instance is configurable with --deviceID. The stack rows are device-wide facts only the stack knows - the protocol version and revision it implements, the services and object types it was configured with, the live object list and address-binding table. The accepted rows are the stack's configured defaults for APDU limits, segmentation, system status and database revision; an application that answered them from its own constants could contradict the stack, so this example does not | Property | Datatype | Served by | Writable | |---|---|---|:---:| diff --git a/docs/objects.json b/docs/objects.json index 75259ab..784a33e 100644 --- a/docs/objects.json +++ b/docs/objects.json @@ -3,7 +3,7 @@ { "type": "Device", "instance": 389001, - "name": "Rainbow", + "name": "Chipkin Example B-SS", "app": [ "Object_Name", "Description",