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>
This commit is contained in:
James Bateman (hp-laptop)
2026-08-11 16:41:53 +01:00
co-authored by Claude Opus 4.8
parent 61f4526d09
commit b31065acd7
3 changed files with 61 additions and 72 deletions
+22 -71
View File
@@ -1,13 +1,8 @@
# 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).
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:
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)
@@ -16,8 +11,7 @@ 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.
HA discovery configs are published under `homeassistant/...`, all sharing one `device` block so entities group under the single rack device.
## Optical architecture
@@ -35,24 +29,15 @@ The rack is a **MOPA** (master-oscillator power-amplifier) at 1550 nm:
**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.
- **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.
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.
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 |
|-------:|------------------------|------|
@@ -66,32 +51,15 @@ spec) and a direct output-to-power-meter connection.
| 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.
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.)
> **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.
> **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`.
- 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:
@@ -103,19 +71,13 @@ gradually** rather than jumping to the setpoint.
| 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.
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.
> **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.
PlatformIO (Arduino framework), pinned in a project-local `.venv` so nothing is installed globally.
```bash
make # show targets
@@ -129,28 +91,15 @@ 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.
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`.)
- [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
@@ -164,6 +113,7 @@ State topics retained under `lab/laser/butterfly_edfa_rack/`:
| `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? |
@@ -179,5 +129,6 @@ Command topics (publish to `lab/laser/butterfly_edfa_rack/set/`):
|--------------|---------|--------|
| `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`) |