Generate a PICS for your device
Build and integrate / Generate a PICS
A PICS (Protocol Implementation Conformance Statement, Annex A) states which object types, services and options a device supports. A Rust BACnetServer can draft one from its own configuration: the objects in its database, the services the full server runs and the details you give it. You complete the rest. This page describes v0.15.0.
The draft describes the configuration you built and nothing more. It is not a test result or a BTL listing. The sample PICS shows the generator’s output for one reference configuration, and the capability tables list what the stack implements as a whole.
Choose where to generate it
Section titled “Choose where to generate it”- Rust:
server.generate_pics(&PicsConfig)on a builtBACnetServerreads the server’s own view of its Device object and the full server’s services. Use it for a device you ship.bacnet_server::pics::generate_pics(&database, &ServerConfig, &PicsConfig)works on anObjectDatabasewithout a server: it lists the objects’ own property rows and takes the executed services from the Device object’s Protocol_Services_Supported. Both return aPics, whichgenerate_markdown()andgenerate_text()render. - Python: the bindings have no PICS generator in v0.15.0;
BACnetServerhas no method for it. Build the same objects in a small Rust program to draft one. - CLI and terminal UI: no
bacnetcommand generates a PICS.
Generate one in Rust
Section titled “Generate one in Rust”This program builds a server with a Device and two objects on 127.0.0.1, drafts its PICS, stops the server and prints the PICS as Markdown. It uses the crates from the server guide’s Cargo.toml; put it in src/main.rs and run cargo run. CI compiles this exact program against v0.15.0.
The product details go on the Device object, and the PICS reads them there along with the protocol version and revision, so PicsConfig holds only what the server can’t report about itself.
use bacnet_objects::DeviceIdentity;use bacnet_objects::analog::AnalogInputObject;use bacnet_objects::binary::BinaryOutputObject;use bacnet_objects::database::ObjectDatabase;use bacnet_objects::device::{DeviceConfig, DeviceObject};use bacnet_server::pics::{DataLinkSupport, DeviceProfile, PicsConfig};use bacnet_server::server::BACnetServer;use bacnet_types::enums::EngineeringUnits;use std::net::Ipv4Addr;
#[tokio::main]async fn main() -> Result<(), Box<dyn std::error::Error>> { // Your product's details, on the Device object; the PICS reads them there. let version = "2.1.0"; // Vendor 0 is a placeholder: use the identifier ASHRAE assigned you. let identity = DeviceIdentity::new(1001, 0)?.with_name("Example air handler"); let mut database = ObjectDatabase::new(); database.add(Box::new(DeviceObject::new(DeviceConfig { identity: identity.clone(), vendor_name: "Example Controls".into(), model_name: "AHU-100".into(), firmware_revision: version.into(), application_software_version: version.into(), ..DeviceConfig::default() })?))?; let celsius = EngineeringUnits::DEGREES_CELSIUS; database.add(Box::new(AnalogInputObject::new(1, "Supply air", celsius)?))?; database.add(Box::new(BinaryOutputObject::new(1, "Supply fan")?))?;
let mut server = BACnetServer::bip_builder() .device(identity) .interface(Ipv4Addr::LOCALHOST) .port(0) .broadcast_address(Ipv4Addr::LOCALHOST) .database(database) .build() .await?;
// What the server can't report about itself: the profile you claim, // and the data links and network roles you ship. let pics_config = PicsConfig { // Claim an Annex L profile only if the product meets it. device_profile: DeviceProfile::Custom("None claimed".into()), data_link_layers: vec![DataLinkSupport::BipV4], ..PicsConfig::default() }; let pics = server.generate_pics(&pics_config).await; server.stop().await?; println!("{}", pics.generate_markdown()); Ok(())}Generate the PICS from the configuration you ship: the same objects, the same identity and the same transport choices. The program binds a port the operating system picks and stops the server once the PICS is drafted; generate_pics also works after stop(). If the PICS needs different text from what the Device reports, edit the generated document.
What the generator fills in
Section titled “What the generator fills in”| Section | Where it comes from |
|---|---|
| Vendor name, model name, firmware revision, application software version | The Device object’s Vendor_Name, Model_Name, Firmware_Revision and Application_Software_Version |
| Vendor ID | The Device object’s Vendor_Identifier, as the server serves it |
| Protocol version and revision | The Device object’s Protocol_Version and Protocol_Revision |
| Supported object types | One section per object type in the database; a type you don’t add isn’t listed |
| Createable and Deleteable | Whether CreateObject and DeleteObject reach the type, from the type’s own declaration |
| Whole value set only by CreateObject | Properties a CreateObject initial value may set that WriteProperty can’t change later |
| Property rows | Each instance’s property metadata, combined across the instances of the type; the Device’s rows come from the server’s view of it |
| Supported services, Executor | The services the full server runs; TimeSynchronization and UTCTimeSynchronization only when the server keeps a clock |
| Supported services, Initiator | PicsGenerator::INITIATED_SERVICES, a fixed list of the requests the server can send in some configuration |
The generator reads the database at the moment you call it. Objects your application adds later, or creates through CreateObject, appear only in a PICS generated after them, and so does a Device value your application changes.
Details read from the Device
Section titled “Details read from the Device”Both generators read the product details and the protocol fields from the Device object the PICS describes, so the PICS and the Device can’t disagree:
- Product details. A database without a Device, or a value that isn’t a character string, leaves the row empty.
- Protocol version and revision. Only a database without a Device, or a Device whose value doesn’t fit, gets the stack’s own values instead:
bacnet_types::PROTOCOL_VERSIONandbacnet_types::PROTOCOL_REVISION, 1 and 22 for ASHRAE 135-2020, which the bundled Device object also reports. - Vendor ID. For
server.generate_picsthe Vendor_Identifier is normally the server’sDeviceIdentity, which the server serves as its Device’s Vendor_Identifier; if the identity’s Device isn’t in the database, the selected Device’s own value is used. The standalonegenerate_picsreads the selected Device’s own value. Either way, theServerConfigidentity’s Vendor ID is used only when the Device has no Vendor_Identifier, or one that doesn’t fit 16 bits, so the vendor block always describes one Device.
The PICS prints these values as plain text. Control characters and line or paragraph separators become spaces, and bidirectional and zero-width characters are dropped, as the CLI and terminal UI drop them. The Markdown also escapes | and the characters that start inline formatting. A Device string can’t add rows or sections to the document, or hide or reorder its own text.
What you fill in
Section titled “What you fill in”Everything in PicsConfig is your declaration; the generator prints it as given and checks none of it.
PicsConfig field |
Default | What to set |
|---|---|---|
device_profile |
B-ASC | The Annex L profile the product meets, or DeviceProfile::Custom to claim none, as the program does; the generator doesn’t check it against your services |
data_link_layers |
BACnet/IP | Each data link the shipped product runs; the generator doesn’t detect them |
network_layer |
Router, BBMD and foreign device all off | The roles the product fills |
character_sets |
UTF-8 | The character sets the product handles |
special_functionality |
None | Free text, printed as its own section |
Annex A asks for more than the generator prints. Add these from your product’s design:
- the date, product name and product description;
- anything else your product claims beyond what the generator lists, backed by your own testing;
- segmentation support and window sizes;
- the details of each data link, such as MS/TP options or BACnet/SC connection limits;
- device address binding, the routing configurations behind a router claim, and gateway options;
- proprietary properties, conditionally writable properties and any property range restrictions.
Read the output
Section titled “Read the output”The Markdown starts with the vendor information and the device profile, then has one section per object type, then the services, data links, network layer options and character sets.
- Access flags.
Ris readable,Wwritable andOoptional. A row combines every instance of its type: a flag means at least one instance has that access, andOappears only when every instance that has the property marks it optional. A row such as State_Text with a “resized through” note can’t be written directly, but writes of the property it names change it. - Initiator column. A Yes means the server can build that request, not that your configuration sends it: COV and event notifications need a subscription or a recipient, audit notifications need audit reporting, and ReadProperty, WriteProperty and Who-Is need a Command action or a Channel member that targets another device.
- Executor column. A Yes means the full server runs the service when a request arrives, subject to your server policy; see restrict who can change a device.
- Client requests aren’t listed. A
BACnetClientin the same application initiates its own services, which this PICS doesn’t record; the capability tables list them.
The sample PICS shows every section for a database that holds one object of each constructible type.
Next steps
Section titled “Next steps”Release v0.15.0 ·Build and integrate. Pre-1.0 APIs; partial conformance.Support & limitations