Files
butterfly-edfa-rack-firmware/README.md
T
James Bateman (hp-laptop) 58475386ba Butterfly EDFA Rack bridge: seed + EDFA over WiFi/MQTT to Home Assistant
ESP32 firmware bridging the 1550 nm MOPA (Koheron CTL200 seed + Agiltron
M511 two-stage EDFA) to MQTT/HA as a single device. Telemetry and control
for both instruments verified end-to-end: MQTT command -> seed lasing ->
booster ramp to 27 dBm (~515 mW) at the documented 2500 mA operating point.

- PlatformIO/Arduino project, project-local venv, Makefile upload flow
- Koheron ASCII driver on UART2; Agiltron binary 0x55AA driver on UART1
- HA discovery (sensors, binary_sensors, switches, numbers); LWT availability
- Static IP, firmware current clamps on seed and booster pump
2026-06-19 10:16:50 +01:00

160 lines
7.9 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. Measured 2026-06-17 with ~6 mW (7.8 dBm) seed into the EDFA:
| Pump-1 | Output-1 | note |
|-------:|---------:|------|
| 500–900 mA | −60 dBm | below transparency, signal absorbed |
| 1200 mA | 21.0 dBm | booster turns on |
| 1500 mA | 23.3 dBm | |
| 2000 mA | 25.9 dBm | |
| 2500 mA | 27.1 dBm | documented operating point, ~515 mW |
Pump current overshoots ~2× briefly on each step up and settles in 2–4 s, so
**ramp gradually** rather than jumping straight to the setpoint.
> **Safety:** this is a Class 4, ~1 W, invisible 1550 nm source. The firmware
> clamps pump-1 to `PUMP_CURRENT_MAX_MA` (3000 mA) and seed current to
> `SEED_CURRENT_MAX_MA`, but those are not interlocks — treat the output as
> hazardous unless it terminates in a power meter / beam dump / closed system.
## 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. (Future: seed `rtset`/`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/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`) |
| `amp/pump` | `1` / `0` | Agiltron pump on / off |
| `amp/pump1_current_ma` | mA | set ACC mode + pump-1 current (clamped ≤ `PUMP_CURRENT_MAX_MA`) |