Skip to main content

Arrow-native HVAC fault detection runtime — lint, test, and run apply_faults_arrow rules on columnar telemetry (PyPI). Use GHCR Docker images for the full edge operator stack.

Project description

Open-FDD

Discord CI MIT Beta Python 3.10+

Open-FDD logo

Open-source HVAC systems supervisory fault detection for buildings — local-first, on-prem, vendor-neutral, and free to run at the edge.

Arrow-native rules, optional DataFusion SQL rules for Rust-ready migration, optional PyPI embeddable runtime, and a full Docker/GHCR edge operator stack: BACnet, bridge API, dashboard, and MCP.

Includes an open, free knowledge layer for AI-assisted building diagnostics, commissioning support, and HVAC fault investigation.

Documentation PDF documentation


Install / run

Full Open-FDD edge stack (Docker / GitHub Container Registry)

Image Role
ghcr.io/bbartling/openfdd-bridge API, dashboard, historian
ghcr.io/bbartling/openfdd-commission BACnet discover, read, poll
ghcr.io/bbartling/openfdd-mcp-rag MCP + doc-search

Manual Installation

This process does not clone the GitHub repository. It only pulls the latest images from GHCR and uses a Bash script to set up the basic file structure on the Linux filesystem.

curl -fsSL -o /tmp/openfdd_edge_bootstrap.sh \
  https://github.com/bbartling/open-fdd/raw/refs/heads/master/scripts/openfdd_edge_bootstrap.sh
bash /tmp/openfdd_edge_bootstrap.sh --start

Update an Existing Site

This backs up site data, runs safe Docker maintenance, updates the containers, validates health, and removes the backup archive on success. See Edge site lifecycle.

cd ~/open-fdd
./scripts/openfdd_site_backup.sh
./scripts/openfdd_site_update.sh

AI Agent Prompt

Area Examples
Edge deploy openfdd_edge_bootstrap.sh, openfdd_site_backup.sh, openfdd_site_update.sh, health checks
Drivers BACnet discover/poll/bind, Niagara station setup, JSON API endpoints
Model & rules BRICK sites/equipment/points, Rule Lab save/bind, batch FDD, tuning brief/apply
Operations Building check-in, zone temps, device poll health, BACnet P8 override scans
Reports rcx_plan_report, rcx_generate_report, list/download saved DOCX
Safety No BACnet writes without approval; never docker compose down -v; never delete workspace/

Deeper route maps: AI agent context · Edge site lifecycle · AGENTS.md · MCP tools in workspace/mcp_server/server.py.

Copy-paste: OpenClaw — fresh Raspberry Pi edge bootstrap

Prompt tuned for OpenClaw on a new Pi. Edge deploy uses GHCR Docker (not a source clone): openfdd-bridge, openfdd-commission, openfdd-mcp-rag. Bootstrap script: scripts/openfdd_edge_bootstrap.sh.

You are OpenClaw running on a fresh Raspberry Pi intended to become an Open-FDD edge device.

Goal:
Install and bootstrap Open-FDD from https://github.com/bbartling/open-fdd on this fresh Raspberry Pi, start the Docker edge stack, then validate that:
1. Docker is installed and working.
2. Open-FDD edge stack is running.
3. Bridge health is good.
4. Auth is configured.
5. MCP sidecar / mcp-rag is online.
6. The install is safe for a local OT/LAN edge box and is not exposed publicly.

Important project facts:
- Open-FDD edge deploy should use the GHCR Docker stack, not a source-code dev install.
- The edge stack uses these images:
  - ghcr.io/bbartling/openfdd-bridge:latest
  - ghcr.io/bbartling/openfdd-commission:latest
  - ghcr.io/bbartling/openfdd-mcp-rag:latest
- The official bootstrap script is:
  https://github.com/bbartling/open-fdd/raw/refs/heads/master/scripts/openfdd_edge_bootstrap.sh
- Raspberry Pi / ARM64: GHCR images must include `linux/arm64`. If pull fails with `no matching manifest for linux/arm64/v8`, see docs/quick-start/raspberry-pi-edge.md — do not treat it as a Docker install failure.
- Platform check script (after bootstrap downloads scripts): `~/open-fdd/scripts/openfdd_check_ghcr_platform.sh`
- Default site root should be:
  ~/open-fdd
- The persistent workspace must not be deleted after creation:
  ~/open-fdd/workspace
- Never run `docker compose down -v`.
- Never delete `workspace/`.
- Do not print secrets into long logs, reports, Git commits, or chat output. You may say where the auth file is located.

Work plan:

Step 0 — Identify host
Run:
set -euo pipefail
whoami
hostname
uname -a
uname -m
cat /etc/os-release || true
ip -4 addr show scope global || true

If `uname -m` is not `aarch64` or `arm64`, warn that the Pi may be 32-bit and GHCR Docker images may not support it. Continue only if Docker image pull succeeds.

Step 1 — Install base packages
Run:
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg lsb-release jq git nano

Step 2 — Install Docker if missing
Check:
docker --version || true
docker compose version || true

If Docker or Docker Compose v2 is missing, install Docker using the official Docker apt repo for Debian/Raspberry Pi OS. Use the OS codename from `/etc/os-release`.

Use commands similar to:
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

. /etc/os-release
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian ${VERSION_CODENAME} stable" \
  | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Then enable Docker:
sudo systemctl enable --now docker
sudo usermod -aG docker "$USER" || true

If the current shell cannot access Docker without sudo yet, either use `newgrp docker` or use `sudo docker` only long enough to validate. Prefer to make the final setup work with normal `docker` commands for this user.

Validate:
docker --version
docker compose version
docker run --rm hello-world

Step 2b — GHCR architecture check (ARM64 / Pi only)
If `uname -m` is `aarch64` or `arm64`, verify GHCR publishes ARM64 before blaming Docker:
docker buildx imagetools inspect ghcr.io/bbartling/openfdd-bridge:latest | grep -E 'linux/arm64|linux/amd64' || true
docker buildx imagetools inspect ghcr.io/bbartling/openfdd-mcp-rag:latest | grep -E 'linux/arm64|linux/amd64' || true

If `linux/arm64` is missing and bootstrap fails with `no matching manifest for linux/arm64/v8`:
- Report clearly: published images are amd64-only for that tag; Pi Docker is fine.
- Offer: (1) wait for multi-arch GHCR publish, (2) QEMU amd64 emulation (slow, lab only), (3) local `./scripts/docker_build.sh` on the Pi.
- Full steps: https://bbartling.github.io/open-fdd/quick-start/raspberry-pi-edge/

Step 3 — Bootstrap Open-FDD edge
Download and run the official bootstrap:
curl -fsSL -o /tmp/openfdd_edge_bootstrap.sh \
  https://github.com/bbartling/open-fdd/raw/refs/heads/master/scripts/openfdd_edge_bootstrap.sh

chmod +x /tmp/openfdd_edge_bootstrap.sh
bash /tmp/openfdd_edge_bootstrap.sh --start

This should create:
~/open-fdd/docker-compose.yml
~/open-fdd/workspace/auth.env.local
~/open-fdd/workspace/data.env.local
~/open-fdd/workspace/bacnet/commissioning/commission.env
~/open-fdd/scripts/openfdd_site_backup.sh
~/open-fdd/scripts/openfdd_site_update.sh

Step 4 — Validate stack
Run:
cd ~/open-fdd
docker compose ps
docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}'

Expected services:
- bridge
- commission
- mcp-rag

All should be `Up` or restarting briefly during first boot.

Step 5 — Validate bridge public health
Run:
curl -sf http://127.0.0.1:8765/health | jq .

Expected:
- HTTP 200
- JSON has `ok: true`
- ideally `auth_required: true`

If health fails, inspect:
cd ~/open-fdd
docker compose logs --since 10m bridge
docker compose logs --since 10m commission
docker compose logs --since 10m mcp-rag

Step 6 — Validate auth file exists without leaking secrets
Run:
test -f ~/open-fdd/workspace/auth.env.local
chmod 600 ~/open-fdd/workspace/auth.env.local || true
grep -E '^OFDD_(OPERATOR_USER|INTEGRATOR_USER|AGENT_USER)=' ~/open-fdd/workspace/auth.env.local

Do not print passwords in your final report. State that dashboard login uses the `integrator` user and the password from:
~/open-fdd/workspace/auth.env.local

Step 7 — Login smoke test
Extract integrator credentials locally and test login:
cd ~/open-fdd
set -a
. ./workspace/auth.env.local
set +a

TOKEN="$(curl -sf -X POST http://127.0.0.1:8765/api/auth/login \
  -H 'Content-Type: application/json' \
  -d "{\"username\":\"${OFDD_INTEGRATOR_USER}\",\"password\":\"${OFDD_INTEGRATOR_PASSWORD}\"}" \
  | jq -r .token)"

test -n "$TOKEN"
test "$TOKEN" != "null"
echo "Login token acquired OK"

Then test authenticated stack health:
curl -sf http://127.0.0.1:8765/health/stack \
  -H "Authorization: Bearer $TOKEN" | jq .

Expected:
- API responds successfully.
- Stack health lists services.
- BACnet may be yellow if no points are commissioned yet; that is acceptable on a fresh Pi.
- Bridge should be green/healthy.

Step 8 — Validate MCP sidecar is online
The Docker edge compose may not publish port 8090 directly to the host. First inspect:
cd ~/open-fdd
docker compose ps mcp-rag
docker compose logs --since 10m mcp-rag

Try health from inside the MCP container:
docker compose exec -T mcp-rag sh -lc '
  command -v curl >/dev/null 2>&1 && curl -sf http://127.0.0.1:8090/health ||
  command -v wget >/dev/null 2>&1 && wget -qO- http://127.0.0.1:8090/health ||
  python - <<PY
import urllib.request
print(urllib.request.urlopen("http://127.0.0.1:8090/health", timeout=5).read().decode())
PY
'

Also test from the bridge container to the compose service name:
docker compose exec -T bridge sh -lc '
  command -v curl >/dev/null 2>&1 && curl -sf http://mcp-rag:8090/health ||
  command -v wget >/dev/null 2>&1 && wget -qO- http://mcp-rag:8090/health ||
  python - <<PY
import urllib.request
print(urllib.request.urlopen("http://mcp-rag:8090/health", timeout=5).read().decode())
PY
'

If `/health` is not available but the container is running, inspect logs for FastMCP startup and check the MCP endpoint:
docker compose logs --since 10m mcp-rag | tail -200

If MCP needs host-local access for OpenClaw, do not expose it publicly. Add a localhost-only port mapping to `~/open-fdd/docker-compose.yml` under the `mcp-rag` service:

ports:
  - "127.0.0.1:8090:8090"

Then recreate only mcp-rag:
cd ~/open-fdd
docker compose up -d --force-recreate mcp-rag
curl -sf http://127.0.0.1:8090/health || true

Do not bind MCP to `0.0.0.0` unless explicitly instructed.

Step 9 — Validate BACnet bind config, but do not start writing to controllers
Show safe config summary:
grep '^BACNET_BIND=' ~/open-fdd/workspace/bacnet/commissioning/commission.env || true
ip -4 addr show scope global || true

If BACNET_BIND looks wrong, report it and suggest the correct LAN IP/prefix. Do not run BACnet writes. On a fresh Pi with no commissioned devices, it is OK if polling has no samples yet.

Step 10 — Optional reboot survival check
Only do this if allowed by the operator. Otherwise skip.
sudo reboot

After reconnect:
cd ~/open-fdd
docker compose ps
curl -sf http://127.0.0.1:8765/health | jq .

Final report format:
Return a concise report with:

1. Host summary:
   - hostname
   - architecture
   - OS
   - LAN IPs

2. Docker status:
   - Docker version
   - Compose version
   - hello-world pass/fail

3. Open-FDD files created:
   - ~/open-fdd/docker-compose.yml
   - ~/open-fdd/workspace/auth.env.local
   - ~/open-fdd/workspace/bacnet/commissioning/commission.env

4. Container status:
   - bridge
   - commission
   - mcp-rag

5. Health results:
   - GET /health
   - authenticated /health/stack
   - MCP /health or log-based MCP startup proof

6. Login:
   - say whether integrator login succeeded
   - do not print passwords or token

7. Next manual steps:
   - open dashboard locally or through a safe localhost/Caddy/Tailscale route
   - use integrator credentials from ~/open-fdd/workspace/auth.env.local
   - commission BACnet / Niagara / JSON API drivers via dashboard or API
   - validate BACNET_BIND before OT polling
   - never delete workspace/
   - update later with:
     cd ~/open-fdd && ./scripts/openfdd_site_backup.sh && ./scripts/openfdd_site_update.sh

Stop immediately and report clearly if:
- Docker cannot install.
- GHCR images do not support this Pi architecture.
- docker compose pull fails.
- Bridge /health never becomes healthy.
- MCP container exits repeatedly.
Copy-paste: OpenClaw — ongoing edge operator (drivers + FDD)

Use after bootstrap when the stack is healthy. Agent connects via MCP (8090) or bridge JWT (8765).

You are OpenClaw maintaining an Open-FDD edge site at ~/open-fdd.

Goal:
Help the human operator keep drivers, model, and FDD healthy — without BACnet writes unless explicitly approved.

You can:
- Check health: GET /health, GET /health/stack (JWT), MCP health_check
- Commission drivers: BACnet discover/poll config, Niagara stations, JSON API endpoints
- Maintain BRICK model: sites, equipment, points (preserve point IDs on import)
- Rule Lab: search cookbook, draft/save/bind PyArrow rules, run POST /api/rules/batch
- Operations: building.checkin, zone temps, device poll health, override scan status
- Reports: rcx_plan_report → rcx_generate_report (DOCX saved to workspace/reports/rcx)
- Updates: ./scripts/openfdd_site_backup.sh then ./scripts/openfdd_site_update.sh

Never:
- docker compose down -v
- delete workspace/
- print passwords, tokens, or auth.env.local contents in chat
- expose MCP or bridge to 0.0.0.0 on the public internet

Start each session with integrator JWT login, then building_agent_checkin or get_building_status.
Report faults, stale points, and recommended next steps in plain language.
Copy-paste: OpenClaw — backup, update & restore

Use for scheduled upgrades or disaster recovery on an existing ~/open-fdd site. Full reference: Edge site lifecycle.

You are OpenClaw upgrading an Open-FDD edge site at ~/open-fdd.

Goal:
Run a safe image upgrade with backup, Docker maintenance, validation, and backup purge — or restore workspace from backup if requested.

Standard upgrade (workspace bind-mount preserved):
  cd ~/open-fdd
  ./scripts/openfdd_site_backup.sh
  ./scripts/openfdd_site_update.sh

What openfdd_site_update.sh does:
1. Verify ~/openfdd-backups/latest/workspace-full.tgz integrity
2. Safe Docker maintenance (container/network/dangling image prune + unused image prune)
   - NEVER: docker volume prune, docker compose down -v
3. docker compose pull && up -d --force-recreate
4. Validate workspace/ layout + GET http://127.0.0.1:8765/health
5. On success: delete ~/openfdd-backups/latest (PURGE_BACKUP_AFTER_SUCCESS=1 default)

Restore workspace from backup (corruption / rollback):
  RESTORE_WORKSPACE=1 ./scripts/openfdd_site_update.sh

Historian cap on restore (default keeps newest ~200 GiB of feather shards):
  RESTORE_WORKSPACE=1 RESTORE_FEATHER_MAX_GIB=200 ./scripts/openfdd_site_update.sh

Restore ALL historian data (no cap):
  RESTORE_WORKSPACE=1 RESTORE_FEATHER_MAX_GIB=0 ./scripts/openfdd_site_update.sh

Useful env vars:
  NEW_TAG=latest                    GHCR tag (or OPENFDD_IMAGE_TAG)
  BACKUP_INCLUDE_POLL_SAMPLES=0     fast backup (skip poll CSV history)
  SKIP_DOCKER_MAINTENANCE=1         skip prune
  PURGE_BACKUP_AFTER_SUCCESS=0      keep backup after successful upgrade
  REQUIRE_BACKUP=0                  upgrade without prior backup (not recommended)

If validation fails:
- Backup is KEPT at ~/openfdd-backups/latest
- Inspect: docker compose logs --since 10m bridge
- Retry restore: RESTORE_WORKSPACE=1 ./scripts/openfdd_site_update.sh

Never:
- docker compose down -v
- delete workspace/
- print auth.env.local passwords in chat

Report: backup size, image tag, health pass/fail, whether backup was purged, feather cap if restore was used.

Python package (PyPI)

Use PyPI when you only need the embeddable Arrow-native FDD runtime — lint, test, and run rules in your own pipelines (cloud, IoT, notebooks) without Docker.

pip install open-fdd
import pyarrow as pa
import pyarrow.compute as pc

from open_fdd.arrow_runtime import run_arrow_rule


def high_sat(table, cfg, context=None):
    return pc.greater(table["SAT"], float(cfg["high"]))


table = pa.table({"SAT": [70.0, 90.0]})

result = run_arrow_rule(high_sat, table, {"high": 85})

print(result.true_count)  # 1

DataFusion SQL (same telemetry table, optional pip install 'open-fdd[datafusion]'):

from open_fdd.arrow_runtime import run_datafusion_sql_rule

SQL = """
SELECT
  *,
  "SAT" > 85.0 AS fault
FROM telemetry
"""

result = run_datafusion_sql_rule(SQL, table, {"min_true_rows": 5, "poll_interval_s": 60})

print(result.true_count)  # 1 — same confirmed count as PyArrow when cfg matches

Rule config fields such as min_elapsed_minutes and min_true_rows apply to both backends (fault confirmation / minimum duration). See fault confirmation.


Develop

git clone https://github.com/bbartling/open-fdd.git && cd open-fdd
python -m venv .venv && source .venv/bin/activate
pip install -e ".[test,dev,analytics]"
pytest open_fdd/tests -q

Contributor layout: AGENTS.md and developer docs.


License

MIT — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

open_fdd-3.1.6.tar.gz (74.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

open_fdd-3.1.6-py3-none-any.whl (76.5 kB view details)

Uploaded Python 3

File details

Details for the file open_fdd-3.1.6.tar.gz.

File metadata

  • Download URL: open_fdd-3.1.6.tar.gz
  • Upload date:
  • Size: 74.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for open_fdd-3.1.6.tar.gz
Algorithm Hash digest
SHA256 3a548fb16ce248ff6731c68347470d610b6a0ae676b6bb9c1abd68deafb12df2
MD5 4767bfd5ebb676822756c04a0a217b97
BLAKE2b-256 b540dec9cf6b9c2b02eae226368d69d36f7481fbe213ee18cd4cc12628a9f832

See more details on using hashes here.

Provenance

The following attestation bundles were made for open_fdd-3.1.6.tar.gz:

Publisher: publish-open-fdd.yml on bbartling/open-fdd

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file open_fdd-3.1.6-py3-none-any.whl.

File metadata

  • Download URL: open_fdd-3.1.6-py3-none-any.whl
  • Upload date:
  • Size: 76.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for open_fdd-3.1.6-py3-none-any.whl
Algorithm Hash digest
SHA256 ee8f272bbda677e0a77600cc6f4f4fe261b4f0d8b5feb299576e97c2bd5e8293
MD5 1d6a685835250078a5eb7a1f2531e2ea
BLAKE2b-256 cfe5b44c6cf904eb3eb64ea89d8164fff3c38b05ab147d9e347d1a6e7e9e574c

See more details on using hashes here.

Provenance

The following attestation bundles were made for open_fdd-3.1.6-py3-none-any.whl:

Publisher: publish-open-fdd.yml on bbartling/open-fdd

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page