Skip to content

Latest commit

Β 

History

31 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Metro Olografix β€” Mesh Planner

A professional, collaborative Meshtastic node deployment planner for the Metro Olografix association. Combines high-fidelity RF propagation modeling with real-time collaboration to build a resilient communication mesh in Abruzzo, Italy.

Main Map View with Coverage Overlays

πŸš€ Key Features

Feature Description
πŸ§ͺ Coverage Tester Public /try wizard β€” anyone can simulate node coverage without signing up. No data stored.
πŸ“‘ Node Management Add, edit, and delete nodes with granular control over RF parameters, height, and equipment.
πŸ“ˆ Deployment Workflow Lifecycle management for nodes: Draft (private), Planned, and Deployed.
🌍 RF Propagation Per-node SPLAT! simulations (ITM model) rendered as GeoTIFF overlays.
πŸ™οΈ Auto-Clutter ESA WorldCover 2021 (10 m) land-cover data for automatic obstacle height estimation.
πŸ›£οΈ Max-SNR Pathfinder Aβ†’B routing that maximises the bottleneck SNR across the best possible relay hops.
πŸ”„ Collaboration Live Activity Feed and instant SSE synchronisation for all connected members.
πŸ›‘οΈ Auth & Privacy OIDC/OAuth2 with PKCE (any provider). Optional public read-only view with coordinate fuzzing.
βš™οΈ Job Queue Asynchronous rendering with progress tracking and one-click global recompute.

πŸ› οΈ Local Development

Requirements: Docker, Docker Compose v2, an OIDC-compliant identity provider.

cp .env.example .env
# Fill in OIDC_ISSUER, OIDC_CLIENT_ID, POSTGRES_PASSWORD
docker compose up
  • Frontend (Vite + HMR): http://localhost:5173
  • Backend API: http://localhost:8000

The compose file builds the backend from source and runs the frontend with the Vite dev server. It is not intended for production β€” use the Helm chart below.


☸️ Production Deployment (Kubernetes / Helm)

Pre-built images are published to GHCR on every push to main:

  • ghcr.io/metro-olografix/mesh-planner-backend:latest
  • ghcr.io/metro-olografix/mesh-planner-frontend:latest

The Helm chart in hack/helm/mesh-planner/ deploys the backend, frontend, PostgreSQL, and an nginx Ingress in a single namespace.

Requirements: Kubernetes 1.24+, Helm 3.10+, nginx Ingress controller, cert-manager (optional, for TLS).

1. Create a values override file

# my-values.yaml
postgresql:
  auth:
    password: "your-strong-password"    # required

zitadel:
  domain: https://auth.example.com      # required β€” OIDC issuer base URL
  clientId: "your-oidc-client-id"       # required

ingress:
  host: mesh.example.com                # required β€” public hostname
  tls:
    enabled: true
    certManager:
      enabled: true
      clusterIssuer: letsencrypt-prod

The images default to ghcr.io/metro-olografix/mesh-planner-*:latest β€” no override needed unless self-hosting images.

2. Install

helm install mesh-planner hack/helm/mesh-planner \
  --namespace mesh-planner --create-namespace \
  --values my-values.yaml

3. Upgrade

helm upgrade mesh-planner hack/helm/mesh-planner \
  --namespace mesh-planner \
  --values my-values.yaml

Key chart values

Value Default Description
backend.storage.splatTiles.size 10Gi Terrain tile cache PVC size
backend.storage.customAssets.enabled false Enable PVC for custom logo/favicon
ingress.className nginx Ingress class name
publicAccess.enabled false Unauthenticated read-only mode
logLevel INFO Backend log verbosity

The Ingress is pre-configured with proxy-buffering: off and extended timeouts β€” both required for the SSE stream on /api/events.

Using a private registry or pinned image tag
# my-values.yaml (additional fields)
imagePullSecrets:
  - name: regcred

backend:
  image:
    registry: your-registry.example.com
    repository: your-org/mesh-planner-backend
    tag: "abc1234"

frontend:
  image:
    registry: your-registry.example.com
    repository: your-org/mesh-planner-frontend
    tag: "abc1234"

Create the pull secret beforehand:

kubectl create secret docker-registry regcred \
  --namespace mesh-planner \
  --docker-server=your-registry.example.com \
  --docker-username=<user> \
  --docker-password=<token>

πŸ” Authentication

Authentication uses the OpenID Connect (OIDC) Authorization Code + PKCE flow and works with any standards-compliant identity provider.

Setting up your IdP

Create a Single Page Application (public client) in your IdP with:

  • Redirect URI: https://your-domain/callback
  • Post-logout redirect URI: https://your-domain/
  • Grant type: Authorization Code
  • Scopes: openid profile email

No client secret is needed (PKCE public client).

Zitadel-specific notes

Zitadel places a project resource ID in the JWT aud claim rather than the OAuth client ID. Leave OIDC_AUDIENCE empty β€” the backend skips audience verification in this case, relying on signature + issuer + expiry checks instead.

For all other providers (Auth0, Okta, Keycloak), set OIDC_AUDIENCE to your API audience or client ID as required by the provider.


🌐 Public Read-Only Mode

When PUBLIC_ACCESS=true, unauthenticated visitors can view the map without logging in.

What they see What is hidden
Node names, statuses, hardware type Exact coordinates (fuzzed Β±500 m)
Live SSE updates Notes, creator identity
Activity feed (node names and authors anonymised) Coverage GeoTIFF overlays
β€” Coverage job status
β€” Jobs tab
β€” Path planner

Coordinates are fuzzed server-side using a deterministic algorithm (djb2 hash of the node ID), so the real position is never transmitted to unauthenticated clients. The same algorithm is used client-side for the authenticated privacy-mode toggle.


πŸ§ͺ Coverage Tester (/try)

The /try page lets anyone β€” no account required β€” simulate LoRa node coverage in a guided 4-step wizard.

Step What happens
1. Location Click the map to drop a pin anywhere in the world.
2. Device Pick from the full hardware catalogue. Recommended devices are highlighted.
3. Setup Set antenna height, surrounding environment, and a Fast / Balanced / Long Range preset.
4. Results A SPLAT! simulation runs server-side (~10–15 s) and the coverage overlay is displayed.

Results include a shareable link that encodes all settings as URL query parameters β€” opening it re-runs the simulation automatically.

Endpoint details

POST /api/try/simulate β€” fully public, no authentication.

Constraint Default Env variable
Max radius 3 km TRY_MAX_RADIUS_KM
Resolution Standard (90 m / 3-arcsecond) β€”
Persistence None β€” GeoTIFF returned directly β€”
Rate limit 6 / IP / hour TRY_RATE_LIMIT_PER_HOUR
Concurrency 2 simultaneous SPLAT! processes TRY_MAX_CONCURRENT

Rate limiting is in-process. For multi-instance deployments, replace the in-memory counter with a shared Redis store.


🎨 Custom Branding

Drop files into the custom/ directory at the project root to override the default logo and favicon. No rebuild required.

File Description
custom/logo.png (or .svg, .webp, .jpg) Replaces the πŸ“‘ emoji in the navbar
custom/favicon.ico (or .png, .svg) Replaces the browser tab icon

Docker Compose: the custom/ directory is bind-mounted read-only into the backend container. Replace the files and restart the backend to apply changes.

Kubernetes: enable the custom assets PVC in your values file, then copy assets into the pod:

backend:
  storage:
    customAssets:
      enabled: true
      size: 50Mi
kubectl cp custom/ mesh-planner/<backend-pod>:/app/custom/

βš™οΈ Configuration Reference

All settings are read from .env (Docker Compose) or environment variables injected by Helm.

Variable Required Description
POSTGRES_PASSWORD βœ… PostgreSQL database password
OIDC_ISSUER βœ… OIDC provider base URL, e.g. https://auth.example.com (no trailing slash)
OIDC_CLIENT_ID βœ… OAuth client ID of the SPA application
OIDC_AUDIENCE β€” Enforce aud claim verification (leave empty for Zitadel)
PUBLIC_ACCESS β€” true to allow unauthenticated read-only access (default: false)
CORS_ORIGINS β€” Comma-separated allowed origins (default: http://localhost:5173)
LOG_LEVEL β€” DEBUG / INFO / WARNING / ERROR (default: INFO)
SPLAT_PATH β€” Path to SPLAT! binaries (default: /app)
TILE_CACHE_DIR β€” Terrain tile cache directory (default: /app/.splat_tiles)
TILE_CACHE_GB β€” Maximum terrain tile cache size in GB (default: 2.0)
TRY_RATE_LIMIT_PER_HOUR β€” Max /try simulations per IP per hour (default: 6)
TRY_MAX_RADIUS_KM β€” Coverage radius cap for /try simulations in km (default: 3.0)
TRY_MAX_CONCURRENT β€” Max simultaneous SPLAT! processes on /try (default: 2)

πŸ—οΈ Architecture & How It Works
graph TD
    UI[Vue 3 Β· Pinia Β· Leaflet] <-->|REST + SSE| API[FastAPI Backend]
    API <-->|SQL| DB[(PostgreSQL)]
    API -->|Spawn| SPLAT[SPLAT! Binary]
    SPLAT <-->|Local Cache| SDF[.SDF Terrain Tiles]
    SDF <-->|Fetch| AWS[AWS Open Data S3]
    API -->|Fetch| ESA[ESA WorldCover S3]
Loading

Node Configuration and Path Planning

Deployment Lifecycle & Privacy

  1. Draft: Private to the creator. Experiment with locations without cluttering the map.
  2. Planned: Shared with all members. Used for proposing new node sites and coordinating hardware.
  3. Deployed: Marked as live. These nodes are used by the Path Planner as potential mesh relays.

RF Propagation Model (SPLAT! + ITM)

Uses the Irregular Terrain Model (ITM / Longley-Rice) β€” path loss over irregular terrain, accounting for diffraction, ground reflections, and atmospheric bending.

  • Model: olditm (Standard ITM, preferred for Meshtastic frequencies).
  • Auto-Invalidation: If any RF-relevant field (lat, lon, hardware, antenna height, etc.) changes, the coverage cache is automatically marked stale and requires a re-run.

Intelligent Clutter Modeling

When a node's environment is set to auto, the backend queries ESA WorldCover 2021 Cloud-Optimized GeoTIFFs on S3, sampling the 10 m land-cover class and applying realistic ground clutter offsets.

Path Planning Algorithm

Finds the path where the worst-case SNR among all hops is as high as possible:

  • Bidirectional Validation: For every hop Aβ†’B, checks that B can also reach A.
  • Dijkstra Optimization: Maximises the bottleneck link quality.
  • Friis Penalty: Free-space hops (no SPLAT data) receive a 15 dB penalty to prefer terrain-verified paths.

πŸ—ƒοΈ Database Migrations

Schema changes are managed with Alembic. Migrations run automatically on startup (alembic upgrade head).

Create a new migration after modifying SQLAlchemy models:

docker compose exec backend alembic revision --autogenerate -m "description"

Inspect migrations:

docker compose exec backend alembic upgrade head   # apply all pending
docker compose exec backend alembic current        # show current revision
docker compose exec backend alembic history        # list all revisions

Existing deployments upgrading from a pre-Alembic version should stamp the database first:

docker compose exec backend alembic stamp head

On Kubernetes:

kubectl exec -n mesh-planner deploy/mesh-planner-backend -- alembic upgrade head

πŸ”„ CI/CD

A GitHub Actions pipeline (.github/workflows/ci.yml) runs on every push and pull request to main:

Job Trigger Description
lint push + PR ruff check and ruff format --check on backend/
test push + PR pytest against a PostgreSQL service container
docker-build PR only Validates both Dockerfiles build successfully
docker-publish push to main Builds and pushes images to GHCR

Published images:

  • ghcr.io/metro-olografix/mesh-planner-backend:<sha> / :latest
  • ghcr.io/metro-olografix/mesh-planner-frontend:<sha> / :latest

πŸ“» LoRa Presets & Hardware

The planner calculates the thermal noise floor as: βˆ’174 dBm + 10Β·log₁₀(BW_Hz) + NF_dB.

Preset SF BW (kHz) Floor (dBm) Min SNR (dB)
SHORT_FAST 7 500 βˆ’114 -7.5
SHORT_SLOW 8 250 βˆ’120 -10.0
MEDIUM_FAST 9 250 βˆ’121 -12.5
MEDIUM_SLOW 10 250 βˆ’121 -15.0
LONG_FAST 11 250 βˆ’121 -17.5
LONG_SLOW 12 125 βˆ’124 -20.0
VERY_LONG_SLOW 12 125 βˆ’124 -20.0

The hardware database contains optimised parameters for over 28 devices, including LilyGo (T-Beam, T-Echo, T-Deck), Heltec, RAKwireless, and Seeed Studio. Custom antenna gains can be set per-node to override hardware defaults.

Releases

Used by

Contributors

Languages