A backend control system for an autonomous wall-finishing robot, featuring Boustrophedon coverage path planning, a FastAPI REST API backed by optimised SQLite, and a single-file Canvas frontend with animated playback.
🔴 Live Demo: https://wall-robot-planner.streamlit.app/ | 📖 API Docs: /api/docs
🔴 Project Walkthrough: https://drive.google.com/file/d/19KlzpEp9Wfg2yT2ZvlR4Hjy2miiZmHVD/view?usp=sharing
# 1. Clone and install
git clone <repo-url>
cd wall-robot-planner
pip install -r requirements.txt
# 2. Run the API (DB is auto-created on first run)
uvicorn backend.main:app --reload --port 8000
# 3. Open the frontend — served automatically at root
open http://localhost:8000
# 4. Run tests
pytest backend/tests/ -vAPI docs available at: http://localhost:8000/docs
frontend/index.html --> FastAPI (backend/main.py)
|-- POST /api/v1/plan <- path_planner service
|-- GET /api/v1/trajectories
|-- GET /api/v1/trajectories/{id}
`-- DELETE /api/v1/trajectories/{id}
SQLite (WAL · 64 MB cache · composite indexes)
Boustrophedon (Greek: "ox-turning") sweeps the wall in horizontal strips, alternating direction each row — the same pattern a farmer uses to plough a field.
Strip 0 ->->->->->->->->->->->->->->->->->->->->
Strip 1 <-<-<-<- [obstacle] <-<-<-<-<-<-<-<-<-<
Strip 2 ->->->->->->->->->->->->->->->->->->->->
Obstacles are handled by decomposing each strip into free sub-segments
(interval subtraction after merging blocked ranges). The robot emits a
transit move to reposition, then a paint move across each free segment.
Key parameters:
tool_width— paint roller/spray width in metresoverlap— inter-strip overlap to prevent missed coveragestep = tool_width - overlap— vertical increment per strip
| Table | Purpose | Key Indexes |
|---|---|---|
| trajectories | Header + computed stats | created_at, name |
| obstacles | Per-plan obstacle definitions | trajectory_id |
| path_segments | Ordered sequence of moves | (trajectory_id, seq) composite |
SQLite optimisations applied on every connection:
| PRAGMA | Effect |
|---|---|
| journal_mode=WAL | Concurrent reads during writes |
| synchronous=NORMAL | Balance durability vs throughput |
| cache_size=-65536 | 64 MB page cache in RAM |
| temp_store=MEMORY | Temp tables in RAM, not disk |
| mmap_size=256 MB | Memory-mapped I/O for large read scans |
| foreign_keys=ON | Cascade deletes enforced at DB level |
Bulk inserts use db.execute(insert(Model), [...]) — a single round-trip
instead of N individual INSERT statements.
| Method | Path | Description |
|---|---|---|
| POST | /api/v1/plan | Generate plan and persist |
| GET | /api/v1/trajectories | List all (paginated) |
| GET | /api/v1/trajectories/{id} | Full detail + segments |
| DELETE | /api/v1/trajectories/{id} | Delete trajectory |
| GET | /api/v1/health | Health check |
Every response includes X-Response-Time-Ms header.
- Canvas-based 2D visualisation — no Matplotlib, no chart library
- Strip-coloured path — each strip gets a distinct colour; transit moves are dashed
- Animated robot — smooth interpolation with glow effect
- Playback controls — play/pause, step forward/back, seek bar, speed slider
- Live coverage ring — updates as playback progresses
- History panel — reload any saved plan from the DB
- Obstacle editor — add/remove rectangular obstacles before planning
{
"name": "Living Room",
"wall_width": 5.0,
"wall_height": 5.0,
"tool_width": 0.25,
"overlap": 0.02,
"obstacles": [
{"label": "Window", "x": 2.0, "y": 1.5, "width": 0.25, "height": 0.25}
]
}-
Raw SQLite over heavy ORM — SQLAlchemy is used only for schema definition and session management; bulk inserts bypass the ORM layer entirely for speed.
-
Single-file frontend — no build step, no Node.js dependency. Open the file directly or let FastAPI serve it via StaticFiles.
-
Denormalised stats on Trajectory — total_segments, paint_length etc. are computed once at plan-generation time and stored, so list queries never need to aggregate path_segments.
-
(trajectory_id, seq) composite index — ordered segment fetches for playback hit this index directly, avoiding a full table scan + sort.