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
54 changes: 54 additions & 0 deletions docs/LEARNING_LOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,60 @@ This file should be updated by Codex after each meaningful change.
### What to learn next
```

## 2026-05-25 - BACnet Read-Only Adapter Foundation

### What changed

Added a read-only BACnet adapter foundation that loads enabled BACnet
connection profiles, polls configured object references through an injected
object reader, and emits normalized `process.measurement.recorded`
FactoryEvents.

### Why it matters

This completes the first foundation pass for the three protocol families in the
connection-management epic. BACnet data can now follow the same safe path as
OPC-UA and MQTT: configured source reads become FactoryEvents without adding
write operations, commandable-property changes, arbitrary object discovery, or
UI ingestion controls.

### How it works

`load_enabled_bacnet_profiles()` reads the local connection profile store and
filters to enabled BACnet profiles. `run_bacnet_read_only_adapter()` passes each
profile to a `BacnetObjectReader`, which reads only configured object
references for the configured device address, device instance, optional network
number, and polling interval. Each numeric object reading becomes a process
measurement event whose source metadata includes the profile ID, device/network
descriptor, object reference, and poll index. Unavailable devices, missing
objects, unconfigured objects, stale readings, and nonnumeric values raise clear
adapter errors.

### How to run it

The foundation is currently exercised from Python code and tests. It reads the
same local profile store used by the connection profile API and writes to the
same event store interface used by ingestion.

### How to test it

```bash
.venv/bin/python -m pytest services/ingestion/tests/test_bacnet_read_only_adapter.py
```

### Key files

- `services/ingestion/factory_ingestion/bacnet_read_only_adapter.py`
- `services/ingestion/tests/test_bacnet_read_only_adapter.py`
- `services/ingestion/README.md`

### What to learn next

Add a real BACnet/IP object reader only after connector runtime orchestration,
network approval, and object mapping rules are defined. Keep BACnet writes and
commandable-property changes out of this path unless a dedicated ADR approves
them.

## 2026-05-25 - MQTT Read-Only Adapter Foundation

### What changed
Expand Down
57 changes: 57 additions & 0 deletions services/ingestion/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,63 @@ configuration access, Sparkplug-style JSON payload mapping, broker-unavailable
errors, malformed payload errors, unmapped topic errors, and the absence of a
publish/writeback surface.

## BACnet Read-Only Adapter Foundation

The read-only BACnet adapter foundation reads enabled BACnet
`ProtocolConnectionProfile` records from the local connection profile store,
uses an injected object reader to poll configured object references, and emits
normalized `process.measurement.recorded` FactoryEvents into an event store.

This foundation does not add a BACnet/IP runtime, object discovery, write path,
commandable-property changes, or UI ingestion controls. Until a connector ADR
explicitly expands the behavior, the adapter is limited to:

- enabled profiles where `protocol = "bacnet"`;
- the configured device address in `endpoint`;
- configured `device_instance` and optional `network_number`;
- configured object references such as `analogInput:1.presentValue`;
- the configured polling interval in `acquisition.poll_interval_seconds`;
- read-only object polling through an injected reader.

Each object reading supplies the object reference, numeric present value, unit,
optional object name, quality, and optional read timestamp. The adapter maps
each reading into a process measurement event and captures source metadata:

- `source.system` is `bacnet:<connection-profile-id>`;
- `source.adapter` is `bacnet-read-only-adapter`;
- `source.source_event_id` includes the connection profile ID, network/device
descriptor, object reference, and poll index;
- `payload.tag_name` stores the configured BACnet object reference.

Stale readings can be rejected by passing `max_reading_age_seconds`; stale
flags from the object reader are rejected immediately. Unavailable devices,
missing objects, unconfigured objects, stale readings, and nonnumeric values
raise clear adapter errors.

### Docker Desktop BACnet demo limitations

Local BACnet/IP demos are UDP based and can behave differently under Docker
Desktop networking than they do on a real plant network. Broadcast discovery,
BBMD/foreign-device registration, host networking, routing between VLANs, and
UDP port exposure can all vary by host OS and Docker configuration. Treat any
Docker Desktop BACnet demo as a local smoke path only.

Production BACnet/IP expectations are separate: configured devices and objects
should be read through network-approved routes, with explicit device addressing,
object mapping, polling intervals, and operations approval. This foundation does
not claim production readiness and does not write BACnet properties.

Run the focused tests:

```bash
.venv/bin/python -m pytest services/ingestion/tests/test_bacnet_read_only_adapter.py
```

The test suite covers fake BACnet clients, device address/network handling,
object references, polling interval access, units/object mapping,
device-unavailable errors, missing object errors, stale reading errors, and the
absence of write or commandable-property surfaces.

## Accepted Event Storage

Accepted events are written to the local JSONL event store:
Expand Down
Loading
Loading