Files
butterfly-edfa-rack-firmware/README.md
T
James Bateman (hp-laptop)andClaude Opus 4.8 b31065acd7 Add settable seed TEC setpoint and °C readout
Expose the Koheron CTL200 thermistor setpoint (rtset) as a settable HA
number in ohms, clamped to SEED_RT_SETPOINT_MIN/MAX_OHM. The former
read-only setpoint sensor is replaced by this control. Add a read-only
seed temperature in °C, computed in firmware from the actual thermistor
resistance via an NTC β-model (constants in config.h; update once the
DFB1550LP thermistor R25/β are confirmed).

Verified end-to-end over MQTT: set/clamp/restore of rtset and the °C
conversion (10 kΩ → 25.00 °C).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-11 16:41:53 +01:00

135 lines
9.4 KiB
Markdown
Raw 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.
# koheron-esp32-bridge
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 `PUMP_CURRENT_MAX_MA` (4000 mA ≈ the 1 W rating) 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
- 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. (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 |
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 ≤ `PUMP_CURRENT_MAX_MA`) |