Make the pump-1 1 W clamp overridable up to the 8000 mA protocol max

Adds a Pump Limit Override: the pump-1 current command is clamped to a
soft 4000 mA ceiling (the 1 W diode rating) by default, liftable to the
8000 mA hard protocol max only while the new amp_pump_override switch is
ON. Fail-safe: the override defaults OFF on every boot, and switching it
OFF immediately re-clamps a running setpoint back down to 4000 mA so the
booster cannot stay parked above 1 W. The HA pump-1 number now spans
0..8000 mA; values above 4000 take effect only with the override on.

PUMP_CURRENT_MAX_MA is split into PUMP_CURRENT_SOFT_MAX_MA (4000) and
PUMP_CURRENT_HARD_MAX_MA (8000). README/BOM updated. Builds clean
(flash 59.3%); not yet flashed (ESP32 needs USB, no OTA).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
James Bateman (hp-laptop)
2026-10-02 14:21:56 +01:00
co-authored by Claude Opus 4.8
parent ffadf4b37b
commit ba1cd1ddcf
4 changed files with 50 additions and 11 deletions
+1 -1
View File
@@ -57,4 +57,4 @@ Both instrument links are 3V3 logic at 115200 8N1 (the CTL200 is 5V-tolerant). U
## Safety ## Safety
Class 4, >1 W, invisible 1550 nm. The firmware current clamps (`SEED_CURRENT_MAX_MA`, `PUMP_CURRENT_MAX_MA`) 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. 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.
+5 -3
View File
@@ -55,7 +55,7 @@ The unit (factory APC setpoint 30 dBm) comfortably exceeds 1 W: 1.22 W at 4500 m
> **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 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.
## Hardware ## Hardware
@@ -101,7 +101,7 @@ Copy `include/secrets.example.h` → `include/secrets.h` (git-ignored) and fill
- [x] **2 — WiFi + MQTT + HA discovery.** Joins WiFi, connects to broker with an LWT availability topic, publishes diagnostic entities (uptime, RSSI, IP). - [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] **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] **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`.) - [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`.)
## MQTT interface ## MQTT interface
@@ -124,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/`):
@@ -133,4 +134,5 @@ Command topics (publish to `lab/laser/butterfly_edfa_rack/set/`):
| `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`) | | `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 |
+12 -4
View File
@@ -26,10 +26,18 @@
// 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 // 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 // native °C setpoint; it regulates the seed TEC to hold the thermistor at this
+32 -3
View File
@@ -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
@@ -152,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 ────────────────────────────────────────────────
@@ -211,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));
@@ -262,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");
} }
} }