Bladeren bron

power: mikrotik off must cut the outlet relay too

The RouterOS API shutdown is best-effort — the device keeps power and can
boot back up (observed: fans at full after 'graceful' shutdown), and an
already-energized relay makes power on a no-op. shutdown_named now always
turns off on_entity via HA after the API call, with -would dry-run lines;
locale strings for the relay messages (en/ru/es/it/fr/de).
Alexander Korolev 3 weken geleden
bovenliggende
commit
5c82201e40
7 gewijzigde bestanden met toevoegingen van 61 en 25 verwijderingen
  1. 3 0
      locales/de/main.ftl
  2. 3 0
      locales/en/main.ftl
  3. 3 0
      locales/es/main.ftl
  4. 3 0
      locales/fr/main.ftl
  5. 3 0
      locales/it/main.ftl
  6. 3 0
      locales/ru/main.ftl
  7. 43 25
      src/power/mikro.rs

+ 3 - 0
locales/de/main.ftl

@@ -58,6 +58,9 @@ power-off-mikrotik-would = würde: RouterOS /system/shutdown { $name } ({ $addre
 power-off-mikrotik-failed = { $name }: RouterOS-Shutdown fehlgeschlagen ({ $error })
 power-on-mikrotik-manual = { $name }: RouterOS-Gerät — kein Remote-Einschalten; manuell einschalten
 power-on-mikrotik-manual-would = würde: { $name} manuell einschalten (RouterOS hat kein Remote-On)
+power-off-mikrotik-relay = { $name }: Steckdose { $relay } abgeschaltet (Strom unterbrochen)
+power-off-mikrotik-relay-would = würde: Steckdose { $relay } abschalten (trennt { $name } vom Strom)
+power-off-mikrotik-relay-failed = { $name }: Steckdose { $relay } konnte nicht abgeschaltet werden ({ $error }) — Stromlosigkeit prüfen
 power-on-mikrotik-relay-would = würde: Relais { $relay} einschalten (versorgt { $name} mit Strom)
 power-off-would = würde: { $action } { $target }
 power-off-shutdown-failed = ✗ { $name } :: { $cmd } :: { $error }

+ 3 - 0
locales/en/main.ftl

@@ -58,6 +58,9 @@ power-off-mikrotik-would = would: RouterOS /system/shutdown { $name } ({ $addres
 power-off-mikrotik-failed = { $name }: RouterOS shutdown failed ({ $error })
 power-on-mikrotik-manual = { $name }: RouterOS device — no remote power-on; power it on manually
 power-on-mikrotik-manual-would = would: power on { $name } manually (RouterOS has no remote on)
+power-off-mikrotik-relay = { $name }: relay { $relay } switched off (power cut)
+power-off-mikrotik-relay-would = would: turn off relay { $relay } (cuts power to { $name })
+power-off-mikrotik-relay-failed = { $name }: could not switch off relay { $relay } ({ $error }) — verify the outlet is de-energized
 power-on-mikrotik-relay-would = would: turn on relay { $relay} (powers on { $name})
 power-off-would = would: { $action } { $target }
 power-off-shutdown-failed = ✗ { $name } :: { $cmd } :: { $error }

+ 3 - 0
locales/es/main.ftl

@@ -58,6 +58,9 @@ power-off-mikrotik-would = haría: RouterOS /system/shutdown { $name } ({ $addre
 power-off-mikrotik-failed = { $name }: fallo el apagado de RouterOS ({ $error })
 power-on-mikrotik-manual = { $name }: dispositivo RouterOS — sin encendido remoto; enciéndalo manualmente
 power-on-mikrotik-manual-would = haría: encender { $name} manualmente (RouterOS no tiene on remoto)
+power-off-mikrotik-relay = { $name }: enchufe { $relay } apagado (sin alimentación)
+power-off-mikrotik-relay-would = acción: apagar el enchufe { $relay } (corta la corriente de { $name })
+power-off-mikrotik-relay-failed = { $name }: no se pudo apagar el enchufe { $relay } ({ $error }) — verifique que quede sin corriente
 power-on-mikrotik-relay-would = haría: activar el relé { $relay} (enciende { $name})
 power-off-would = sería: { $action } { $target }
 power-off-shutdown-failed = ✗ { $name } :: { $cmd } :: { $error }

+ 3 - 0
locales/fr/main.ftl

@@ -58,6 +58,9 @@ power-off-mikrotik-would = ferait : RouterOS /system/shutdown { $name } ({ $addr
 power-off-mikrotik-failed = { $name } : arrêt RouterOS en échec ({ $error })
 power-on-mikrotik-manual = { $name } : appareil RouterOS — pas d'allumage distant ; allumer manuellement
 power-on-mikrotik-manual-would = ferait : allumer { $name} manuellement (RouterOS n'a pas d'on distant)
+power-off-mikrotik-relay = { $name }: prise { $relay } coupée (alimentation interrompue)
+power-off-mikrotik-relay-would = action : couper la prise { $relay } (coupe le courant de { $name })
+power-off-mikrotik-relay-failed = { $name }: impossible de couper la prise { $relay } ({ $error }) — vérifier que le courant est coupé
 power-on-mikrotik-relay-would = ferait : activer le relais { $relay} (alimente { $name})
 power-off-would = serait : { $action } { $target }
 power-off-shutdown-failed = ✗ { $name } :: { $cmd } :: { $error }

+ 3 - 0
locales/it/main.ftl

@@ -58,6 +58,9 @@ power-off-mikrotik-would = eseguirebbe: RouterOS /system/shutdown { $name } ({ $
 power-off-mikrotik-failed = { $name }: arresto RouterOS fallito ({ $error })
 power-on-mikrotik-manual = { $name }: dispositivo RouterOS — nessun accensione remota; accendere manualmente
 power-on-mikrotik-manual-would = eseguirebbe: accendere { $name} manualmente (RouterOS non ha on remoto)
+power-off-mikrotik-relay = { $name }: presa { $relay } spenta (alimentazione interrotta)
+power-off-mikrotik-relay-would = azione: spegnere la presa { $relay } (toglie corrente a { $name })
+power-off-mikrotik-relay-failed = { $name }: impossibile spegnere la presa { $relay } ({ $error }) — verificare che sia senza corrente
 power-on-mikrotik-relay-would = eseguirebbe: attivare il relé { $relay} (alimenta { $name})
 power-off-would = sarebbe: { $action } { $target }
 power-off-shutdown-failed = ✗ { $name } :: { $cmd } :: { $error }

+ 3 - 0
locales/ru/main.ftl

@@ -61,6 +61,9 @@ power-off-mikrotik-would = бы: RouterOS /system/shutdown { $name } ({ $address
 power-off-mikrotik-failed = { $name }: сбой RouterOS shutdown ({ $error })
 power-on-mikrotik-manual = { $name }: устройство RouterOS — remote power-on невозможен; включите вручную
 power-on-mikrotik-manual-would = бы: включить { $name} вручную (у RouterOS нет remote on)
+power-off-mikrotik-relay = { $name }: розетка { $relay } обесточена
+power-off-mikrotik-relay-would = будет: выключить розетку { $relay } (обесточивает { $name })
+power-off-mikrotik-relay-failed = { $name }: не удалось обесточить розетку { $relay } ({ $error }) — проверьте, что питание снято
 power-on-mikrotik-relay-would = бы: включить реле { $relay} (запитывает { $name})
 power-off-would = будет: { $action } { $target }
 power-off-shutdown-failed = ✗ { $name } :: { $cmd } :: { $error }

+ 43 - 25
src/power/mikro.rs

@@ -1,7 +1,7 @@
 //! `[mikrotik]` dispatch helpers for the power flow: entity-name lookup,
 //! dry-run lines, and the graceful RouterOS API shutdown loop.
 
-use tracing::warn;
+use tracing::{info, warn};
 
 use crate::config::{Config, MikrotikConfig, MikrotikDevice, Node};
 
@@ -52,22 +52,52 @@ pub fn on_line(name: &str, relay: Option<&str>) {
 
 /// Graceful RouterOS API shutdown for each named device (warns on
 /// failure — a router that fails to shut down must not block the rest of
-/// the power flow).
+/// the power flow), then a hard power cut: the `on_entity` outlet relay is
+/// switched off via HA when configured. Without it the API shutdown is not
+/// final — the router keeps power (fans spinning, can boot back up) and
+/// `power on`, which is just the relay, would be a no-op on an
+/// already-energized outlet.
 pub async fn shutdown_named(config: &Config, names: &[String]) {
     for name in names {
-        if let Some(dev) = device(config.mikrotik.as_ref(), name)
-            && let Err(e) = crate::mikrotik::shutdown_device(dev).await
-        {
-            warn!(
-                "{}",
-                fluent_i18n::t!("power-off-mikrotik-failed", { "name" => &dev.name, "error" => e.to_string() })
-            );
+        let Some(dev) = device(config.mikrotik.as_ref(), name) else {
+            continue;
+        };
+        if let Err(e) = crate::mikrotik::shutdown_device(dev).await {
+            warn!("{}", fluent_i18n::t!("power-off-mikrotik-failed", { "name" => &dev.name, "error" => e.to_string() }));
+        }
+        let Some(relay) = &dev.on_entity else {
+            continue; // no relay configured: the API shutdown is all we have
+        };
+        match &config.home_assistant {
+            Some(ha_cfg) => match crate::home_assistant::Client::new(ha_cfg) {
+                Ok(ha) => match ha.turn_off(relay).await {
+                    Ok(()) => info!("{}", fluent_i18n::t!("power-off-mikrotik-relay", { "name" => &dev.name, "relay" => relay })),
+                    Err(e) => warn!("{}", fluent_i18n::t!("power-off-mikrotik-relay-failed", { "name" => &dev.name, "relay" => relay, "error" => e.to_string() })),
+                },
+                Err(e) => warn!("{}", fluent_i18n::t!("power-off-mikrotik-relay-failed", { "name" => &dev.name, "relay" => relay, "error" => e.to_string() })),
+            },
+            None => warn!("{}", fluent_i18n::t!("power-off-mikrotik-relay-failed", { "name" => &dev.name, "relay" => relay, "error" => "no HA configured" })),
         }
     }
 }
 
 /// Dry-run lines for `power off`: the ssh line per node plus the entity
 /// line (RouterOS API line for configured devices).
+/// Dry-run "power off" line set for one mikrotik device: RouterOS API
+/// shutdown plus the outlet relay cut when one is configured.
+fn dry_off_device(dev: &MikrotikDevice) {
+    println!(
+        "{}",
+        fluent_i18n::t!("power-off-mikrotik-would", { "name" => &dev.name, "address" => &dev.address })
+    );
+    if let Some(relay) = &dev.on_entity {
+        println!(
+            "{}",
+            fluent_i18n::t!("power-off-mikrotik-relay-would", { "name" => &dev.name, "relay" => relay })
+        );
+    }
+}
+
 pub fn dry_off(config: &Config, targets: &[Node], extras: &[String]) {
     for node in targets {
         println!(
@@ -76,10 +106,7 @@ pub fn dry_off(config: &Config, targets: &[Node], extras: &[String]) {
         );
         if let Some(entity) = &node.entity {
             match device(config.mikrotik.as_ref(), entity) {
-                Some(dev) => println!(
-                    "{}",
-                    fluent_i18n::t!("power-off-mikrotik-would", { "name" => &dev.name, "address" => &dev.address })
-                ),
+                Some(dev) => dry_off_device(dev),
                 None => println!(
                     "{}",
                     fluent_i18n::t!("power-off-entity-would", { "entity" => entity.to_string() })
@@ -89,10 +116,7 @@ pub fn dry_off(config: &Config, targets: &[Node], extras: &[String]) {
     }
     for id in extras {
         match device(config.mikrotik.as_ref(), id) {
-            Some(dev) => println!(
-                "{}",
-                fluent_i18n::t!("power-off-mikrotik-would", { "name" => &dev.name, "address" => &dev.address })
-            ),
+            Some(dev) => dry_off_device(dev),
             None => println!(
                 "{}",
                 fluent_i18n::t!("power-switch-would-off-extra", { "extra" => id.to_string() })
@@ -147,10 +171,7 @@ pub fn dry_switch_off(config: &Config, nodes: &[Node], extras: &[String]) {
         );
         if let Some(entity) = &node.entity {
             match device(config.mikrotik.as_ref(), entity) {
-                Some(dev) => println!(
-                    "{}",
-                    fluent_i18n::t!("power-off-mikrotik-would", { "name" => &dev.name, "address" => &dev.address })
-                ),
+                Some(dev) => dry_off_device(dev),
                 None => println!(
                     "{}",
                     fluent_i18n::t!("power-switch-would-off", { "entity" => entity.to_string() })
@@ -160,10 +181,7 @@ pub fn dry_switch_off(config: &Config, nodes: &[Node], extras: &[String]) {
     }
     for id in extras {
         match device(config.mikrotik.as_ref(), id) {
-            Some(dev) => println!(
-                "{}",
-                fluent_i18n::t!("power-off-mikrotik-would", { "name" => &dev.name, "address" => &dev.address })
-            ),
+            Some(dev) => dry_off_device(dev),
             None => println!(
                 "{}",
                 fluent_i18n::t!("power-switch-would-off-extra", { "extra" => id.to_string() })