Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2a004d6f1 | ||
|
|
1a54819b3b | ||
|
|
0be75bf029 | ||
|
|
91b0f96029 | ||
|
|
58c061bd9c | ||
|
|
f97d02a533 | ||
|
|
ba1cd1ddcf | ||
|
|
ffadf4b37b | ||
|
|
da466efdb3 | ||
|
|
b31065acd7 |
@@ -0,0 +1,60 @@
|
|||||||
|
# Bill of materials: Butterfly EDFA Rack (MOPA)
|
||||||
|
|
||||||
|
Everything needed to rebuild the 1550 nm MOPA that this firmware drives: a Koheron-driven DFB seed into an Agiltron M511 EYDFA booster, bridged to WiFi/MQTT by an ESP32. Quantities are per rack. Where an electrical spec is not firmly established in the lab notes it is marked "confirm from datasheet" and the datasheet is named; do not treat those as verified.
|
||||||
|
|
||||||
|
Datasheets and manuals referenced below live in the Obsidian vault under `Lab/Equipment/Lasers/` (`Butterfly_EDFA_Rack/` for the CTL200 guide, `Agiltron EDFA/` for the M511 manuals and `edfa.py`).
|
||||||
|
|
||||||
|
## Control / bridge electronics
|
||||||
|
|
||||||
|
| Item | Qty | Notes |
|
||||||
|
|------|-----|-------|
|
||||||
|
| ESP32-D0WD-V3 dev board (WROOM-32 class) with CP2102N USB-UART | 1 | The bridge MCU. This unit's MAC is `b0:cb:d8:8f:ba:10`; enumerates as `/dev/ttyUSB0`. Any WROOM-32 class board works; keep UART1 off the flash-clashing default pins (see wiring). |
|
||||||
|
| 5 V USB supply + data cable for the ESP32 | 1 | Powers the board and is the flash/console link (UART0). |
|
||||||
|
|
||||||
|
## Seed stage (master oscillator)
|
||||||
|
|
||||||
|
| Item | Qty | Notes |
|
||||||
|
|------|-----|-------|
|
||||||
|
| Thorlabs DFB1550LP | 1 | 1550 nm DFB laser diode in a 14-pin butterfly package with integrated TEC, thermistor and monitor photodiode. The seed. |
|
||||||
|
| Koheron CTL200 (v5+) digital laser-diode controller | 1 | Butterfly-mount current + TEC controller. ASCII serial at 115200 8N1. Firmware v0.21 on this unit. Default thermistor setpoint 10 kΩ (`rtset`). |
|
||||||
|
| Koheron 5.9 V / 3 A barrel power supply | 1 | Shipped with the CTL200. The CTL200 accepts 5 V or 6 V at the barrel; set SW3 ON for a 5 V supply (compliance voltages then drop by 1 V). The terminal-block `VCC` pin is a 3V3 output, not an input, so it must not be used to power the board. |
|
||||||
|
|
||||||
|
## Power-amplifier stage (booster)
|
||||||
|
|
||||||
|
| Item | Qty | Notes |
|
||||||
|
|------|-----|-------|
|
||||||
|
| Agiltron M511 High-Power EYDFA module, PN `EDFA-1C51113331111` | 1 | Two-stage Er/Yb-doped fibre amplifier, 1550 nm, factory APC setpoint 30 dBm (~1 W). This unit: SN `Y4022708`, UART device ID `0x0000006F`. Purchased via Laser Components, 2024-08-22. Pump-1 is the active booster stage; pump-2 is unused on this unit. Manual: `EDFA_511_23-40dBm Componenet_User Manual_e.pdf`, protocol: `EYDFA_M511 High-Power_UART protocol_V1_e.pdf`. |
|
||||||
|
| Agiltron M511 power adaptor board v1.10 (OEM connection board) | 1 | Breakout: XT30 power input + the P2 control header (3V3 UART). P2 pin order from the end opposite the XT30: N(3.3V), G, RX, SCK, MO, MI, TX, NS. Photo: `Agiltron EDFA/EDFA_connection_board.jpg`. |
|
||||||
|
| DC supply for the M511 via XT30 | 1 | The pump diodes draw several amps at full power; confirm voltage and current rating from the M511 manual before sourcing. Size it for the 1 W (≈4000 mA pump-1) operating point with headroom. |
|
||||||
|
|
||||||
|
## Optical / fibre
|
||||||
|
|
||||||
|
| Item | Qty | Notes |
|
||||||
|
|------|-----|-------|
|
||||||
|
| 1550 nm SMF patch cords (SMF-28), FC/APC | 3+ | Seed out → attenuator → EDFA in, and EDFA out → load. |
|
||||||
|
| Inline fixed optical attenuator (≈3 dB pad) | 1 | Keeps the EDFA input within the ≤5 dBm benchtop input spec during high-power runs. |
|
||||||
|
| High-power-rated output connector or fusion splice | 1 | **Required above 1 W** per the Agiltron manual; a plain connector can burn at the output. |
|
||||||
|
| Beam dump or calibrated power meter | 1 | To terminate the Class-4, >1 W, invisible 1550 nm output. Never leave it open. |
|
||||||
|
|
||||||
|
## Interconnect / wiring
|
||||||
|
|
||||||
|
| Item | Qty | Notes |
|
||||||
|
|------|-----|-------|
|
||||||
|
| 3V3 jumper wires (Dupont / ferruled) | — | ESP32 ↔ CTL200 terminal block, ESP32 ↔ M511 P2 header. |
|
||||||
|
| Common ground link | 1 | Tie ESP32, CTL200 and M511 grounds together. |
|
||||||
|
|
||||||
|
### Signal wiring
|
||||||
|
|
||||||
|
Both instrument links are 3V3 logic at 115200 8N1 (the CTL200 is 5V-tolerant). UART1 is remapped to GPIO25/26 because its default pins clash with the WROOM SPI flash.
|
||||||
|
|
||||||
|
| ESP32 pin | Goes to | Link |
|
||||||
|
|-----------|---------|------|
|
||||||
|
| GPIO17 (TX2) → | CTL200 RX (terminal block) | Koheron, ASCII, `\r\n`, `>>` prompt |
|
||||||
|
| GPIO16 (RX2) ← | CTL200 TX | |
|
||||||
|
| GPIO26 (TX1) → | M511 P2 pin 3 (RX) | Agiltron, binary 0x55AA packets |
|
||||||
|
| GPIO25 (RX1) ← | M511 P2 pin 7 (TX) | |
|
||||||
|
| GND | CTL200 GND and M511 P2 pin 2 (G) | common ground |
|
||||||
|
|
||||||
|
## Safety
|
||||||
|
|
||||||
|
Class 4, >1 W, invisible 1550 nm. The firmware current clamps (`SEED_CURRENT_MAX_MA`, and the soft/hard pump-1 ceilings `PUMP_CURRENT_SOFT_MAX_MA` / `PUMP_CURRENT_HARD_MAX_MA`, the latter reachable only with the Pump Limit Override switch on) are guardrails, not interlocks. Always drop the pump to 0 before unmating any fibre, ramp the pump gradually (it overshoots ~2× on a step up and settles in 2–4 s), and terminate the output in a power meter or beam dump. See the repo README for the measured pump-current vs output-power curve.
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# Koheron CTL200 -> WiFi/MQTT bridge on ESP32.
|
# Butterfly EDFA Rack bridge (Koheron CTL200 seed + Agiltron M511 EDFA) on ESP32.
|
||||||
#
|
#
|
||||||
# All commands run PlatformIO from the project-local venv, so you never need
|
# All commands run PlatformIO from the project-local venv, so you never need
|
||||||
# `pio` on your PATH. Just `make`, `make upload`, `make monitor`.
|
# `pio` on your PATH. Just `make`, `make upload`, `make monitor`.
|
||||||
|
|||||||
@@ -1,13 +1,8 @@
|
|||||||
# koheron-esp32-bridge
|
# butterfly-edfa-rack-firmware
|
||||||
|
|
||||||
ESP32 firmware that bridges the **Butterfly EDFA Rack** — a 1550 nm MOPA:
|
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).
|
||||||
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`).
|
The whole rack is presented as a **single HA device** (`butterfly_edfa_rack`). Topics, mirroring the lab's EDFA→MQTT convention:
|
||||||
Topics, mirroring the lab's EDFA→MQTT convention:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
lab/laser/butterfly_edfa_rack/availability online | offline (LWT)
|
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/amp/<key> Agiltron M511 (increment 4)
|
||||||
lab/laser/butterfly_edfa_rack/set/<...> commands (increment 5)
|
lab/laser/butterfly_edfa_rack/set/<...> commands (increment 5)
|
||||||
```
|
```
|
||||||
HA discovery configs are published under `homeassistant/...`, all sharing one
|
HA discovery configs are published under `homeassistant/...`, all sharing one `device` block so entities group under the single rack device.
|
||||||
`device` block so entities group under the single rack device.
|
|
||||||
|
|
||||||
## Optical architecture
|
## 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:
|
**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
|
- **Pre-amp pump** — runs semi-independently at ~170 mA, lifts the seed to ~16 dBm (`amp/preamp_current_ma`, `amp/preamp_power_dbm`).
|
||||||
~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`).
|
||||||
- **Booster pump = "pump-1"** — the controllable output stage
|
- **"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.
|
||||||
(`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
|
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.
|
||||||
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)
|
### Pump-1 vs output characterisation (2026-06-17)
|
||||||
|
|
||||||
Two sweeps. The transparency sweep used ~6 mW (7.8 dBm) seed into the EDFA; the
|
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.
|
||||||
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 |
|
| Pump-1 | Output-1 (internal PD) | note |
|
||||||
|-------:|------------------------|------|
|
|-------:|------------------------|------|
|
||||||
@@ -66,32 +51,17 @@ spec) and a direct output-to-power-meter connection.
|
|||||||
| 4000 mA | 30.1 dBm / 1026 mW | **1 W rated ceiling** |
|
| 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) |
|
| 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
|
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.
|
||||||
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
|
> **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.)
|
||||||
> 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
|
> **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.
|
||||||
> `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
|
## Hardware
|
||||||
|
|
||||||
- ESP32-D0WD-V3 dev board (WROOM-32 class), CP2102N USB bridge → `/dev/ttyUSB0`,
|
Full parts list (seed, amplifier, supplies, fibre, wiring) is in [`BOM.md`](BOM.md).
|
||||||
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).
|
- The ESP32 USB port is used **only** for flashing/console (UART0).
|
||||||
- Two instrument links on separate hardware UARTs:
|
- Two instrument links on separate hardware UARTs:
|
||||||
|
|
||||||
@@ -103,19 +73,13 @@ gradually** rather than jumping to the setpoint.
|
|||||||
| GPIO25 (RX1) ← | Agiltron M511 TX (P2 pin 7) | |
|
| GPIO25 (RX1) ← | Agiltron M511 TX (P2 pin 7) | |
|
||||||
| GND | both grounds | |
|
| GND | both grounds | |
|
||||||
|
|
||||||
All links are 3V3 logic (CTL200 is 5V-tolerant). UART1 is remapped off its
|
All links are 3V3 logic (CTL200 is 5V-tolerant). UART1 is remapped off its default flash-clashing pins.
|
||||||
default flash-clashing pins.
|
|
||||||
|
|
||||||
> **Network:** the firmware pins a static IP `192.168.0.150` (gateway `.1`,
|
> **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.
|
||||||
> /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
|
## Toolchain
|
||||||
|
|
||||||
PlatformIO (Arduino framework), pinned in a project-local `.venv` so nothing
|
PlatformIO (Arduino framework), pinned in a project-local `.venv` so nothing is installed globally.
|
||||||
is installed globally.
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
make # show targets
|
make # show targets
|
||||||
@@ -129,28 +93,15 @@ A fresh checkout self-bootstraps the venv on first `make`.
|
|||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
Copy `include/secrets.example.h` → `include/secrets.h` (git-ignored) and fill
|
Copy `include/secrets.example.h` → `include/secrets.h` (git-ignored) and fill in WiFi + MQTT details. Needed from increment 2 onward.
|
||||||
in WiFi + MQTT details. Needed from increment 2 onward.
|
|
||||||
|
|
||||||
## Increment roadmap
|
## Increment roadmap
|
||||||
|
|
||||||
- [x] **1 — toolchain check.** Blink + serial heartbeat; confirms build/upload/monitor.
|
- [x] **1 — toolchain check.** Blink + serial heartbeat; confirms build/upload/monitor.
|
||||||
- [x] **2 — WiFi + MQTT + HA discovery.** Joins WiFi, connects to broker with an
|
- [x] **2 — WiFi + MQTT + HA discovery.** Joins WiFi, connects to broker with an LWT availability topic, publishes diagnostic entities (uptime, RSSI, IP).
|
||||||
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] **3 — Seed telemetry (read-only).** UART2 → Koheron `lason`/`ilaser`/`vlaser`/
|
- [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.
|
||||||
`rtset`/`rtact`/`itec`/`vtec`/`tboard`/`iphd`/`err`; published under `seed/...`
|
- [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`.)
|
||||||
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
|
## MQTT interface
|
||||||
|
|
||||||
@@ -164,6 +115,7 @@ State topics retained under `lab/laser/butterfly_edfa_rack/`:
|
|||||||
| `seed/laser_enabled` | seed laser on (1/0) |
|
| `seed/laser_enabled` | seed laser on (1/0) |
|
||||||
| `seed/current_ma`, `seed/voltage_v` | seed drive |
|
| `seed/current_ma`, `seed/voltage_v` | seed drive |
|
||||||
| `seed/rt_setpoint_ohm`, `seed/rt_actual_ohm` | thermistor setpoint / actual |
|
| `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/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 |
|
| `seed/photodiode_ma`, `seed/error` | seed monitor PD, CTL200 error code |
|
||||||
| `amp/link` | Agiltron reachable? |
|
| `amp/link` | Agiltron reachable? |
|
||||||
@@ -172,6 +124,7 @@ State topics retained under `lab/laser/butterfly_edfa_rack/`:
|
|||||||
| `amp/pump1_current_ma`, `amp/pump1_set_ma` | booster pump actual / setpoint |
|
| `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/input_power_dbm`, `amp/output1_power_dbm`, `amp/output2_power_dbm` | optical powers |
|
||||||
| `amp/pump_enabled`, `amp/warnings` | booster state, decoded warnings |
|
| `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/`):
|
Command topics (publish to `lab/laser/butterfly_edfa_rack/set/`):
|
||||||
|
|
||||||
@@ -179,5 +132,7 @@ Command topics (publish to `lab/laser/butterfly_edfa_rack/set/`):
|
|||||||
|--------------|---------|--------|
|
|--------------|---------|--------|
|
||||||
| `seed/laser` | `1` / `0` | Koheron `lason` |
|
| `seed/laser` | `1` / `0` | Koheron `lason` |
|
||||||
| `seed/current_ma` | mA | Koheron `ilaser` (clamped ≤ `SEED_CURRENT_MAX_MA`) |
|
| `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/pump` | `1` / `0` | Agiltron pump on / off |
|
||||||
| `amp/pump1_current_ma` | mA | set ACC mode + pump-1 current (clamped ≤ `PUMP_CURRENT_MAX_MA`) |
|
| `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 |
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# Home Assistant integration
|
||||||
|
|
||||||
|
Config for presenting the Butterfly EDFA Rack in Home Assistant. The rack's sensors, switches and numbers are auto-discovered over MQTT (the firmware publishes `homeassistant/.../config` topics), so these files only add what discovery cannot: derived mW power sensors and a dedicated dashboard.
|
||||||
|
|
||||||
|
Target: the HA instance on the lab broker host (`192.168.0.102`), running in Docker with its config bind-mounted at `~/HA/homeassistant/` (that is HA's `/config`).
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
| File | Goes to | Purpose |
|
||||||
|
|------|---------|---------|
|
||||||
|
| `configuration.snippet.yaml` | merge into `/config/configuration.yaml` | MQTT template sensors (dBm→mW for the EDFA input, pre-amp and output PDs) + the `butterfly-rack` YAML dashboard registration |
|
||||||
|
| `scripts.snippet.yaml` | merge into `/config/scripts.yaml` | `butterfly_edfa_shutdown` emergency-off script |
|
||||||
|
| `ui-lovelace-butterfly.yaml` | copy to `/config/ui-lovelace-butterfly.yaml` | the dashboard itself (graphs of power in mW + temperatures, dual dBm/mW output readout, staged seed/booster controls, emergency-off) |
|
||||||
|
|
||||||
|
The `*.snippet.yaml` files hold top-level keys (`mqtt:`, `lovelace:`, and the script) to merge into the matching HA files — they are not standalone configs. `ui-lovelace-butterfly.yaml` is copied whole.
|
||||||
|
|
||||||
|
## Deploy
|
||||||
|
|
||||||
|
From this directory, with the HA container named `homeassistant`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
host=lab@192.168.0.102
|
||||||
|
scp ui-lovelace-butterfly.yaml $host:/tmp/
|
||||||
|
ssh $host 'docker cp /tmp/ui-lovelace-butterfly.yaml homeassistant:/config/ui-lovelace-butterfly.yaml'
|
||||||
|
# then hand-merge the two snippets into /config/configuration.yaml and
|
||||||
|
# /config/scripts.yaml, and validate + restart:
|
||||||
|
ssh $host 'docker exec homeassistant python -m homeassistant --script check_config -c /config'
|
||||||
|
ssh $host 'docker restart homeassistant'
|
||||||
|
```
|
||||||
|
|
||||||
|
The dashboard reloads on browser refresh; changes to `configuration.yaml`/`scripts.yaml` need the restart. The dashboard appears in the sidebar as **Butterfly EDFA Rack** (`/butterfly-rack`).
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- The `*_mw` sensors are grouped onto the same `butterfly_edfa_rack` device via the shared `identifiers`.
|
||||||
|
- mW figures are derived from the internal-PD dBm telemetry, which reads ~0.5 dB optimistic — indicative, not calibrated.
|
||||||
|
- The seed temperature setpoint is the CTL200 thermistor resistance (`rtset`, ohms); the °C readout is a firmware-side NTC β-model. See the repo root README.
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
# Butterfly EDFA Rack — snippet to merge into Home Assistant's configuration.yaml.
|
||||||
|
# These are top-level keys; add them alongside your existing config (the rack's
|
||||||
|
# sensors/switches/numbers come in automatically over MQTT discovery, so only
|
||||||
|
# the derived mW power sensors and the dashboard registration live here).
|
||||||
|
#
|
||||||
|
# The internal PDs read ~0.5 dB optimistic, so the mW figures are indicative.
|
||||||
|
|
||||||
|
mqtt:
|
||||||
|
sensor:
|
||||||
|
- name: "Output Power mW"
|
||||||
|
unique_id: butterfly_edfa_rack_out_mw
|
||||||
|
state_topic: "lab/laser/butterfly_edfa_rack/amp/output1_power_dbm"
|
||||||
|
value_template: "{{ (10 ** (value | float / 10)) | round(1) }}"
|
||||||
|
unit_of_measurement: "mW"
|
||||||
|
device_class: power
|
||||||
|
state_class: measurement
|
||||||
|
availability_topic: "lab/laser/butterfly_edfa_rack/availability"
|
||||||
|
payload_available: "online"
|
||||||
|
payload_not_available: "offline"
|
||||||
|
device: { identifiers: ["butterfly_edfa_rack"] }
|
||||||
|
# EDFA input PD = seed AFTER the external attenuators (not seed output power).
|
||||||
|
- name: "Amp Input Power mW"
|
||||||
|
unique_id: butterfly_edfa_rack_ampin_mw
|
||||||
|
state_topic: "lab/laser/butterfly_edfa_rack/amp/input_power_dbm"
|
||||||
|
value_template: "{{ (10 ** (value | float / 10)) | round(3) }}"
|
||||||
|
unit_of_measurement: "mW"
|
||||||
|
device_class: power
|
||||||
|
state_class: measurement
|
||||||
|
availability_topic: "lab/laser/butterfly_edfa_rack/availability"
|
||||||
|
payload_available: "online"
|
||||||
|
payload_not_available: "offline"
|
||||||
|
device: { identifiers: ["butterfly_edfa_rack"] }
|
||||||
|
- name: "Pre-amp Power mW"
|
||||||
|
unique_id: butterfly_edfa_rack_preamp_mw
|
||||||
|
state_topic: "lab/laser/butterfly_edfa_rack/amp/preamp_power_dbm"
|
||||||
|
value_template: "{{ (10 ** (value | float / 10)) | round(1) }}"
|
||||||
|
unit_of_measurement: "mW"
|
||||||
|
device_class: power
|
||||||
|
state_class: measurement
|
||||||
|
availability_topic: "lab/laser/butterfly_edfa_rack/availability"
|
||||||
|
payload_available: "online"
|
||||||
|
payload_not_available: "offline"
|
||||||
|
device: { identifiers: ["butterfly_edfa_rack"] }
|
||||||
|
|
||||||
|
# YAML-mode dashboard registration. Points at ui-lovelace-butterfly.yaml, which
|
||||||
|
# must sit next to configuration.yaml in the HA config directory.
|
||||||
|
lovelace:
|
||||||
|
mode: storage
|
||||||
|
dashboards:
|
||||||
|
butterfly-rack:
|
||||||
|
mode: yaml
|
||||||
|
title: Butterfly EDFA Rack
|
||||||
|
icon: mdi:laser-pointer
|
||||||
|
show_in_sidebar: true
|
||||||
|
filename: ui-lovelace-butterfly.yaml
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# Butterfly EDFA Rack — snippet to merge into Home Assistant's scripts.yaml.
|
||||||
|
# Emergency shutdown: ramp pump-1 to 0, disable the booster, then the seed.
|
||||||
|
|
||||||
|
butterfly_edfa_shutdown:
|
||||||
|
alias: Butterfly EDFA emergency shutdown
|
||||||
|
icon: mdi:alarm-light
|
||||||
|
mode: single
|
||||||
|
sequence:
|
||||||
|
- service: number.set_value
|
||||||
|
target:
|
||||||
|
entity_id: number.butterfly_edfa_rack_amp_pump_current
|
||||||
|
data:
|
||||||
|
value: 0
|
||||||
|
- service: switch.turn_off
|
||||||
|
target:
|
||||||
|
entity_id: switch.butterfly_edfa_rack_amp_pump
|
||||||
|
- service: switch.turn_off
|
||||||
|
target:
|
||||||
|
entity_id: switch.butterfly_edfa_rack_seed_laser
|
||||||
@@ -0,0 +1,321 @@
|
|||||||
|
# Butterfly EDFA Rack (MOPA) — laser control panel.
|
||||||
|
# Registered in configuration.yaml (lovelace: dashboards: butterfly-rack).
|
||||||
|
#
|
||||||
|
# Entities are auto-discovered over MQTT; the *_mw power sensors are MQTT
|
||||||
|
# template sensors defined in configuration.butterfly.yaml (dBm -> mW).
|
||||||
|
# Styling uses card-mod (installed via HACS).
|
||||||
|
#
|
||||||
|
# Two tabs:
|
||||||
|
# Control — the panel: status strip, output readout, the two
|
||||||
|
# stages, emergency stop. Everything you touch.
|
||||||
|
# Diagnostics & Trends — the history graphs plus network / TEC / error
|
||||||
|
# detail, kept off the control tab so it stays clean.
|
||||||
|
title: Butterfly EDFA Rack
|
||||||
|
views:
|
||||||
|
# ===================================================================
|
||||||
|
# TAB 1 — CONTROL PANEL
|
||||||
|
# ===================================================================
|
||||||
|
- title: Control
|
||||||
|
path: control
|
||||||
|
icon: mdi:laser-pointer
|
||||||
|
type: sections
|
||||||
|
# Single column so the sections stack in operating order:
|
||||||
|
# Status -> Output -> Seed -> Booster -> Emergency.
|
||||||
|
max_columns: 1
|
||||||
|
sections:
|
||||||
|
# ---- Output readout (2 units) + SHUTDOWN (1 unit, square) -------
|
||||||
|
- type: grid
|
||||||
|
cards:
|
||||||
|
# Big output-power readout.
|
||||||
|
# HA's markdown card strips inline styles, so size/centre via
|
||||||
|
# card-mod piercing the ha-markdown shadow (the three spans are the
|
||||||
|
# value, the " mW" unit, and the "Output power" label).
|
||||||
|
- type: markdown
|
||||||
|
grid_options:
|
||||||
|
columns: 8
|
||||||
|
rows: 3
|
||||||
|
content: >
|
||||||
|
<div><span>{{ states('sensor.butterfly_edfa_rack_output_power_mw')
|
||||||
|
}}</span><span> mW</span><br><span>Output power</span></div>
|
||||||
|
card_mod:
|
||||||
|
style:
|
||||||
|
.: |
|
||||||
|
ha-card {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
ha-markdown$: |
|
||||||
|
div { text-align: center !important; width: 100%; line-height: 1.05; }
|
||||||
|
div span:nth-of-type(1) { font-size: 3.6rem; font-weight: 700; }
|
||||||
|
div span:nth-of-type(2) { font-size: 1.7rem; font-weight: 600; }
|
||||||
|
div span:nth-of-type(3) { color: var(--secondary-text-color); }
|
||||||
|
# One-tap emergency stop. Runs butterfly_edfa_shutdown: pump-1 to 0,
|
||||||
|
# booster off, then seed off (EDFA first, then seed). No confirmation
|
||||||
|
# so it acts immediately; the action is safe (it only turns things off).
|
||||||
|
- type: button
|
||||||
|
name: SHUTDOWN
|
||||||
|
icon: mdi:alarm-light
|
||||||
|
icon_height: 40px
|
||||||
|
show_state: false
|
||||||
|
grid_options:
|
||||||
|
columns: 4
|
||||||
|
rows: 3
|
||||||
|
tap_action:
|
||||||
|
action: call-service
|
||||||
|
service: script.butterfly_edfa_shutdown
|
||||||
|
card_mod:
|
||||||
|
style: |
|
||||||
|
ha-card {
|
||||||
|
background: #c0392b;
|
||||||
|
color: #fff;
|
||||||
|
--primary-text-color: #fff;
|
||||||
|
--paper-item-icon-color: #fff;
|
||||||
|
font-weight: 800;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---- Rack status (full width) -----------------------------------
|
||||||
|
- type: grid
|
||||||
|
column_span: 2
|
||||||
|
cards:
|
||||||
|
- type: heading
|
||||||
|
heading: Butterfly EDFA Rack
|
||||||
|
heading_style: title
|
||||||
|
icon: mdi:laser-pointer
|
||||||
|
- type: heading
|
||||||
|
heading: 1550 nm MOPA · Class 4 · invisible 1550 nm — treat output as hazardous
|
||||||
|
heading_style: subtitle
|
||||||
|
- type: glance
|
||||||
|
state_color: true
|
||||||
|
columns: 4
|
||||||
|
grid_options:
|
||||||
|
columns: 12
|
||||||
|
# Read-only status lamps. The actual on/off toggles live in the
|
||||||
|
# Seed and Booster stage cards below (tap_action: none here so this
|
||||||
|
# strip does not duplicate those controls).
|
||||||
|
entities:
|
||||||
|
- entity: binary_sensor.butterfly_edfa_rack_seed_link
|
||||||
|
name: Seed link
|
||||||
|
tap_action:
|
||||||
|
action: none
|
||||||
|
- entity: binary_sensor.butterfly_edfa_rack_amp_link
|
||||||
|
name: Amp link
|
||||||
|
tap_action:
|
||||||
|
action: none
|
||||||
|
- entity: binary_sensor.butterfly_edfa_rack_seed_laser_enabled
|
||||||
|
name: Seed
|
||||||
|
tap_action:
|
||||||
|
action: none
|
||||||
|
- entity: switch.butterfly_edfa_rack_amp_pump
|
||||||
|
name: Booster
|
||||||
|
tap_action:
|
||||||
|
action: none
|
||||||
|
- type: tile
|
||||||
|
entity: sensor.butterfly_edfa_rack_amp_warnings
|
||||||
|
name: Status
|
||||||
|
icon: mdi:alert-decagram
|
||||||
|
grid_options:
|
||||||
|
columns: 12
|
||||||
|
card_mod:
|
||||||
|
style: |
|
||||||
|
ha-card {
|
||||||
|
{% set w = states('sensor.butterfly_edfa_rack_amp_warnings') %}
|
||||||
|
{% if w in ['none','Pump is OFF','unknown','unavailable'] or w == '' %}
|
||||||
|
--tile-color: var(--green-color, #43a047);
|
||||||
|
{% else %}
|
||||||
|
--tile-color: var(--red-color, #e53935);
|
||||||
|
{% endif %}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---- Output readout ---------------------------------------------
|
||||||
|
- type: grid
|
||||||
|
column_span: 1
|
||||||
|
cards:
|
||||||
|
- type: heading
|
||||||
|
heading: Output
|
||||||
|
heading_style: subtitle
|
||||||
|
icon: mdi:arrow-left-bold
|
||||||
|
- type: tile
|
||||||
|
entity: sensor.butterfly_edfa_rack_output_power
|
||||||
|
name: Output dBm
|
||||||
|
icon: mdi:sine-wave
|
||||||
|
grid_options:
|
||||||
|
columns: 6
|
||||||
|
- type: tile
|
||||||
|
entity: sensor.butterfly_edfa_rack_amp_input_power_mw
|
||||||
|
name: EDFA in
|
||||||
|
icon: mdi:arrow-right-bold
|
||||||
|
grid_options:
|
||||||
|
columns: 6
|
||||||
|
|
||||||
|
# ---- Stage 1: seed ----------------------------------------------
|
||||||
|
- type: grid
|
||||||
|
column_span: 1
|
||||||
|
cards:
|
||||||
|
- type: heading
|
||||||
|
heading: Seed · Stage 1 (CTL200)
|
||||||
|
heading_style: subtitle
|
||||||
|
icon: mdi:alpha-s-circle-outline
|
||||||
|
- type: entities
|
||||||
|
state_color: true
|
||||||
|
entities:
|
||||||
|
- entity: switch.butterfly_edfa_rack_seed_laser
|
||||||
|
name: Seed laser (ON first)
|
||||||
|
- entity: number.butterfly_edfa_rack_seed_set_current
|
||||||
|
name: Set current (mA)
|
||||||
|
- entity: number.butterfly_edfa_rack_seed_thermistor_setpoint
|
||||||
|
name: Temp setpoint (Ω)
|
||||||
|
- type: tile
|
||||||
|
entity: sensor.butterfly_edfa_rack_seed_current
|
||||||
|
name: Current
|
||||||
|
icon: mdi:current-dc
|
||||||
|
grid_options:
|
||||||
|
columns: 6
|
||||||
|
- type: tile
|
||||||
|
entity: sensor.butterfly_edfa_rack_seed_temperature
|
||||||
|
name: Seed temp
|
||||||
|
icon: mdi:thermometer
|
||||||
|
grid_options:
|
||||||
|
columns: 6
|
||||||
|
- type: tile
|
||||||
|
entity: sensor.butterfly_edfa_rack_seed_photodiode_current
|
||||||
|
name: Monitor PD
|
||||||
|
icon: mdi:current-dc
|
||||||
|
grid_options:
|
||||||
|
columns: 6
|
||||||
|
|
||||||
|
# ---- Stage 2: booster -------------------------------------------
|
||||||
|
- type: grid
|
||||||
|
column_span: 1
|
||||||
|
cards:
|
||||||
|
- type: heading
|
||||||
|
heading: Booster · Stage 2 (M511, ACC)
|
||||||
|
heading_style: subtitle
|
||||||
|
icon: mdi:alpha-b-circle-outline
|
||||||
|
- type: entities
|
||||||
|
state_color: true
|
||||||
|
entities:
|
||||||
|
- entity: switch.butterfly_edfa_rack_amp_pump
|
||||||
|
name: Pump enable
|
||||||
|
- entity: number.butterfly_edfa_rack_amp_pump_current
|
||||||
|
name: Pump-1 set (mA)
|
||||||
|
# Lifts the pump-1 clamp from 4000 mA (1 W) to the 8000 mA max.
|
||||||
|
- entity: switch.butterfly_edfa_rack_amp_pump_limit_override
|
||||||
|
name: Pump limit override (→ 8000 mA)
|
||||||
|
- type: tile
|
||||||
|
entity: sensor.butterfly_edfa_rack_amp_pump_1_current
|
||||||
|
name: Pump-1 current
|
||||||
|
icon: mdi:current-dc
|
||||||
|
grid_options:
|
||||||
|
columns: 6
|
||||||
|
- type: tile
|
||||||
|
entity: sensor.butterfly_edfa_rack_amp_module_temperature
|
||||||
|
name: Module temp
|
||||||
|
icon: mdi:thermometer
|
||||||
|
grid_options:
|
||||||
|
columns: 6
|
||||||
|
|
||||||
|
# ===================================================================
|
||||||
|
# TAB 2 — DIAGNOSTICS & TRENDS
|
||||||
|
# ===================================================================
|
||||||
|
- title: Diagnostics & Trends
|
||||||
|
path: diagnostics
|
||||||
|
icon: mdi:chart-line
|
||||||
|
type: sections
|
||||||
|
max_columns: 3
|
||||||
|
sections:
|
||||||
|
- type: grid
|
||||||
|
column_span: 2
|
||||||
|
cards:
|
||||||
|
- type: heading
|
||||||
|
heading: Trends
|
||||||
|
heading_style: subtitle
|
||||||
|
icon: mdi:chart-line
|
||||||
|
- type: history-graph
|
||||||
|
title: Output & pre-amp PD (mW)
|
||||||
|
hours_to_show: 1
|
||||||
|
entities:
|
||||||
|
- entity: sensor.butterfly_edfa_rack_output_power_mw
|
||||||
|
name: Output PD
|
||||||
|
- entity: sensor.butterfly_edfa_rack_pre_amp_power_mw
|
||||||
|
name: Pre-amp PD
|
||||||
|
- type: history-graph
|
||||||
|
title: EDFA input PD (mW) — seed after attenuators
|
||||||
|
hours_to_show: 1
|
||||||
|
entities:
|
||||||
|
- entity: sensor.butterfly_edfa_rack_amp_input_power_mw
|
||||||
|
name: EDFA input
|
||||||
|
- type: history-graph
|
||||||
|
title: Seed monitor PD (mA — relative, uncalibrated)
|
||||||
|
hours_to_show: 1
|
||||||
|
entities:
|
||||||
|
- entity: sensor.butterfly_edfa_rack_seed_photodiode_current
|
||||||
|
name: Seed monitor PD
|
||||||
|
- type: history-graph
|
||||||
|
title: Temperatures (°C)
|
||||||
|
hours_to_show: 1
|
||||||
|
entities:
|
||||||
|
- entity: sensor.butterfly_edfa_rack_seed_temperature
|
||||||
|
name: Seed
|
||||||
|
- entity: sensor.butterfly_edfa_rack_seed_board_temperature
|
||||||
|
name: Seed board
|
||||||
|
- entity: sensor.butterfly_edfa_rack_amp_module_temperature
|
||||||
|
name: Amp module
|
||||||
|
- entity: sensor.butterfly_edfa_rack_amp_pre_amp_temperature
|
||||||
|
name: Amp pre-amp
|
||||||
|
|
||||||
|
- type: grid
|
||||||
|
column_span: 1
|
||||||
|
cards:
|
||||||
|
- type: heading
|
||||||
|
heading: Seed detail
|
||||||
|
heading_style: subtitle
|
||||||
|
icon: mdi:alpha-s-circle-outline
|
||||||
|
- type: entities
|
||||||
|
entities:
|
||||||
|
- entity: sensor.butterfly_edfa_rack_seed_voltage
|
||||||
|
name: Seed voltage
|
||||||
|
- entity: sensor.butterfly_edfa_rack_seed_thermistor
|
||||||
|
name: Thermistor (actual)
|
||||||
|
- entity: sensor.butterfly_edfa_rack_seed_tec_current
|
||||||
|
name: TEC current
|
||||||
|
- entity: sensor.butterfly_edfa_rack_seed_tec_voltage
|
||||||
|
name: TEC voltage
|
||||||
|
- entity: sensor.butterfly_edfa_rack_seed_board_temperature
|
||||||
|
name: Board temp
|
||||||
|
- entity: sensor.butterfly_edfa_rack_seed_error_code
|
||||||
|
name: Error code
|
||||||
|
- type: heading
|
||||||
|
heading: Amp detail
|
||||||
|
heading_style: subtitle
|
||||||
|
icon: mdi:alpha-b-circle-outline
|
||||||
|
- type: entities
|
||||||
|
entities:
|
||||||
|
- entity: sensor.butterfly_edfa_rack_amp_input_power
|
||||||
|
name: Input power (dBm)
|
||||||
|
- entity: sensor.butterfly_edfa_rack_amp_pre_amp_power
|
||||||
|
name: Pre-amp power (dBm)
|
||||||
|
- entity: sensor.butterfly_edfa_rack_amp_pre_amp_current
|
||||||
|
name: Pre-amp current
|
||||||
|
- entity: sensor.butterfly_edfa_rack_amp_pre_amp_temperature
|
||||||
|
name: Pre-amp temp
|
||||||
|
- entity: sensor.butterfly_edfa_rack_amp_tec_current
|
||||||
|
name: TEC current
|
||||||
|
- entity: sensor.butterfly_edfa_rack_amp_warnings
|
||||||
|
name: Warnings
|
||||||
|
- type: heading
|
||||||
|
heading: Bridge (ESP32)
|
||||||
|
heading_style: subtitle
|
||||||
|
icon: mdi:chip
|
||||||
|
- type: entities
|
||||||
|
entities:
|
||||||
|
- entity: sensor.butterfly_edfa_rack_ip_address
|
||||||
|
name: IP address
|
||||||
|
- entity: sensor.butterfly_edfa_rack_wifi_signal
|
||||||
|
name: WiFi signal
|
||||||
|
- entity: sensor.butterfly_edfa_rack_uptime
|
||||||
|
name: Uptime
|
||||||
+27
-4
@@ -26,10 +26,33 @@
|
|||||||
// HA number sliders are capped here. The hardware allows 8000 mA pump, but
|
// HA number sliders are capped here. The hardware allows 8000 mA pump, but
|
||||||
// the lab notes report ~2500 mA pump-1 -> ~27 dBm (~515 mW). Keep a margin.
|
// the lab notes report ~2500 mA pump-1 -> ~27 dBm (~515 mW). Keep a margin.
|
||||||
#define SEED_CURRENT_MAX_MA 300 // CTL200 ilmax default is 1000; seed DFB is low-power
|
#define SEED_CURRENT_MAX_MA 300 // CTL200 ilmax default is 1000; seed DFB is low-power
|
||||||
// Pump-1 clamp = the 1 W rated operating point (~4000 mA, measured 2026-06-17).
|
// Pump-1 current clamp. Two ceilings:
|
||||||
// Raise deliberately only for supervised over-rating characterisation; it is a
|
// SOFT (default) = the 1 W rated operating point (~4000 mA, measured
|
||||||
// guardrail against accidental over-drive, not a safety interlock.
|
// 2026-06-17). Guards against accidental over-drive. NOT a safety interlock.
|
||||||
#define PUMP_CURRENT_MAX_MA 4000
|
// HARD = the Agiltron protocol / hardware maximum (8000 mA). Reachable only
|
||||||
|
// while the "Pump limit override" switch is ON (set/amp/pump_override), for
|
||||||
|
// supervised over-rating characterisation. Driving the 1 W-rated pump diode
|
||||||
|
// toward 8000 mA trades diode lifetime (and risks damage) for extra power.
|
||||||
|
// The HA number slider spans 0..HARD; a command above SOFT is clamped to SOFT
|
||||||
|
// unless the override is ON. Turning the override OFF re-clamps a running
|
||||||
|
// setpoint back down to SOFT immediately.
|
||||||
|
#define PUMP_CURRENT_SOFT_MAX_MA 4000
|
||||||
|
#define PUMP_CURRENT_HARD_MAX_MA 8000
|
||||||
|
|
||||||
|
// Seed thermistor setpoint (CTL200 `rtset`) clamp, in ohms. The CTL200 has no
|
||||||
|
// native °C setpoint; it regulates the seed TEC to hold the thermistor at this
|
||||||
|
// resistance. The default operating point is 10 kΩ. The range below spans
|
||||||
|
// roughly 39…13 °C on the β-model (a guardrail, not a diode-safe interlock —
|
||||||
|
// narrow it if the DFB1550LP datasheet gives a tighter window).
|
||||||
|
#define SEED_RT_SETPOINT_MIN_OHM 6000
|
||||||
|
#define SEED_RT_SETPOINT_MAX_OHM 16000
|
||||||
|
|
||||||
|
// NTC β-model for the read-only resistance→°C conversion (display only; the
|
||||||
|
// CTL200 does its own regulation in ohms). Assumes a 10 kΩ @ 25 °C NTC with
|
||||||
|
// β = 3435 K. Update if the seed thermistor's R25/β are known to differ.
|
||||||
|
#define SEED_THERMISTOR_R25_OHM 10000.0f
|
||||||
|
#define SEED_THERMISTOR_BETA_K 3435.0f
|
||||||
|
#define SEED_THERMISTOR_T25_K 298.15f
|
||||||
|
|
||||||
// ── Serial links to the instruments (wired in later increments) ─────
|
// ── Serial links to the instruments (wired in later increments) ─────
|
||||||
// UART0 stays the USB console. The classic ESP32 has 3 hardware UARTs.
|
// UART0 stays the USB console. The classic ESP32 has 3 hardware UARTs.
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
; PlatformIO project for the Koheron CTL200 -> WiFi/MQTT bridge on ESP32.
|
; PlatformIO project for the Butterfly EDFA Rack bridge (Koheron seed + Agiltron M511 EDFA) on ESP32.
|
||||||
; Board: ESP32-D0WD-V3 (WROOM-32 class) on a CP2102N USB bridge.
|
; Board: ESP32-D0WD-V3 (WROOM-32 class) on a CP2102N USB bridge.
|
||||||
|
|
||||||
[env:esp32dev]
|
[env:esp32dev]
|
||||||
|
|||||||
+56
-4
@@ -22,6 +22,13 @@ static Koheron koheron(Serial2, KOHERON_RX_PIN, KOHERON_TX_PIN, KOHERON_BA
|
|||||||
static const uint8_t kAmpDevId[4] = {AGILTRON_DEV_ID};
|
static const uint8_t kAmpDevId[4] = {AGILTRON_DEV_ID};
|
||||||
static Agiltron agiltron(Serial1, AGILTRON_RX_PIN, AGILTRON_TX_PIN, AGILTRON_BAUD, kAmpDevId);
|
static Agiltron agiltron(Serial1, AGILTRON_RX_PIN, AGILTRON_TX_PIN, AGILTRON_BAUD, kAmpDevId);
|
||||||
|
|
||||||
|
// Pump-1 limit override. false => clamp pump-1 commands to the SOFT (1 W)
|
||||||
|
// ceiling; true => allow up to the HARD (protocol) ceiling. Defaults OFF on
|
||||||
|
// every boot (fail-safe). pump1SetMa tracks the last commanded setpoint so the
|
||||||
|
// override can re-clamp a running pump down when it is switched back off.
|
||||||
|
static bool pumpOverride = false;
|
||||||
|
static int pump1SetMa = 0;
|
||||||
|
|
||||||
// Probe each instrument link once per boot to reveal its framing.
|
// Probe each instrument link once per boot to reveal its framing.
|
||||||
#define KOHERON_RAW_PROBE 1
|
#define KOHERON_RAW_PROBE 1
|
||||||
#define AGILTRON_PROBE 1
|
#define AGILTRON_PROBE 1
|
||||||
@@ -120,8 +127,8 @@ static void publishDiscovery() {
|
|||||||
binaryDiscovery("seed_laser_enabled", "Seed Laser Enabled", "seed/laser_enabled", "1", "0", nullptr, false);
|
binaryDiscovery("seed_laser_enabled", "Seed Laser Enabled", "seed/laser_enabled", "1", "0", nullptr, false);
|
||||||
sensorDiscovery("seed_current", "Seed Current", "seed/current_ma", "mA", "current", "mdi:current-dc", false);
|
sensorDiscovery("seed_current", "Seed Current", "seed/current_ma", "mA", "current", "mdi:current-dc", false);
|
||||||
sensorDiscovery("seed_voltage", "Seed Voltage", "seed/voltage_v", "V", "voltage", nullptr, false);
|
sensorDiscovery("seed_voltage", "Seed Voltage", "seed/voltage_v", "V", "voltage", nullptr, false);
|
||||||
sensorDiscovery("seed_rt_setpoint", "Seed Thermistor Setpoint", "seed/rt_setpoint_ohm", "Ω", nullptr, "mdi:resistor", false);
|
|
||||||
sensorDiscovery("seed_rt_actual", "Seed Thermistor", "seed/rt_actual_ohm", "Ω", nullptr, "mdi:resistor", false);
|
sensorDiscovery("seed_rt_actual", "Seed Thermistor", "seed/rt_actual_ohm", "Ω", nullptr, "mdi:resistor", false);
|
||||||
|
sensorDiscovery("seed_temp", "Seed Temperature", "seed/temp_c", "°C", "temperature", nullptr, false);
|
||||||
sensorDiscovery("seed_tec_current", "Seed TEC Current", "seed/tec_current_a", "A", "current", nullptr, false);
|
sensorDiscovery("seed_tec_current", "Seed TEC Current", "seed/tec_current_a", "A", "current", nullptr, false);
|
||||||
sensorDiscovery("seed_tec_voltage", "Seed TEC Voltage", "seed/tec_voltage_v", "V", "voltage", nullptr, false);
|
sensorDiscovery("seed_tec_voltage", "Seed TEC Voltage", "seed/tec_voltage_v", "V", "voltage", nullptr, false);
|
||||||
sensorDiscovery("seed_board_temp", "Seed Board Temperature", "seed/board_temp_c", "°C", "temperature", nullptr, false);
|
sensorDiscovery("seed_board_temp", "Seed Board Temperature", "seed/board_temp_c", "°C", "temperature", nullptr, false);
|
||||||
@@ -132,6 +139,10 @@ static void publishDiscovery() {
|
|||||||
switchDiscovery("seed_laser", "Seed Laser", "seed/laser_enabled", "seed/laser", "1", "0", "mdi:laser-pointer");
|
switchDiscovery("seed_laser", "Seed Laser", "seed/laser_enabled", "seed/laser", "1", "0", "mdi:laser-pointer");
|
||||||
numberDiscovery("seed_set_current", "Seed Set Current", "seed/current_ma", "seed/current_ma",
|
numberDiscovery("seed_set_current", "Seed Set Current", "seed/current_ma", "seed/current_ma",
|
||||||
0, SEED_CURRENT_MAX_MA, 1, "mA", "mdi:current-dc");
|
0, SEED_CURRENT_MAX_MA, 1, "mA", "mdi:current-dc");
|
||||||
|
// Seed TEC setpoint: settable in ohms (CTL200 `rtset`); state mirrors the
|
||||||
|
// `seed/rt_setpoint_ohm` telemetry. °C is exposed read-only via seed/temp_c.
|
||||||
|
numberDiscovery("seed_set_rt_setpoint", "Seed Thermistor Setpoint", "seed/rt_setpoint_ohm", "seed/rt_setpoint_ohm",
|
||||||
|
SEED_RT_SETPOINT_MIN_OHM, SEED_RT_SETPOINT_MAX_OHM, 10, "Ω", "mdi:thermometer");
|
||||||
|
|
||||||
// Amp (Agiltron M511)
|
// Amp (Agiltron M511)
|
||||||
binaryDiscovery("amp_link", "Amp Link", "amp/link", AVAIL_ONLINE, AVAIL_OFFLINE, "connectivity", true);
|
binaryDiscovery("amp_link", "Amp Link", "amp/link", AVAIL_ONLINE, AVAIL_OFFLINE, "connectivity", true);
|
||||||
@@ -148,7 +159,10 @@ static void publishDiscovery() {
|
|||||||
// Amp controls
|
// Amp controls
|
||||||
switchDiscovery("amp_pump", "Amp Pump", "amp/pump_enabled", "amp/pump", "1", "0", "mdi:flash");
|
switchDiscovery("amp_pump", "Amp Pump", "amp/pump_enabled", "amp/pump", "1", "0", "mdi:flash");
|
||||||
numberDiscovery("amp_set_pump_current", "Amp Pump Current", "amp/pump1_set_ma", "amp/pump1_current_ma",
|
numberDiscovery("amp_set_pump_current", "Amp Pump Current", "amp/pump1_set_ma", "amp/pump1_current_ma",
|
||||||
0, PUMP_CURRENT_MAX_MA, 10, "mA", "mdi:current-dc");
|
0, PUMP_CURRENT_HARD_MAX_MA, 10, "mA", "mdi:current-dc");
|
||||||
|
// Override the soft (1 W) pump-1 clamp up to the hard protocol ceiling.
|
||||||
|
switchDiscovery("amp_pump_override", "Amp Pump Limit Override", "amp/pump_override", "amp/pump_override",
|
||||||
|
"1", "0", "mdi:lock-open-variant");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── State publishing ────────────────────────────────────────────────
|
// ── State publishing ────────────────────────────────────────────────
|
||||||
@@ -161,6 +175,14 @@ static void publishDiag() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Each seed value is the raw string the CTL200 returns (already numeric text).
|
// Each seed value is the raw string the CTL200 returns (already numeric text).
|
||||||
|
// NTC β-model: thermistor resistance (Ω) -> °C. Display-only approximation;
|
||||||
|
// the CTL200 regulates in ohms, not against this figure.
|
||||||
|
static float seedOhmToC(float r) {
|
||||||
|
float invT = 1.0f / SEED_THERMISTOR_T25_K
|
||||||
|
+ logf(r / SEED_THERMISTOR_R25_OHM) / SEED_THERMISTOR_BETA_K;
|
||||||
|
return 1.0f / invT - 273.15f;
|
||||||
|
}
|
||||||
|
|
||||||
struct SeedPoint { const char *cmd; const char *sub; };
|
struct SeedPoint { const char *cmd; const char *sub; };
|
||||||
static const SeedPoint seedPoints[] = {
|
static const SeedPoint seedPoints[] = {
|
||||||
{"lason", "seed/laser_enabled"},
|
{"lason", "seed/laser_enabled"},
|
||||||
@@ -177,14 +199,18 @@ static const SeedPoint seedPoints[] = {
|
|||||||
|
|
||||||
static void publishSeed() {
|
static void publishSeed() {
|
||||||
bool any = false;
|
bool any = false;
|
||||||
|
float rtActOhm = 0;
|
||||||
for (const auto &pt : seedPoints) {
|
for (const auto &pt : seedPoints) {
|
||||||
String v = koheron.query(pt.cmd);
|
String v = koheron.query(pt.cmd);
|
||||||
if (v.length()) {
|
if (v.length()) {
|
||||||
mqtt.publish((String(TOPIC_BASE) + "/" + pt.sub).c_str(), v.c_str(), true);
|
mqtt.publish((String(TOPIC_BASE) + "/" + pt.sub).c_str(), v.c_str(), true);
|
||||||
|
if (strcmp(pt.cmd, "rtact") == 0) rtActOhm = v.toFloat();
|
||||||
any = true;
|
any = true;
|
||||||
}
|
}
|
||||||
mqtt.loop(); // keep MQTT serviced between (potentially slow) UART round-trips
|
mqtt.loop(); // keep MQTT serviced between (potentially slow) UART round-trips
|
||||||
}
|
}
|
||||||
|
if (rtActOhm > 0)
|
||||||
|
mqtt.publish(TOPIC_BASE "/seed/temp_c", String(seedOhmToC(rtActOhm), 2).c_str(), true);
|
||||||
mqtt.publish(TOPIC_BASE "/seed/link", any ? AVAIL_ONLINE : AVAIL_OFFLINE, true);
|
mqtt.publish(TOPIC_BASE "/seed/link", any ? AVAIL_ONLINE : AVAIL_OFFLINE, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,6 +221,7 @@ static void pub(const char *sub, const String &v) {
|
|||||||
static void publishAmp() {
|
static void publishAmp() {
|
||||||
AmpStatus s = agiltron.getStatus();
|
AmpStatus s = agiltron.getStatus();
|
||||||
mqtt.publish(TOPIC_BASE "/amp/link", s.valid ? AVAIL_ONLINE : AVAIL_OFFLINE, true);
|
mqtt.publish(TOPIC_BASE "/amp/link", s.valid ? AVAIL_ONLINE : AVAIL_OFFLINE, true);
|
||||||
|
pub("amp/pump_override", pumpOverride ? "1" : "0"); // reflect override state
|
||||||
if (!s.valid) return;
|
if (!s.valid) return;
|
||||||
pub("amp/module_temp_c", String(s.module_temp_c, 1));
|
pub("amp/module_temp_c", String(s.module_temp_c, 1));
|
||||||
pub("amp/preamp_temp_c", String(s.preamp_temp_c, 1));
|
pub("amp/preamp_temp_c", String(s.preamp_temp_c, 1));
|
||||||
@@ -230,6 +257,13 @@ static void onMqtt(char *topic, byte *payload, unsigned int len) {
|
|||||||
koheron.query((String("ilaser ") + String(ma, 1)).c_str());
|
koheron.query((String("ilaser ") + String(ma, 1)).c_str());
|
||||||
pub("seed/current_ma", String(ma, 3));
|
pub("seed/current_ma", String(ma, 3));
|
||||||
|
|
||||||
|
} else if (t == TOPIC_CMD "/seed/rt_setpoint_ohm") {
|
||||||
|
long ohm = p.toInt();
|
||||||
|
if (ohm < SEED_RT_SETPOINT_MIN_OHM) ohm = SEED_RT_SETPOINT_MIN_OHM;
|
||||||
|
if (ohm > SEED_RT_SETPOINT_MAX_OHM) ohm = SEED_RT_SETPOINT_MAX_OHM; // firmware clamp
|
||||||
|
koheron.query((String("rtset ") + String(ohm)).c_str());
|
||||||
|
pub("seed/rt_setpoint_ohm", String(ohm));
|
||||||
|
|
||||||
} else if (t == TOPIC_CMD "/amp/pump") {
|
} else if (t == TOPIC_CMD "/amp/pump") {
|
||||||
bool on = (p == "1" || p.equalsIgnoreCase("on") || p.equalsIgnoreCase(AVAIL_ONLINE));
|
bool on = (p == "1" || p.equalsIgnoreCase("on") || p.equalsIgnoreCase(AVAIL_ONLINE));
|
||||||
bool ok = on ? agiltron.pumpOn() : agiltron.pumpOff();
|
bool ok = on ? agiltron.pumpOn() : agiltron.pumpOff();
|
||||||
@@ -239,11 +273,29 @@ static void onMqtt(char *topic, byte *payload, unsigned int len) {
|
|||||||
} else if (t == TOPIC_CMD "/amp/pump1_current_ma") {
|
} else if (t == TOPIC_CMD "/amp/pump1_current_ma") {
|
||||||
int ma = (int)p.toFloat();
|
int ma = (int)p.toFloat();
|
||||||
if (ma < 0) ma = 0;
|
if (ma < 0) ma = 0;
|
||||||
if (ma > PUMP_CURRENT_MAX_MA) ma = PUMP_CURRENT_MAX_MA; // firmware clamp
|
int cap = pumpOverride ? PUMP_CURRENT_HARD_MAX_MA : PUMP_CURRENT_SOFT_MAX_MA;
|
||||||
|
if (ma > cap) ma = cap; // firmware clamp (soft unless overridden)
|
||||||
agiltron.setPump1Mode(Agiltron::ACC); // current-control mode (lab note: avoid APC steps)
|
agiltron.setPump1Mode(Agiltron::ACC); // current-control mode (lab note: avoid APC steps)
|
||||||
bool ok = agiltron.setPump1Current(ma);
|
bool ok = agiltron.setPump1Current(ma);
|
||||||
Serial.printf("[cmd] amp pump1 = %d mA -> %s\n", ma, ok ? "ack" : "NO-ACK");
|
pump1SetMa = ma;
|
||||||
|
Serial.printf("[cmd] amp pump1 = %d mA (cap %d) -> %s\n", ma, cap, ok ? "ack" : "NO-ACK");
|
||||||
pub("amp/pump1_set_ma", String(ma));
|
pub("amp/pump1_set_ma", String(ma));
|
||||||
|
|
||||||
|
} else if (t == TOPIC_CMD "/amp/pump_override") {
|
||||||
|
bool on = (p == "1" || p.equalsIgnoreCase("on") || p.equalsIgnoreCase(AVAIL_ONLINE));
|
||||||
|
pumpOverride = on;
|
||||||
|
Serial.printf("[cmd] pump limit override -> %s\n",
|
||||||
|
on ? "ON (hard 8000 mA)" : "OFF (soft 4000 mA)");
|
||||||
|
// Disabling the override must not leave the booster parked above 1 W: if the
|
||||||
|
// running setpoint is above the soft cap, re-clamp it down immediately.
|
||||||
|
if (!on && pump1SetMa > PUMP_CURRENT_SOFT_MAX_MA) {
|
||||||
|
pump1SetMa = PUMP_CURRENT_SOFT_MAX_MA;
|
||||||
|
agiltron.setPump1Mode(Agiltron::ACC);
|
||||||
|
agiltron.setPump1Current(pump1SetMa);
|
||||||
|
pub("amp/pump1_set_ma", String(pump1SetMa));
|
||||||
|
Serial.printf("[cmd] override off: re-clamped pump1 to %d mA\n", pump1SetMa);
|
||||||
|
}
|
||||||
|
pub("amp/pump_override", on ? "1" : "0");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user