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
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
# 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`) |
|
||||
Reference in New Issue
Block a user