Reference¶
The complete surface of bussard: every command and flag, the environment variables, the model directory and its YAML fields, the validation diagnostics, the JSON contracts, and the MCP tools. Verified against the code; bussard <command> --help is always authoritative for the build you run.
Conventions¶
- Addresses: group addresses (GAs) are 3-level strings like
3/2/0; individual addresses (IAs) arearea.line.devicelike1.1.4; DPTs are"1.001". --dir <DIR>(defaultknx): the model directory. Every command that touches the model takes it.--gateway <HOST>: override the gatewayhost[:port]for tunneling (port defaults to 3671).--routing: force KNXnet/IP routing (multicast) instead of tunneling.- Filters (
monitor --filter,capture --filter): a comma-separated list of GAs (3/2/0), GA prefixes (3/or3/2/), or IAs (1.1.30). - Confirmation: commands that write to devices confirm on a terminal (
y/N), naming the resolved gateway (host:port). Without a TTY they refuse unless--yesis passed (--yes-downloadforimport-product). This coverswrite,flash,apply,assignandadopt. - Real-gateway safety: a write command whose resolved gateway is not loopback (not
127.0.0.0/8or::1) refuses to run unless you opt in with--allow-remote-gatewayorBUSSARD_ALLOW_REAL_GATEWAY=1. Loopback gateways (the local simulator, the test suite) are always allowed. Reads (monitor,read,scan,plan,reconstruct) are never gated. The two long-running servers pass the same gate once, at startup, when they are able to transmit:mcp --allow-writes,viz --allow-writes, andviz --watch-prog. Without those flags neither server can write, so neither is gated. SAFETY.md is the read-before-your-first-write guide to all of this.
Global flags¶
These apply to every subcommand:
-v/--verbose(repeatable): raise log verbosity.-v=info,-vv=debug,-vvv=trace. The default (no flag) iswarn. An explicitRUST_LOGoverrides this entirely, soRUST_LOG=bussard_transport=tracestill works for targeted tracing.--timing: print the invocation's wall-clock time to stderr on exit (e.g.took 1.23s). Off by default.
Exit codes¶
0 on success, non-zero on any error. Meaningful cases:
validateexits non-zero when there are errors (warnings and infos alone exit 0).readexits non-zero when no response arrives within the timeout, so scripts can detect a dead group object.planandapplyexit 0 when there is nothing to do.- Refusals (protected GA without
--force, non-TTY without--yes, unsupported device mask, unsupported load procedure) exit non-zero before anything is written.
Commands¶
bussard init¶
Create a fresh model directory: discover the gateway, write the skeleton.
| Flag | Default | Meaning |
|---|---|---|
--dir <DIR> |
knx |
The directory to create the model in. |
--gateway <HOST> |
discover | Use this gateway host[:port] instead of discovering one. |
--routing |
off | Configure KNXnet/IP routing (multicast) instead of tunneling. |
Gateway discovery is multicast and does not cross subnets. If it finds nothing, init still writes a valid skeleton with a placeholder gateway.
bussard import [PROJECT]¶
Import an existing .knxproj (or xknxproject JSON dump) into the model. Re-import is idempotent: hand edits to names, DPTs, descriptions and protected: flags survive where the address is unchanged; device files whose address left the project are pruned.
| Flag / arg | Default | Meaning |
|---|---|---|
[PROJECT] |
The .knxproj file to import (omit when using --from-json). |
|
--from-json <FILE> |
Import from an xknxproject JSON dump instead of a .knxproj. |
|
--password <PASSWORD> |
Project password. Falls back to BUSSARD_PROJECT_PASSWORD, then an interactive prompt. |
|
--dir <DIR> |
knx |
The model directory to write. |
A re-import always takes the generated sections (com-object tables, link wiring, parameters) from the project and always preserves hand-authored fields, reporting every difference instead of overwriting it. There is no flag for that: it is the only behaviour the merge implements.
bussard scan [LINE]¶
Scan a line for devices: mask version, manufacturer, order number, and the delta against the model (known, unexpected, missing). Sweeps addresses sequentially, so a full line takes a while.
| Flag / arg | Default | Meaning |
|---|---|---|
[LINE] |
1.1 |
The line to scan. |
--from <N> |
0 |
The first device number to probe (0-255). |
--to <N> |
255 |
The last device number to probe (0-255). |
--dir <DIR> |
knx |
The model directory. |
--json |
off | Emit JSON instead of the table format. |
--gateway <HOST> |
Gateway override. | |
--routing |
off | Force routing transport. |
bussard assign [ADDRESS]¶
Assign an individual address to the device in programming mode. Refuses when more than one device is in programming mode. Confirms on a terminal (naming the gateway); a non-TTY needs --yes (an explicit address is not itself consent).
| Flag / arg | Default | Meaning |
|---|---|---|
[ADDRESS] |
next free on the line | The address to assign, e.g. 1.1.47. |
--yes |
off | Skip the confirmation prompt (required for a non-TTY assign). |
--dir <DIR> |
knx |
The model directory. |
--gateway <HOST> |
Gateway override. | |
--routing |
off | Force routing transport. |
--allow-remote-gateway |
off | Permit a write to a non-loopback gateway (or set BUSSARD_ALLOW_REAL_GATEWAY=1). |
bussard reconstruct [ADDRESS]¶
Read a device's tables back over the bus and diff them against the model, or (with --line) sweep a whole line and synthesize a fresh model. System B (mask 07B0) only; other masks are recorded as stubs in line mode and refused in single-device mode. The diff compares GA sets per object; send/listen direction is not recoverable from the address and association tables alone.
| Flag / arg | Default | Meaning |
|---|---|---|
[ADDRESS] |
The single device to read, e.g. 1.1.4 (mutually exclusive with --line). |
|
--line <LINE> |
Sweep a whole line, e.g. 1.1, and synthesize a fresh model (requires --out). |
|
--from <N> |
0 |
First device number to probe in line mode (0-255). |
--to <N> |
255 |
Last device number to probe in line mode (0-255). |
--out <DIR> |
Line mode only: the fresh model directory. Must be absent or empty; reconstruction never merges into an existing model. | |
--dir <DIR> |
knx |
The model directory (line mode: connection defaults only). |
--json |
off | Emit JSON instead of the report format. |
--gateway <HOST> |
Gateway override. | |
--routing |
off | Force routing transport. |
bussard describe <ADDRESS>¶
Introspect a device over the bus: discover its interface objects and, for each, enumerate every property's description (PID with a name when known, data type, element count, and read/write access levels) as a table. Read-only on the bus — it sends A_DeviceDescriptor_Read, A_PropertyValue_Read (object discovery) and A_PropertyDescription_Read, never a write. Unlike reconstruct it does not resolve group tables; it describes the device's raw property set, which is useful for commissioning and diagnostics on an unknown device.
| Flag / arg | Default | Meaning |
|---|---|---|
<ADDRESS> |
The device to introspect, e.g. 1.1.4. |
|
--dir <DIR> |
knx |
The model directory (connection defaults). |
--json |
off | Emit JSON instead of the table format. |
--gateway <HOST> |
Gateway override. | |
--routing |
off | Force routing transport. |
When the model marks the device secure-capable, the output carries a KNX Secure: line (and a secure object in --json, with secure_capable and the Data Secure state). That is inspection only: bussard cannot program a Secure device — see SAFETY.md.
bussard keyring <FILE>¶
Inspect an ETS KNX Secure keyring export (.knxkeys): print what it carries — the device individual addresses, the tunnel/management interface addresses, whether a backbone key is present, and how many group keys there are. No key material is ever printed, in text or JSON.
| Flag / arg | Default | Meaning |
|---|---|---|
<FILE> |
The .knxkeys file to inspect. |
|
--json |
off | Emit JSON instead of the text summary. |
The keyring password comes from BUSSARD_KEYRING_PASSWORD and is deliberately not a flag, so it never lands in shell history or a process listing. Reading a keyring does not enable Secure writes; see SAFETY.md.
bussard import-product [FILE]¶
Import vendor product data (.knxprod): cache it under <dir>/vendor/ and generate one model file per application program under <dir>/models/. Three modes: a local file (positional), --order-number to look the file up in the pointer index and download it, or --list to show the index. Details in product-data.md.
| Flag / arg | Default | Meaning |
|---|---|---|
[FILE] |
The .knxprod file to import (positional mode). |
|
--dir <DIR> |
knx |
The model directory. |
--order-number <ORDER> |
Look the .knxprod up in the pointer index by order number and download it from the vendor (with confirmation). |
|
--yes-download |
off | Skip the download confirmation prompt. Only meaningful with --order-number; required on a non-TTY. |
--inner <NAME> |
Import one named inner archive from a multi-product .knxprod instead of every one it contains. |
|
--list |
List the product-data pointer index and exit. |
Downloads are verified against the index by byte size and SHA-256; a mismatch is a hard error. Order-number matching is case- and whitespace-insensitive but keeps interior separators (AKK-0216.03 matches akk-0216.03, not AKK021603).
bussard adopt¶
Guide a new device from programming mode into the model: product data, address assignment with order-number cross-check, a rich device file, and ready-to-paste groups.yaml / links.yaml snippets. Interactive; needs a terminal (or BUSSARD_ADOPT_ADDRESS plus --product for scripted runs).
| Flag | Default | Meaning |
|---|---|---|
--product <FILE> |
cached model | The vendor .knxprod for the new device. |
--yes |
off | Skip the confirmation prompt (required for a non-TTY, scripted adopt). |
--dir <DIR> |
knx |
The model directory. |
--gateway <HOST> |
Gateway override. | |
--routing |
off | Force routing transport. |
--allow-remote-gateway |
off | Permit a write to a non-loopback gateway (or set BUSSARD_ALLOW_REAL_GATEWAY=1). |
bussard flash --product <FILE> <ADDRESS>¶
Flash an application program from vendor product data into a device (the ETS-free application download). Supports System B (07B0 and the 57B0/27B0 variants) and System 7 (0705/0701/0700). Pre-flight plan first; refuses before any write on an unsupported mask family, a mask mismatch, or an unsupported load-procedure operation. The parameter memory image is computed from the vendor defaults plus the device file's parameters: overrides, so a flash also carries parameter changes. No backup exists for a flash; recovery is re-running flash.
The same pre-flight also checks the device is factory-fresh (issue #79), read-only, before anything is written: it reads each object's load state and, on System B, the resident application id (PID_PROGRAM_VERSION). A device carrying a different application, one it cannot identify, or a load state it cannot read at all is refused; --force overrides. Re-flashing the same application is allowed without --force (it is the documented recovery path after an interrupted flash) and prints a notice, because it still resets the parameters to the vendor defaults plus the model's overrides and rewrites the tables from the model's links. See the flash section in SAFETY.md for the full table.
| Flag / arg | Default | Meaning |
|---|---|---|
<ADDRESS> |
The device to program, e.g. 1.0.10. |
|
--product <FILE> |
required | The vendor .knxprod containing the application program. |
--application <REF> |
sole/matching application | The application program id. Mutually exclusive with --order-number. |
--order-number <ORDER> |
Select the application by hardware order number (e.g. AKK-0216.03), resolved through the product's hardware catalogue. Exactly one match is required. |
|
--dir <DIR> |
knx |
The model directory. |
--yes |
off | Skip the interactive confirmation (dangerous; for scripts). |
--force |
off | Flash a device that is not factory-fresh: it already carries a different (or unidentifiable) application, or its load state could not be read. Destructive: the resident application, its parameters and its links are overwritten with no backup. Not needed to re-flash the same application. |
--bcu-key <HEX> |
free access | The device's BCU access key, in hex (FFFFFFFF or 0x11223344), presented with A_Authorize on every management connect. Unset presents the free-access key (FFFFFFFF), correct for an unkeyed device; a keyed device needs its project key here or it denies access. |
--allow-remote-gateway |
off | Permit a flash to a non-loopback gateway (or set BUSSARD_ALLOW_REAL_GATEWAY=1). |
--gateway <HOST> |
Gateway override. | |
--routing |
off | Force routing transport. |
The confirmation names the resolved gateway (flash <app> to <target> via <host:port>?).
Supported load-procedure operations: Unload, Load, LoadCompleted, RelSegment, WriteRelMem, WriteMem, WriteProp, CompareProp, LoadImageProp, Restart on a single-LSM System B device. Procedures containing LdCtrlAbsSegment, LdCtrlTaskSegment, LdCtrlTaskCtrl1, or unrecognized ops (e.g. LdCtrlCompareRelMem) are refused whole, before any write. After writing, flash verifies the application reads back as Loaded and spot-checks written segments byte-for-byte.
Only flash takes --bcu-key; plan, apply and reconstruct always authorize with the free-access key, so a device with a BCU key set denies them.
bussard plan <ADDRESS>¶
Read a device's live tables and show what apply would change. Read-only on the bus. Refuses to compute an empty table set for a device with no links in the model (that would wipe it). System B (x7B0) and System 7 (0705 / 0701); on System 7 the tables are read straight out of the 0x4000 / 0x4201 memory regions, bounded by the region size.
| Flag / arg | Default | Meaning |
|---|---|---|
<ADDRESS> |
The device to plan for, e.g. 1.1.4. |
|
--dir <DIR> |
knx |
The model directory. |
--json |
off | Emit JSON instead of the report format. |
--gateway <HOST> |
Gateway override. | |
--routing |
off | Force routing transport. |
bussard apply <ADDRESS>¶
Apply the model's link tables to a device: plan, confirm, back up, write, verify. The pre-state tables are written to <dir>/captures/backups/<ia>-<timestamp>.json before any write; tables are rewritten wholesale, so re-running apply is idempotent. System B (x7B0) and System 7 (0705 / 0701). On System 7 only the two table load-state machines are driven (Unload, StartLoading, allocate, 12-octet writes with read-back verify, TaskSegment, LoadCompleted) — parameters are untouched and the device is not restarted, so a link change costs no downtime.
| Flag / arg | Default | Meaning |
|---|---|---|
<ADDRESS> |
The device to program, e.g. 1.1.4. |
|
--dir <DIR> |
knx |
The model directory. |
--yes |
off | Skip the interactive confirmation (dangerous; for scripts). |
--allow-remote-gateway |
off | Permit a write to a non-loopback gateway (or set BUSSARD_ALLOW_REAL_GATEWAY=1). |
--gateway <HOST> |
Gateway override. | |
--routing |
off | Force routing transport. |
bussard validate¶
Validate the YAML model and report diagnostics (see the diagnostics table). Exits non-zero on errors.
| Flag | Default | Meaning |
|---|---|---|
--dir <DIR> |
knx |
The model directory. |
--format <FORMAT> |
text |
text (rustc-style diagnostics) or json (a JSON array). |
bussard monitor¶
Live-monitor the bus, decoding telegrams against the model. Unknown GAs and DPTs degrade to raw hex, never a failure.
| Flag | Default | Meaning |
|---|---|---|
--dir <DIR> |
knx |
The model directory. |
--json |
off | Emit JSON Lines (see the telegram JSON contract) instead of the pretty text format. |
--filter <EXPR> |
all | Only show matching telegrams (GAs, GA prefixes, IAs). |
--gateway <HOST> |
Gateway override. | |
--routing |
off | Force routing transport. |
bussard capture --to <DB>¶
Capture telegrams to a SQLite database (see the capture database).
| Flag | Default | Meaning |
|---|---|---|
--to <DB> |
required | The database file to write (created if absent). |
--dir <DIR> |
knx |
The model directory. |
--filter <EXPR> |
all | Only capture matching telegrams. |
--gateway <HOST> |
Gateway override. | |
--routing |
off | Force routing transport. |
bussard read <GA>¶
Read a group value from the bus: send a GroupValueRead, print the typed response. Exits non-zero when no response arrives within 3 seconds.
| Flag / arg | Default | Meaning |
|---|---|---|
<GA> |
The group address to read, e.g. 3/2/0. |
|
--dir <DIR> |
knx |
The model directory. |
--gateway <HOST> |
Gateway override. | |
--routing |
off | Force routing transport. |
bussard write <GA> <VALUE>¶
Write a group value to the bus. The value is human-typed (on/off, up/down, a number, a percentage like 75%) and encoded against the GA's DPT from groups.yaml. Confirms on a terminal, naming the GA, value and resolved gateway; a non-TTY needs --yes.
| Flag / arg | Default | Meaning |
|---|---|---|
<GA> |
The group address to write, e.g. 3/0/4. |
|
<VALUE> |
The value to encode. | |
--dpt <DPT> |
the GA's DPT | The DPT to encode as. |
--force |
off | Write even if the GA is marked protected: true in the model. |
--yes |
off | Skip the confirmation prompt (required for a non-TTY write). |
--dir <DIR> |
knx |
The model directory. |
--gateway <HOST> |
Gateway override. | |
--routing |
off | Force routing transport. |
--allow-remote-gateway |
off | Permit a write to a non-loopback gateway (or set BUSSARD_ALLOW_REAL_GATEWAY=1). |
bussard ha-config¶
Generate the Home Assistant KNX integration YAML from the model. Derivation rules and the ha.yaml override file are documented in ha-config.md.
| Flag | Default | Meaning |
|---|---|---|
--dir <DIR> |
knx |
The model directory. |
--out <FILE> |
stdout | Output file. |
bussard mcp¶
Run the MCP server over stdio (see the MCP server).
| Flag | Default | Meaning |
|---|---|---|
--dir <DIR> |
knx |
The model directory (required for the server). |
--gateway <HOST> |
Gateway override. | |
--routing |
off | Force routing transport. |
--passive |
off | Never transmit on the bus; omits the knx_read_group tool. |
--allow-writes |
off | Register the knx_write_group tool. Mutually exclusive with --passive. |
--allow-remote-gateway |
off | Permit --allow-writes against a non-loopback (real) gateway. The same gate as bussard write; without it a write-enabled server pointed at a real gateway refuses to start. |
--capture-db <PATH> |
A bussard capture database to extend knx_recent_telegrams history beyond the in-memory ring. |
bussard viz¶
Serve the network-visualization website: an HTTP server that renders the model as a bus-spine diagram and streams live traffic (see the viz server).
| Flag | Default | Meaning |
|---|---|---|
--listen <ADDR> |
127.0.0.1:8080 |
The address to bind the HTTP server to. A non-loopback bind is allowed but warns: the port is unauthenticated. |
--dir <DIR> |
knx |
The model directory (required; a bad model is a hard error so the protected-GA gate never fails open). |
--gateway <HOST> |
Gateway override. On connect failure the server degrades to model-only mode. | |
--routing |
off | Force routing transport. |
--watch-prog |
off | Watch for devices in KNX programming mode and highlight them in the UI. Periodically broadcasts A_IndividualAddress_Read on the bus and surfaces the responders in /api/state's prog field and the prog SSE event. This is active bus traffic, so it is off by default and must be enabled explicitly; never point it at a real installation unattended. Takes effect only when a bus is connected. |
--allow-writes |
off | Arm POST /api/group-write. Off by default: a bare bussard viz is a viewer and the endpoint answers 403 writes_disabled. |
--allow-remote-gateway |
off | Permit --allow-writes or --watch-prog against a non-loopback (real) gateway. The same gate as bussard write; without it such a server refuses to start. |
--allow-host <HOST> |
Also answer requests whose Host header is this name (repeatable). Loopback names and bare IP literals are always accepted; any other name is refused, because that is how DNS rebinding reaches this port. |
Environment variables¶
| Variable | Meaning |
|---|---|
BUSSARD_PROJECT_PASSWORD |
Password for a protected .knxproj when --password is not given. Keep it in an untracked .env, never in the repo. |
BUSSARD_KEYRING_PASSWORD |
Password for a .knxkeys keyring read by bussard keyring. There is deliberately no flag for it, so it never lands in shell history or a process listing. |
BUSSARD_ALLOW_REAL_GATEWAY |
Set to 1 to permit a write command against a non-loopback (real) gateway, equivalent to --allow-remote-gateway. Loopback gateways never need it. |
RUST_LOG |
Log filter (e.g. debug, bussard_transport=trace). Overrides -v/--verbose when set. |
BUSSARD_ADOPT_ADDRESS |
The target address for adopt, for driving the wizard from a script or test (together with --product). |
BUSSARD_ASSIGN_WAIT_MS |
Test knob: shrinks the programming-mode wait budget of assign and adopt. Unset in normal use. |
BUSSARD_SCAN_DISCOVERY_MS |
Test knob: shrinks the per-address probe timeout of scan and reconstruct --line. Unset in normal use. |
BUSSARD_WIRE_TRACE |
Set to 1 to log every KNXnet/IP datagram as hex on stderr. The diagnostic of last resort when a gateway behaves unexpectedly; very noisy. |
BUSSARD_FLASH_L4_TIMEOUT_MS |
Flash knob: the per-exchange Layer 4 timeout. Raise it for a slow device or a lossy link. |
BUSSARD_FLASH_REBOOT_WAIT_MS |
Flash knob: how long to wait for a device to come back after a restart. |
BUSSARD_FLASH_RECONNECT_EXCHANGES |
Flash knob: how many exchanges to attempt while reconnecting after a restart. |
Test-harness variables (BUSSARD_VIRTUAL_DEVICE*, BUSSARD_TEST_MULTICAST, BUSSARD_PRODUCT_CORPUS) gate the integration test suites, never the CLI; they are documented in the tests-support/ READMEs.
The model directory¶
knx/
bussard.yaml # connection config
groups.yaml # the group-address plan
links.yaml # com-object → GA assignments
devices/ # one file per device, e.g. 1.1.4-jalousieaktor-wohnen.yaml
ha.yaml # optional ha-config overrides (see ha-config.md)
models/ # generated from .knxprod; git-ignored
vendor/ # cached .knxprod originals; git-ignored
captures/ # local captures and apply backups; git-ignored
bussard.yaml, groups.yaml, links.yaml and devices/ are the source of truth and belong in git. models/, vendor/ and captures/ are local-only; init and import-product plant the .gitignore entries. All YAML is parsed strictly: unknown fields and duplicate keys are errors. Emission is deterministic and sorted, so re-imports and hand edits produce minimal diffs. Every generated file carries a banner naming what generated it and what is hand-editable.
bussard.yaml¶
connection:
transport: tunnel # tunnel | routing
gateway: "192.0.2.10:3671" # host:port for tunneling (optional)
multicast: "224.0.23.12:3671" # addr:port for routing (optional; this is the default)
| Field | Type | Meaning |
|---|---|---|
connection.transport |
tunnel | routing |
The transport used to reach the bus. |
connection.gateway |
string, optional | Gateway host:port for tunneling. |
connection.multicast |
string, optional | Multicast addr:port for routing; defaults to 224.0.23.12:3671. |
groups.yaml¶
project: "Demo House" # optional metadata
imported_from: "demo-house.knxproj" # optional provenance
ranges:
"3": { name: "Central" } # a main group
"3/2": { name: "Alarms" } # a middle group
groups:
"3/0/4":
name: "Living Room Blind Move"
dpt: "1.008"
"3/2/0":
name: "Wind Alarm"
dpt: "1.005"
description: "weather station -> every blind channel"
protected: true
| Field | Type | Meaning |
|---|---|---|
project |
string, optional | Project name metadata. |
imported_from |
string, optional | Provenance (the source .knxproj). |
ranges |
map, optional | Named main/middle ranges, keyed by "3" (main) or "3/2" (middle); each has a name. |
groups.<ga>.name |
string | Display name. Hand-editable; survives re-import. |
groups.<ga>.dpt |
string, optional | Datapoint type, e.g. "1.008". Without it, monitor cannot decode the value (W011). |
groups.<ga>.description |
string, optional | Free text. |
groups.<ga>.protected |
bool, default false |
Safety-critical GA: the CLI refuses writes without --force, MCP refuses outright. Serialized only when true. |
links.yaml¶
links:
"1.1.4":
- object: 12
name: "A: Blind Up/Down" # informational; refreshed on import
listen: ["3/0/4"]
"1.1.30":
- object: 3
name: "Wind Alarm 1"
send: "3/2/0"
| Field | Type | Meaning |
|---|---|---|
links.<ia> |
list | The device's links, keyed by individual address. |
object |
number | The ETS com-object number, the stable handle. |
name |
string, optional | The informational com-object name. This file is its single home; it appears nowhere else. |
send |
GA, optional | The single sending GA (at most one, mirroring KNX association semantics). |
listen |
list of GAs | The listening GAs (any number). |
devices/*.yaml¶
address: is the device identity; the filename slug is cosmetic (but must start with the address, E002). Everything above the GENERATED marker is hand-editable; module_bases: and com_objects: below it are regenerated on re-import.
address: "1.1.4"
name: "Blind Actuator 4-fold"
location: { floor: "Ground Floor", room: "Utility Room" }
product:
manufacturer: "Northwind Controls"
order_number: "BA-4"
application_ref: "M-0004_A-20D6-25-D965"
mask: "07B0"
channels:
A: { name: "Blind 1 - Living Room" }
parameters:
"wind-alarm-1@MD-1_M-3_MI-1_P-3_R-45": "1"
# --- GENERATED: regenerated on re-import; hand edits here are lost. ---
module_bases:
MD-1_M-3_MI-1: 1797
com_objects:
12: { dpt: "1.008", flags: "CW", channel: "A" }
| Field | Type | Meaning |
|---|---|---|
address |
IA | The device identity. |
name |
string | Display name. |
description |
string, optional | Free text. |
location.floor, location.room |
strings, optional | Physical location. |
product.manufacturer |
string, optional | Manufacturer name. |
product.manufacturer_ref |
string, optional | Manufacturer reference id. |
product.order_number |
string, optional | Catalogue order number; the join key into product-data models. |
product.hardware_ref |
string, optional | Hardware reference id. |
product.application_ref |
string, optional | Application-program id; matches a models/*.yaml identity. |
product.mask |
string, optional | Mask version, e.g. "07B0"; decides property- vs memory-based links. |
channels.<key>.name |
string | Human channel label. The block is emitted only when real labels are known. |
parameters.<key> |
map string → string, optional | Parameter values that differ from the vendor default, keyed <name-slug>@<ref-id> (the ref id disambiguates repeated module-instance parameters). Imported from the ETS project and replaced wholesale on re-import; hand-editable between imports. Validated against the product model in models/ (E016/E017); flash writes them into parameter memory. |
module_bases.<ref> |
map string → number, generated | Per-module-instance memory base offsets from the ETS project. A module parameter's effective offset is its declared offset plus the instance base; flash needs this to place per-channel parameters. Do not edit. |
com_objects.<n>.dpt |
string, optional | Datapoint type. |
com_objects.<n>.size |
string, optional | Declared size like "1 bit"; serialized only when there is no DPT (otherwise the size follows from the DPT). |
com_objects.<n>.flags |
string | Compact CRWTUI flag string. W = accepts writes (a command input), T = transmits (a status output). |
com_objects.<n>.ref |
string, optional | Cross-reference id from the product data. |
com_objects.<n>.channel |
string, optional | Owning channel key. |
Com-object entries carry no name (it lives in links.yaml).
Generated model files (models/*.yaml)¶
One file per application program, generated by import-product and never hand-edited. The field-by-field description lives in product-data.md.
Validation diagnostics¶
validate (and the knx_validate MCP tool) run strict parsing, then these rule passes:
| Code | Rule | Severity |
|---|---|---|
| E001 | link references a GA not defined in groups.yaml |
error |
| E002 | device file name does not start with its address: |
error |
| E003 | link references a com object the device doesn't have | error |
| E004 | conflicting object/DPT sizes on one GA | error |
| W005 | same size but different DPT subtypes on one GA | warning |
| W006 | more than one sending object on a GA | warning |
| E007 | impossible flags (no C flag; send without T) |
error |
| W008 | listening object with neither W nor U | warning |
| I009 | orphaned com object (T or W, never linked) | info |
| I010 | GA defined but never linked | info |
| W011 | GA without a DPT (monitor can't decode it) | warning |
| E012 | reserved GA 0/0/0 defined in groups.yaml |
error |
| E013 | link for a device that has no device file | error |
| E014 | the same object linked more than once on one device | error |
| I015 | GA marked protected: true (listed so a reviewer sees the guarded set) |
info |
| E016 | parameter key malformed (no @) or not in the product model |
error |
| E017 | parameter value invalid (unparseable, out of range, not an enum member) | error |
| I017 | parameter value equals the vendor default (redundant) | info |
| I018 | device has parameters: but no product model in models/ to validate against |
info |
Telegram JSON contract¶
monitor --json emits one object per line; the MCP telegram tools mirror the same fields. Absent fields are null, so the schema is uniform.
| Field | Meaning |
|---|---|
ts_utc |
RFC3339 UTC timestamp (same name as the SQLite capture column). |
source |
Sender IA. |
source_name |
The sending device's name from the model. |
destination |
Destination GA. |
destination_name |
The GA's name from groups.yaml. |
dest_type |
Destination address type. |
apci |
The APCI (e.g. GroupValueWrite). |
payload |
Raw payload, hex. |
value |
The decoded, typed value. |
dpt |
The DPT used to decode. |
object_name |
The sending com-object's informational name from links.yaml. |
note |
Decode diagnostics, e.g. a size mismatch. |
The capture database¶
capture --to <DB> writes SQLite. The telegrams table stores the raw cEMI bytes plus a decoded snapshot, so telegrams can be re-decoded after model fixes:
CREATE TABLE telegrams (
id INTEGER PRIMARY KEY,
ts_utc TEXT NOT NULL, -- RFC3339 UTC
source TEXT NOT NULL, -- IA
destination TEXT NOT NULL, -- GA
apci TEXT NOT NULL,
raw_cemi BLOB NOT NULL,
decoded TEXT -- JSON snapshot at capture time
);
CREATE INDEX idx_telegrams_dest_ts ON telegrams (destination, ts_utc);
The MCP server¶
bussard mcp serves the Model Context Protocol over stdio. Three tiers:
| Tier | Flag | On the bus |
|---|---|---|
| Passive | --passive |
Never transmits. The bus-touching read tools (knx_read_group, knx_describe_device) are not registered. |
| Read (default) | none | May send GroupValueReads, rate-limited. |
| Write | --allow-writes |
Adds knx_write_group. |
Bus operations share one rate limiter (minimum 250 ms between operations, at most two in flight). A GA marked protected: true is hard-refused by knx_write_group with no MCP override; the LLM must ask a human, who can run bussard write ... --force from the CLI. Programming and download (plan, apply, flash) are CLI-only and not exposed over MCP.
Tools¶
| Tool | Parameters | Returns |
|---|---|---|
knx_project_summary |
none | Project name, device/GA/link counts, floors and rooms, GA main-range names, bus connection status, validation counts. Call first. |
knx_model_lookup |
query (substring), limit (default 50, max 500) |
Case-insensitive matches across GA names/addresses, device names/IAs, room names, com-object names, grouped by kind. |
knx_get_group |
ga |
The GA's definition, every link sending to or listening on it, and the last telegram seen on it. |
knx_get_device |
address |
One device's identity, product, location, channels, com-object table, and links. |
knx_recent_telegrams |
limit (default 50, max 1000), ga (GA or prefix), source (IA), since (RFC3339), all optional |
Recent decoded telegrams, oldest first. With --capture-db, windows that predate the in-memory ring are topped up from the capture database. |
knx_wait_for_telegram |
timeout_seconds (max 300), ga, source (optional) |
Blocks until a matching telegram arrives or the timeout elapses. A timeout is a normal result, not an error. Enables "press the button now" debugging. |
knx_validate |
none | Every diagnostic (code, severity, message, location) plus counts. |
knx_read_group |
ga |
Transmits a GroupValueRead and returns the decoded response. Omitted in --passive mode. |
knx_describe_device |
address |
Introspects a device: enumerates its interface objects and each property's description (PID, type, element count, access levels). Read-only on the bus. Omitted in --passive mode. |
knx_write_group |
ga, value (human-typed), dpt (optional override) |
A GroupValueWrite. Registered only with --allow-writes; refuses protected GAs outright, and refuses a dpt that contradicts the GA's DPT in the model (the override is for GAs the model does not type). |
The model is not frozen at startup: the server re-reads the model directory when its files change, so a protected: true or a corrected dpt: added to groups.yaml mid-session is in force on the next tool call. A model that fails to parse is not swapped in; the server keeps the last good one and warns on stderr.
The viz server¶
bussard viz serves a single self-contained website (no CDN, no build step, works offline) that turns the model into a live picture of the installation. Open the listen address in a browser.
The page shows:
- Topology. A bus-spine diagram grouped by floor and room: every device as a card with its name, address, and type; the group-address plan as a tree on the right. Selecting a device or GA draws its links and lists its senders and listeners.
- Live traffic. Telegrams pulse along the spine from sender to listeners and flash the affected cards and tree rows. A bottom log shows time, source, GA, decoded value, and DPT, with a filter grammar and pause.
- State. The last value seen on each GA, decoded against its DPT.
- Problems. Com objects with no link and GAs with no sender or no listener are surfaced for the P5 review.
- Test writes. Per-DPT widgets send a GroupValueWrite from the page. A
protectedGA is disabled until you arm a force checkbox. The confirmation is the echoed telegram on the live stream.
Structure is read-only: devices, groups, and links are edited in the YAML model. An edit does not need a restart — POST /api/reload (the ⟳ button next to the bus status) re-reads the directory and swaps the model in place; see reloading the model.
Writes are off by default. Without --allow-writes the send widgets are there but POST /api/group-write answers 403 writes_disabled, so a bare bussard viz cannot put anything on the bus. With writes armed, every send asks for an explicit confirmation naming the GA and the resolved gateway before it goes out, and a protected GA additionally needs the force checkbox.
API¶
The website is driven by a small JSON/SSE API on the same listen address.
| Endpoint | Returns |
|---|---|
GET / |
The website shell. GET /assets/{file} serves the embedded JS/CSS modules. |
GET /api/model |
The precomputed model projection (devices, groups, ranges, links, senders/listeners). Rebuilt in place by POST /api/reload. |
GET /api/state |
{bus: {state, transport, connected, gateway, loopback}, seq, values, prog}, where gateway is the resolved endpoint (192.0.2.10:3671, or multicast 224.0.23.12:3671; null in model-only mode) and loopback says whether it is a loopback address, where values is the last value, payload, DPT, and source per GA, and prog is an array of the individual addresses currently observed in programming mode (empty when none, or when --watch-prog is off). |
GET /api/traffic |
Server-Sent Events. event: bus (connection state, always sent first), event: telegram (id: is the seq, data: is the telegram JSON plus seq), event: model (the model was reloaded, data: is {model_version, stats}, so the page refetches /api/model), event: prog (the programming-mode set changed, data: is {devices: [ia, ...]}; only emitted with --watch-prog), event: gap (the subscriber fell behind and should re-snapshot). ?backlog=N (default 50) replays recent telegrams; a reconnect with Last-Event-ID resumes with no duplicates and no gaps. |
POST /api/group-write |
{address, value?, payload?, dpt?, force?}. Encodes and sends a GroupValueWrite; returns 200 with an echo of what was written. |
POST /api/reload |
Reloads the model from disk and swaps it in atomically. Returns 200 with {model_version, stats} on success, or 422 model_invalid (keeping the old model) when the model on disk is broken. |
POST /api/group-write takes exactly one of value or payload. value is a human string encoded through the same parse_value + encode stack as bussard write (dpt overrides the GA's modelled DPT). payload is raw bytes as hex (even length, upper- or lowercase, for example "0b64") sent verbatim, for exotic DPTs with no string grammar. When a DPT is known for a payload write (from the model or dpt), the decoded byte length is checked against the DPT's expected size: a sub-byte (packable) DPT takes exactly one byte with value <= 0x3f and is sent packed into the 6-bit APDU exactly as the value path would; a byte-sized DPT takes that many whole octets. When no DPT is known anywhere, the raw write is still allowed but sent unpacked as a full data octet, because a lone 1-byte payload <= 0x3f is ambiguous between the packed 1-bit form and a byte-sized value and the full octet is the form every device reads correctly.
The write policy matches bussard write: 403 writes_disabled for every write when the server was started without --allow-writes; 400 for a bad address, an un-encodable value, bad hex, both or neither of value/payload, or a payload whose length does not match a known DPT; 403 for a protected GA without force (which applies to raw writes identically); 422 when no DPT can be resolved for a value write (supply dpt); and 503 when the bus is unavailable. The success echo carries payload (the hex bytes sent) and sets value to null for raw writes. Errors are {"error": {"code", "message"}}.
Reloading the model¶
The knx/ YAML is meant to be edited by hand and by LLMs; POST /api/reload picks up those edits without restarting the server. It re-runs Model::load on the model directory and swaps the shared model plus its precomputed /api/model projection in one atomic move, bumping a model_version counter (the initial model is version 1). The reload button in the page header (next to the bus status dot) calls it and surfaces any error inline.
The load-failure semantics are the point: a broken model never replaces a good one. On success the endpoint returns 200 with {ok, model_version, stats} (stats is {devices, groups, links}) and emits a model event on the SSE stream carrying {model_version, stats}, so every connected page refetches /api/model and rebuilds its views. On a load error it returns 422 {"error": {"code": "model_invalid", "message": <the LoadError, naming the offending file>}} and keeps serving the previous model unchanged, with no swap and no SSE event. The protected-GA write gate and the live decoder both read the current model, so a reload's newly protected GAs are enforced and its renamed addresses resolve on the very next write and telegram.
Watching programming mode¶
With --watch-prog the server runs a background probe that puts a broadcast A_IndividualAddress_Read on the bus every few seconds and collects the responders for about a second, exactly as bussard assign does. Every device in KNX programming mode answers with its own individual address (the software equivalent of the red programming LED on real hardware). The responding set is reported in /api/state's prog array and, whenever it changes, on the SSE stream as a prog event carrying {devices: [ia, ...]}, so the UI can highlight a device the moment its programming button is pressed and drop the highlight when it is released.
This is opt-in because the probe generates active bus traffic: it must never run unnoticed against a real installation. It is off by default, takes effect only when the bus is connected, and reuses the server's single tunnel (it never opens a second connection). While the bus is disconnected the set is cleared and probing pauses; it resumes on reconnect.
Who may reach the server¶
The listen port is unauthenticated: anything that can open a TCP connection to it can read the whole model, and, with --allow-writes, write to the bus. Keep the default 127.0.0.1 bind unless you have a reason not to.
Two browser-specific holes are closed regardless of the bind address:
Hostallow-list. A request is served only when itsHostheader is a bare IP literal,localhost(or a*.localhostname), or a name passed with--allow-host. Anything else gets403 forbidden. Without this, an attacker page onevil.examplecan repoint its own DNS name at127.0.0.1and make the browser treathttp://evil.example:8080/api/...as same-origin with the attacker's page, giving it full read (and write) access. The attacker cannot forge theHostheader, and rebinding needs a name, which is why an IP literal is safe.Origincheck. Any state-changing request (POST) whoseOriginis not itself an allowed host is refused with403 forbidden. This is what stops a plain cross-site form post atPOST /api/reload, which takes no body and so is reachable without any preflight. A request with noOriginat all is allowed, because no browser omits it here; that iscurl, not an attack.
Degraded and offline modes¶
If the gateway cannot be reached at startup, the server runs in model-only mode: the topology, tree, inspector, and problems panels all work, but there is no live traffic and POST /api/group-write returns 503. If the gateway drops mid-session, the bus actor reconnects in the background: /api/state flips to reconnecting, the SSE stream emits a bus event, and writes fail with 503 until the tunnel recovers. No restart is needed.
The server takes one tunnel connection for the whole session. Whether a second tool (a concurrent bussard write, flash, or scan) can share the gateway depends on the gateway: knx-sim currently accepts more than one tunnel, but real gateways vary, and traffic on one tunnel is not necessarily mirrored to another. To drive test traffic that the page will always see, use POST /api/group-write on the viz server itself.