Files
James Bateman (hp-laptop)andClaude Opus 4.8 c2a004d6f1 Rename project to butterfly-edfa-rack; broaden descriptions beyond Koheron
The repo bridges both instruments (Koheron CTL200 seed + Agiltron M511
EDFA), so 'koheron-esp32-bridge' was too narrow. Align the repo name and
descriptions to the identifier the whole stack already uses
(butterfly_edfa_rack): README title, Makefile/platformio headers, and the
homeassistant/ copies. Device id, MQTT topics and entity ids are
unchanged, so no reflash or lost HA history. Sibling HA repo renamed to
butterfly-edfa-rack-homeassistant.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-10-02 16:33:51 +01:00

139 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# butterfly-edfa-rack-firmware
ESP32 firmware that bridges the **Butterfly EDFA Rack** — a 1550 nm MOPA: a [Koheron CTL200](https://www.koheron.com/photonics/ctl200-digital-laser-controller/) driving a DFB seed into an Agiltron M511 EYDFA power amplifier (output up to ~1 W / 30 dBm) — to WiFi/MQTT for monitoring and control from Home Assistant (broker `192.168.0.102:1883`, anonymous).
The whole rack is presented as a **single HA device** (`butterfly_edfa_rack`). Topics, mirroring the lab's EDFA→MQTT convention:
```
lab/laser/butterfly_edfa_rack/availability online | offline (LWT)
lab/laser/butterfly_edfa_rack/diag/<key> ESP/network diagnostics
lab/laser/butterfly_edfa_rack/seed/<key> Koheron CTL200 (increment 3)
lab/laser/butterfly_edfa_rack/amp/<key> Agiltron M511 (increment 4)
lab/laser/butterfly_edfa_rack/set/<...> commands (increment 5)
```
HA discovery configs are published under `homeassistant/...`, all sharing one `device` block so entities group under the single rack device.
## Optical architecture
The rack is a **MOPA** (master-oscillator power-amplifier) at 1550 nm:
```
Koheron CTL200 Agiltron M511 EYDFA (two-stage)
┌──────────────┐ ┌─────────────────────────────────────┐
│ DFB1550LP │ fibre │ pre-amp stage ──► booster stage │ fibre
│ seed diode ├─────────►│ (pump diode A) (pump diode B, ├────────► output
│ + TEC │ │ ~170 mA, ~16 dBm "pump-1") │ (up to ~1 W
└──────────────┘ └─────────────────────────────────────┘ / 30 dBm)
ilaser, lason input PD preamp PD output-1 PD
```
**The M511 has two internal pump diodes**, and the firmware reads both:
- **Pre-amp pump** — runs semi-independently at ~170 mA, lifts the seed to ~16 dBm (`amp/preamp_current_ma`, `amp/preamp_power_dbm`).
- **Booster pump = "pump-1"** — the controllable output stage (`amp/pump1_current_ma`, set via `set/amp/pump1_current_ma`).
- **"Pump-2"** is a second booster channel that this unit leaves at 0 mA regardless of settings (per `Lab/Equipment/Lasers/Agiltron EDFA`); we don't drive it. It is read but not exposed as a control.
Run the booster in **ACC (constant-current)** mode — APC stepping quantises the output. Below transparency the booster erbium fibre *absorbs* the pre-amp output, so output stays at the PD floor (−60 dBm) until the pump is high enough.
### Pump-1 vs output characterisation (2026-06-17)
Two sweeps. The transparency sweep used ~6 mW (7.8 dBm) seed into the EDFA; the high-power sweep added a 3 dB pad (input 3.2 dBm, within the ≤5 dBm benchtop spec) and a direct output-to-power-meter connection.
| Pump-1 | Output-1 (internal PD) | note |
|-------:|------------------------|------|
| 500–900 mA | −60 dBm | below transparency, signal absorbed |
| 1200 mA | 21.0 dBm | booster turns on |
| 1500 mA | 23.1 dBm | |
| 2000 mA | 25.9 dBm | |
| 2500 mA | 27.1 dBm / 514 mW | documented 0.5 W operating point |
| 3000 mA | 28.3 dBm / 679 mW | |
| 3500 mA | 29.3 dBm / 857 mW | |
| 4000 mA | 30.1 dBm / 1026 mW | **1 W rated ceiling** |
| 4500 mA | 30.85 dBm / 1216 mW | over-rating; curve still ~linear (not saturated) |
The unit (factory APC setpoint 30 dBm) comfortably exceeds 1 W: 1.22 W at 4500 mA with the output still climbing ~0.74 dB/500 mA and module temp only ~30 °C — so its true saturated maximum is higher, but driving a 1 W-rated pump diode past rating trades lifetime for diminishing return. Pump current overshoots ~2× briefly on each step up and settles in 2–4 s, so **ramp gradually** rather than jumping to the setpoint.
> **Internal PD vs external power meter:** at 4500 mA the internal PD reported 1.22 W while a Thorlabs meter measured **1.05 W** at the output (~0.6 dB high / residual loss). Treat the `amp/output*_power_dbm` telemetry as indicative, ~0.5–0.6 dB optimistic, not a calibrated power reading. (The earlier lab notes saw the opposite sign through a different connection, so the offset is path-dependent.)
> **Safety:** Class 4, >1 W, invisible 1550 nm. The firmware clamps pump-1 to a soft 4000 mA ceiling (≈ the 1 W rating, `PUMP_CURRENT_SOFT_MAX_MA`), liftable to the 8000 mA hard protocol max (`PUMP_CURRENT_HARD_MAX_MA`) only while the Pump Limit Override switch is on, and seed current to `SEED_CURRENT_MAX_MA` — guardrails against accidental over-drive, **not interlocks**. Above 1 W the Agiltron manual requires matched high-power connectors or a splice on the output, else the output fibre/amplifier can be damaged. Always drop the pump to 0 before unmating any fibre. Treat the output as hazardous unless it terminates in a power meter / beam dump.
## Hardware
Full parts list (seed, amplifier, supplies, fibre, wiring) is in [`BOM.md`](BOM.md).
- ESP32-D0WD-V3 dev board (WROOM-32 class), CP2102N USB bridge → `/dev/ttyUSB0`, MAC `b0:cb:d8:8f:ba:10`.
- The ESP32 USB port is used **only** for flashing/console (UART0).
- Two instrument links on separate hardware UARTs:
| ESP32 | Instrument | Protocol |
|----------------|-------------------------------------|----------|
| GPIO17 (TX2) → | Koheron CTL200 RX (terminal block) | ASCII, 115200 8N1, `\r\n`, `>>` prompt |
| GPIO16 (RX2) ← | Koheron CTL200 TX | |
| GPIO26 (TX1) → | Agiltron M511 RX (P2 pin 3) | binary 0x55AA packets, 115200 8N1 |
| GPIO25 (RX1) ← | Agiltron M511 TX (P2 pin 7) | |
| GND | both grounds | |
All links are 3V3 logic (CTL200 is 5V-tolerant). UART1 is remapped off its default flash-clashing pins.
> **Network:** the firmware pins a static IP `192.168.0.150` (gateway `.1`, /24) — see `USE_STATIC_IP` in `include/config.h`. This avoids the clash with the BKtel EDFA Pi on `.101` that a DHCP lease originally caused. `.150` is outside any device documented in the HA notes; move it if your DHCP pool overlaps.
## Toolchain
PlatformIO (Arduino framework), pinned in a project-local `.venv` so nothing is installed globally.
```bash
make # show targets
make build # compile
make upload # compile + flash (PORT=/dev/ttyUSB0 by default)
make monitor # serial console at 115200
make detect # identify the chip on the port
```
A fresh checkout self-bootstraps the venv on first `make`.
## Configuration
Copy `include/secrets.example.h` → `include/secrets.h` (git-ignored) and fill in WiFi + MQTT details. Needed from increment 2 onward.
## Increment roadmap
- [x] **1 — toolchain check.** Blink + serial heartbeat; confirms build/upload/monitor.
- [x] **2 — WiFi + MQTT + HA discovery.** Joins WiFi, connects to broker with an LWT availability topic, publishes diagnostic entities (uptime, RSSI, IP).
- [x] **3 — Seed telemetry (read-only).** UART2 → Koheron `lason`/`ilaser`/`vlaser`/`rtset`/`rtact`/`itec`/`vtec`/`tboard`/`iphd`/`err`; published under `seed/...` as HA sensors. Verified live against CTL200 fw v0.21 (echo + value + `>>` framing, parsed correctly).
- [x] **4 — Amp telemetry (read-only).** UART1 → Agiltron `get_status` (binary 0x55AA, ported from `edfa.py`); publishes module/pre-amp temp, pre-amp & pump-1 currents, input/pre-amp/output dBm, warnings under `amp/...`. Verified against the live unit (SN Y4022708, PN EDFA-1C51113331111, device ID 0x0000006F). `amp/pump_enabled` reads the authoritative `get_settings` field.
- [x] **5 — control.** `set/...` for seed (`lason`, `ilaser`) and amp (pump on/off, pump-1 ACC current) as HA switch/number entities, with firmware current clamps. Verified end-to-end: MQTT command → seed lasing → booster ramp to 27 dBm. Seed TEC setpoint (`rtset`, in ohms) is also settable, with a read-only °C readout derived from the thermistor. A **Pump Limit Override** switch (`set/amp/pump_override`) lifts the pump-1 clamp from the soft 4000 mA (1 W) guardrail to the 8000 mA protocol max for supervised over-rating; it defaults off on every boot and re-clamps a running pump back to 4000 mA when switched off. (Future: seed `tecon`, amp APC power, Koheron `save`.)
## MQTT interface
State topics retained under `lab/laser/butterfly_edfa_rack/`:
| Topic suffix | Meaning |
|--------------|---------|
| `availability` | `online` / `offline` (LWT) |
| `diag/{uptime,rssi,ip}` | ESP / network diagnostics |
| `seed/link` | Koheron reachable? |
| `seed/laser_enabled` | seed laser on (1/0) |
| `seed/current_ma`, `seed/voltage_v` | seed drive |
| `seed/rt_setpoint_ohm`, `seed/rt_actual_ohm` | thermistor setpoint / actual |
| `seed/temp_c` | seed diode temperature (from `rt_actual` via NTC β-model, indicative) |
| `seed/tec_current_a`, `seed/tec_voltage_v`, `seed/board_temp_c` | seed TEC / board |
| `seed/photodiode_ma`, `seed/error` | seed monitor PD, CTL200 error code |
| `amp/link` | Agiltron reachable? |
| `amp/module_temp_c`, `amp/preamp_temp_c` | EDFA temperatures |
| `amp/preamp_current_ma`, `amp/preamp_power_dbm` | pre-amp stage |
| `amp/pump1_current_ma`, `amp/pump1_set_ma` | booster pump actual / setpoint |
| `amp/input_power_dbm`, `amp/output1_power_dbm`, `amp/output2_power_dbm` | optical powers |
| `amp/pump_enabled`, `amp/warnings` | booster state, decoded warnings |
| `amp/pump_override` | pump-1 limit override on (1/0) |
Command topics (publish to `lab/laser/butterfly_edfa_rack/set/`):
| Topic suffix | Payload | Action |
|--------------|---------|--------|
| `seed/laser` | `1` / `0` | Koheron `lason` |
| `seed/current_ma` | mA | Koheron `ilaser` (clamped ≤ `SEED_CURRENT_MAX_MA`) |
| `seed/rt_setpoint_ohm` | Ω | Koheron `rtset`, seed TEC setpoint (clamped `SEED_RT_SETPOINT_MIN/MAX_OHM`) |
| `amp/pump` | `1` / `0` | Agiltron pump on / off |
| `amp/pump1_current_ma` | mA | set ACC mode + pump-1 current (clamped ≤ soft 4000 mA, or ≤ hard 8000 mA when override on) |
| `amp/pump_override` | `1` / `0` | lift the pump-1 clamp from soft (4000 mA / 1 W) to hard (8000 mA). Default off; switching off re-clamps a running pump to soft |