Skip to content
Open
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
39 changes: 39 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.12"]

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: pip

# Ultralytics varsayilan olarak CUDA'li torch cekiyor (~2.5 GB). CI'da GPU
# olmadigi icin once CPU tekerlegini kuruyoruz; ayni surumu bulan pip
# sonraki adimda tekrar indirmiyor.
- name: Install CPU-only PyTorch
run: |
python -m pip install -U pip
python -m pip install torch --index-url https://download.pytorch.org/whl/cpu

- name: Install package with dev extras
run: python -m pip install -e ".[dev]"

- name: Run test suite
run: pytest tests/ -v
200 changes: 109 additions & 91 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,54 +1,58 @@
# RoomGate AI 🚪⚡
# RoomGate AI

[![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/)
[![YOLO Engine](https://img.shields.io/badge/Ultralytics-YOLO26-orange.svg)](https://github.com/ultralytics/ultralytics)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![Build Status](https://img.shields.io/badge/build-passing-brightgreen.svg)]()
[![Code Style](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)

**RoomGate AI** is an enterprise-grade, high-performance Smart Access & Occupancy Monitoring System engineered for real-time computer vision processing, spatial region-of-interest (ROI) filtering, and automated hardware access control.

Designed for high-throughput operational environments, RoomGate AI features a multi-threaded video stream pipeline, non-blocking asynchronous event logging, an intuitive glassmorphic visual HUD, and embedded web streaming capabilities.

---
Occupancy-aware door control. A YOLO detector counts people inside a defined
region of the frame, a debounced decision engine turns that count into an
allow/deny verdict, and a serial relay acts on it — with the firmware
auto-relocking so a software failure cannot leave a door open.

## 🌟 Key Architecture & Capabilities

- ⚡ **High-Throughput Threaded Acquisition**: Dedicated background `ThreadedCamera` pipeline ensuring non-blocking video capture and zero I/O frame drops.
- 🎯 **Spatial Region-of-Interest (ROI) Engine**: Real-time geometric polygon containment checks with configurable spatial boundaries.
- 🔐 **Deterministic Decision Engine**: Temporal median filtering and confidence thresholding to guarantee stable, chatter-free access control decisions.
- 🛠️ **Hardware Lock Integration**: Direct serial protocol interfacing with Arduino/ESP32 relay modules, dry-run safety modes, and fail-safe command throttling.
- 📊 **Asynchronous SQLite WAL Event Logging**: Non-blocking SQLite event persistence utilizing Write-Ahead Logging (WAL), indexed analytical schemas, and image snapshot archiving.
- 🖥️ **Glassmorphic Operational Visual HUD**: Real-time telemetry overlay featuring dynamic access indicators, live FPS metrics, inference latency timing, and bounding box spatial tracking.
- 🌐 **Embedded Web Dashboard & MJPEG Stream**: Integrated HTTP web dashboard serving a real-time MJPEG video feed and JSON status REST API for remote administrative monitoring.
- 📈 **Performance Diagnostic Tools**: Command-line benchmark and diagnostic suite for evaluating hardware acceleration, frame throughput, and sub-millisecond latency.
[![CI](https://github.com/emrefbulut/RoomGate-AI/actions/workflows/ci.yml/badge.svg)](https://github.com/emrefbulut/RoomGate-AI/actions/workflows/ci.yml)
[![Python 3.10+](https://img.shields.io/badge/python-3.10%20%7C%203.12-blue.svg)](https://www.python.org/)
[![Ultralytics YOLO26](https://img.shields.io/badge/Ultralytics-YOLO26-orange.svg)](https://github.com/ultralytics/ultralytics)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

---

## 🏗️ System Architecture
## Pipeline

```mermaid
flowchart TD
A[Camera / Video Source] -->|Raw Frames| B[ThreadedCapture Pipeline]
B -->|Latest Frame| C[YOLO Spatial Detector & Tracker]
C -->|Detections| D[ROI Spatial Filter]
D -->|Inside Count| E[Occupancy Decision Engine]
E -->|Access Command| F[Hardware Relay Controller]
E -->|Event Record| G[Async SQLite WAL Logger]
C -->|Annotated Stream| H[Glassmorphic HUD Renderer]
H -->|Frame Render| I[OpenCV Window & Web MJPEG Server]
A["Camera / video source"] -->|"threaded capture"| B["Latest frame"]
B --> C["YOLO26 detector<br/>CUDA · TensorRT · ONNX · OpenVINO"]
C --> D["ROI polygon filter"]
D --> E["Decision engine<br/>median over N frames"]
E -->|"OPEN / DENY"| F["Relay controller<br/>serial, throttled"]
E --> G["Async SQLite WAL logger"]
C --> H["HUD renderer"]
H --> I["OpenCV window"]
H --> J["MJPEG dashboard"]
F -->|"auto-relock 3 s"| K["Arduino / ESP32 lock"]
```

---

## 🚀 Installation & Setup
## Design decisions worth knowing

**The decision engine fails safe.** If inference throws, the frame is not
silently treated as "nobody is here" — that would open the door on a model or
camera failure. Detection errors produce an explicit `DENY` with reason
`detection_unavailable`.

**Two independent debounces.** Occupancy is the median over the last N frames,
so a single dropped or spurious detection cannot flip the verdict. Separately,
the relay refuses to repeat the same command inside a minimum interval.

### Prerequisites
- Python 3.10+
- OpenCV 4.8+
- PyTorch 2.0+
**The firmware does not trust the host.** `arduino_relay_lock.ino` re-locks
3 seconds after any `OPEN`, so a crashed or disconnected controller cannot leave
the door unlocked. The software layer is an optimisation, not the safety
boundary.

### Environment Installation
**Counters track transitions, not frames.** `total_grants` increments when the
verdict changes, not once per frame — at 30 FPS the latter would just be a
frame counter wearing a meaningful name.

---

## Install

```powershell
python -m venv .venv
Expand All @@ -57,113 +61,118 @@ python -m pip install -U pip
python -m pip install -e .
```

---

## 💻 Usage & CLI Reference
Requires Python 3.10+, OpenCV 4.8+, and PyTorch 2.0+ (pulled in by Ultralytics).

### 1. Live Monitoring Mode
---

Launch the live access monitor using default system configuration:
## Usage

```powershell
roomgate-ai monitor --config config/default.yaml
```

To run in simulation mode (dry-run hardware relay with synthetic camera feed):
Explore without a camera or a relay:

```powershell
roomgate-ai monitor --dry-run
```

To enable the live **Web Dashboard** on port `8080`:

```powershell
roomgate-ai monitor --web --web-port 8080
```
*Access the live stream dashboard at `http://localhost:8080`.*
| Command | Purpose |
| :--- | :--- |
| `roomgate-ai monitor` | Live detection, decision, relay, HUD |
| `roomgate-ai web` | Headless MJPEG dashboard and status API |
| `roomgate-ai status` | Runtime environment and CUDA/MPS diagnostics |
| `roomgate-ai benchmark --frames 100` | Throughput and inference latency |
| `roomgate-ai collect` | Capture raw images for a dataset |
| `roomgate-ai dataset-report` | Validate dataset structure and labels |
| `roomgate-ai make-dataset-yaml` | Generate the Ultralytics dataset descriptor |
| `roomgate-ai train` | Fine-tune on a custom dataset |
| `roomgate-ai export` | Export to ONNX / TensorRT / OpenVINO |

---

### 2. Diagnostics & Performance Benchmarks

Run hardware acceleration diagnostics (CUDA / MPS / CPU availability):
## Web dashboard

```powershell
roomgate-ai status
roomgate-ai monitor --web --web-port 8080
```

Execute latency and frame rate benchmark tests:
> **Security.** The dashboard streams a live camera feed and occupancy data. It
> binds to `127.0.0.1` by default. To reach it from another machine, set
> `web.host` — and set `web.access_token` at the same time, otherwise anyone on
> the network can watch the room. With a token configured, open
> `http://<host>:8080/?token=<your-token>`.

```powershell
roomgate-ai benchmark --frames 100
```
The token is accepted as `Authorization: Bearer <token>` or as a `?token=`
query parameter; the query form exists because `<img>` and MJPEG streams cannot
send custom headers.

---

### 3. Full Subcommand Reference
## Configuration

| Subcommand | Description |
| :--- | :--- |
| `roomgate-ai monitor` | Executes the real-time access monitoring pipeline |
| `roomgate-ai web` | Runs the headless web dashboard and MJPEG stream server |
| `roomgate-ai benchmark` | Evaluates system throughput (FPS) and latency metrics |
| `roomgate-ai status` | Displays runtime software environment and CUDA diagnostics |
| `roomgate-ai collect` | Collects raw image datasets for specific operational scenarios |
| `roomgate-ai dataset-report` | Validates dataset structure and label completeness |
| `roomgate-ai train` | Fine-tunes object detection models on custom datasets |
| `roomgate-ai export` | Exports trained models to ONNX or TensorRT formats |

---

## ⚙️ Configuration Schema (`config/default.yaml`)
Full reference: [`config/default.yaml`](config/default.yaml).

```yaml
camera:
source: 0
source: 0 # index, file path, or RTSP URL
width: 1280
height: 720
fps: 30
threaded: true
threaded: true # decouple capture from inference
buffer_size: 1 # keep only the newest frame

model:
weights: "yolo26n.pt"
confidence: 0.35
iou: 0.7
device: null
device: null # null = auto-select CUDA / MPS / CPU
tracker: "bytetrack.yaml"
frame_skip: 0 # reuse detections for N frames on slow hardware
export_format: null # engine | onnx | openvino
auto_export: true
half_precision: false

occupancy:
max_occupancy: 3
confirmation_frames: 5
confirmation_frames: 5 # median window
minimum_average_confidence: 0.35
log_every_seconds: 1.0

roi:
name: "main_room"
points:
- [80, 80]
- [1200, 80]
- [1200, 700]
- [80, 700]
points: [[80, 80], [1200, 80], [1200, 700], [80, 700]]

relay:
enabled: false
port: "COM3"
baudrate: 9600
open_command: "OPEN"
lock_command: "LOCK"
deny_command: "DENY"
min_command_interval_seconds: 1.0

logging:
sqlite_path: "logs/events.sqlite3"
snapshot_dir: "snapshots"
save_event_snapshots: false
wal_mode: true

web:
enabled: false
host: "127.0.0.1" # only widen together with access_token
port: 8080
quality: 80
access_token: null
```

---

## 🔌 Hardware Relay Protocol
## Hardware

RoomGate AI interfaces with hardware relays (Arduino, ESP32, or industrial controllers) via high-speed serial communication.
Wiring notes: [`docs/hardware_wiring_tr.md`](docs/hardware_wiring_tr.md).
Dataset strategy: [`docs/dataset_strategy_tr.md`](docs/dataset_strategy_tr.md).

Deploy firmware located at `hardware/arduino_relay_lock/arduino_relay_lock.ino` to your micro-controller board, then enable serial control in `config/default.yaml`:
Flash [`hardware/arduino_relay_lock/arduino_relay_lock.ino`](hardware/arduino_relay_lock/arduino_relay_lock.ino),
then enable the serial link:

```yaml
relay:
Expand All @@ -172,18 +181,27 @@ relay:
baudrate: 9600
```

---
The sketch accepts `OPEN`, `LOCK`, and `DENY`, acknowledges each on the serial
line, and re-locks automatically after `OPEN_MS` (3 s by default).

## 🧪 Verification & Automated Testing
---

Execute the unit test suite:
## Testing

```powershell
python -m pip install -e ".[dev]"
pytest tests/ -v
```

Covered: ROI containment, the decision engine including the fail-safe path and
transition counting, dashboard authorisation (bearer, query token, wrong token,
missing token, malformed scheme, prefix-only attempts), event logging, dataset
validation, config parsing, and relay throttling.

CI runs the same suite on Python 3.10 and 3.12 for every push and pull request.

---

## 📄 License
## License

This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for complete details.
MIT — see [LICENSE](LICENSE).
5 changes: 4 additions & 1 deletion config/default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ logging:

web:
enabled: false
host: "0.0.0.0"
# Pano canli kamera goruntusu yayinlar. "0.0.0.0" yapip agdaki herkese
# acacaksaniz mutlaka bir access_token tanimlayin.
host: "127.0.0.1"
port: 8080
quality: 80
access_token: null
5 changes: 5 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ dependencies = [
"ultralytics",
]

[project.optional-dependencies]
dev = [
"pytest>=8.0",
]

[project.scripts]
roomgate-ai = "roomgate.cli:main"
roomgate = "roomgate.cli:main"
Expand Down
6 changes: 5 additions & 1 deletion roomgate/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,11 @@ def main(argv: list[str] | None = None) -> int:
web_dashboard = None
if args.command == "web" or args.web:
port = getattr(args, "port", None) or getattr(args, "web_port", 8080)
web_dashboard = RoomGateWebDashboard(host=settings.web.host, port=port)
web_dashboard = RoomGateWebDashboard(
host=settings.web.host,
port=port,
access_token=settings.web.access_token,
)

no_win = args.no_window or (args.command == "web")
RoomGateMonitor(settings, detector, relay, logger_inst, web_dashboard).run(show=not no_win)
Expand Down
9 changes: 7 additions & 2 deletions roomgate/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,13 @@ class LoggingSettings:
@dataclass(frozen=True)
class WebSettings:
enabled: bool = False
host: str = "0.0.0.0"
# Pano canli kamera goruntusu ve doluluk verisi yayinlar. Varsayilan olarak
# yalnizca yerel makineden erisilebilir; agdaki herkese acmak icin host
# degeri acikca degistirilmeli ve access_token tanimlanmalidir.
host: str = "127.0.0.1"
port: int = 8080
quality: int = 80
access_token: str | None = None


@dataclass(frozen=True)
Expand Down Expand Up @@ -175,8 +179,9 @@ def load_settings(path: str | Path = "config/default.yaml") -> AppSettings:
),
web=WebSettings(
enabled=bool(web.get("enabled", False)),
host=str(web.get("host", "0.0.0.0")),
host=str(web.get("host", "127.0.0.1")),
port=int(web.get("port", 8080)),
quality=int(web.get("quality", 80)),
access_token=(str(web["access_token"]) if web.get("access_token") else None),
),
)
Loading
Loading