Skip to content

Local Setup & Architecture

Project Structure

text
ir2mqtt/
├── backend/              # FastAPI application (Python)
│   ├── routers/          # API endpoints (devices, bridges, automations, …)
│   ├── tests/            # Backend unit tests (pytest)
│   └── main.py           # App entrypoint
├── frontend/             # Vue 3 + Vite + TypeScript
│   ├── src/
│   │   ├── stores/       # Pinia stores (one per domain)
│   │   ├── views/        # Page-level components
│   │   └── components/   # Shared components
│   └── e2e/
│       ├── *.spec.ts     # Mocked E2E tests (Playwright)
│       ├── doc-gifs/     # Playwright specs that record GIFs for the docs
│       └── integration/  # Full-stack integration tests (real MQTT)
├── tools/
│   ├── simulator/        # PyQt6 GUI + CLI for simulating IR bridges
│   └── sim_server.py     # HTTP control server for integration/doc-gif tests
├── docs/                 # VitePress documentation site
│   └── public/gifs/      # Auto-generated GIFs (committed, served by VitePress)
├── scripts/              # Dev/CI shell scripts
└── Makefile              # Task runner — run `make help` for all targets

First-time Setup

bash
make setup

Creates .venv, installs Python dependencies (pip install -e ".[dev]"), runs npm install in frontend/, and activates Husky git hooks.

Node & Python versions

Node.js ≥ 22 and Python 3.12+ are required. make setup will abort if Node is too old.

Starting the Dev Server

bash
make dev          # Full stack via Docker Compose (backend + frontend + MQTT)
make dev-local    # Native processes + Mosquitto in Docker (needed for Mac serial port access)

dev-local is preferred on macOS because Docker Desktop can't expose USB serial ports to containers.


All Make Targets

Run make help at any time for a quick reference.

Development

CommandWhat it does
make setupFirst-time setup — venv, pip, npm, Husky hooks
make devStart full stack via Docker Compose
make dev-localStart backend + frontend natively, Mosquitto in Docker
make update-depsRegenerate requirements.dev.txt for the current Python version

Linting

CommandWhat it does
make lintLint everything (frontend + backend + simulator)
make lint-frontendESLint with auto-fix on frontend/
make lint-backendRuff check + format on backend/ and tools/

Testing

CommandWhat it does
make test-backendpytest backend/tests/
make test-frontendvue-tsc type check + vitest unit tests
make test-e2ePlaywright mocked E2E — starts a temporary backend automatically
make test-integrationFull-stack integration tests (requires Docker for Mosquitto)
make test-simulatorSimulator unit tests (no display required)
make test-simulator-guiSimulator GUI tests (requires PyQt6 + display)
make testAll of the above
make checklint + test — run this before pushing

Database Migrations

CommandWhat it does
make migrateApply all pending Alembic migrations
make migrate-checkVerify models and migrations are in sync
make migrate-stampMark the current DB state as up-to-date without running migrations
make migration-create M="message"Generate a new migration from model changes

Always run make migrate-check before opening a PR that touches SQLAlchemy models. If it reports a diff, run make migration-create M="describe_change" and commit the generated file.

Docker

CommandWhat it does
make docker-buildBuild image for the current platform (fast, for local testing)
make docker-build-multiBuild for linux/amd64 + linux/arm64 (like CI)
make docker-runRun the built image + Mosquitto at http://localhost:8099
make docker-stopStop and remove the local test containers

Documentation

CommandWhat it does
make docs-devStart local VitePress server at http://localhost:5173
make doc-gifsGenerate all documentation GIFs (requires Docker + ffmpeg)
make showcaseGenerate the UI showcase GIF

Generating doc GIFs

make doc-gifs starts a full simulation stack (Mosquitto + backend with MQTT + sim-server), pre-spawns a virtual IR bridge, runs the Playwright recording specs, and converts each .webm to an optimised .gif in docs/public/gifs/.

bash
make doc-gifs               # regenerate all GIFs
make doc-gifs FILTER=bridges  # regenerate only docs/public/gifs/bridges.gif

Prerequisites: Docker and ffmpeg must be installed.


Release Process

Releases follow semantic versioning (MAJOR.MINOR.PATCH). The steps below bump the version everywhere it is recorded, generate a fresh showcase GIF, and tag the commit.

1. Bump the version

bash
make release VERSION=1.2.0

This updates the version string in:

  • frontend/package.json
  • pyproject.toml

…and then runs make showcase to regenerate the showcase GIF.

2. Run the full check

bash
make check

All tests and linters must be green before tagging.

bash
make doc-gifs

Commit the updated GIFs alongside the version bump so the docs stay in sync.

4. Commit and tag

bash
git add -A
git commit -m "chore: release v1.2.0"
git tag v1.2.0
git push origin main --tags

Pushing a v*.*.* tag triggers the CI pipeline which builds and pushes the multi-platform Docker image to ghcr.io.

Hotfixes

For a hotfix on an older release, branch off the relevant tag (git checkout -b hotfix/1.1.1 v1.1.0), apply the fix, then run make release VERSION=1.1.1 on that branch.


Environment Variables

The backend reads the following variables at startup (all optional in development):

VariableDefaultDescription
DATABASE_URLsqlite+aiosqlite:///./data/ir2mqtt.dbSQLAlchemy async URL
MQTT_BROKERlocalhostMQTT broker hostname
MQTT_PORT1883MQTT broker port
APP_MODEstandalonestandalone or ha_app
APP_ENVproductiondevelopment enables auto-reload
LOG_LEVELINFOuvicorn / app log level
IRDB_PATH./data/ir_dbPath to the IR database files
OPTIONS_FILE./data/options.yamlHA app options file path

In make dev-local and make test-e2e these are set automatically via the startup scripts.


Firmware (ir2mqtt_bridge)

The ESPHome bridge firmware lives in a separate ir2mqtt_bridge repository. Below is a quick reference for contributors working on the component.

Project Structure

text
ir2mqtt_bridge/
├── components/
│   └── ir2mqtt_bridge/        # Core component folder
│       ├── __init__.py        # Python config & code-generation logic
│       ├── ir2mqtt_bridge.h   # C++ header
│       └── ir2mqtt_bridge.cpp # C++ implementation
├── examples/                  # Ready-to-use YAML configurations
└── MANUAL.md                  # Detailed firmware documentation

ESPHome requires component logic inside components/<name>/. The __init__.py acts as the bridge between YAML configuration and the generated C++ code.

Continuous Integration

On every push to main and every PR, a GitHub Actions workflow:

  1. Installs ESPHome.
  2. Creates a dummy secrets.yaml.
  3. Patches the example files to use the local component code.
  4. Compiles all four example configurations (lan, wifi, serial, multi_serial).

This ensures changes to the C++ code or configuration schema don't silently break the provided examples.

Technical Notes

Why no mqtt_id in the config?

The MQTT client is a global component in ESPHome — there is always exactly one instance (mqtt::global_mqtt_client). The __init__.py auto-detects whether MQTT is present in the YAML and wires it up automatically, keeping the user configuration clean.

Conditional compilation: USE_UART, USE_MQTT, USE_LIGHT

When ESPHome parses your YAML, __init__.py checks which features are active and injects compiler flags (e.g. cg.add_define("USE_UART")). The C++ code wraps feature logic in #ifdef blocks:

cpp
#ifdef USE_UART
  // Only compiled if uart_id is set
#endif

Features that aren't configured are never compiled into the firmware, keeping the binary as small as possible.