Self-hosted pool control for Pentair panels. Everything on your Raspberry Pi. Nothing in anyone's cloud.
Moonpool is a full replacement for Pentair ScreenLogic: a fast, beautiful, phone-first web app that sits on top of nodejs-poolController (njsPC) and adds authentication, roles, automations, history, alerts, voice control, and an AI copilot. Users, history, scenes, chat threads, the audit log — all of it lives in a single SQLite file on the Pi.
njsPC does the heroic work of speaking Pentair's RS-485 protocol — but it has no authentication and its bundled UIs aren't built for handing to your family or opening to the internet. Moonpool is the security and experience layer: the browser never talks to njsPC directly; every request goes through Moonpool's session- and role-checked API, and every state change is audited.
- Night-swim UI — deep teal-navy glass panels over animated water caustics, liquid-fill temperature dials that ripple while heating, spring physics everywhere, OLED-black and light themes, custom accent colors. Mobile-first PWA: install to your home screen, offline last-known state.
- Everything ScreenLogic did, better — circuits & features, thermostat dials with heat modes, IntelliFlo RPM control with live watts, IntelliChlor output/salt/super-chlorinate, IntelliBrite themes + saved light combos, schedule CRUD with a visual week view and conflict warnings.
- Discovered, not hardcoded — the UI renders whatever bodies, circuits, pumps, chlorinators, light groups and chem controllers your panel reports.
- Roles — Owner / Family / Guest with per-circuit and per-scene guest visibility. Local accounts, no cloud sign-ups.
- Automations — time, cron, sunrise/sunset offsets, temperature thresholds, salt low, freeze protect, controller events. Scenes, schedules, one-shot timers and the copilot all ride the same engine.
- Pool Copilot — natural language in, structured tool calls out. Three switchable brains: local Ollama (private, free), OpenAI API key, or Sign in with ChatGPT (Codex-style OAuth, uses your subscription). The model only parses intent — Moonpool validates, bounds-checks, role-checks, confirms first, and audits. Plus a UniFi-style ask bar on the dashboard.
- Weather-aware — keyless Open-Meteo forecasts power confirmations like "Heat the pool? Rain is forecast tomorrow 3–4 PM (80%)", an evaporation-based water-level estimator ("down ~1.4 in, no rain coming — add water"), and freeze/heat advisories. Optional WeatherFlow Tempest integration (local UDP): measured rain, on-site wind, and lightning "out of the pool" alerts.
- Voice — Siri Shortcuts (5-minute setup, no dev account) and a private Alexa custom skill, both with token auth and full auditing.
- History & insight — temperature/pump/salt/chemistry charts with daily rollups, equipment runtime and energy cost at your $/kWh, chemistry logging with dosing math sized to your pool volume.
- Alerts — self-hosted web push (no push provider account): faults, freeze protect, salt low, chemistry out of range, spa ready, controller offline, water level low, lightning.
![]() |
![]() |
One line on your Pi (or any Linux box with Docker — the installer offers to install Docker for you):
curl -fsSL https://github.com/v6iox/poolsystem/releases/latest/download/install.sh | bashAn interactive, arrow-key installer walks you through everything: real pool
vs. demo simulator, RS-485 adapter auto-detection, timezone & location, the
copilot's AI brain, optional remote access — then builds the stack and hands
you the URL. Re-run it any time to reconfigure; install.sh --update
upgrades in place.
git clone <this repo> moonpool && cd moonpool
npm install && cp .env.example .env.local && npm run devOpen http://localhost:3000 — MOCK_MODE boots a full pool simulator (pool +
spa, 10 circuits, VS pump, chlorinator, color lights, drifting temperatures)
so every screen, the copilot, and the weather features work with zero
equipment.
→ The complete setup guide covers everything: hardware list, flashing the Pi, RS-485 wiring, Docker bring-up, phone install, remote access without a VPN app (Cloudflare Tunnel / Tailscale Funnel), push notifications, copilot brains, Siri & Alexa, Tempest, backups, and running alongside ScreenLogic during the transition.
The short version:
ssh pi@moonpool.local
curl -fsSL https://get.docker.com | sh
git clone <this repo> moonpool && cd moonpool
cp .env.example .env && nano .env # AUTH_SECRET, TZ, lat/long, MOCK_MODE=false
docker compose up -d --build
docker compose exec ollama ollama pull qwen3:1.7b ┌────────────────────────── Raspberry Pi ──────────────────────────┐
│ │
Pentair panel ──RS-485──▶ USB adapter ──▶ njsPC (no auth, internal only) │
│ ▲ REST + Socket.IO │
│ │ │
Phone/laptop ◀──HTTPS──▶ Moonpool (Next.js) │ │
▲ │ ├─ session auth (scrypt + SQLite) │
│ │ ├─ validated control layer → audit log │
Cloudflare │ ├─ SSE state bridge (role-filtered) │
Tunnel / │ ├─ automations worker (cron·sun·thresholds·events) │
Tailscale │ ├─ history sampler + rollups ┌────────────┐ │
Funnel │ ├─ web-push alerts │ SQLite │ │
│ ├─ Tempest UDP listener │ moonpool.db│ │
│ └─ copilot engine ──▶ Ollama/OpenAI └────────────┘ │
└──────────────────────────────────────────────────────────────────┘
Design decisions worth knowing:
- No external database. SQLite (
better-sqlite3, WAL). The idempotent schema (src/server/db/schema.sql) applies itself at boot — that file is the migration. - One action vocabulary. Every button, scene, automation, schedule, voice
command and copilot plan produces the same typed
PoolActions, executed by one validation + audit path (src/server/control.ts). - Server-held realtime. The server owns the single Socket.IO connection to njsPC and fans role-filtered snapshots out to browsers over SSE. Optimistic UI with automatic rollback on every control.
- AI can't touch hardware. Any model — local or cloud — only emits tool calls. Bounds checks (setpoints 60–104 °F), circuit whitelisting, role rules, confirmation-first cards and auditing all happen in Moonpool.
Moonpool can dial out to Moonpool Cloud (moonpool-cloud, a separate,
privately-run service) for remote access from the mobile app when you're away
from home — device claiming, request relay, and push fan-out ride a single
outbound WebSocket the Pi opens to the cloud (see CONTRACT.md §3 for the
wire protocol, src/server/cloudlink/ for the client).
This is strictly opt-in, and off by default. Leaving the four
MOONPOOL_CLOUD_* variables unset (or MOONPOOL_CLOUD_ENABLED=false) means
Moonpool never attempts a connection, never imports the tunnel code path at
runtime beyond a no-op check, and every local feature — the dashboard,
automations, alerts, the copilot — keeps working exactly as if the cloud
didn't exist. The cloud is never load-bearing: even once connected, no local
request ever waits on it, and losing the connection degrades to "no remote
access" with zero effect on LAN use. A self-hosted install stays fully local
until someone deliberately turns this on.
Remote requests never bypass local auth. A relayed phone request carries a
short-lived, signed actor assertion (CONTRACT §12), not a raw user id. Moonpool
verifies it — signature, expiry (60s, hard-capped at 120s regardless of what
the token claims), replay, and that it's scoped to this exact device — maps it
to the local account it's linked to, and only then mints a normal, ephemeral,
single-request local session for that user. Nothing in the app's own route
guards ever reads the raw header; the local HTTP surface can't be tricked by
anyone on the LAN into acting as someone else, and a cloud "staff" claim never
grants any local permission — see src/server/cloudlink/actor-*.ts.
Any physical interface cable is the "Moonpool Link cable, compatible with
Pentair® EasyTouch®/IntelliTouch®/IntelliCenter®" — never "a Pentair cable"
or "Pentair Moonpool". Nominative use only; Moonpool is not affiliated with
or endorsed by Pentair. See docs/AGPL-BOUNDARY.md for the njsPC licensing
boundary this project depends on.
- njsPC and Ollama are never published on host ports — only Moonpool is.
- scrypt password hashing, httpOnly session cookies, login rate limiting.
- Guests only see/touch what an Owner explicitly shares.
- Voice endpoints use single-purpose revocable tokens (hash-stored); the Alexa endpoint additionally verifies Amazon's request signatures.
- Secrets (VAPID keys, API keys, OAuth tokens) live only in the Pi's SQLite.
npm run dev # MOCK_MODE dev server
npm run typecheck # strict TS, no `any`
npm test # includes the 30-utterance copilot eval (mock parser)
npm run build # production build (standalone)COPILOT_LIVE=true npm test additionally runs the eval against a live LLM
backend. PRs welcome — keep the design language (tokens in
src/app/globals.css) and add loading/empty/error/offline states to anything
you build.
- nodejs-poolController by @tagyoureit and contributors — the foundation this stands on. Moonpool shares no code with it; it drives njsPC's REST + Socket.IO API. Go star it.
- Open-Meteo — keyless weather + evapotranspiration.
- WeatherFlow Tempest — UDP broadcast reference for the local station integration.
- Next.js, Tailwind, Motion, Radix, Recharts, TanStack Query, dnd-kit, better-sqlite3, Ollama.
This software controls physical equipment — heaters, pumps, valves, chlorinators. It is provided as is, without warranty of any kind. Keep your panel's built-in safeties in place, supervise early use, and understand that you run it at your own risk. Not affiliated with Pentair, OpenAI, Amazon, Apple, or WeatherFlow.


