commit abcb6b75535601bca3ead127a6f9314626d1c4e5 Author: James Bateman (hp-laptop) Date: Fri Oct 2 13:35:40 2026 +0100 Butterfly EDFA Rack: Home Assistant config and dashboard Fresh repo holding only the Butterfly-rack HA side (the firmware and BOM live in koheron-esp32-bridge). Contents, pulled from the live HA host: - configuration.butterfly.yaml: dBm->mW template sensors + the YAML dashboard registration, to merge into /config/configuration.yaml - scripts.butterfly.yaml: the butterfly_edfa_shutdown emergency-off script - ui-lovelace-butterfly.yaml: the dashboard - deploy.sh: scp the dashboard, check_config, restart HA - .gitignore guards against committing the HA DB, logs and secrets Co-Authored-By: Claude Opus 4.8 (1M context) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9e92106 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +# This repo holds only the Butterfly-rack HA config, not a full HA checkout. +# Guard against accidentally committing runtime/DB/secret artefacts if someone +# copies files in from a live /config. +secrets.yaml +home-assistant_v2.db* +*.log +*.log.* +.storage/ +.cloud/ +__pycache__/ +*.pyc +*.bak-* +*~ diff --git a/README.md b/README.md new file mode 100644 index 0000000..50948e3 --- /dev/null +++ b/README.md @@ -0,0 +1,37 @@ +# butterfly-rack-homeassistant + +Home Assistant configuration for the **Butterfly EDFA Rack** (the 1550 nm MOPA: Koheron CTL200 seed + Agiltron M511 EYDFA booster, bridged to MQTT by an ESP32). The firmware and bill of materials live in the separate `koheron-esp32-bridge` repo; this repo is only the HA side. + +Almost everything is auto-discovered over MQTT: the firmware publishes `homeassistant/.../config` topics, so the rack's sensors, switches and numbers appear by themselves under one device, `butterfly_edfa_rack`. These files add only what discovery cannot: the derived mW power sensors, the emergency-shutdown script, and the dashboard. + +## Files + +| File | Where it goes on the HA host | Purpose | +|------|------------------------------|---------| +| `configuration.butterfly.yaml` | merge into `/config/configuration.yaml` | mW template sensors (dBm→mW for the EDFA input, pre-amp and output PDs) + the `butterfly-rack` YAML-dashboard registration | +| `scripts.butterfly.yaml` | merge into `/config/scripts.yaml` | `butterfly_edfa_shutdown` emergency-off script | +| `ui-lovelace-butterfly.yaml` | copy whole to `/config/ui-lovelace-butterfly.yaml` | the dashboard itself | +| `deploy.sh` | run from this repo | push the dashboard to the host, validate config, restart HA | + +`configuration.butterfly.yaml` and `scripts.butterfly.yaml` hold top-level keys to merge into the matching HA files; they are not standalone configs. `ui-lovelace-butterfly.yaml` is copied whole. + +## The HA host + +HA runs in Docker on `lab-desktop.local` (broker host `192.168.0.102`). The compose file bind-mounts `~/HA/homeassistant/` on the host as the container's `/config`, and the container is named `homeassistant`. The MQTT broker is anonymous, so this config needs no secrets; `secrets.yaml` on the host is unrelated and is git-ignored here. + +Reach it with `ssh lab@lab-desktop.local`. The web UI is `http://lab-desktop.local:8123`; the dashboard shows in the sidebar as **Butterfly Rack** at `/butterfly-rack`. + +## Deploy + +```bash +./deploy.sh # scp the dashboard, check_config, restart HA +./deploy.sh --dash-only # just the dashboard (no restart; browser refresh picks it up) +``` + +The dashboard reloads on a browser refresh. Changes to `configuration.yaml` or `scripts.yaml` need the restart. Merge the two snippet files into their targets on the host by hand the first time (or whenever their keys change); `deploy.sh` does not merge them for you, so it does not clobber unrelated config. + +## Notes + +- The `*_mw` sensors derive 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`, in ohms); the °C readout is a firmware-side NTC β-model. See the firmware repo README. +- Safety: Class 4, >1 W, invisible 1550 nm. The dashboard's PUMP OFF / SEED OFF / SHUTDOWN controls and the shutdown script drop the pump before the seed; they are operational conveniences, not hardware interlocks. diff --git a/configuration.butterfly.yaml b/configuration.butterfly.yaml new file mode 100644 index 0000000..58f9d7c --- /dev/null +++ b/configuration.butterfly.yaml @@ -0,0 +1,63 @@ +# Butterfly EDFA Rack: snippet to merge into Home Assistant's configuration.yaml. +# +# The rack's sensors, switches and numbers arrive automatically over MQTT +# discovery (the ESP32 firmware publishes homeassistant/.../config topics), so +# this file only adds what discovery cannot: mW optical-power sensors derived +# from the dBm telemetry, and the YAML-mode dashboard registration. +# +# These are top-level keys. Merge them into the existing configuration.yaml on +# the HA host (do not drop its default_config:, frontend:, etc.). If an `mqtt:` +# key already exists there, merge the sensors into it rather than adding a +# second `mqtt:` block. +# +# The internal PDs read ~0.5 dB optimistic, so treat the mW figures as +# indicative, not calibrated. + +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 HA's /config directory. +lovelace: + mode: storage + dashboards: + butterfly-rack: + mode: yaml + title: Butterfly Rack + icon: mdi:laser-pointer + show_in_sidebar: true + filename: ui-lovelace-butterfly.yaml diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..3de5393 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +# Deploy the Butterfly-rack dashboard to the lab HA host. +# +# ./deploy.sh push dashboard, validate config, restart HA +# ./deploy.sh --dash-only push dashboard only (browser refresh picks it up) +# +# The snippet files (configuration.butterfly.yaml, scripts.butterfly.yaml) are +# merged into /config by hand the first time; this script never edits them on +# the host, so it cannot clobber unrelated config. +set -euo pipefail + +HOST="${HA_HOST:-lab@lab-desktop.local}" +CONFIG_DIR="${HA_CONFIG_DIR:-/home/lab/HA/homeassistant}" # bind-mounted as /config +CONTAINER="${HA_CONTAINER:-homeassistant}" +DASH="ui-lovelace-butterfly.yaml" + +cd "$(dirname "$0")" + +echo "Copying $DASH to $HOST:$CONFIG_DIR/ ..." +scp "$DASH" "$HOST:$CONFIG_DIR/$DASH" + +if [[ "${1:-}" == "--dash-only" ]]; then + echo "Dashboard copied. Refresh the browser to see it." + exit 0 +fi + +echo "Validating config in the container ..." +ssh "$HOST" "docker exec $CONTAINER python -m homeassistant --script check_config -c /config" + +echo "Restarting $CONTAINER ..." +ssh "$HOST" "docker restart $CONTAINER" +echo "Done. HA is restarting; the dashboard is at http://lab-desktop.local:8123/butterfly-rack" diff --git a/scripts.butterfly.yaml b/scripts.butterfly.yaml new file mode 100644 index 0000000..dbc5e05 --- /dev/null +++ b/scripts.butterfly.yaml @@ -0,0 +1,16 @@ +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 diff --git a/ui-lovelace-butterfly.yaml b/ui-lovelace-butterfly.yaml new file mode 100644 index 0000000..38932c4 --- /dev/null +++ b/ui-lovelace-butterfly.yaml @@ -0,0 +1,136 @@ +# Butterfly EDFA Rack (MOPA) — YAML-mode dashboard. +# Registered in configuration.yaml (lovelace: dashboards: butterfly-rack). +# Entities auto-discovered over MQTT; the *_mw power sensors are MQTT template +# sensors defined in configuration.yaml (dBm -> mW). +title: Butterfly Rack +views: + - title: MOPA + path: mopa + icon: mdi:laser-pointer + cards: + - type: vertical-stack + title: Butterfly EDFA Rack (MOPA, 1550 nm, Class 4) + cards: + # ===== Graphs at the top: powers (mW) and temperatures ===== + - type: history-graph + title: EDFA 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 + + # Seed-side signal: CTL200 monitor photodiode current. Tracks seed + # output relatively; NOT a calibrated optical power. + - 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 + + # ===== Status + headline values ===== + - type: glance + columns: 3 + entities: + - entity: binary_sensor.butterfly_edfa_rack_seed_link + name: Seed link + - entity: binary_sensor.butterfly_edfa_rack_amp_link + name: Amp link + - entity: sensor.butterfly_edfa_rack_amp_warnings + name: Warnings + + - type: glance + title: Power now (internal PDs, indicative) + columns: 3 + entities: + - entity: sensor.butterfly_edfa_rack_amp_input_power_mw + name: EDFA in mW + - entity: sensor.butterfly_edfa_rack_output_power + name: Out dBm + - entity: sensor.butterfly_edfa_rack_output_power_mw + name: Out mW + + # ===== Stage 1: seed (Koheron CTL200) ===== + - type: entities + title: Stage 1 — Seed (turn on FIRST) + state_color: true + entities: + - entity: switch.butterfly_edfa_rack_seed_laser + name: Seed laser + - entity: number.butterfly_edfa_rack_seed_set_current + name: Seed current set (<= 300 mA) + - entity: sensor.butterfly_edfa_rack_seed_current + name: Seed current (actual) + - entity: number.butterfly_edfa_rack_seed_thermistor_setpoint + name: Seed temp setpoint (Ω, rtset) + - entity: sensor.butterfly_edfa_rack_seed_temperature + name: Seed temperature (from thermistor) + - entity: sensor.butterfly_edfa_rack_seed_photodiode_current + name: Seed monitor PD (mA, relative) + + # ===== Stage 2: booster (Agiltron M511, ACC) ===== + - type: entities + title: Stage 2 — Booster (ACC; ramp gently, settles 2-4 s) + state_color: true + entities: + - entity: switch.butterfly_edfa_rack_amp_pump + name: Booster pump enable + - entity: number.butterfly_edfa_rack_amp_pump_current + name: Pump-1 current set (<= 4000 mA ~ 1 W) + - entity: sensor.butterfly_edfa_rack_amp_pump_1_current + name: Pump-1 current (actual) + - entity: sensor.butterfly_edfa_rack_amp_module_temperature + name: Module temp + + # ===== Emergency off ===== + - type: horizontal-stack + cards: + - type: button + name: PUMP OFF + icon: mdi:power-off + icon_height: 40px + tap_action: + action: call-service + service: switch.turn_off + target: + entity_id: switch.butterfly_edfa_rack_amp_pump + - type: button + name: SEED OFF + icon: mdi:power-off + icon_height: 40px + tap_action: + action: call-service + service: switch.turn_off + target: + entity_id: switch.butterfly_edfa_rack_seed_laser + - type: button + name: SHUTDOWN + icon: mdi:alarm-light + icon_height: 40px + tap_action: + action: call-service + service: script.butterfly_edfa_shutdown + confirmation: + text: Ramp pump to 0, disable booster, then seed?