An open-source IoT platform for smart buildings and facility management.
PlaceBrain turns a building, a warehouse, or a greenhouse into a first-class software object. Each place has its own team (owner / admin / viewer), its own fleet of IoT devices, its own sensors and actuators, and its own thresholds and alerts. A modern event-driven backend ties that together so you can see what's happening across all of your sites in real time — and act on it.
Facility management is stuck between two uncomfortable options: heavy commercial suites (locked-in pricing, vendor-specific hardware) or generic IoT platforms that require months of integration before they look like a product. PlaceBrain sits in the middle: a batteries-included, multi-tenant platform with a polished web UI, a clean HTTP API, realtime telemetry over MQTT, and a modern stack you can actually evolve.
- Multi-tenant from day one. A place is a first-class entity with its own member roles, not a retrofitted afterthought.
- Realtime by default. MQTT over WebSocket streams telemetry and alerts to the browser; Kafka event-sources the backend.
- Modern, boring stack. Python 3.14, Vue 3, FastAPI, gRPC, Kafka, TimescaleDB, EMQX, Traefik. Nothing exotic, nothing deprecated.
- Event-driven cascade. Deleting a place doesn't fan out through orchestrated gRPC calls — it emits an event and the platform reacts. Easier to reason about, easier to extend.
flowchart LR
browser([Web app<br/>Vue 3 SPA])
device([IoT device])
subgraph edge[Edge]
traefik[Traefik<br/>HTTP + TLS + MQTT TCP]
emqx[EMQX<br/>MQTT broker]
end
subgraph backend[Backend services]
gateway[gateway<br/>FastAPI]
auth[auth<br/>gRPC :50051]
places[places<br/>gRPC :50052]
devices[devices<br/>gRPC :50053]
collector[collector<br/>gRPC :50054]
end
subgraph data[Data plane]
pg[(PostgreSQL<br/>auth_db / places_db / devices_db)]
ts[(TimescaleDB<br/>telemetry_db)]
redis[(Redis<br/>role cache + MQTT creds)]
kafka[[Kafka<br/>KRaft, per-event topics]]
end
browser -->|HTTPS /api| traefik --> gateway
browser -->|WSS /mqtt| traefik --> emqx
device -->|MQTT :1883| traefik --> emqx
gateway -->|gRPC| auth
gateway -->|gRPC| places
gateway -->|gRPC| devices
gateway -->|gRPC| collector
auth --- pg
places --- pg
devices --- pg
devices --- redis
collector --- ts
collector --- redis
emqx -- authn/acl webhook --> gateway
emqx == telemetry.readings / telemetry.status ==> kafka
places ==> kafka
devices ==> kafka
kafka ==> devices
kafka ==> collector
collector -- alerts --> emqx
devices -- commands --> emqx
Inter-service contracts live in contracts — .proto files plus Pydantic event models, published to PyPI as placebrain-contracts. No service hardcodes a topic name or a role integer.
| Repo | Role |
|---|---|
| infra | Docker Compose + configs + Makefile. Start here. |
| contracts | gRPC protos + Kafka/MQTT event schemas, published as placebrain-contracts |
| workflows | Reusable GitHub Actions (Python lint + mypy, frontend lint + type-check) |
| auth | JWT, OTP email verification, users (gRPC :50051) |
| places | Places, members, roles; Kafka producer (gRPC :50052) |
| devices | Devices, sensors, actuators, thresholds, MQTT auth (gRPC :50053) |
| collector | Telemetry ingest, threshold evaluation, alerts (gRPC :50054, TimescaleDB) |
| gateway | HTTP/REST facade in front of the gRPC services (FastAPI :8000) |
| frontend | Vue 3 SPA, TanStack Query, MQTT.js realtime, Tailwind v4 |
mkdir placebrain && cd placebrain
git clone https://github.com/PlaceBrain/infra.git
# clone the rest of the repos alongside — see infra/README.md for the full script
cd infra && cp .env.example .env && make devAfter about a minute the whole stack is at http://localhost.
- Event-driven core. Every mutation that matters to more than one service becomes a Kafka event. Subscribers consume typed Pydantic models directly — there is no manual
EVENT_MAPorisinstancedispatch. Cascading deletes use publisher chains (@router.subscriber+@router.publisheron the same handler). - Clean Architecture in every Python service. Handler → Service → Repository → DB. Dishka DI with two scopes (
APPfor singletons,REQUESTfor per-call UoW). Typed exceptions mapped togrpc.StatusCodein the handler layer, never string-matched. - TimescaleDB for telemetry. The collector writes to a hypertable with
COPYin batches; reads usetime_bucket_gapfillfor charting, raw access for fine-grained inspection. Retention and compression policies are first-class. - MQTT done properly. Devices authenticate with a persistent bcrypt-hashed token; users get short-lived Redis-backed credentials invalidated on membership changes. Authorization webhooks flow through the gateway so there is one place to reason about security.
- Frontend built to scale. Feature-Sliced Design layout (app / pages / widgets / features / entities / shared), TanStack Query for server state, strict TypeScript, a small in-house UI kit with accessibility baked in.
PlaceBrain today is the boring, solid foundation. The ambitious part is next:
- User-authored automations. Let each place express rules like "if greenhouse humidity drops below 50% between 08:00 and 20:00, turn on mister A for 3 minutes". Both time-triggered and condition-triggered, defined via the UI.
- Scenarios. Named, shareable recipes on top of automations: "weekend mode", "energy-save mode", "site-empty alerting".
- Organizations on top of places. Multiple places under one billing/identity umbrella, with cross-place roles and dashboards.
- Commercial applications that we think are a natural fit: office HVAC and occupancy, warehouse temperature/humidity compliance, greenhouse automation, energy monitoring for small-to-mid-size portfolios.
These are deliberately framed as directions, not commitments.
Apache License 2.0 — see LICENSE.