A 24/7 controller for the Noo‑Psyche K7 Pro / K7 Mini aquarium light that runs on a Raspberry Pi, is reachable from your whole home LAN, updates itself over the air, and speaks Traditional Chinese.
Independent community project. Not affiliated with or endorsed by Noo‑Psyche. Built on bitbarista/k7‑led‑controller (MIT) — the ESP32 firmware, the desktop PC Bridge, the Android app and the shared web UI all still live in this repo, unchanged.
The upstream ESP32 controller joins the lamp's own Wi‑Fi AP as its only
network — which strands it on the lamp's private 192.168.4.x island,
unreachable from your home LAN. The K7's LAN mode (lamp joins your router)
is widely reported as flaky.
A Raspberry Pi has two network interfaces, so it sits on both at once:
K7 Pro AP Raspberry Pi home router / LAN
┌───────────┐ wlan0 ┌────────────────┐ eth0 ┌──────────────────┐
│192.168.4.1│◄──Wi-Fi────│ k7-pi-bridge │◄──cable──►│ any browser · HA │
│ :8266 │ (like a phone)│ :80 :8266 │ │ on your LAN │
└───────────┘ └────────────────┘ └──────────────────┘
- wlan0 → the lamp's AP — the Pi is the only client, so the lamp stays in
the mode that actually works. (
wlan0is hardened to never carry a default route: if the LAN cable is unplugged the Pi loses internet rather than black‑holing everything through the lamp.) - eth0 → your LAN — wired, rock‑solid.
- Open
http://<pi-hostname>/from any device on your network. - If the Pi is off, the lamp keeps running the last native schedule it was given, so a Pi outage never means a dark tank.
| Always‑on engine on the Pi | The full lighting engine (arduino/src/Effects.cpp ported to Go) runs as a systemd service: 24‑slot schedule interpolation, Feed / Maintenance timed overrides, Lunar (synodic + moonrise‑tracked), Siesta, Acclimation, Seasonal Shift — all the ESP32's runtime features, none of the ESP32 needed. Smooth Ramp toggles whether the engine drives the lamp live (~10‑min cadence) or the lamp runs the pushed schedule on its own while the engine stays dormant. |
| Over‑the‑air updates | Tag a release on GitHub → the Pi verifies it (SHA‑256), swaps it in atomically, and self‑restarts, with automatic rollback if the new build won't stay healthy. A "Check for updates" button and an Auto toggle live in the top bar (Auto is off by default). Applying prompts for confirmation, and POST /api/update/apply requires {"confirm":true,"tag":"<exact target>"} so a bare or replayed request can't trigger an update. Click the version chip for the full release history. |
| Traditional‑Chinese UI | A conservative overlay translates the basic UI text (Save, Read, Push, Apply, …); proper nouns are left alone. 中/EN switch in the top bar. |
| Per‑lamp profile storage | Saved profiles are keyed by the lamp's MAC (data/profiles/<lamp>/) — swapping or running two lamps never mixes them, and an OTA update never touches them. Existing profiles migrate automatically. |
| Live spectrum value table | An always‑open grid under the chart: type an exact % per hour per channel and the chart follows live; drag the chart and the numbers follow. Plus ±1h rotate and ±1% power nudge per channel. |
| Explicit apply | Nothing reaches the lamp until you press ⬆ Push — the master slider and Shift no longer auto‑write. The Push button shows a pulsing marker when there are unsent edits. |
| Wi‑Fi signal indicator | Live RSSI / quality to the lamp in the top bar, colour‑coded. |
| Raw 8266 proxy | :8266 on the LAN side forwards straight to the lamp, so the desktop PC Bridge or any protocol tool can drive it through the Pi. |
| Home Assistant (planned) | A REST surface + a small custom_components/k7_lamp integration. |
The upstream file tree (pc-bridge/, shared-ui/, arduino/) is never
edited — everything above is additive, in pi-bridge/, so this fork stays
mergeable with upstream.
For the upstream author and anyone evaluating this fork — the complete list of what is different, kept current with every release.
| File | Change |
|---|---|
README.md |
Rewritten for the Pi variant (this file). The only edited upstream file. |
pc-bridge/, shared-ui/, arduino/, android/ and the upstream tools/
scripts are byte-for-byte upstream. git diff upstream/master -- pc-bridge shared-ui arduino is empty.
| Path | What |
|---|---|
pi-bridge/ |
The entire Pi controller: own Go module, zero third-party dependencies. |
.github/workflows/pi-bridge.yml |
Cross-compiles linux/arm64, publishes a GitHub Release with SHA256SUMS on each pi-v* tag. |
tools/parity_check.py |
Diffs endpoint JSON between two controllers (ESP32 vs pi-bridge). |
tools/check_k7tcp_sync.py |
CI guard: the lamp-protocol code vendored into pi-bridge/ still matches pc-bridge/internal/k7tcp/. |
tools/check_httpapi_sync.py |
Advisory drift report for the vendored HTTP server. |
homeassistant/k7_lamp/ (planned) |
Home Assistant custom integration. |
| Source (upstream) | Copy (in pi-bridge/) |
Delta |
|---|---|---|
pc-bridge/internal/k7tcp/client.go |
pi-bridge/internal/k7tcp/client.go |
One line: net.JoinHostPort in connect() instead of fmt.Sprintf("%s:%d"), to silence a Go 1.27 vet warning and handle IPv6. check_k7tcp_sync.py applies the same transform to the upstream file before diffing, so real drift is still caught. |
pc-bridge/internal/bridge/server.go |
pi-bridge/internal/httpapi/server.go |
+~70 lines, header-documented: New(Options) injects identity + all 18 capability flags (upstream hard-codes both); added getters (StateSnapshot, Device, …) for the always-on engine. |
arduino/src/Effects.cpp, Moon.cpp |
pi-bridge/internal/engine/ |
Ported C++ → Go, math-for-math, with golden-vector tests. |
arduino/src/Presets.h |
pi-bridge/internal/httpapi/presets.json |
Generated by upstream's own tools/generate_pc_bridge_presets.py. |
- Smooth Ramp is the "who drives the lamp" switch. Off (default): ⬆ Push sends the whole 24-slot schedule to the lamp once (0x1007) with every effect folded in as a snapshot for today — acclimation, seasonal shift, tracked lunar, siesta, master — and the engine then goes dormant; the lamp runs the schedule itself. On: the engine drives the lamp live, recomputing the interpolated output every ~10 minutes and pushing on change. Either way a Feed/Maintenance timer still works, and the lamp is handed back its own schedule when the timer ends.
- Read pulls the schedule from the lamp (live
readAll) on this platform too — upstream only does that forpc-bridge. - Push is explicit — the master slider and Day-shift stage changes locally and only reach the lamp on ⬆ Push (upstream auto-pushes each change).
- Day-shift actually moves the Base schedule. The
◀ ▶Shift buttons rotate the real 24 rows on the Base chart, in place — the curve visibly moves, the chart stays on Base (upstream only bumps a counter the Base view never renders). The+Nhreadout is a running total that resets to+0hafter Push. Nothing is sent to the server as a separate shift parameter, so there is no double-shift. - Spectrum value table sits open under the chart (not collapsed) so the drag chart and the exact %-per-hour grid are visible together, columns aligned with the header.
- Hourly gridlines on the schedule chart (upstream rules only every 4h, where its labels are) — easier to read a time off the curve.
- "Checks" panel is live —
/api/warnings/statusreports real conditions (clock not set, lamp unreachable, weak Wi-Fi to the lamp, an all-zero schedule that would leave the tank dark, a failed write). Upstreampc-bridgenever implemented the endpoint, so the panel was always empty. - Settings page (
setup_portal) — a ⚙ modal in the top bar for lamp model / IP / port, timezone + lat/lon, update channel, plus Restart service and Factory reset (both confirmed). The ESP32's Wi-Fi onboarding portal has no shared-UI panel; this is pi-bridge's equivalent. With it, all 18 capability flags aretrue— full 1:1 with the ESP32. - Today's lamp-write counter in the top bar —
auto(engine) vsmanual(your Push / Preview), reset at local midnight and persisted todata/writes.jsonso a restart or a mid-day OTA doesn't zero it. - System monitor — a 📊 button in the top bar opens a live view of the Pi: CPU load, RAM, SoC temperature, disk, the Go process (RSS / heap / goroutines), engine state and lamp-link health, refreshed every 5 s.
- Soak endpoint —
GET /api/diagreturns that same snapshot (plus CPU load, mem, temp, disk) and the tail of an hourlydata/soak.log, for reviewing a multi-day unattended run. - "Checks" shows an all-clear — a green "✓ no issues detected" instead of a
blank card when
/api/warnings/statusis empty. - Applying an update needs only
{confirm:true}—tagis advisory; if a newer release appeared since the page loaded, that newer one is installed and the response names it. /api/lamp/readretries and is frame-aware (k7tcp.ReadAllRobust) — it re-asks up to 4× and returns the moment a fullAB AA … BBframe decodes, instead of upstream's single shot + fixed 5 s drain. On a real K7 Pro over weak Wi-Fi this took a read from ~5 s to ~1 s and removed spurious502s.- One process clock —
time.Localis pinned to the configured timezone at startup, so the wall time the engine schedules against and the H:M:S bundled into every lampSyncTime/PushSchedulealways agree (a stock headless Pi OS is UTC; the "Checks" panel warns if the OS zone still disagrees). - Lamp clock upkeep — every ⬆ Push bundles the time; on top of that the engine re-syncs the clock once a day at ~04:00 and immediately after the lamp link recovers (a power-cycled lamp gets its clock back in seconds). The daily pass also reads the lamp's stored schedule and re-pushes if it drifted from what pi-bridge last sent (never a blank schedule over a real one).
- Model auto-detect — the lamp's own name (
k7_…/k7m…) sets K7 Pro (6-channel) vs K7 Mini (3-channel) on every Read; a wrongdevicein config is corrected automatically. - Configurable Smooth Ramp cadence — the ⚙ settings page has a "send every N minutes" field (2–60, default 10) for when Smooth Ramp is on.
- OEM factory curves —
preset:oem-sps/-lps/-slare the Noo-Psyche app's built-in "Factory Settings" schedules, transcribed from the APK, for a known-good starting point. - All 18 capability flags are advertised
true, so the shared UI shows every control (upstreampc-bridgehides 9).
- Builds and ships
linux/arm64only — Raspberry Pi 3B / 3B+ / 4 / 5 / Zero 2 W / CM3+ on a 64-bit OS. 32-bit models (Pi 1, Pi 2, Zero / Zero W) are out of scope.
Debian (Bookworm / Trixie), arm64, dual‑homed: eth0 on your LAN, wlan0
joined to the lamp's AP (K7_Pro…, PSK 12345678).
git clone https://github.com/cp296944/k7-led-Raspberry-controller
cd k7-led-Raspberry-controller
sudo pi-bridge/deploy/install.sh # bootstraps the binary from the latest release
sudo pi-bridge/deploy/setup-network.sh # wlan0 never-default hardening (optional, recommended)Then open http://<pi-hostname>/ from any device on your LAN. From then on the
service updates itself from this repo's GitHub Releases (via the top‑bar button;
Auto is off by default).
pi-bridge/deploy/uninstall.sh removes it (keeps data/ unless --purge).
See pi-bridge/README.md and
pi-bridge/docs/DESIGN.md for the internals, and
pi-bridge/PROGRESS.md for live status.
Everything the upstream shared UI offers works here — the Pi simply advertises every capability as available:
- Read the current schedule and mode from the lamp; edit the 24‑hour schedule on a drag‑and‑drop chart or the live value table
- Additive colour‑preview strip; Effective Today view; Right Now output bars backed by the engine's real computed output; schedule‑aware checks
- Built‑in preset library (Fish Only, LPS/SPS/Mixed/Soft Reef, Acclimation, Shallow SPS, Dino Suppression, …); named profiles saved per‑lamp
- Master brightness + per‑channel brightness‑cap sliders; per‑channel visibility toggles; type‑exact value entry; Day‑shift to slide the whole photoperiod
- Manual mode with live preview
- Smooth Ramp — on: the Pi drives the lamp live, recomputing the interpolated output every ~10 min and pushing on change. Off (default): the Pi pushes the full schedule once and the lamp runs it itself
- Feed mode — timed white boost, 1–100 % / 1–60 min
- Maintenance mode — timed balanced inspection light, 1–100 % / 1–180 min
- Lunar — royal‑blue over the 29.5‑day synodic cycle, fixed or moonrise‑tracked window, night clamp, schedule‑aware cutoff
- Siesta — midday dimming window
- Acclimation — start dimmer, recover over N days
- Seasonal Shift — move the photoperiod earlier/later across the year
- Preset export/import, community profiles, backup export/import
- K7 Mini (3 channels) and K7 Pro (6 channels)
| Variant | Platform | Always‑on |
|---|---|---|
| ESP32‑S3 controller | phone / browser | yes — but stranded on the lamp's AP |
| PC Bridge | Windows, Linux | no — schedule push only |
| Android app | Android | no — push + Feed/Maintenance widget |
Upstream setup and flashing guides: bitbarista.github.io/k7-led-controller/guide.html.
- The lamp accepts one TCP connection at a time and has no locking; the engine, the web API and the proxy share a single mutex so they never collide.
- Threat model. The K7 protocol on
:8266is unauthenticated plaintext — anyone who can open a socket to the lamp has full control (this is the vendor design, not something the bridge adds). The bridge's own HTTP API and the raw:8266proxy also carry no auth. On a normal home LAN behind a router that's the same exposure the OEM app has; do not port‑forward:80or:8266to the internet. If you must reach the bridge off‑box, put it behind your own authenticated reverse proxy / VPN. The bridge never writes the lamp's Wi‑Fi credentials and keepswlan0never-default, so a compromised bridge can't pivot onto your LAN through the lamp link. - The Noo‑Psyche lamp firmware is closed, so we can't verify how it manages flash write‑wear for the continuous luminance commands. Smooth Ramp writes more often — it's off by default here for that reason.
- Applying a change takes ~1 s (a full TCP round‑trip); rapid changes are coalesced to the latest value. This is the lamp protocol, not a bug.
masterbranch is protected (no force‑push, no deletion).
MIT, same as upstream. Enormous credit to bitbarista for the reverse‑engineered protocol, the shared UI, and the whole original project — if it helps your reef, support them on Ko‑fi.