Skip to content
Draft
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
47 changes: 47 additions & 0 deletions .env.1password.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# 1Password environment file for the home dashboard.
#
# 1. Copy this file to .env.1password (gitignored).
# 2. Replace vault/item/field names with your 1Password secret references.
# 3. Sign in: op signin
# 4. Run: make dashboard-1password
#
# Find secret references for an item:
# op item get "Yale Access" --format json | jq '.fields[] | {label, reference}'
#
# Syntax: op://<vault>/<item>/[section/]<field>

# Yale lock (Yale Access / August app)
YALE_BRAND=yale_access
YALE_LOGIN_METHOD=email
YALE_USERNAME="op://Home/Yale Access/username"
YALE_PASSWORD="op://Home/Yale Access/password"
# Optional: lock device ID (defaults to first lock)
# YALE_LOCK_ID=
YALE_AUTH_CACHE_FILE=.yale_auth_cache
YALE_POLL_INTERVAL_SECONDS=15
YALE_ACTIVITY_LIMIT=25

# UniFi Protect (NVR / Dream Machine)
UNIFI_HOST=192.168.1.1
UNIFI_PORT=443
UNIFI_USERNAME="op://Home/UniFi Protect/username"
UNIFI_PASSWORD="op://Home/UniFi Protect/password"
# Optional API key for public API features
# UNIFI_API_KEY="op://Home/UniFi Protect/credential"
UNIFI_VERIFY_SSL=false
# Optional: default camera ID (defaults to first camera)
# UNIFI_CAMERA_ID=
UNIFI_SNAPSHOT_WIDTH=1280
UNIFI_SNAPSHOT_HEIGHT=720
UNIFI_SNAPSHOT_REFRESH_SECONDS=2

# Web dashboard
DASHBOARD_HOST=127.0.0.1
DASHBOARD_PORT=8080

# Weather (Bureau of Meteorology — recommended for Australia)
WEATHER_PROVIDER=bom
BOM_PRODUCT_ID=IDN60801
BOM_STATION_ID=94768
WEATHER_LOCATION_NAME=Sydney
WEATHER_REFRESH_SECONDS=900
44 changes: 44 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Yale lock (Yale Access / August app)
YALE_BRAND=yale_access
YALE_LOGIN_METHOD=email
YALE_USERNAME=you@example.com
YALE_PASSWORD=your-yale-password
# Optional: lock device ID (defaults to first lock)
# YALE_LOCK_ID=
YALE_AUTH_CACHE_FILE=.yale_auth_cache
YALE_POLL_INTERVAL_SECONDS=15
YALE_ACTIVITY_LIMIT=25

# UniFi Protect (NVR / Dream Machine)
UNIFI_HOST=192.168.1.1
UNIFI_PORT=443
UNIFI_USERNAME=your-unifi-user
UNIFI_PASSWORD=your-unifi-password
# Optional API key for public API features
# UNIFI_API_KEY=
UNIFI_VERIFY_SSL=false
# Optional: default camera ID (defaults to first camera)
# UNIFI_CAMERA_ID=
UNIFI_SNAPSHOT_WIDTH=1280
UNIFI_SNAPSHOT_HEIGHT=720
UNIFI_SNAPSHOT_REFRESH_SECONDS=2

# Web dashboard
DASHBOARD_HOST=127.0.0.1
DASHBOARD_PORT=8080

# Weather (Bureau of Meteorology — recommended for Australia)
# Find product + station IDs from the JSON link on a BOM station page:
# https://www.bom.gov.au/catalogue/data-feeds.shtml
WEATHER_PROVIDER=bom
BOM_PRODUCT_ID=IDN60801
BOM_STATION_ID=94768
WEATHER_LOCATION_NAME=Sydney
WEATHER_REFRESH_SECONDS=900

# Alternative: Open-Meteo via coordinates (no API key)
# WEATHER_PROVIDER=open_meteo
# WEATHER_LATITUDE=-33.8688
# WEATHER_LONGITUDE=151.2093
# WEATHER_LOCATION_NAME=Home
# WEATHER_TIMEZONE=Australia/Sydney
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ coverage.xml

# Environments
.env
.env.1password
.venv
env/
venv/
Expand All @@ -64,6 +65,6 @@ venv.bak/
# VSCode
.vscode/
ml_agent/__pycache__/__init__.cpython-313.pyc
uv.lock
ml_agent/__pycache__/__init__.cpython-313.pyc
ml_agent/tools/__pycache__/google_docs_tool.cpython-313.pyc
.yale_auth_cache
1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.13
19 changes: 17 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,17 @@ ROOT_DIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
TESTPATH := $(ROOT_DIR)/tests/

.PHONY: install
install: ## Install virtual environment with uv
@echo "🚀 Creating virtual environment using uv"
install: ## Create .venv and install dependencies with uv
@command -v uv >/dev/null 2>&1 || { \
echo "❌ uv is not installed. See https://docs.astral.sh/uv/getting-started/installation/"; \
exit 1; \
}
@echo "🚀 Syncing virtual environment with uv (Python $$(cat .python-version))"
@uv sync

.PHONY: sync
sync: install ## Alias for install (uv sync)

.PHONY: check
check: ## Check lock file consistency and run static code analysis
@echo "🚀 Checking lock file consistency with 'pyproject.toml'"
Expand All @@ -28,6 +35,14 @@ test: ## Run all tests
--cov-report=term-missing \
--junitxml=junit.xml

.PHONY: dashboard
dashboard: ## Run Yale lock + UniFi camera web dashboard
@uv run home-dashboard

.PHONY: dashboard-1password
dashboard-1password: ## Run dashboard with secrets from 1Password
@./scripts/run-dashboard-1password.sh

.PHONY: web
web: ## Run the ADK web demo server
@uv run adk web --reload src/agents/
Expand Down
109 changes: 107 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@ Experiments with Google Agent Development Kit (ADK) for building AI agents with

## Quick Start

This project uses [uv](https://docs.astral.sh/uv/) for Python version management, virtual environments, and dependencies. The lockfile (`uv.lock`) pins exact versions for reproducible installs.

```bash
# Install dependencies
# Install uv (once per machine)
curl -LsSf https://astral.sh/uv/install.sh | sh

# Create .venv and install dependencies from uv.lock
make install

# Configure Ollama (create .env file)
Expand All @@ -18,6 +23,8 @@ make web
make prefect-server
```

All commands run through `uv run`, so you do not need to activate the virtual environment manually.

## Components

### Agents
Expand All @@ -37,7 +44,7 @@ make prefect-server

## Prerequisites

- Python 3.13+
- [uv](https://docs.astral.sh/uv/) (installs and manages Python 3.13 per `.python-version`)
- Ollama installed and running
- Google OAuth credentials (`credentials.json`) for Google Docs API

Expand All @@ -50,8 +57,100 @@ make prefect-server # Start Prefect server and serve flows
make prefect-flows # Serve flows (server must be running)
make test # Run tests
make check # Lint and type check
make dashboard # Run Yale lock + UniFi camera dashboard
make dashboard-1password # Run dashboard with 1Password secrets
```

## Home Dashboard (Yale Lock + UniFi Camera)

A web dashboard for monitoring and controlling a Yale smart lock and viewing UniFi Protect camera snapshots.

### Setup

```bash
# Sync the uv environment (uses uv.lock + .python-version)
make install

# Copy and edit environment variables
cp .env.example .env
```

To add or upgrade a dependency, use uv and commit the updated lockfile:

```bash
uv add <package> # runtime dependency
uv add --dev <package> # dev dependency
uv lock # refresh uv.lock after manual pyproject edits
```

Configure `.env` with your Yale, UniFi, and weather settings (see `.env.example` for all options).

**Weather** appears alongside the UniFi camera feed. For Australian locations, use the [Bureau of Meteorology](https://www.bom.gov.au/) observation JSON feeds (no API key):

```bash
WEATHER_PROVIDER=bom
BOM_PRODUCT_ID=IDN60801 # state product ID from the BOM JSON URL
BOM_STATION_ID=94768 # station WMO ID from the same URL
WEATHER_LOCATION_NAME=Sydney
```

Example feed: `https://reg.bom.gov.au/fwo/IDN60801/IDN60801.94768.json`

Alternatively, set `WEATHER_PROVIDER=open_meteo` with `WEATHER_LATITUDE` and `WEATHER_LONGITUDE` for global coverage via Open-Meteo.

### Run

```bash
make dashboard
```

Open `http://127.0.0.1:8080` in your browser.

### Run with 1Password

Store Yale and UniFi credentials in 1Password, then inject them at runtime with the [1Password CLI](https://developer.1password.com/docs/cli/) (`op`). Non-secret settings (hostnames, ports, weather coordinates) stay as plaintext in your env file; only passwords and usernames use `op://` secret references.

```bash
# Install the 1Password CLI and sign in
op signin

# Create your local env file from the example
cp .env.1password.example .env.1password

# Edit .env.1password — replace vault/item/field names with your references.
# List field references for an item:
op item get "Yale Access" --format json | jq '.fields[] | {label, reference}'

# Start the dashboard (secrets are resolved by op run, never written to disk)
make dashboard-1password
```

Optional environment variables:

| Variable | Default | Purpose |
|----------|---------|---------|
| `ONEPASSWORD_ENV_FILE` | `.env.1password` | Path to the env file with `op://` references |
| `ONEPASSWORD_ENVIRONMENT` | _(unset)_ | 1Password Environment ID (`op run --environment`) |

Example with a 1Password Environment plus a local override file:

```bash
ONEPASSWORD_ENVIRONMENT=env_abc123 make dashboard-1password
```

### Features

- **Yale lock**: live lock/door status, recent activity feed, lock/unlock controls
- **UniFi camera**: camera selector, live snapshot feed, and local weather panel (temperature, wind, humidity, rain since 9am)
- **Real-time updates**: Server-Sent Events push status changes to the browser
- **Dynamic background**: sky gradient reflects time of day and live weather (BOM or Open-Meteo)

### Requirements

- Yale lock connected via Yale Access / August app with Wi-Fi bridge
- UniFi Protect NVR (Cloud Key+, Dream Machine, etc.) on your network
- [uv](https://docs.astral.sh/uv/) with Python 3.13 (see `.python-version`)

## Project Structure

```
Expand All @@ -61,6 +160,12 @@ src/
│ ├── agent.py # Agent definition
│ └── tools/
│ └── google_docs_tool.py # Google Docs integration
├── yale_lock/ # Yale lock + UniFi camera dashboard
│ ├── server.py # FastAPI application
│ ├── yale_client.py # Yale lock integration
│ ├── unifi_client.py # UniFi Protect camera integration
│ ├── static/ # Web UI assets
│ └── templates/ # HTML templates
└── workflows/
├── pipeline.py # Main Prefect workflow
├── serve.py # Flow serving entry point
Expand Down
22 changes: 21 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ dependencies = [
"google-api-python-client>=2.100.0",
"prefect>=3.0.0",
"markdown-it-py>=3.0.0",
"yalexs>=9.2.10",
"aiofiles>=25.1.0",
"fastapi>=0.139.0",
"pydantic-settings>=2.14.2",
"uiprotect>=15.3.0",
"httpx>=0.28.1",
]

[build-system]
Expand All @@ -30,6 +36,9 @@ requires = [
]
build-backend = "setuptools.build_meta"

[tool.uv]
default-groups = ["dev"]

[dependency-groups]
dev = [
"ipykernel>=6.29.5",
Expand All @@ -41,6 +50,7 @@ dev = [
"deptry>=0.23.0",
"pytest-cov>=4.0.0",
"types-pyyaml>=6.0.12.20250809",
"playwright>=1.61.0",
]

[tool.agent_packs]
Expand All @@ -57,6 +67,10 @@ warn_unused_ignores = true
show_error_codes = true
ignore_missing_imports = true

[[tool.mypy.overrides]]
module = "yale_lock.*"
disable_error_code = ["no-any-unimported"]

[tool.basedpyright]
reportAny = false
reportExplicitAny = false
Expand All @@ -79,4 +93,10 @@ source = ["src"]
omit = ["server.py"]

[tool.deptry.per_rule_ignores]
DEP002 = ["uvicorn"]
DEP002 = ["uvicorn"]

[project.scripts]
home-dashboard = "yale_lock.__main__:main"

[tool.setuptools.packages.find]
where = ["src"]
44 changes: 44 additions & 0 deletions scripts/run-dashboard-1password.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/usr/bin/env bash
# Run the home dashboard with secrets injected by the 1Password CLI.

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"

cd "$PROJECT_ROOT"

ENV_FILE="${ONEPASSWORD_ENV_FILE:-.env.1password}"
OP_ARGS=()

if ! command -v op >/dev/null 2>&1; then
echo "❌ 1Password CLI (op) is not installed."
echo " Install: https://developer.1password.com/docs/cli/get-started/"
exit 1
fi

if ! op account list >/dev/null 2>&1; then
echo "❌ Not signed in to 1Password. Run: op signin"
exit 1
fi

if [[ ! -f "$ENV_FILE" ]]; then
echo "❌ Environment file not found: $ENV_FILE"
echo " Copy .env.1password.example to $ENV_FILE and set your op:// references."
exit 1
fi

if ! command -v uv >/dev/null 2>&1; then
echo "❌ uv is not installed."
echo " Install: https://docs.astral.sh/uv/getting-started/installation/"
exit 1
fi

uv sync --quiet

if [[ -n "${ONEPASSWORD_ENVIRONMENT:-}" ]]; then
OP_ARGS+=(--environment "$ONEPASSWORD_ENVIRONMENT")
fi

echo "🔐 Starting home dashboard with 1Password secrets ($ENV_FILE)..."
exec op run "${OP_ARGS[@]}" --env-file="$ENV_FILE" -- uv run home-dashboard
Loading