diff --git a/README.md b/README.md index cc4936d..2389a37 100644 --- a/README.md +++ b/README.md @@ -1,65 +1,65 @@ # Spectra -Display random Unsplash photos on an Inky Impression e-paper display, with a web interface for uploading your own images. +Display random Unsplash photos on an Inky Impression e-paper display, with a web +interface for uploading your own images. -## Features +--- -- Fetches random high-resolution photos from Unsplash (cached to gallery) -- Upload your own images via the web interface (drag-and-drop, multi-file) -- Centres, scales, and rotates images to fit the display -- Colour saturation tuning for e-paper -- Display orientation — configure the physical mounting direction (0°, 90°, 180°, 270°) - — Unsplash orientation filter is auto-derived (portrait/landscape/squarish) -- Web dashboard with preview, gallery, rotation queue, and config editor -- MQTT integration for remote commands and status reporting -- Simulation mode for testing without hardware (configurable resolution) -- systemd integration for automatic updates on a schedule -- Randomised update intervals to avoid predictable refreshes -- Config hot-reload — no service restart needed for setting changes +- [Requirements](#requirements) +- [Quick start](#quick-start) +- [Configuration](#configuration) + - [Display orientation](#display-orientation) + - [MQTT](#mqtt) +- [Usage](#usage) + - [Display loop](#display-loop) + - [Web interface](#web-interface) +- [systemd services](#systemd-services) +- [Running tests](#running-tests) +- [Project structure](#project-structure) + +--- ## Requirements -- Raspberry Pi (any model with GPIO) +- Raspberry Pi with GPIO - [Pimoroni Inky Impression](https://shop.pimoroni.com/products/inky-impression) (4.0", 7.3", or 13.3") - Python 3.9+ - [Unsplash API access key](https://unsplash.com/developers) -## Installation - -Run the installer on a Raspberry Pi: - -```bash -./install.sh -``` - -The installer will: - -1. Install the Pimoroni Inky library (skipped if not a Raspberry Pi) -2. Install Python dependencies (including web server and MQTT) -3. Install the `spectra` package -4. Copy the default config to `/etc/spectra/config.yaml` -5. Install and enable both the display and web interface systemd services - -### Manual installation +## Quick start ```bash +# Install +cp config.example.yaml config.yaml # edit with your Unsplash key pip install -r requirements.txt pip install -e . -# Optional: install dev dependencies for running tests -pip install -e ".[dev]" +# Run once in simulation mode +spectra --once --simulate + +# Start the web interface +spectra web + +# Run the display loop continuously +spectra +``` + +Or on a Pi with systemd: + +```bash +sudo ./install.sh +sudo systemctl start spectra spectra-web ``` ## Configuration -Copy `config.example.yaml` to `config.yaml` and fill in your Unsplash access key. -spectra searches for `config.yaml` in this order: +spectra looks for `config.yaml` in this order: 1. `$PWD/config.yaml` 2. `~/.config/spectra/config.yaml` 3. `/etc/spectra/config.yaml` -Or pass a custom path: `spectra -c /path/to/config.yaml` +Pass a custom path with `spectra -c /path/to/config.yaml`. ```yaml unsplash: @@ -69,7 +69,7 @@ unsplash: display: saturation: 0.5 - orientation: 0 # 0, 90, 180, 270 (degrees clockwise) + orientation: 0 resolution: width: 1600 height: 1200 @@ -91,83 +91,67 @@ paths: cache: /var/cache/spectra ``` -> **Note:** The `unsplash.orientation` key is no longer used. The Unsplash -> orientation filter (`landscape`/`portrait`/`squarish`) is now derived -> automatically from `display.orientation` + `display.resolution`. +### Display orientation + +Set `display.orientation` to match how the panel is physically mounted. The +Unsplash orientation filter (`landscape`/`portrait`/`squarish`) is derived +automatically — the old `unsplash.orientation` config key is ignored. + +| Value | Unsplash filter | Effect | +|-------|----------------|--------| +| 0 | landscape | Default horizontal mount | +| 90 | portrait | Vertical mount | +| 180 | landscape | Upside-down horizontal | +| 270 | portrait | Upside-down vertical | + +Images are cropped to the effective aspect ratio, resized, then rotated for the +physical panel. Preview and thumbnails reflect the configured orientation. +Change it live from Settings > Display Orientation or via config hot-reload. + +### MQTT + +When enabled, the display loop subscribes to `{prefix}/command/#` and publishes +status to `{prefix}/status`. See [MQTT integration docs](docs/mqtt.md) for +commands, Home Assistant examples, and debugging. ## Usage ### Display loop ```bash -# Run once and exit -spectra --once - -# Run once in simulation mode -spectra --once --simulate - -# Simulate at a specific resolution -spectra --once --simulate --width 800 --height 480 - -# Run with a custom config +spectra # run continuously +spectra --once # fetch one photo and exit +spectra --once --simulate # save to /tmp/spectra_last.png +spectra --simulate --width 800 --height 480 spectra -c /path/to/config.yaml - -# Run continuously -spectra ``` ### Web interface ```bash -# Start the web interface (default: http://0.0.0.0:5000) -spectra web - -# With custom host/port -spectra web --host 0.0.0.0 --port 5000 - -# Verbose logging (debug mode is limited to localhost) -spectra web -v +spectra web # http://0.0.0.0:5000 +spectra web --port 8080 +spectra web -v # verbose (debug mode only on localhost) ``` -The web interface provides: - -- **Dashboard** — display status, schedule info, quick actions (refresh, clear) -- **Gallery** — browse all uploaded and cached Unsplash images, trigger "show now" -- **Upload** — drag-and-drop multiple image files with progress tracking -- **Settings** — live-edit Unsplash, display (including orientation), schedule, and MQTT configuration -- **Preview** — preview how an image will look on the display (respects current orientation) - -### Display orientation - -Set `display.orientation` in config or via Settings > Display > Display Orientation: - -| Value | Unsplash filter | Use case | -|-------|----------------|-------------------------| -| 0 | landscape | Default horizontal mount | -| 90 | portrait | Vertical mount | -| 180 | landscape | Upside-down horizontal | -| 270 | portrait | Upside-down vertical | - -Images are cropped to the effective aspect ratio, resized, then rotated to match -the physical panel. The preview and thumbnails reflect the configured orientation. - -### MQTT - -When MQTT is enabled in the config, the display loop subscribes to commands and publishes status. See [MQTT integration docs](docs/mqtt.md) for details. +| Page | What it does | +|------|-------------| +| **Dashboard** | Display status, schedule, quick actions (refresh, clear) | +| **Gallery** | Browse images, trigger "show now", delete | +| **Upload** | Drag-and-drop multi-file upload with progress | +| **Settings** | Live-edit Unsplash, display, schedule, MQTT | +| **Preview** | See how an image looks on the display | ## systemd services -Two systemd services are installed: +Two units are installed by `install.sh`: -- `spectra.service` — the display loop (fetches Unsplash, processes triggers) -- `spectra-web.service` — the web interface (Flask + htmx) +- `spectra.service` — display loop +- `spectra-web.service` — web interface ```bash sudo systemctl start spectra sudo systemctl stop spectra-web -sudo systemctl status spectra-web - -# View logs journalctl -u spectra -f journalctl -u spectra-web -f ``` @@ -180,48 +164,45 @@ pytest tests/ ``` 107 tests covering config loading, display processing (crop, resize, rotation), -trigger atomicity, MQTT parsing, and the full web API (all 19 routes). +trigger atomicity, MQTT parsing, and all 19 API routes. ## Project structure ``` spectra/ -├── config.example.yaml # Example config (copy to config.yaml) -├── .gitignore # Ignores config.yaml (contains secrets) -├── install.sh # Installer script -├── pyproject.toml # Package metadata -├── requirements.txt # Python dependencies +├── config.example.yaml +├── .gitignore +├── install.sh +├── pyproject.toml +├── requirements.txt ├── spectra/ -│ ├── __init__.py -│ ├── __main__.py # python -m spectra entry point -│ ├── cli.py # CLI argument parsing and main loop -│ ├── config.py # Configuration loader -│ ├── config_manager.py # Config read/write with YAML write-back -│ ├── display.py # Inky display abstraction and image processing -│ ├── fetcher.py # Unsplash API client -│ ├── library.py # Unsplash image caching to SQLite library -│ ├── mqtt.py # MQTT client (commands + status) -│ ├── trigger.py # Shared trigger file (web server → display loop) +│ ├── cli.py # CLI, display loop, config hot-reload +│ ├── config.py # Config loader with deep-merge +│ ├── config_manager.py # Config read/write +│ ├── display.py # Image processing, rotation, hardware abstraction +│ ├── fetcher.py # Unsplash API client +│ ├── library.py # Unsplash image caching to SQLite +│ ├── mqtt.py # MQTT client (commands + heartbeat) +│ ├── trigger.py # Thread-safe trigger file (web → display loop) │ └── web/ -│ ├── server.py # Flask app factory and API routes -│ ├── models.py # SQLAlchemy models -│ ├── templates/ # Jinja2 templates (6 pages) -│ ├── static/ # CSS and JS -│ └── __init__.py +│ ├── server.py # Flask app, 19 API routes +│ ├── models.py # SQLAlchemy models +│ ├── templates/ # 6 Jinja2 pages +│ └── static/ # CSS + JS ├── tests/ -│ ├── test_api.py # 42 API endpoint tests -│ ├── test_config.py # Config loading and deep-merge -│ ├── test_display.py # Display processing and orientation -│ ├── test_gallery.py # Upload + delete flow -│ ├── test_mqtt.py # MQTT path and lookup -│ ├── test_orientation.py # Orientation edge cases -│ ├── test_trigger.py # Trigger atomicity -│ └── conftest.py # Test fixtures +│ ├── test_api.py +│ ├── test_config.py +│ ├── test_display.py +│ ├── test_gallery.py +│ ├── test_mqtt.py +│ ├── test_orientation.py +│ ├── test_trigger.py +│ └── conftest.py ├── systemd/ -│ ├── spectra.service # Display loop systemd unit -│ └── spectra-web.service # Web interface systemd unit +│ ├── spectra.service +│ └── spectra-web.service └── docs/ - ├── display-orientation.md # Display orientation design doc - ├── web-interface.md # Web interface documentation - └── mqtt.md # MQTT integration documentation + ├── display-orientation.md + ├── web-interface.md + └── mqtt.md ```