# Local dev stack: real Home Assistant talking to a MockPanel running on # the host. Lets you click around the UI and grab screenshots without a # physical Omni controller. # # make dev-up # start # make dev-logs # tail HA logs # make dev-down # stop and clean # # On every container start the HA service pip-installs the local # `omni-pca` library from ../ into site-packages (the version pinned in # the integration manifest isn't on PyPI yet, and we want our latest # v1/ subpackage available either way). Source changes in src/omni_pca # require a ``docker compose restart homeassistant`` to take effect. # # Once running, open http://localhost:8123 and: # 1. Onboard with any name / location. # 2. Settings -> Devices & Services -> Add Integration -> # "HAI/Leviton Omni Panel". # 3. Use one of: # Mock panel (TCP): # host host.docker.internal # port 14369 # transport TCP # controller_key 000102030405060708090a0b0c0d0e0f # Real panel (UDP, v1 wire protocol): # host # port 4369 # transport UDP # controller_key <32 hex chars from the panel's .pca file> services: mock-panel: image: ghcr.io/astral-sh/uv:python3.14-bookworm-slim working_dir: /tmp/mock volumes: - ../src:/tmp/mock/src:ro - ./run_mock_panel.py:/tmp/mock/run_mock_panel.py:ro # Mount the captured .pca fixtures read-only so the mock can # optionally seed its state from a real export. Set # OMNI_PCA_FIXTURE in dev/.env (or pass on the command line) to # activate; left unset, the mock uses the hard-coded sample. - /home/kdm/home-auto/HAI:/fixtures:ro environment: PYTHONPATH: /tmp/mock/src OMNI_PCA_FIXTURE: ${OMNI_PCA_FIXTURE:-} command: - sh - -c - "uv pip install --system --quiet cryptography && python /tmp/mock/run_mock_panel.py --host 0.0.0.0 --port 14369" ports: - "14369:14369" networks: - default homeassistant: image: ghcr.io/home-assistant/home-assistant:2026.5 container_name: omni-pca-dev-ha depends_on: - mock-panel volumes: - ./ha-config:/config - ../custom_components/omni_pca:/config/custom_components/omni_pca:ro # Make the whole library project (pyproject + src/ + dist/) available # so the entrypoint override below can pip-install from local source # before /init starts. This gives HA real dist-info for # ``omni-pca==2026.5.10`` (which isn't on PyPI yet) and ensures the # v1 subpackage is present. - ../:/opt/omni-pca-src:ro # Keep 8123 mapped on localhost for direct access during development; # public traffic comes in via caddy-docker-proxy on the `caddy` net. ports: - "8123:8123" extra_hosts: - "host.docker.internal:host-gateway" environment: - TZ=America/Boise networks: - default - caddy labels: caddy: juliet.warehack.ing caddy.reverse_proxy: "{{upstreams 8123}}" # HA uses WebSockets for the frontend (lovelace state updates, # config flow, etc.) so we need the streaming-friendly settings # from CLAUDE.md, otherwise caddy closes the socket every ~15s. caddy.reverse_proxy.flush_interval: "-1" caddy.reverse_proxy.transport: http caddy.reverse_proxy.transport.read_timeout: "0" caddy.reverse_proxy.transport.write_timeout: "0" caddy.reverse_proxy.transport.keepalive: 5m caddy.reverse_proxy.transport.keepalive_idle_conns: "10" caddy.reverse_proxy.stream_timeout: 24h caddy.reverse_proxy.stream_close_delay: 5s # HA's image entrypoint is /init (s6-overlay). We pre-install our # local library against site-packages so HA's manifest-requirement # check finds it, then exec /init normally. entrypoint: - sh - -c - | set -e pip install --quiet --no-deps --upgrade /opt/omni-pca-src exec /init # InfluxDB v2 + Grafana stack — kept inline rather than `extends:`-ing # ../grafana/docker-compose.yml so this file stays self-contained and # the named volumes get scoped to this compose project. The bundle # compose stays the canonical ship-to-users version; we share its # provisioning files via the volume mount on the grafana service. influxdb: image: influxdb:2.7-alpine container_name: omni-pca-dev-influxdb restart: unless-stopped environment: DOCKER_INFLUXDB_INIT_MODE: setup DOCKER_INFLUXDB_INIT_USERNAME: ${INFLUX_USERNAME:-admin} DOCKER_INFLUXDB_INIT_PASSWORD: ${INFLUX_PASSWORD} DOCKER_INFLUXDB_INIT_ORG: omni-pca DOCKER_INFLUXDB_INIT_BUCKET: ha DOCKER_INFLUXDB_INIT_ADMIN_TOKEN: ${INFLUX_TOKEN} DOCKER_INFLUXDB_INIT_RETENTION: 30d volumes: - influxdb-data:/var/lib/influxdb2 - influxdb-config:/etc/influxdb2 ports: - "8086:8086" networks: - default healthcheck: test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8086/health"] interval: 10s timeout: 3s retries: 5 start_period: 10s grafana: image: grafana/grafana:11.4.0 container_name: omni-pca-dev-grafana restart: unless-stopped depends_on: influxdb: condition: service_healthy environment: GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_PASSWORD} GF_AUTH_ANONYMOUS_ENABLED: "false" GF_USERS_ALLOW_SIGN_UP: "false" GF_LOG_LEVEL: warn INFLUX_URL: http://influxdb:8086 INFLUX_TOKEN: ${INFLUX_TOKEN} volumes: - grafana-data:/var/lib/grafana - ../grafana/provisioning:/etc/grafana/provisioning:ro ports: - "3000:3000" networks: - default - caddy labels: caddy: grafana-omni.juliet.warehack.ing caddy.reverse_proxy: "{{upstreams 3000}}" healthcheck: test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:3000/api/health || exit 1"] interval: 10s timeout: 3s retries: 5 start_period: 15s volumes: influxdb-data: influxdb-config: grafana-data: networks: caddy: external: true