release v1.0

This commit is contained in:
2026-07-21 12:39:10 +02:00
parent 2d2cdfad3b
commit a5a2b80d5d
19 changed files with 1245 additions and 2 deletions
+332 -2
View File
@@ -1,3 +1,333 @@
# apagado_seguro # Apagado Seguro de Instalacion
Este proyecto pretende brindar las herramientas para poder ejecutar un apagado
ordenado y seguro de una instalacion basandose en la monitorizacion de la red
de alimentacion y el estado de una bateria de respaldo, a traves de la informacion
que transmite el Cerbo GX de Victron Energy y con HomeAssistant como coordinador.
Este proyecto pretende brindar las herramientas para poder ejecutar un apagado ordenado y seguro de una instalacion basandose en la monitorizacion de la red de alimentacion y el estado de una bateria de respaldo, a traves de la informacion que transmite el Cerbo GX de Victron Energy y con HomeAssistant como coordinador. ## Resumen del proceso de apagado y encendido
Desde homeassistant se monitoriza los datos que brinda el Cerbo y el
Multiplus (inversor) de Victron. Principalmente fijandose en 2 datos concretos,
la carga de la bateria y el estado de la red de alimentacion.
La automatizacion de apagado tiene 2 fases.
En la primera, la raspi se encarga de apagar los datalogger, equipos de servicio
secundarios como raspis de dinamica, NVR o similar y parar la base de datos.
Esto permite que la sincronizacion con nuestro servidor no se rompa de forma
inesperada.
Esta primera fase se dispara cuando se cumplan las siguientes
condiciones simultaneamente:
- No hay alimentacion
- La carga de la bateria esta por debajo del primer umbral
Tras esta primera fase, la raspi de estatica, que es la que gestiona todo, se
mantiene encendida mientras la bateria no baje de un segundo umbral.
Esto da la opcion de reencender el sistema si vuelve la tension. De esta forma
cuando la bateria vuelva a un nivel estable se ejecutaria una automatizacion
de encendido. Empezando por la base de datos, seguido de volver a dar tension
al resto de equipos encendiendo los Shellys.
En caso de que la carga siguiese bajando, al llegar a un segundo umbral,
se dispararia la segunda fase.
El objetivo principal de esta es apagar la raspi de estatica de forma segura.
Para ello el homeassistant dispara un script de apagado que revisa que se haya
ejecutado la fase 1 (si no lo ha hecho lo hace ahora), sincroniza los discos y
ordena el poweroff del sistema.
A parte de eso, el Cerbo GX cortocircuita su rele 1, que controla el Shelly
que corta la alimentacion de la Raspi, con un delay de 60s para dar tiempo a
la automatizacion de homeassistant a completarse.
De forma similar a como lo haria la raspi, el Cerbo desactivara el rele una vez
la bateria vuelva a tener carga suficiente; cerrando de nuevo el Shelly, dando
alimentacion a la raspi y disparando la automatizacion de encendido.
![Esquema de fases](/docs/images/esquema_eventos.png)
## Descripcion detallada del proyecto
### Arquitectura
![Esquema de Arquitectura](/docs/images/esquema_arquitectura_instalacion.png)
### Fase 1 - Apagado de dataloggers, equipos de servicio y BDD
El objetivo principal de esta primera fase es apagar todos los equipos que
escriban en la base de datos y cerrar la misma de forma segura para evitar
fallos de sincronizacion. Una vez superado el umbral se ejecuta la autoamtizacion `apagado ordenado` de homeassistant. El proceso es el siguiente:
1. Homeassistant apaga los relés Shelly que controlan la alimentacion de los arduinos.
2. Homeassistant lanza el comando `touch /shutdown-signal/apagar` que crea el
archivo `apagar` en la ruta `/shutdown-signal/` del contenedor que está
vinculada con la ruta `/opt/shutdown-signal/` del host. Este archivo funciona
como flag o señal para iniciar el proceso de apagado en el host.
3. En el host está el script [shutdown-watcher.sh](/scripts/shutdown-watcher.sh)
corriendo como servicio. Este script monitoriza la ruta donde homeassistant genera
el archivo de señal y en funcion del nombre del archivo generado ejecuta un script
u otro.
4. La creacion del archivo `apagar` desencadena la ejecucion del script
[orchestrate-shutdown.sh](/scripts/orchestrate-shutdown.sh)
5. Este script empieza ejecutando un segundo script
[service-nodes-shutdown.sh](/scripts/services-nodes-shutdown.sh) encargado de
conectarse a una lista de equipos de servicio por ssh y ejecutar un poweroff
en estos.
6. Despues se ejecuta el script [mariadb-shutdown.sh](/scripts/mariadb-shutdown.sh)
que para el contenedor de `mariadb`, es decir la base de datos. Esto asegura
que los procesos de la misma se finalizan y la sincro se pausa sin errores.
7. Despues de 30s se corta la alimentacion de los equipos de servicios apagando
los Shellys correspondientes.
> [!NOTE] Todos los script tienen una funcion de log que permite ver si se han
> ejecutado correctamente.
> El archivo de log esta en la ruta `/var/log/safe-shutdown.log`
Con eso acabaria la primera fase de apagado.
Mientras no se sobrepase el segundo umbral la Raspi y el homeassistant se
mantendra encendido monitorizando la alimentacion para poder reiniciar todos
los equipos si se volviese a un estado estable por encima del primer umbral.
### Fase 2 - Apagado total
Esta fase es practicamente un clon de la primera (se reejecuta todo por si no
se hubiese ejecutado previamente) con el añadido de parar el homeassistant,
sincronizar los discos de la raspi y apagar la misma. Para poder cortar la
alimentacion de la Raspi una vez esta ya está apagada haremos uso de la funcion
de control de generador de respaldo del Cerbo GX de Victron. Con esta podremos
controlar un relé que mande una señal de apagado al Shelly que corta la
alimentacion de la Raspi con un retardo de 60s para asegurar que el script de
apagado ya haya finalizado. De esta forma el proceso seria el siguiente:
1. Homeassistant apaga los relés de los arduinos.
2. Homeassistant crea el archivo `apagado_total`.
3. Se ejecuta el script
[orchestrate-total-shutdown.sh](/scripts/orchestrate-total-shutdown.sh)
4. Este ejecuta [service-nodes-shutdown.sh](/scripts/service-nodes-shutdown.sh)
y [mariadb-shutdown.sh](/scripts/mariadb-shutdown.sh)
5. Para el homeassistant, sincroniza los discos y ordena el poweroff.
6. El Cerbo GX cuando detecta la bajada de bateria espera 60s y cortocircuita el relé
1 cuya salida normalmente abierta (NA) esta conectada a la entrada del Shelly que
controla la alimentacion. La salida del Shelly estará invertida respecto a su entrada
de forma que cuando reciba una señal positiva se apague.
Hecho esto la reinicializacion dependerá integramente del Cerbo GX que desactivara
la salida del rele 1 una vez la bateria vuelva a un nivel estable. Esto encendera la raspi, la cual a su vez iniciara el proceso de encendido ordenado ya que la bateria deberia estar por encima del primer umbral.
### Encendido Ordenado
De forma similar al resto esta es una automatizacion que se ejecuta una vez la
bateria supera el umbral establecido para el encendido, en este caso en sentido
positivo.
De nuevo se crea un archivo de señal con el nombre de `encender` que
desencadena la ejecucion del script
[orchestrate-startup.sh](/scripts/orchestrate-startup.sh).
El proceso quedaria asi:
1. Homeassistant crea el archivo `encender`.
2. Se ejecuta [orchestrate-startup.sh](/scripts/orchestrate-startup.sh)
que comprueba el estado de la base de datos iniciandola si no lo está.
3. Pasados 30s se encienden los Shellys de la instalacion devolviendo
la alimentacion tanto a los equipos de servicio como a los arduinos.
El encendido de estos deberia ser automático.
Este encendido ordenado permite que la base de datos ya este activa antes de
que el resto de equipos empiece a enviar datos evitando la perdida de la
primera lectura.
## Despliegue
### Requisitos
Software para el control y monitorizacion de procesos:
- Systemd
- Docker
- Homeassistant
- inotify-tools
- Scripts del proyecto
Para la conexion entre nodos:
- Clave SSH sin passphrase generada en el nodo principal (raspi estatica)
con su respectiva clave publica autorizada en cada nodo de servicio.
Hardware:
- Sistema de baterias de Victron con inversor y Cerbo
- Relés con integracion en homeassistant (Shellys)
### Instalación
> [!NOTE]
> Comprobacion rapida de los archivos con sus permisos correspondientes:
> ```bash
> ls -l /usr/local/sbin/db-clean-stop.sh \
> /usr/local/sbin/shutdown-service-nodes.sh \
> /usr/local/sbin/orchestrate-shutdown.sh \
> /usr/local/sbin/orchestrate-startup.sh \
> /usr/local/sbin/orchestrate-full-shutdown.sh \
> /usr/local/sbin/shutdown-watcher.sh
> ls -l /etc/systemd/system/shutdown-watcher.service
> which inotifywait || sudo apt install inotify-tools
> ```
#### Scripts
Todos los scripts deben ir instalados en la carpeta `/usr/local/sbin/` con permisos
de ejecucion.
En caso de que los scripts no esten ya instalados podremos instalarlos
copiandolos a la maquina y lanzando el siguiente comando:
```bash
sudo install -m 755 <script-path> /usr/local/sbin/<script.sh>
```
#### Clave SSH
El script de `service-nodes-shutdown.sh` se conecta por SSH sin contraseña a
los distintos equipos de servicio. Para ello hace falta generar una clave
SSH en el nodo principal (raspi de estatica) e instalar la clave publica correspondiente en cada nodo de
servicio.
Generar clave SSH en el nodo principal:
```bash
sudo ssh-keygen -t ed25519 -f /root/.ssh/id_shutdown -N "" -C "shutdown-orchestrator"
sudo ls -l /root/.ssh/id_shutdown # debe ser -rw------- (600), propietario root
```
Instalar clave publica en un nodo (desde el principal):
```bash
sudo ssh-copy-id -i /root/.ssh/id_shutdown.pub USER_DEL_NODO@IP_DEL_NODO
```
Comprobacion (debe devolver el hostname del nodo de servicio):
```bash
sudo ssh -i /root/.ssh/id_shutdown shutdown-user@IP_DEL_NODO 'hostname'
```
#### Carpeta de señal y volumen de Docker
Los archivos de señal se generan en la carpeta `/opt/shutdown-signal`, sino existe
hay que crearla:
```bash
sudo mkdir -p /opt/shutdown-signal
```
Para que homeassistant pueda escribir en ella hay que montarla como volumen
del contenedor en el `compose.yaml`
```yaml
homeassistant:
...
volumes:
- ...
- /opt/shutdown-signal:/shutdown-signal
```
Despues de añadirlo recrea el volumen:
```bash
docker compose up -d homeassistant # debe decir "Recreating"
```
#### Shell Commands de Homeassistant
Los comandos de shell que crean los archivos que sirven como señal para el
watcher se definen en la configuracion de homeassistant:
```yaml
# configuration.yaml
shell_command:
trigger_shutdown: touch /shutdown-signal/apagar
trigger_total_shutdown: touch /shutdown-signal/apagado_total
trigger_startup: touch /shutdown-signal/encender
```
#### Activar el Watcher
El servicio del watcher `shutdown-watcher.service` debe ir
con permisos de lectura y escritura en la carpeta de systemd
`/etc/systemd/system/`. Si no esta instalado instalar:
```bash
sudo install -m 644 shutdown-watcher.service /etc/systemd/system/shutdown-watcher.service
```
Para activarlo en systemd:
```bash
sudo systemctl daemon-reload
sudo systemctl enable --now shutdown-watcher.service
sudo systemctl status shutdown-watcher.service # debe salir "active (running)"
```
#### Estructura final de archivos
De esta forma los archivos correspondientes a este proyecto quedarian de la
siguiente forma:
```bash
# Servicio del watcher
/etc/systemd/system/
└── shutdown-watcher.service
# Carpeta que monitoriza el watcher
/opt/shutdown-signal/
# Homeassistant
/.../servidor-campo/
├── compose.yaml
└── homeassistant/
└── config_ha/
└── configuration.yaml
# Clave SSH
/root/.ssh/
├── id_shutdown
└── id_shutdown.pub
# Scripts
/usr/local/sbin/
├── mariadb-shutdown.sh
├── orchestrate-shutdown.sh
├── orchestrate-startup.sh
├── orchestrate-total-shutdown.sh
├── service-nodes-shutdown.sh
└── shutdown-watcher.sh
```
### Configuracion
Una vez este todo instalado hay distintas configuraciones a realizar en
funcion de la instalacion.
#### Automatizaciones Homeassistant
Como minimo habra que configurar los reles a apagar o encender. Tambien
pueden variar los umbrales, triggers y delays.
Todo esto se puede configurar desde la seccion de
`/configuracion/automatizaciones_y_escenas/` de la web de Homeassistant.
Ejemplos
- [apagado_ordenado_fase1](/docs/examples/apagado_ordenado_fase1.yaml.example)
- [apagado_total_fase2](/docs/examples/apagado_total_fase2.yaml.example)
- [encendido_ordenado](/docs/examples/encendido_ordenado.yaml.example)
#### IP-s nodos de servicio - service-nodes-shutdown.sh
En la cabecera de [service-nodes-shutdown.sh](/scripts/service-nodes-shutdown.sh)
en el array `NODES` se definen el usuario, IP y comando para el apagado de los
distintos nodos de servicio con el siguiente formato: `user@IP|command`
```bash
# service-nodes-shutdown.sh
NODES=(
"pi@192.168.1.219|sudo /usr/sbin/poweroff" # Raspi Dinamica
"root@192.168.1.50|/sbin/poweroff" # NVR
)
```
#### Cerbo y Shelly del nodo principal
Para el apagado completo debemos encender y configurar la funcion de
`Arranque/parada del generador` del rele 1 del Cerbo GX.
Desde su interfaz remota:
- Habilitar:
`/Ajustes/Integraciones/Reles/Funcion (Rele 1)/Arranque/parada del generador`
![Configuracion Cerbo GX](/docs/images/ConfigCerbo.png)
- Configurar:
`/Ajustes/Dispositivos/Grupo Electrogeno/`
- Funcion de arranque automatico -> ON
- Ajustes/Condiciones/Estado de carga de la bateria -> Definir umbrales y retardos
![Umbrales Cerbo GX](/docs/images/UmbralesCerbo.png)
> [!WARNING]
> Esta funcion controlara la alimentacion del nodo principal cortando cualquier
> proceso que se pueda estar ejecutando en el mismo. Conviene definir un
> retardo suficientemente amplio para que el proceso de apagado total finalice
> correctamente.
Una vez cofigurado esto debemos conectar la salida NA del rele 1 del Cerbo a una
entrada del Shelly y configurar la accion de la misma en este:
- OUT Input/Output settings:
- Input mode -> Switch
- Output type -> Toggle
- IN Invert -> ON
> [!NOTE]
> La funcion de Arranque/Parada del generador de Cerbo esta pensada para encender un
> generador cuando la bateria baje de un umbral pero nosotros lo usamos justamente
> para lo contrario, apagar el nodo principal, es por esto que la entrada del
> Shelly se invierte.
+120
View File
@@ -0,0 +1,120 @@
services:
mariadb:
container_name: mariadb
image: mariadb:11
restart: always
environment:
- MARIADB_ROOT_PASSWORD=Ant3na2021
- TZ=Europe/Madrid
ports:
- "3307:3306"
volumes:
- mariadb_data:/var/lib/mysql
- ./mariadb/init:/docker-entrypoint-initdb.d:ro
- ./mariadb/conf.d/replication.cnf:/etc/mysql/mariadb.conf.d/99-replication.cnf:ro
networks:
- teknes-net
phpmyadmin:
container_name: phpmyadmin
image: phpmyadmin:latest
restart: always
# ports:
# - "80:80"
environment:
- PMA_HOST=mariadb
- PMA_PORT=3306
- PMA_ABSOLUTE_URI=http://localhost/phpmyadmin/
- TZ=Europe/Madrid
depends_on:
- mariadb
networks:
- teknes-net
nginx:
container_name: nginx
image: nginx:alpine
restart: always
extra_hosts:
- "host.docker.internal:host-gateway"
ports:
- "80:80"
volumes:
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
- phpmyadmin
networks:
- teknes-net
app-servidor-campo:
container_name: app-servidor-campo
build: ./app-servidor-campo
init: true
restart: on-failure
ports:
- "5566:5566"
volumes:
- ./app-servidor-campo/app:/home/teknes/ServidorCampo
environment:
- TZ=Europe/Madrid
depends_on:
- mariadb
networks:
- teknes-net
# mqtt:
# container_name: mqtt
# image: eclipse-mosquitto
# restart: unless-stopped
# network_mode: host
# volumes:
# - ./homeassistant/config_mqtt:/mosquitto/config
# - ./homeassistant/data_mqtt:/mosquitto/data
# - ./homeassistant/log_mqtt:/mosquitto/log
# environment:
# - TZ=Europe/Madrid
homeassistant:
container_name: homeassistant
image: ghcr.io/home-assistant/home-assistant:stable
restart: always
privileged: true
network_mode: host
volumes:
- ./homeassistant/config_ha:/config
- /etc/localtime:/etc/localtime:ro
- /run/dbus:/run/dbus:ro
- /opt/shutdown-signal:/shutdown-signal # Fichero de señal de apagado
environment:
- TZ=Europe/Madrid
depends_on:
# - mqtt
- file-editor
file-editor:
container_name: file-editor
image: causticlab/hass-configurator-docker:latest
restart: unless-stopped
network_mode: host
volumes:
- ./homeassistant/config_file-editor:/config
- ./homeassistant/config_ha:/hass-config
environment:
- TZ=Europe/Madrid
# node-red:
# container_name: node-red
# image: nodered/node-red:latest
# restart: unless-stopped
# network_mode: host
# volumes:
# - ./homeassistant/data_nodered:/data
# environment:
# - TZ=Europe/Madrid
volumes:
mariadb_data:
networks:
teknes-net:
driver: bridge
+148
View File
@@ -0,0 +1,148 @@
# Loads default set of integrations. Do not remove.
default_config:
http:
use_x_forwarded_for: true
trusted_proxies:
- 172.18.0.0/16
panel_iframe:
configurator:
title: Configurator
icon: mdi:wrench
#url: http://192.168.1.220:3218
url: http://ncie-benamira:54753
shell_command:
trigger_shutdown: touch /shutdown-signal/apagar
trigger_total_shutdown: touch /shutdown-signal/apagado_total
trigger_startup: touch /shutdown-signal/encender
# zigbee2mqtt:
# title: Zigbee2mqtt
# icon: mdi:zigbee
# #url: http://192.168.1.220:8080
# url: http://teknes-instalacion.ddns.net:54754
#switch:
# - platform: group
# name: Instalacion
# entities:
# - switch.caja_instalacion
#
# - platform: group
# name: Cajas
# entities:
# - switch.caja_eje1_pila1_204_210
# - switch.caja_eje1_pila2_205_211
# - switch.caja_eje1_pila3_201
# - switch.caja_eje2_pila1_203_212
# - switch.caja_eje2_pila2_206_213
# - switch.caja_eje2_pila3_202
# - platform: mqtt
# name: "Caja instalacion"
# state_topic: "zigbee2mqtt/caja-instalacion/status"
# command_topic: "zigbee2mqtt/caja-instalacion"
# payload_on: "ON"
# payload_off: "OFF"
# qos: 0
# retain: true
# restore_state: true
#
# - platform: mqtt
# name: "Caja eje1 pila1 204 210"
# state_topic: "zigbee2mqtt/caja-eje1-pila1-204-210/status"
# command_topic: "zigbee2mqtt/caja-eje1-pila1-204-210"
# payload_on: "ON"
# payload_off: "OFF"
# qos: 0
# retain: true
# restore_state: true
#
# - platform: mqtt
# name: "Caja eje1 pila2 205 211"
# state_topic: "zigbee2mqtt/caja-eje1-pila2-205-211/status"
# command_topic: "zigbee2mqtt/caja-eje1-pila2-205-211"
# payload_on: "ON"
# payload_off: "OFF"
# qos: 0
# retain: true
# restore_state: true
#
# - platform: mqtt
# name: "Caja eje1 pila3 201"
# state_topic: "zigbee2mqtt/caja-eje1-pila3-201/status"
# command_topic: "zigbee2mqtt/caja-eje1-pila3-201"
# payload_on: "ON"
# payload_off: "OFF"
# qos: 0
# retain: true
# restore_state: true
#
# - platform: mqtt
# name: "Caja eje2 pila1 203 212"
# state_topic: "zigbee2mqtt/caja-eje2-pila1-203-212/status"
# command_topic: "zigbee2mqtt/caja-eje2-pila1-203-212"
# payload_on: "ON"
# payload_off: "OFF"
# qos: 0
# retain: true
# restore_state: true
#
# - platform: mqtt
# name: "Caja eje2 pila2 206 213"
# state_topic: "zigbee2mqtt/caja-eje2-pila2-206-213/status"
# command_topic: "zigbee2mqtt/caja-eje2-pila2-206-213"
# payload_on: "ON"
# payload_off: "OFF"
# qos: 0
# retain: true
# restore_state: true
#
# - platform: mqtt
# name: "Caja eje2 pila3 202"
# state_topic: "zigbee2mqtt/caja-eje2-pila3-202/status"
# command_topic: "zigbee2mqtt/caja-eje2-pila3-202"
# payload_on: "ON"
# payload_off: "OFF"
# qos: 0
# retain: true
# restore_state: true
#
#telegram_bot:
# - platform: polling
# api_key: 6092066180:AAHJOCNkbjnNg5NUcEOL2kjLDKd7AFecu3s
# allowed_chat_ids:
# #335322529:sergio, 5735162:Iñigo
# - 335322529
# - 5735162
#mqtt:
# discovery: true
# #broker: mqtt://192.168.1.220
# broker: mqtt://teknes-instalacion.ddns.net:54755
# birth_message:
# topic: 'homeassistant/status'
# payload: 'online'
# will_message:
# topic: 'homeassistant/status'
# payload: 'offline'
# Load frontend themes from the themes folder
#frontend:
#themes: !include_dir_merge_named themes
# Text to speech
tts:
- platform: google_translate
api:
automation: !include automations.yaml
script: !include scripts.yaml
scene: !include scenes.yaml
+13
View File
@@ -0,0 +1,13 @@
[Unit]
Description=Watcher de senal de apagado ordenado
After=network.target docker.service
Wants=docker.service
[Service]
Type=simple
ExecStart=/usr/local/sbin/shutdown-watcher.sh
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,37 @@
alias: Apagado Ordenado FASE 1
description: ""
# Trigger
triggers:
- trigger: numeric_state
entity_id: # Entidad que controla el trigger
- sensor.pylontech_battery_id_512_carga
below: 99 # Umbral
conditions: []
actions:
# Apagado de datalogger
- action: switch.turn_off
metadata: {}
target:
entity_id: # Relés dataloggers
- switch.instalacion
- ...
data: {}
# Comando que genera el archivo de señal "apagar"
- action: shell_command.trigger_shutdown
metadata: {}
data: {}
# Retardo de 1 min
- delay:
hours: 0
minutes: 1
seconds: 0
milliseconds: 0
# Apagado de nodos de servicio
- action: switch.turn_off
metadata: {}
target:
entity_id: # Reles nodos de servicio
- switch.dinamica_principal
- switch.nvr_nvr
data: {}
mode: single
@@ -0,0 +1,37 @@
alias: Apagado TOTAL FASE 2
description: ""
# Trigger
triggers:
- trigger: numeric_state
entity_id: # Entidad que controla el tigger
- sensor.pylontech_battery_id_512_carga
below: 95 # Umbral
conditions: []
actions:
# Apagado de datalogger
- action: switch.turn_off
metadata: {}
target:
entity_id: # Reles dataloggers
- switch.instalacion
- ...
data: {}
# Comando que genera el archivo de señal "apagado_total"
- action: shell_command.trigger_total_shutdown
metadata: {}
data: {}
# Retardo de 1 min
- delay:
hours: 0
minutes: 1
seconds: 0
milliseconds: 0
# Apagado de nodos de servicio
- action: switch.turn_off
metadata: {}
target:
entity_id: # Reles nodos de servicio
- switch.dinamica_principal
- switch.nvr_nvr
data: {}
mode: single
@@ -0,0 +1,33 @@
alias: Encendido Ordenado
description: ""
# Trigger
triggers:
- trigger: numeric_state
entity_id:
- sensor.pylontech_battery_id_512_carga
above: 98
conditions: []
actions:
# Comando que genera el archivo de señal "encender"
- action: shell_command.trigger_startup
metadata: {}
data: {}
# Encendido nodos de servicio
- action: switch.turn_on
metadata: {}
target:
entity_id:
- switch.dinamica_principal
- switch.nvr_nvr
data: {}
# Retardo 30s
- delay: "00:00:30"
# Encendido de dataloggers
- action: switch.turn_on
metadata: {}
target:
entity_id:
- switch.instalacion
- ...
data: {}
mode: single
Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

@@ -0,0 +1,103 @@
<mxfile host="app.diagrams.net">
<diagram name="Página-1" id="Rbo6d7JlExwHucXn9K6S">
<mxGraphModel dx="1949" dy="1137" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="ZHIbSSpnAtk2rk2qm88V-1" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=light-dark(#1BA1E2,#0066CC);strokeColor=#006EAF;fontColor=light-dark(#FFFFFF,#FFFFFF);" value="&lt;div&gt;SISTEMA VICTRON&lt;/div&gt;&lt;div&gt;(Bateria + Inversor + ...)&lt;/div&gt;" vertex="1">
<mxGeometry height="80" width="160" x="40" y="200" as="geometry" />
</mxCell>
<mxCell id="ZHIbSSpnAtk2rk2qm88V-30" edge="1" parent="1" source="ZHIbSSpnAtk2rk2qm88V-2" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.25;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;endArrow=none;endFill=0;" target="ZHIbSSpnAtk2rk2qm88V-24">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="ZHIbSSpnAtk2rk2qm88V-34" edge="1" parent="1" source="ZHIbSSpnAtk2rk2qm88V-2" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.75;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;endArrow=none;endFill=0;" target="ZHIbSSpnAtk2rk2qm88V-25">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="ZHIbSSpnAtk2rk2qm88V-2" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" value="" vertex="1">
<mxGeometry height="200" width="280" x="320" y="160" as="geometry" />
</mxCell>
<mxCell id="ZHIbSSpnAtk2rk2qm88V-4" parent="1" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;rounded=0;" value="&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;&#x9;&lt;/span&gt;Nodo Principal (Raspi Estatica)&lt;/div&gt;" vertex="1">
<mxGeometry height="40" width="280" x="320" y="160" as="geometry" />
</mxCell>
<mxCell id="ZHIbSSpnAtk2rk2qm88V-5" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" value="&lt;div&gt;MariaDB&lt;/div&gt;&lt;div&gt;(Docker)&lt;/div&gt;" vertex="1">
<mxGeometry height="60" width="120" x="340" y="280" as="geometry" />
</mxCell>
<mxCell id="ZHIbSSpnAtk2rk2qm88V-6" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" value="&lt;div&gt;HomeAssistant&lt;/div&gt;&lt;div&gt;(Docker)&lt;/div&gt;" vertex="1">
<mxGeometry height="60" width="110" x="470" y="280" as="geometry" />
</mxCell>
<mxCell id="ZHIbSSpnAtk2rk2qm88V-7" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;" value="&lt;div&gt;Watcher + Scripts&lt;/div&gt;&lt;div&gt;(Host)&lt;/div&gt;" vertex="1">
<mxGeometry height="60" width="240" x="340" y="200" as="geometry" />
</mxCell>
<mxCell id="ZHIbSSpnAtk2rk2qm88V-13" parent="1" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;dashPattern=8 8;fillColor=none;" value="" vertex="1">
<mxGeometry height="130" width="210" x="740" y="130" as="geometry" />
</mxCell>
<mxCell id="ZHIbSSpnAtk2rk2qm88V-14" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#b0e3e6;strokeColor=#0e8088;" value="Raspi Dinamica" vertex="1">
<mxGeometry height="50" width="80" x="760" y="140" as="geometry" />
</mxCell>
<mxCell id="ZHIbSSpnAtk2rk2qm88V-15" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#b0e3e6;strokeColor=#0e8088;" value="NVR" vertex="1">
<mxGeometry height="50" width="80" x="760" y="200" as="geometry" />
</mxCell>
<mxCell id="ZHIbSSpnAtk2rk2qm88V-16" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#b0e3e6;strokeColor=#0e8088;" value="..." vertex="1">
<mxGeometry height="50" width="80" x="850" y="140" as="geometry" />
</mxCell>
<mxCell id="ZHIbSSpnAtk2rk2qm88V-17" parent="1" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" value="Maquinas de Servicio" vertex="1">
<mxGeometry height="30" width="130" x="750" y="100" as="geometry" />
</mxCell>
<mxCell id="ZHIbSSpnAtk2rk2qm88V-18" parent="1" style="rounded=0;whiteSpace=wrap;html=1;dashed=1;fillColor=none;" value="" vertex="1">
<mxGeometry height="130" width="210" x="740" y="310" as="geometry" />
</mxCell>
<mxCell id="ZHIbSSpnAtk2rk2qm88V-19" parent="1" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fad7ac;strokeColor=#b46504;" value="Caja 1" vertex="1">
<mxGeometry height="50" width="80" x="760" y="320" as="geometry" />
</mxCell>
<mxCell id="ZHIbSSpnAtk2rk2qm88V-20" parent="1" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fad7ac;strokeColor=#b46504;" value="Caja 2" vertex="1">
<mxGeometry height="50" width="80" x="760" y="380" as="geometry" />
</mxCell>
<mxCell id="ZHIbSSpnAtk2rk2qm88V-21" parent="1" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fad7ac;strokeColor=#b46504;" value="Caja 3" vertex="1">
<mxGeometry height="50" width="80" x="850" y="320" as="geometry" />
</mxCell>
<mxCell id="ZHIbSSpnAtk2rk2qm88V-22" parent="1" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fad7ac;strokeColor=#b46504;" value="..." vertex="1">
<mxGeometry height="50" width="80" x="850" y="380" as="geometry" />
</mxCell>
<mxCell id="ZHIbSSpnAtk2rk2qm88V-23" parent="1" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" value="Instalacion / Dataloggers" vertex="1">
<mxGeometry height="30" width="140" x="750" y="280" as="geometry" />
</mxCell>
<mxCell id="ZHIbSSpnAtk2rk2qm88V-24" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#0050ef;fontColor=#ffffff;strokeColor=#001DBC;" value="&lt;div&gt;Reles&amp;nbsp;&lt;/div&gt;&lt;div&gt;Shellys&lt;/div&gt;" vertex="1">
<mxGeometry height="70" width="80" x="640" y="140" as="geometry" />
</mxCell>
<mxCell id="ZHIbSSpnAtk2rk2qm88V-25" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#0050ef;fontColor=#ffffff;strokeColor=#001DBC;" value="&lt;div&gt;Reles&amp;nbsp;&lt;/div&gt;&lt;div&gt;Shellys&lt;/div&gt;" vertex="1">
<mxGeometry height="70" width="80" x="640" y="320" as="geometry" />
</mxCell>
<mxCell id="ZHIbSSpnAtk2rk2qm88V-26" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#0050ef;fontColor=#ffffff;strokeColor=#001DBC;" value="&lt;div&gt;Reles&amp;nbsp;&lt;/div&gt;&lt;div&gt;Shellys&lt;/div&gt;" vertex="1">
<mxGeometry height="45" width="80" x="220" y="217.5" as="geometry" />
</mxCell>
<mxCell id="ZHIbSSpnAtk2rk2qm88V-33" edge="1" parent="1" source="ZHIbSSpnAtk2rk2qm88V-24" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0.006;entryY=0.239;entryDx=0;entryDy=0;entryPerimeter=0;endArrow=none;endFill=0;" target="ZHIbSSpnAtk2rk2qm88V-13">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="ZHIbSSpnAtk2rk2qm88V-36" edge="1" parent="1" source="ZHIbSSpnAtk2rk2qm88V-25" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=-0.001;entryY=0.192;entryDx=0;entryDy=0;entryPerimeter=0;endArrow=none;endFill=0;" target="ZHIbSSpnAtk2rk2qm88V-18">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="ZHIbSSpnAtk2rk2qm88V-37" edge="1" parent="1" source="ZHIbSSpnAtk2rk2qm88V-26" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=-0.003;entryY=0.405;entryDx=0;entryDy=0;entryPerimeter=0;endArrow=none;endFill=0;" target="ZHIbSSpnAtk2rk2qm88V-2">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="ZHIbSSpnAtk2rk2qm88V-38" edge="1" parent="1" source="ZHIbSSpnAtk2rk2qm88V-1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=-0.035;entryY=0.479;entryDx=0;entryDy=0;entryPerimeter=0;endArrow=none;endFill=0;" target="ZHIbSSpnAtk2rk2qm88V-26">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="ZHIbSSpnAtk2rk2qm88V-43" connectable="0" parent="1" style="group" value="" vertex="1">
<mxGeometry height="180" width="120" x="80" y="360" as="geometry" />
</mxCell>
<mxCell id="ZHIbSSpnAtk2rk2qm88V-10" parent="ZHIbSSpnAtk2rk2qm88V-43" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;fillColor=#bac8d3;strokeColor=#23445d;" value="" vertex="1">
<mxGeometry height="80" width="120" y="100" as="geometry" />
</mxCell>
<mxCell id="ZHIbSSpnAtk2rk2qm88V-40" parent="ZHIbSSpnAtk2rk2qm88V-43" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;fillColor=#bac8d3;strokeColor=#23445d;" value="Servidor Oficina" vertex="1">
<mxGeometry height="80" width="120" y="50" as="geometry" />
</mxCell>
<mxCell id="ZHIbSSpnAtk2rk2qm88V-41" parent="ZHIbSSpnAtk2rk2qm88V-43" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;fillColor=#bac8d3;strokeColor=#23445d;" value="" vertex="1">
<mxGeometry height="80" width="120" as="geometry" />
</mxCell>
<mxCell id="ZHIbSSpnAtk2rk2qm88V-44" edge="1" parent="1" source="ZHIbSSpnAtk2rk2qm88V-40" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;dashed=1;dashPattern=12 12;endArrow=none;endFill=0;" target="ZHIbSSpnAtk2rk2qm88V-5">
<mxGeometry relative="1" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

+115
View File
@@ -0,0 +1,115 @@
<mxfile host="app.diagrams.net">
<diagram name="Página-1" id="zj4EZcJ4NP1zZ97JjuqS">
<mxGraphModel dx="682" dy="398" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="XWSsugYarvkDZePYYIKa-10" parent="1" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#b1ddf0;strokeColor=#10739e;" value="" vertex="1">
<mxGeometry height="200" width="180" x="560" y="80" as="geometry" />
</mxCell>
<mxCell id="XWSsugYarvkDZePYYIKa-9" parent="1" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#b1ddf0;strokeColor=#10739e;" value="" vertex="1">
<mxGeometry height="200" width="80" x="280" y="80" as="geometry" />
</mxCell>
<mxCell id="XWSsugYarvkDZePYYIKa-8" parent="1" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#b1ddf0;strokeColor=#10739e;" value="" vertex="1">
<mxGeometry height="200" width="80" x="120" y="80" as="geometry" />
</mxCell>
<mxCell id="XWSsugYarvkDZePYYIKa-3" parent="1" style="shape=partialRectangle;whiteSpace=wrap;html=1;top=0;left=0;fillColor=none;rotation=90;" value="" vertex="1">
<mxGeometry height="675" width="237.5" x="300" y="-179.5" as="geometry" />
</mxCell>
<mxCell id="XWSsugYarvkDZePYYIKa-5" edge="1" parent="1" style="endArrow=none;dashed=1;html=1;dashPattern=1 3;strokeWidth=2;rounded=0;" value="">
<mxGeometry height="50" relative="1" width="50" as="geometry">
<mxPoint x="67.5" y="158.25" as="sourcePoint" />
<mxPoint x="767.5" y="158.25" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="XWSsugYarvkDZePYYIKa-6" edge="1" parent="1" style="endArrow=none;dashed=1;html=1;dashPattern=1 3;strokeWidth=2;rounded=0;" value="">
<mxGeometry height="50" relative="1" width="50" as="geometry">
<mxPoint x="64" y="240" as="sourcePoint" />
<mxPoint x="764" y="240" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="XWSsugYarvkDZePYYIKa-12" edge="1" parent="1" style="endArrow=none;html=1;rounded=1;curved=0;strokeWidth=3;strokeColor=light-dark(#000000,#FF3333);" value="">
<mxGeometry height="50" relative="1" width="50" as="geometry">
<mxPoint x="80" y="80" as="sourcePoint" />
<mxPoint x="210" y="80" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="XWSsugYarvkDZePYYIKa-14" edge="1" parent="1" source="XWSsugYarvkDZePYYIKa-9" style="endArrow=none;html=1;rounded=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;strokeWidth=3;strokeColor=light-dark(#000000,#FF3333);" value="">
<mxGeometry height="50" relative="1" width="50" as="geometry">
<mxPoint x="390" y="350" as="sourcePoint" />
<mxPoint x="210" y="80" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="XWSsugYarvkDZePYYIKa-15" edge="1" parent="1" source="XWSsugYarvkDZePYYIKa-9" style="endArrow=none;html=1;rounded=0;exitX=1.013;exitY=0.208;exitDx=0;exitDy=0;strokeWidth=3;strokeColor=light-dark(#000000,#FF3333);exitPerimeter=0;" value="">
<mxGeometry height="50" relative="1" width="50" as="geometry">
<mxPoint x="350" y="280" as="sourcePoint" />
<mxPoint x="280" y="180" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="XWSsugYarvkDZePYYIKa-16" edge="1" parent="1" style="endArrow=none;html=1;rounded=0;strokeWidth=3;strokeColor=light-dark(#000000,#FF3333);" value="">
<mxGeometry height="50" relative="1" width="50" as="geometry">
<mxPoint x="470" y="280" as="sourcePoint" />
<mxPoint x="360" y="120" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="XWSsugYarvkDZePYYIKa-17" edge="1" parent="1" style="endArrow=none;html=1;rounded=0;strokeWidth=3;strokeColor=light-dark(#000000,#FF3333);" value="">
<mxGeometry height="50" relative="1" width="50" as="geometry">
<mxPoint x="743" y="79.19" as="sourcePoint" />
<mxPoint x="470" y="280" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="XWSsugYarvkDZePYYIKa-18" parent="1" style="shape=waypoint;sketch=0;fillStyle=solid;size=6;pointerEvents=1;points=[];resizable=0;rotatable=0;perimeter=centerPerimeter;snapToPoint=1;" value="" vertex="1">
<mxGeometry height="20" width="20" x="254" y="148" as="geometry" />
</mxCell>
<mxCell id="XWSsugYarvkDZePYYIKa-19" parent="1" style="shape=waypoint;sketch=0;fillStyle=solid;size=6;pointerEvents=1;points=[];resizable=0;rotatable=0;perimeter=centerPerimeter;snapToPoint=1;" value="" vertex="1">
<mxGeometry height="20" width="20" x="376" y="148" as="geometry" />
</mxCell>
<mxCell id="XWSsugYarvkDZePYYIKa-21" parent="1" style="shape=waypoint;sketch=0;fillStyle=solid;size=6;pointerEvents=1;points=[];resizable=0;rotatable=0;perimeter=centerPerimeter;snapToPoint=1;" value="" vertex="1">
<mxGeometry height="20" width="20" x="300" y="148" as="geometry" />
</mxCell>
<mxCell id="XWSsugYarvkDZePYYIKa-22" parent="1" style="shape=waypoint;sketch=0;fillStyle=solid;size=6;pointerEvents=1;points=[];resizable=0;rotatable=0;perimeter=centerPerimeter;snapToPoint=1;" value="" vertex="1">
<mxGeometry height="20" width="20" x="432" y="230" as="geometry" />
</mxCell>
<mxCell id="XWSsugYarvkDZePYYIKa-24" parent="1" style="shape=waypoint;sketch=0;fillStyle=solid;size=6;pointerEvents=1;points=[];resizable=0;rotatable=0;perimeter=centerPerimeter;snapToPoint=1;" value="" vertex="1">
<mxGeometry height="20" width="20" x="626" y="148" as="geometry" />
</mxCell>
<mxCell id="XWSsugYarvkDZePYYIKa-25" parent="1" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#b1ddf0;strokeColor=#10739e;" value="" vertex="1">
<mxGeometry height="10" width="20" x="80" y="299" as="geometry" />
</mxCell>
<mxCell id="XWSsugYarvkDZePYYIKa-26" edge="1" parent="1" style="endArrow=none;html=1;rounded=0;strokeWidth=3;strokeColor=light-dark(#000000,#FF3333);" value="">
<mxGeometry height="50" relative="1" width="50" as="geometry">
<mxPoint x="101" y="318" as="sourcePoint" />
<mxPoint x="81" y="318" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="XWSsugYarvkDZePYYIKa-28" parent="1" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" value="100" vertex="1">
<mxGeometry height="30" width="60" x="29" y="64" as="geometry" />
</mxCell>
<mxCell id="XWSsugYarvkDZePYYIKa-29" parent="1" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" value="Umbral 1" vertex="1">
<mxGeometry height="30" width="60" y="143" as="geometry" />
</mxCell>
<mxCell id="XWSsugYarvkDZePYYIKa-30" parent="1" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" value="Umbral 2" vertex="1">
<mxGeometry height="30" width="60" y="225" as="geometry" />
</mxCell>
<mxCell id="XWSsugYarvkDZePYYIKa-31" parent="1" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;rounded=0;" value="&lt;div align=&quot;left&quot;&gt;Tension ON&lt;/div&gt;&lt;div align=&quot;left&quot;&gt;BAT&lt;/div&gt;" vertex="1">
<mxGeometry height="40" width="70" x="111" y="290" as="geometry" />
</mxCell>
<mxCell id="XWSsugYarvkDZePYYIKa-32" parent="1" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" value="E1" vertex="1">
<mxGeometry height="25" width="60" x="220" y="157.5" as="geometry" />
</mxCell>
<mxCell id="XWSsugYarvkDZePYYIKa-33" parent="1" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" value="E2" vertex="1">
<mxGeometry height="20" width="40" x="310" y="160" as="geometry" />
</mxCell>
<mxCell id="XWSsugYarvkDZePYYIKa-34" parent="1" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" value="E3" vertex="1">
<mxGeometry height="30" width="60" x="372" y="130" as="geometry" />
</mxCell>
<mxCell id="XWSsugYarvkDZePYYIKa-35" parent="1" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" value="E4" vertex="1">
<mxGeometry height="20" width="54" x="430" y="220" as="geometry" />
</mxCell>
<mxCell id="XWSsugYarvkDZePYYIKa-36" parent="1" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" value="&lt;div&gt;E5&lt;/div&gt;" vertex="1">
<mxGeometry height="20" width="60" x="620" y="160" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

+44
View File
@@ -0,0 +1,44 @@
#!/usr/bin/env bash
# mariadb_clean_stop.sh - Cierre limipio del contenedor de MariaDB en una maquina
# CONFIGURACION ---------------------------------------------------------------
set -uo pipefail
# -u -> si no existe una variable devuelve error en vez de tratarla como cadnea vacia
# -o pipefail -> devuelve los errores de cualquier pipe no solo del ultimo
# VARIABLES
CONTAINER="mariadb"
GRACE=60
LOG="/var/log/safe-shutdown.log"
# -----------------------------------------------------------------------------
# funcion para escribir en el log
log() { echo "$(date -Is) [mariadb-safe-stop] $*" | tee -a "$LOG"; }
# PROGRAMA --------------------------------------------------------------------
log "inicio"
# Comprobacion de que el contenedor esta corriendo
# Lista los nombres de los contenedores activos y busca exactamente el nombre de $CONTAINER
# Si no lo encuentra escribe un aviso en el log y sale con 0
if ! docker ps --format '{{.Names}}' | grep -qx "$CONTAINER"; then
log "AVISO: el contenedor '$CONTAINER' no esta corriendo. Nada que hacer."
exit 0
fi
# Parada del contenedor
log "parando contenedor '$CONTAINER' (tiempo de gracia: ${GRACE}s)..."
if docker stop -t "$GRACE" "$CONTAINER" >/dev/null; then
log "OK: Contenedor parado con exito"
else
log "ERROR: docker stop falló"
exit 1
fi
# Verificacion del estado del contenedor
if docker ps --format '{{.Names}}' | grep -qx "$CONTAINER"; then
log "ERROR: '$CONTAINER' sigue activo despues del stop"
exit 1
fi
log "contenedor '$CONTAINER' detenido correctamente"
log "fin"
+39
View File
@@ -0,0 +1,39 @@
#!/usr/bin/env bash
# orchestrate-shutdown.sh — secuencia completa de apagado ordenado.
# Lo dispara el watcher al detectar un fichero de señal.
# Orden:
# 1. HA apaga los Shellys de la instalacion
# 2. HA crea el fichero de señal
# 3. Se inicia este script
# 3.1. Apagado de raspis y/o equipos de servicio
# 3.2. Parado de MariaDB
set -uo pipefail
# CONFIGURACION----------------------------------------------------------------
LOG="/var/log/safe-shutdown.log"
APAGADO_NODOS="/usr/local/sbin/service-nodes-shutdown.sh"
APAGADO_BDD="/usr/local/sbin/mariadb-shutdown.sh"
# -----------------------------------------------------------------------------
log() { echo "$(date -Is) [orchestrate] $*" | tee -a "$LOG"; }
log "===== SECUENCIA DE APAGADO INICIADA ====="
# 1. Apagar la(s) raspi(s) de servicio por SSH (espera a que hagan halt).
log "paso 1: apagando raspis de servicio..."
if "$APAGADO_NODOS"; then
log "paso 1 ok"
else
log "paso 1: aviso, el script para el apagado de los nodos devolvio error"
fi
# 2. Cerrar la base de datos limpiamente.
log "paso 2: cerrando la base de datos..."
if "$APAGADO_BDD"; then
log "paso 2 OK"
else
log "paso 2: ERROR al cerrar la BD"
fi
log "===== SECUENCIA DE APAGADO COMPLETADA ====="
+56
View File
@@ -0,0 +1,56 @@
#!/usr/bin/env bash
# orchestrate-startup.sh — secuencia de arranque tras un rebote (corre en el HOST).
# Lo dispara el watcher al detectar el fichero de señal 'encender'.
# Orden INVERSO al apagado: primero la BD (para que esté lista antes de que
# los escritores vuelvan), luego HA reenciende Shelly/nodos por su cuenta.
#
# NOTA: el reencendido de los Shelly (Arduinos y raspi de servicio) lo hace la
# automatización de HA. Este script solo se ocupa de lo que vive en el host:
# arrancar el contenedor de la base de datos y confirmar que responde.
set -uo pipefail
# CONFIG ----------------------------------------------------------------------
CONTAINER="mariadb"
LOG="/var/log/safe-shutdown.log"
# -----------------------------------------------------------------------------
log() { echo "$(date -Is) [orchestrate-startup] $*" | tee -a "$LOG"; }
log "===== SECUENCIA DE ARRANQUE INICIADA ====="
# ¿Ya está corriendo? (idempotente: por si la señal llega dos veces)
if docker ps --format '{{.Names}}' | grep -qx "$CONTAINER"; then
log "el contenedor '$CONTAINER' ya está corriendo. Nada que hacer."
log "===== SECUENCIA DE ARRANQUE COMPLETADA ====="
exit 0
fi
# 1. Arrancar el contenedor de la BD.
log "arrancando contenedor '$CONTAINER'..."
if docker start "$CONTAINER" >/dev/null; then
log "docker start devolvió OK"
else
log "ERROR: docker start falló"
exit 1
fi
# 2. Esperar a que MariaDB acepte conexiones (no basta con que el contenedor
# esté 'Up': el servidor tarda unos segundos en estar listo).
log "esperando a que MariaDB acepte conexiones..."
ready=0
for i in {1..30}; do
if docker exec "$CONTAINER" mariadb-admin ping --silent &>/dev/null; then
ready=1
log "MariaDB responde (tras ${i}s)"
break
fi
sleep 1
done
if [[ "$ready" -eq 0 ]]; then
log "ERROR: MariaDB no respondió tras 30s"
exit 1
fi
log "===== SECUENCIA DE ARRANQUE COMPLETADA ====="
+70
View File
@@ -0,0 +1,70 @@
#!/usr/bin/env bash
# total-shutdown.sh — FASE 2: apagado TOTAL de la raspi principal.
# Corre en el HOST. Cierra BD + HA y finalmente apaga la propia máquina.
#
# ATENCIÓN: la última acción es 'poweroff'. Tras ejecutarse, la raspi se apaga.
# Solo volverá a encenderse cuando reciba de nuevo alimentación (corte físico +
# retorno de tensión). Si la apagas con corriente aún disponible, NO arrancará
# sola: requerirá corte/retorno de alimentación o encendido manual.
#
# Este script asume que la FASE 1 (Arduinos + raspi de servicio) ya se ejecutó.
# Pensado para dispararse solo cuando el SoC cruza el segundo umbral (más bajo)
# y la red sigue sin volver.
set -uo pipefail
# CONFIG ----------------------------------------------------------------------
DB_CONTAINER="mariadb"
HA_CONTAINER="homeassistant"
APAGADO_NODOS="/usr/local/sbin/service-nodes-shutdown.sh"
APAGADO_BDD="/usr/local/sbin/mariadb-shutdown.sh"
LOG="/var/log/safe-shutdown.log"
# -----------------------------------------------------------------------------
log() { echo "$(date -Is) [full-shutdown] $*" | tee -a "$LOG"; }
log "===== FASE 2: APAGADO TOTAL INICIADO ====="
# 1. Comprobar que los nodos de servicio estan apagados
log "paso 1: asegurando el apagado de nodos de servicio..."
if "$apagado_nodos"; then
log "paso 1: OK, todos los nodos apagados"
else
log "paso 1: ERROR, el script de apagado de nodos fallo"
fi
# 2. Cerrar la BD limpiamente (si sigue corriendo). Reutiliza el script probado.
if docker ps --format '{{.Names}}' | grep -qx "$DB_CONTAINER"; then
log "paso 2: la BD sigue corriendo, cerrándola..."
if "$APAGADO_BDD"; then
log "paso 2 OK"
else
log "paso 2: AVISO, el cierre de BD devolvió error (continuamos igualmente)"
fi
else
log "paso 2: la BD ya estaba parada (cerrada en fase 1). Saltando."
fi
# 3. Parar Home Assistant limpiamente.
if docker ps --format '{{.Names}}' | grep -qx "$HA_CONTAINER"; then
log "paso 3: parando Home Assistant..."
if docker stop -t 30 "$HA_CONTAINER" >/dev/null; then
log "paso 3 OK"
else
log "paso 3: AVISO, docker stop de HA devolvió error (continuamos)"
fi
else
log "paso 3: Home Assistant no estaba corriendo. Saltando."
fi
# 4. Forzar que todo baje a disco antes de cortar.
log "paso 4: sincronizando discos (sync)..."
sync
sleep 2
sync
log "paso 4 OK"
# 5. Apagar la propia máquina. ESTA ES LA ÚLTIMA LÍNEA QUE SE EJECUTA.
log "paso 5: apagando la raspi principal. Hasta luego."
log "===== FASE 2: APAGADO TOTAL COMPLETADO (poweroff a continuación) ====="
sync
systemctl poweroff
+58
View File
@@ -0,0 +1,58 @@
#!/usr/bin/env bash
# service-nodes-shutdown.sh - Apagado de la(s) raspi(s) de servicio por SSH de
# forma segura. Se ejecuta en la raspi principal. Ordena el poweroff y espera
# a que deje de hacer ping antes de continuar. Despues corta la alimentacion
# fisica a traves del relé correspondiente.
# REQUISITOS:
# - Claves SSH sin passphrase en /root/.ssh/id_shutdown autorizada en cada nodo
# - Regla sudoers NOPASSWD para /usr/sbin/poweroff en cada nodo
set -uo pipefail
# CONFIGURACION ---------------------------------------------------------------
NODES=(
"pi@192.168.1.219|sudo /usr/sbin/poweroff" # Raspi Dinamica
"root@192.168.1.50|/sbin/poweroff" # NVR
)
SSH_KEY="/root/.ssh/id_shutdown"
POWEROFF_CMD="sudo /usr/sbin/poweroff"
TIMEOUT_HALT=60 # Timeout para el apagado
LOG="/var/log/safe-shutdown.log"
SSH_OPTS="-i $SSH_KEY -o BatchMode=yes -o ConnectTimeout=5 -o StrictHostKeyChecking=accept-new"
# -----------------------------------------------------------------------------
log() { echo "$(date -Is) [service-nodes-shutdown] $*" | tee -a "$LOG"; }
log "inicio (${#NODES[@]} nodo/s)"
for entry in "${NODES[@]}"; do
host_user="${entry%%|*}"
host="${host_user#*@}" # extrae la IP/host del NODE
cmd="${entry#*|}"
log "apagando $host_user ..."
# Ordenar poweroff por ssh
if ssh $SSH_OPTS "$host_user" "$cmd"; then
log "$entry: orden de poweroff enviada"
else # Si falla avisamos y seguimos (puede no ser un error real)
log "$entry: AVISO, el SSH devolvio error"
fi
# Esperamos la NO respuesta de ping
halted=0
for ((i=1; i<TIMEOUT_HALT; i++)); do
if ! ping -c1 -W1 "$host" &>/dev/null; then
log "$host: apagado confirmado (dejo de responder tras ${i}s)"
halted=1
break
fi
sleep 1
done
if [[ "$halted" -eq 0 ]]; then
log "$host: AVISO, sigue respondiendo tras ${TIMEOUT_HALT}s (apagado no confirmado)"
fi
done
log "fin"
+40
View File
@@ -0,0 +1,40 @@
#!/usr/bin/env bash
set -uo pipefail
# CONFIGURACION ---------------------------------------------------------------
SIGNAL_DIR="/opt/shutdown-signal"
SHUTDOWN_ORCHESTRATOR="/usr/local/sbin/orchestrate-shutdown.sh"
TOTAL_SHUTDOWN_ORCHESTRATOR="/usr/local/sbin/orchestrate-total-shutdown.sh"
STARTUP_ORCHESTRATOR="/usr/local/sbin/orchestrate-startup.sh"
LOG="/var/log/safe-shutdown.log"
# PROGRAMA --------------------------------------------------------------------
log() { echo "$(date -Is) [watcher] $*" | tee -a "$LOG"; }
log "watcher iniciado, vigilando $SIGNAL_DIR/"
while true; do
inotifywait -q -e create -e moved_to "$SIGNAL_DIR" |
while read -r dir event file; do
case "$file" in
apagar)
log "señal detectada ($file). Lanzando secuencia de apagado."
"$SHUTDOWN_ORCHESTRATOR"
rm -f "$SIGNAL_DIR/apagar"
log "señal consumida y borrada."
;;
encender)
log "señal detectada ($file). Lanzando secuencia de encendido."
"$STARTUP_ORCHESTRATOR"
rm -f "$SIGNAL_DIR/encender"
log "señal consumida y borrada."
;;
apagado_total)
log "señal detectada ($file). Lanzando secuencia de apagado TOTAL."
"$TOTAL_SHUTDOWN_ORCHESTRATOR"
rm -f "$SIGNAL_DIR/apagado_total"
log "señal consumida y borrada."
;;
esac
done
done