A browser-based home automation dashboard for wall-mounted iPads and touch-screen displays. Overlay interactive hotspots on a floorplan image to view and control Home Assistant entities in real time.
- Presentation mode — fullscreen kiosk display with live entity state updates via WebSocket
- Visual editor — drag, resize, and configure hotspots directly on the floorplan
- 16 built-in hotspot types — action buttons, text labels, state images, state icons, badges, scene triggers, blinds/covers, bin day reminders, weather cards, temperature gauges, wind roses, battery monitors, clocks, custom placeholders, and power/solar energy monitors
- Solar-aware energy breakdown — Power usage graphs split each box's usage into solar- vs. grid-sourced kWh for every hour/day/week/month, using the floorplan's Solar config to compute each hour's self-consumption ratio
- Rules engine — map entity states to colors, labels, and images with conditional logic
- Day/night image cycling — swap floorplan images automatically based on solar position
- Screensaver — inactivity detection with configurable timeout
- Revision history — undo/redo and full audit trail for all changes
- Asset manager — upload and manage floorplan images and hotspot icons
- Role-based access — admin and viewer roles with JWT authentication
- Extensible — register new hotspot types without touching core code
floorplan-ha/
├── apps/
│ ├── api/ # Fastify REST API + WebSocket proxy
│ └── web/ # React + Vite frontend
├── packages/
│ ├── ha-client/ # Home Assistant REST & WebSocket clients
│ └── shared/ # Shared TypeScript types, Zod schemas, rules engine
├── docker/ # Dockerfiles + Nginx config
└── docker-compose.yml
Stack:
| Layer | Technology |
|---|---|
| Frontend | React 18, Vite, TypeScript, Tailwind CSS, Zustand, TanStack Query |
| Backend | Node.js, Fastify 5, Prisma 6, Argon2, Jose (JWT) |
| Database | PostgreSQL 16 |
| HA integration | WebSocket (real-time) + REST API |
| Deployment | Docker Compose, Nginx |
Prerequisites: Docker and Docker Compose installed, a running Home Assistant instance with a long-lived access token.
# 1. Clone the repo
git clone https://github.com/YOUR_USERNAME/floorplan-ha.git
cd floorplan-ha
# 2. Configure environment
cp .env.example .envEdit .env and set at minimum:
HA_BASE_URL=http://homeassistant.local:8123
HA_TOKEN=your_long_lived_access_token
SESSION_SECRET=generate_with_openssl_rand_base64_32
ADMIN_EMAIL=admin@example.com
ADMIN_PASSWORD=your_admin_password# 3. Start everything
docker compose up
# Dashboard: http://localhost:5173
# API: http://localhost:3001Prerequisites: Node.js 20+, PostgreSQL 16 (or use the Docker Compose db service)
# Install dependencies
npm install
# Start just the database
docker compose up db -d
# Run database migrations and seed the admin user
npm run db:migrate -w apps/api
# Start both dev servers (API on :3001, web on :5173)
npm run devOther useful commands:
npm run build # Build all packages and apps
npm run test # Run all test suites
npm run typecheck # Full TypeScript type check
npm run lint # ESLint across the monorepo
npm run format # Prettier formatting| Variable | Required | Default | Description |
|---|---|---|---|
HA_BASE_URL |
Yes | — | Home Assistant URL (no trailing slash) |
HA_TOKEN |
Yes | — | Long-lived access token (never exposed to frontend) |
MA_BASE_URL |
No | — | Music Assistant server URL (no trailing slash). Enables the full scrolling play queue (up to 50 tracks) and library Browse; without it, Queue falls back to current + next track only, and Browse is unavailable |
MA_TOKEN |
No | — | Long-lived access token from Music Assistant's own Settings (never exposed to frontend) |
SESSION_SECRET |
Yes | — | JWT signing secret (≥32 chars) |
DATABASE_URL |
Yes | — | PostgreSQL connection string |
ADMIN_EMAIL |
Seed only | admin@localhost |
Initial admin account email |
ADMIN_PASSWORD |
Seed only | changeme123 |
Initial admin account password |
API_PORT |
No | 3001 |
Backend port |
APP_PORT |
No | 5173 |
Frontend port |
ASSET_STORAGE_PATH |
No | /uploads |
Where uploaded images are stored |
BACKUP_STORAGE_PATH |
No | /backups |
Where automatic backups are written |
CORS_ORIGIN |
No | http://localhost:5173 |
Allowed frontend origin |
| Type | Description |
|---|---|
action |
Tap/hold/double-tap to call any Home Assistant service |
text |
Display a live entity attribute with a configurable template |
state_image |
Show different images based on on/off state with crossfade transitions |
state_icon |
MDI icon with state-driven color and optional battery badge |
badge |
Pill label with configurable state-to-text and state-to-color mapping |
scene |
Single-tap scene or script trigger |
blind |
Cover/blind position control with drag slider; long-press for group control |
bins |
Bin day reminder driven by a Home Assistant calendar entity |
weather |
Full weather card with forecast, hourly breakdown, and UV index |
temperature_gauge |
Circular temperature badge; indoor gauges drive the heatmap overlay |
windrose |
Compass rose showing live wind direction and speed |
battery |
Aggregate battery monitor; tap to reveal individual levels on the floorplan |
power |
Heatmap of placed power sensors with per-sensor and combined usage history; usage graphs split into solar- vs. grid-sourced kWh when a Solar hotspot is configured on the same floorplan |
solar |
Live solar production, grid import/export, and home consumption, with panel-level detail and a solar production forecast |
clock |
Analog or digital clock with timezone, date, and custom label support |
custom |
Placeholder for your own renderer — register via registerHotspotType() |
See docs/extending-hotspots.md for a step-by-step guide to adding new hotspot types without modifying core files.
- In HA, go to Profile → Long-Lived Access Tokens and create a new token.
- Paste the token into
HA_TOKENin your.envfile. - Set
HA_BASE_URLto the URL of your HA instance (accessible from the machine running the API container). - The backend proxies all HA communication — the token is never sent to the browser.
- Log in as admin and go to Assets to upload your floorplan image (PNG or JPG).
- Go to Dashboards → New Dashboard and select your uploaded image.
- Open the dashboard and click Edit to enter the visual editor.
- Click Add Hotspot, choose a type, then drag it to position it on the floorplan.
- Use the config panel to bind it to a Home Assistant entity or service.
- Click Save when done.
- The HA long-lived token is stored server-side only and proxied through the API — it is never sent to the browser.
- Passwords are hashed with Argon2id.
- All API routes (except login) require a valid JWT.
- An email allowlist restricts who can register.
- Change the default
ADMIN_PASSWORDimmediately after first login.
Pull requests are welcome. For significant changes, please open an issue first.
# Run tests before submitting
npm run test
npm run typecheck
npm run lint