chore(edge): retire sml2mqtt for good (REN-802)
The SML power-meter reader was retired by owner decision on 2026-09-19: grid data comes from the E380 on the CAN bus (wiki sml-to-e380-grid-meter-migration). Its reader has been unplugged since then. - register: edge-1 loses write sml2mqtt/# (live, no broker restart) - bridge templates: no sml2mqtt topic, no legacy sml2mqtt bridge - remove role power-meter-sml, play install-power-meter.yml, inventory group power_meter and the vault indirection - edge-mqtt-bridge play: pre_tasks stop and remove the leftover compose project on edge-1 on the next pipeline run (idempotent) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
527c90ee9b
commit
4daee5fcea
@@ -62,7 +62,6 @@ inventory change.
|
||||
|---|---|---|
|
||||
| Innomaker USB2CAN (Vitocal/VX3 + E380) | `heating_can` | `plays/install-heating-can.yml` |
|
||||
| Nabu Casa ZWA-2 Z-Wave radio | `zwave_gateway` | `plays/install-zwave-gateway.yml` |
|
||||
| FTDI serial power-meter head | `power_meter` | `plays/install-power-meter.yml` |
|
||||
|
||||
Add the host to the matching group, then run that playbook. They depend on
|
||||
the base play having run: all three need the local MQTT bridge, and two of
|
||||
@@ -87,7 +86,6 @@ kubectl -n home-assistant get secret zwave-js-session -o jsonpath='{.data.sessio
|
||||
|
||||
```yaml
|
||||
# vault.yml
|
||||
vault_edge_sml2mqtt_mqtt_password: "..."
|
||||
vault_edge_open3e_mqtt_password: "..."
|
||||
vault_edge_zwavejs_session_secret: "..."
|
||||
vault_edge_loki_password: "..." # dedicated edge-1 Loki push cred (see "Logging & eMMC")
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
# Secret indirection — real values live in vault.yml (ansible-vault) next
|
||||
# to this file; see docs/edge-host.md for how to populate it from
|
||||
# the cluster secrets.
|
||||
edge_sml2mqtt_mqtt_password: "{{ vault_edge_sml2mqtt_mqtt_password }}"
|
||||
edge_open3e_mqtt_password: "{{ vault_edge_open3e_mqtt_password }}"
|
||||
edge_zwavejs_session_secret: "{{ vault_edge_zwavejs_session_secret }}"
|
||||
# Loki push credential (dedicated 'edge-1' user, Traefik basic-auth on the
|
||||
@@ -39,8 +38,8 @@ chrony_hwtimestamp: false
|
||||
|
||||
# --- Local MQTT contract -------------------------------------------------
|
||||
# Shared between the broker and its clients, so it belongs to the inventory
|
||||
# rather than to either side's role. mqtt-bridge listens here; heating-can and
|
||||
# power-meter-sml publish here; and the bridge authenticates to the cluster
|
||||
# rather than to either side's role. mqtt-bridge listens here; heating-can
|
||||
# publishes here; and the bridge authenticates to the cluster
|
||||
# broker AS the open3e user, so it needs that identity too.
|
||||
#
|
||||
# Keeping these in one role's defaults only worked while a single play loaded
|
||||
|
||||
@@ -41,13 +41,9 @@ edge-1.iot.renner.uno
|
||||
[heating_can]
|
||||
edge-1.iot.renner.uno
|
||||
|
||||
[power_meter]
|
||||
edge-1.iot.renner.uno
|
||||
|
||||
[edge_host:children]
|
||||
zwave_gateway
|
||||
heating_can
|
||||
power_meter
|
||||
|
||||
# NTP servers. edge-1 was added here so the role would survive the PVE hosts
|
||||
# being retired for bare-metal Talos, which cannot run chrony or keepalived
|
||||
|
||||
@@ -7,5 +7,27 @@
|
||||
hosts: edge_host
|
||||
become: true
|
||||
|
||||
# The SML power-meter reader (sml2mqtt) is retired for good: owner decision
|
||||
# 2026-09-19, grid data comes from the E380 on the CAN bus. Its role and
|
||||
# play are gone; this removes the leftover compose project from edge-1 on
|
||||
# the next pipeline run. Idempotent: a no-op once the directory is gone.
|
||||
pre_tasks:
|
||||
- name: Check for the retired sml2mqtt compose project
|
||||
ansible.builtin.stat:
|
||||
path: /opt/edge-host/sml2mqtt/docker-compose.yml
|
||||
register: edge_sml2mqtt_compose
|
||||
|
||||
- name: Stop the retired sml2mqtt container
|
||||
ansible.builtin.command:
|
||||
cmd: docker compose down --remove-orphans
|
||||
chdir: /opt/edge-host/sml2mqtt
|
||||
when: edge_sml2mqtt_compose.stat.exists
|
||||
changed_when: true
|
||||
|
||||
- name: Remove the retired sml2mqtt directory
|
||||
ansible.builtin.file:
|
||||
path: /opt/edge-host/sml2mqtt
|
||||
state: absent
|
||||
|
||||
roles:
|
||||
- mqtt-bridge
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#
|
||||
# plays/install-zwave-gateway.yml [zwave_gateway] ZWA-2 radio
|
||||
# plays/install-heating-can.yml [heating_can] Innomaker USB2CAN
|
||||
# plays/install-power-meter.yml [power_meter] FTDI meter head
|
||||
#
|
||||
# Splitting the roles without splitting the plays would have kept the coupling:
|
||||
# one play applying every role to one group is the monolith again, a layer up.
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
---
|
||||
# Power-meter reader (sml2mqtt) for hosts with the FTDI serial meter head
|
||||
# attached.
|
||||
#
|
||||
# Targets [power_meter]: membership means the meter head is physically present.
|
||||
#
|
||||
# Requires the base play (plays/install-edge-host.yml) to have run: it needs
|
||||
# docker and publishes to the local MQTT bridge.
|
||||
- name: Install power-meter reader
|
||||
hosts: power_meter
|
||||
become: true
|
||||
|
||||
roles:
|
||||
- power-meter-sml
|
||||
@@ -1,8 +1,8 @@
|
||||
# Managed by roles/mqtt-bridge (REN-1307). edge-mqtt-cred appends
|
||||
# remote_username/remote_password to this and writes the result to
|
||||
# /etc/mosquitto/bridge.d/{{ edge_mqtt_bridge_account }}.conf. One bridge for
|
||||
# both topic sets, as the register account `{{ edge_mqtt_bridge_account }}`
|
||||
# (ACL readwrite open3e/#, write sml2mqtt/#).
|
||||
# open3e, as the register account `{{ edge_mqtt_bridge_account }}`
|
||||
# (ACL readwrite open3e/#). sml2mqtt is retired (SML reader, 2026-09-19).
|
||||
connection {{ edge_mqtt_bridge_account }}
|
||||
address {{ edge_mqtt_cluster_host }}:{{ edge_mqtt_cluster_tls_port }}
|
||||
bridge_cafile {{ edge_mqtt_bridge_cafile }}
|
||||
@@ -19,4 +19,3 @@ notifications_local_only true
|
||||
notification_topic $SYS/broker/connection/{{ edge_mqtt_bridge_account }}/state
|
||||
topic open3e/# out 0
|
||||
topic open3e/cmnd in 0
|
||||
topic sml2mqtt/# out 0
|
||||
|
||||
@@ -19,19 +19,3 @@ restart_timeout 5 30
|
||||
queue_qos0_messages true
|
||||
topic open3e/# out 0
|
||||
topic open3e/cmnd in 0
|
||||
|
||||
# --- Bridge 2: power-meter topics, as the cluster `sml2mqtt` user (ACL
|
||||
# `write sml2mqtt/#`). Publish-only, so no `in` topics.
|
||||
connection edge1-sml2mqtt
|
||||
address {{ edge_mqtt_cluster_host }}:{{ edge_mqtt_cluster_tls_port }}
|
||||
bridge_cafile {{ edge_mqtt_bridge_cafile }}
|
||||
bridge_insecure false
|
||||
remote_username sml2mqtt
|
||||
remote_password {{ edge_sml2mqtt_mqtt_password }}
|
||||
remote_clientid edge1-bridge-sml2mqtt
|
||||
cleansession false
|
||||
try_private true
|
||||
notifications false
|
||||
restart_timeout 5 30
|
||||
queue_qos0_messages true
|
||||
topic sml2mqtt/# out 0
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
---
|
||||
# Power-meter reader: sml2mqtt against the FTDI serial meter head.
|
||||
|
||||
# Service root. Kept per-role so the role is usable on its own.
|
||||
edge_base_dir: /opt/edge-host
|
||||
|
||||
# Own compose project directory. Unchanged from the path used under the
|
||||
# previous shared project.
|
||||
sml_base_dir: "{{ edge_base_dir }}/sml2mqtt"
|
||||
|
||||
# The compose file that used to hold both this service and zwave-js. Removed
|
||||
# once, so the fixed container_name is released for the new projects.
|
||||
edge_legacy_compose: "{{ edge_base_dir }}/docker-compose.yml"
|
||||
|
||||
# Stable by-id symlink -- see the zwave-gateway role.
|
||||
edge_stromzaehler_device: /dev/serial/by-id/usb-FTDI_FT230X_Basic_UART_DK0E1GP5-if00-port0
|
||||
|
||||
# Hand-pinned on purpose: critical meter bridge, deliberately excluded from
|
||||
# image automation.
|
||||
edge_sml2mqtt_image: spacemanspiff2007/sml2mqtt:3.6.0
|
||||
|
||||
# Secret from inventory/group_vars/edge_host/vault.yml:
|
||||
# edge_sml2mqtt_mqtt_password
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
- name: Restart sml2mqtt
|
||||
ansible.builtin.command:
|
||||
cmd: docker compose up -d --force-recreate
|
||||
chdir: "{{ sml_base_dir }}"
|
||||
changed_when: true
|
||||
@@ -1,55 +0,0 @@
|
||||
---
|
||||
# Power-meter reader: sml2mqtt against the FTDI serial meter head.
|
||||
#
|
||||
# Its own compose project. It shared a compose file with the Z-Wave gateway,
|
||||
# which meant a change to either recreated both -- two unrelated services bound
|
||||
# to different USB devices. Data paths are unchanged.
|
||||
|
||||
- name: Create sml2mqtt directory
|
||||
ansible.builtin.file:
|
||||
path: "{{ sml_base_dir }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
# sml2mqtt reads only a config file and has no env override for the password,
|
||||
# so the rendered config carries it -- 0600, root-owned, same trust boundary as
|
||||
# the k8s init-container approach it replaced.
|
||||
- name: Deploy sml2mqtt config
|
||||
ansible.builtin.template:
|
||||
src: sml2mqtt-config.yml.j2
|
||||
dest: "{{ sml_base_dir }}/config.yml"
|
||||
mode: "0600"
|
||||
notify: Restart sml2mqtt
|
||||
|
||||
- name: Deploy sml2mqtt compose project
|
||||
ansible.builtin.template:
|
||||
src: docker-compose.yml.j2
|
||||
dest: "{{ sml_base_dir }}/docker-compose.yml"
|
||||
mode: "0644"
|
||||
notify: Restart sml2mqtt
|
||||
|
||||
- name: Pull sml2mqtt image
|
||||
ansible.builtin.command:
|
||||
cmd: docker compose pull
|
||||
chdir: "{{ sml_base_dir }}"
|
||||
changed_when: false
|
||||
|
||||
# Tolerates a missing USB device, as for the Z-Wave gateway.
|
||||
- name: Start sml2mqtt
|
||||
ansible.builtin.command:
|
||||
cmd: docker compose up -d
|
||||
chdir: "{{ sml_base_dir }}"
|
||||
register: sml_compose_up
|
||||
failed_when: >-
|
||||
sml_compose_up.rc != 0
|
||||
and 'error gathering device information' not in sml_compose_up.stderr
|
||||
changed_when: >-
|
||||
'Started' in sml_compose_up.stderr or 'Created' in sml_compose_up.stderr
|
||||
or 'Started' in sml_compose_up.stdout or 'Creating' in sml_compose_up.stdout
|
||||
|
||||
- name: Warn if sml2mqtt is waiting for its USB device
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
sml2mqtt could not start -- the FTDI meter head is not present:
|
||||
{{ sml_compose_up.stderr_lines | select('search', 'device') | list }}
|
||||
when: sml_compose_up.rc != 0
|
||||
@@ -1,21 +0,0 @@
|
||||
# Managed by ansible (roles/power-meter-sml).
|
||||
#
|
||||
# Its own compose project -- see the role tasks for why it no longer shares a
|
||||
# file with the Z-Wave gateway.
|
||||
#
|
||||
# Outbound-only (serial in, MQTT out). Host networking sidesteps the docker
|
||||
# NAT66 gap: the broker LB is IPv6-first and bridge-network v6 SYNs leave
|
||||
# unmasqueraded and die. The host stack reaches it fine (open3e proves it
|
||||
# continuously).
|
||||
services:
|
||||
sml2mqtt:
|
||||
image: {{ edge_sml2mqtt_image }}
|
||||
container_name: sml2mqtt
|
||||
restart: unless-stopped
|
||||
network_mode: host
|
||||
# Serial exclusivity: this is the ONLY reader of the meter port --
|
||||
# HA's edl21 integration stays disabled.
|
||||
devices:
|
||||
- "{{ edge_stromzaehler_device }}:/dev/stromzaehler"
|
||||
volumes:
|
||||
- ./config.yml:/sml2mqtt/config.yml:ro
|
||||
@@ -1,32 +0,0 @@
|
||||
# Managed by ansible (roles/power-meter-sml). Ported 1:1 from the k8s ConfigMap
|
||||
# sml2mqtt-config; only host and password delivery differ (broker is now
|
||||
# reached via its LoadBalancer hostname, password rendered by ansible
|
||||
# instead of an init container).
|
||||
logging:
|
||||
level: INFO
|
||||
file: stdout # 'stdout' -> docker logs (NOT '' — that opens the dir)
|
||||
|
||||
mqtt:
|
||||
connection:
|
||||
identifier: sml2mqtt-meter
|
||||
host: {{ edge_mqtt_host }}
|
||||
port: {{ edge_mqtt_port }}
|
||||
user: sml2mqtt
|
||||
password: {{ edge_sml2mqtt_mqtt_password }}
|
||||
topic prefix: sml2mqtt
|
||||
defaults:
|
||||
qos: 0
|
||||
retain: false
|
||||
last will:
|
||||
topic: status
|
||||
|
||||
general:
|
||||
Wh in kWh: false # keep raw Wh to match edl21 history — HA long-term
|
||||
# stats continue with zero unit discontinuity
|
||||
republish after: 120 # re-emit unchanged values every 120 s
|
||||
|
||||
inputs:
|
||||
- type: serial
|
||||
url: /dev/stromzaehler # FTDI FT230X, mapped by docker compose
|
||||
timeout: 3
|
||||
# 9600 8N1 are sml2mqtt defaults for SML/EDL21 — no override needed.
|
||||
@@ -69,7 +69,9 @@
|
||||
- name: edge-1
|
||||
auth: password
|
||||
deliver: {esp32: "https://edge-1.iot.renner.uno:8443"}
|
||||
acl: ["readwrite open3e/#", "write sml2mqtt/#"]
|
||||
# No sml2mqtt/#: the SML meter reader is retired for good (owner,
|
||||
# 2026-09-19; grid data comes from the E380 on the CAN bus).
|
||||
acl: ["readwrite open3e/#"]
|
||||
# Shellies (REN-1017): one password account per device, pushed by the
|
||||
# controller through shelly-manager's per-device API. Name shelly-<id> =
|
||||
# shelly-manager device id. ACL as the former shared user `shellies`
|
||||
|
||||
Reference in New Issue
Block a user