# Spectra 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 - Raspberry Pi (any model 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 ```bash pip install -r requirements.txt pip install -e . # Optional: install dev dependencies for running tests pip install -e ".[dev]" ``` ## Configuration Copy `config.example.yaml` to `config.yaml` and fill in your Unsplash access key. spectra searches 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` ```yaml unsplash: access_key: "your_access_key_here" query: "nature" collections: "" display: saturation: 0.5 orientation: 0 # 0, 90, 180, 270 (degrees clockwise) resolution: width: 1600 height: 1200 schedule: interval_hours: 1 random_delay_seconds: 300 mqtt: enabled: false broker: localhost port: 1883 topic_prefix: spectra client_id: spectra-display username: "" password: "" 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`. ## 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 -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 ``` 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. ## systemd services Two systemd services are installed: - `spectra.service` — the display loop (fetches Unsplash, processes triggers) - `spectra-web.service` — the web interface (Flask + htmx) ```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 ``` ## Running tests ```bash pip install -e ".[dev]" pytest tests/ ``` 107 tests covering config loading, display processing (crop, resize, rotation), trigger atomicity, MQTT parsing, and the full web API (all 19 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 ├── 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) │ └── web/ │ ├── server.py # Flask app factory and API routes │ ├── models.py # SQLAlchemy models │ ├── templates/ # Jinja2 templates (6 pages) │ ├── static/ # CSS and JS │ └── __init__.py ├── 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 ├── systemd/ │ ├── spectra.service # Display loop systemd unit │ └── spectra-web.service # Web interface systemd unit └── docs/ ├── display-orientation.md # Display orientation design doc ├── web-interface.md # Web interface documentation └── mqtt.md # MQTT integration documentation ```