Aller au contenu principal

Daemon C++

Le daemon (nodes/agent, C++17, namespace oa) est l'automate proprement dit. Il est bâti autour de gestionnaires spécialisés, câblés par la classe Node.

:::tip Référence complète du code Cette page est un survol. Pour une documentation exhaustive (chaque fichier, classe et fonction), voir Daemon C++ — référence du code.

Si tu veux aussi le listing intégral (code source complet collé dans la doc), voir Daemon C++ — code complet commenté. :::

Arborescence

nodes/agent/
├── CMakeLists.txt
├── include/
│ ├── config.hpp # chargement de node.json
│ ├── gpio_manager.hpp # accès libgpiod
│ ├── grpc_server.hpp # daemon gRPC :9080 (JSON, activable)
│ ├── local_api.hpp # serveur HTTP :8080 (activable)
│ ├── local_db.hpp # persistance SQLite
│ ├── log.hpp # journalisation
│ ├── meter_manager.hpp # compteurs
│ ├── mqtt_client.hpp # client MQTT
│ ├── node.hpp # orchestrateur
│ ├── relay_manager.hpp # relais
│ ├── scheduler.hpp # planification cron
│ ├── secure_onboarding.hpp # vérification X.509 + autorisation
│ ├── valve_manager.hpp # vannes
│ └── watchdog.hpp # watchdog matériel
└── src/ # implémentations correspondantes + main.cpp

GpioManager

Encapsule libgpiod et l'accès à /dev/gpiochip0. Il fournit une abstraction pour réserver des lignes en entrée ou en sortie et lire/écrire leur niveau, isolant le reste du code des détails du noyau.

RelayManager

  • Pilote des relais actifs à l'état bas (active-low) : écrire 0 ferme le relais.
  • Persiste chaque changement d'état dans LocalDb, de sorte que la position est restaurée après un redémarrage.
  • Actions supportées : on, off, toggle.

ValveManager

  • Gère des électrovannes avec une fermeture de sécurité automatique : une vanne ouverte se referme seule après un délai (par défaut 300 s), configurable par commande.
  • Actions : open, close, éventuellement assorties d'une durée en secondes.

MeterManager

  • Compte les impulsions (compteurs d'eau, d'énergie…).
  • Cumule la valeur et la persiste pour survivre aux redémarrages.

Scheduler

  • Déclenche des actions selon une règle cron journalière (minute + heure).
  • S'appuie sur la surface de commande de Node pour agir sur relais et vannes.

Watchdog

  • Ouvre /dev/watchdog et le rearme périodiquement (~15 s).
  • Si la boucle principale se bloque, le watchdog matériel redémarre le node, garantissant la disponibilité.

LocalDb

  • Base SQLite stockée dans /var/lib/leukos/node.db.
  • Persiste l'état des relais et les valeurs des compteurs.
  • Restaurée au démarrage par les gestionnaires concernés.

Boucle principale (main.cpp)

Le point d'entrée charge la configuration, construit un Node, appelle start() puis run() (bloquant). La boucle poll à poll_interval_ms (250 ms) : lecture, logique, persistance et publication d'état.

LocalApi (HTTP)

  • Serveur HTTP/1.1 embarqué (sockets POSIX), :8080 par défaut.
  • Activable via api.enabled dans node.json (mécanisme conservé, simplement désactivable).
  • Endpoints : GET /status, POST /relay/<id>/<on|off|toggle>, POST /valve/<id>/<open|close>[/seconds].

Onboarding réseau sécurisé (ESP32 Ethernet)

Le daemon implémente le flux sécurisé décrit dans docs/Network.md :

  • ingestion d'un device découvert,
  • vérification cryptographique du certificat X.509 contre la CA locale,
  • contrôle de liste d'autorisation (allowed_device_ids),
  • activation manuelle par affectation de pièce,
  • persistance SQLite des états discovered|verified|rejected|active.

Endpoints additionnels :

  • POST /secure/discover (payload JSON de découverte + certificat PEM)
  • GET /secure/pending (devices vérifiés mais non activés)
  • GET /secure/devices (registre complet)
  • POST /secure/activate/<device_id>/<room> (activation explicite)

GrpcServer (daemon gRPC)

  • Daemon gRPC embarqué tournant sur son propre thread, :9080 par défaut.
  • Activable via grpc.enabled (désactivé par défaut).
  • Utilise le service générique de gRPC avec des charges utiles JSON : aucun protoc/protobuf requis sur la cible, cohérent avec la convention JSON-over-gRPC du central (Saturndnsdhcp).
  • Service leukos.AgentService : Health, Status, Relay ({id, action}), Valve ({id, action, seconds}).
  • Saturn s'y connecte comme client (NODE_GRPC_ENABLED, NODE_GRPC_PORT), avec repli automatique sur l'API HTTP.

Dépendances de compilation

libgpiod, jansson (JSON), sqlite, mosquitto, openssl, grpc (grpc++).

Installation systemd (hôte Linux appserver)

Build et packaging .deb local depuis les sources de l'agent :

cd /var/workspaces/supervisor/apps/leukOS
sudo apt-get update
sudo apt-get install -y \
cmake build-essential pkg-config \
libjansson-dev libsqlite3-dev libgpiod-dev libmosquitto-dev \
libgrpc++-dev protobuf-compiler-grpc

cd nodes/agent
cmake -S . -B build-pack -DLEUKOS_AGENT_ENABLE_SYSTEMD_PACKAGING=ON
cmake --build build-pack -j"$(nproc)"
cpack -G DEB --config build-pack/CPackConfig.cmake

Installation du paquet (avec résolution automatique de toutes les dépendances runtime) :

sudo apt-get update
sudo apt-get install -y /var/workspaces/supervisor/apps/leukOS/nodes/agent/leukos-agent_1.0.0_amd64.deb

# postinst bootstrap automatiquement :
# - /etc/leukos/node.json depuis node.json.example si absent
# - /var/lib/leukos
# - activation/restart de leukos-agent et leukos-agent-heartbeat.timer
# - démarrage de mosquitto si mqtt.host est local (127.0.0.1/localhost/::1)

# ajuster ensuite /etc/leukos/node.json selon la machine
# (id node, ports API/gRPC, plugins bridge, etc.)

sudo systemctl enable --now leukos-agent leukos-agent-heartbeat.timer
systemctl status leukos-agent --no-pager -l
systemctl status leukos-agent-heartbeat.timer --no-pager -l

Sur appserver, des conflits de ports peuvent exister avec l'infra centrale. Exemple de config runtime compatible (/etc/leukos/node.json) :

{
"node": {"id": "appserver-agent", "name": "LeukOS Agent Appserver", "site": "appserver", "poll_interval_ms": 250},
"database": {"path": "/var/lib/leukos/node.db"},
"mqtt": {"host": "127.0.0.1", "port": 1883, "base_topic": "openautomation", "keepalive_s": 30},
"api": {"enabled": true, "bind": "0.0.0.0", "port": 18080},
"grpc": {"enabled": true, "bind": "0.0.0.0", "port": 19080},
"watchdog": {"enabled": true, "timeout_s": 15},
"gpio": {"enabled": false, "chip": "gpiochip0"},
"device_bridge": {
"enabled": true,
"probe_interval_s": 15,
"plugins": [
{"name": "arduino_atmega2560", "enabled": true, "transport": "serial", "port": "/dev/ttyUSB0", "baud": 115200, "heartbeat_cmd": "", "heartbeat_ack": ""},
{"name": "esp32", "enabled": false, "transport": "serial", "port": "/dev/ttyUSB0", "baud": 115200, "heartbeat_cmd": "PING\\n", "heartbeat_ack": "PONG"},
{"name": "esp8266", "enabled": false, "transport": "serial", "port": "/dev/ttyUSB1", "baud": 115200, "heartbeat_cmd": "PING\\n", "heartbeat_ack": "PONG"},
{"name": "stm32", "enabled": false, "transport": "serial", "port": "/dev/ttyACM1", "baud": 115200, "heartbeat_cmd": "PING\\n", "heartbeat_ack": "PONG"},
{"name": "rp2040", "enabled": false, "transport": "serial", "port": "/dev/ttyACM2", "baud": 115200, "heartbeat_cmd": "PING\\n", "heartbeat_ack": "PONG"}
]
},
"secure_network": {
"enabled": true,
"require_activation": true,
"require_crl": false,
"discovery_ttl_s": 300,
"ca_cert_path": "/etc/leukos/pki/ca.crt",
"crl_path": "/etc/leukos/pki/crl.pem",
"mqtt_topic_prefix": "home",
"allowed_device_ids": ["HOME-CTRL-000001", "HOME-CTRL-000002"]
},
"relays": [], "valves": [], "meters": [], "schedules": []
}

Ce profil correspond au cas où un seul port série (/dev/ttyUSB0) est présent sur la machine hôte, avec ATmega2560 branché dessus.

Heartbeat vers Mercure

Le service/timer leukos-agent-heartbeat publie un état synthétique vers POST /health/state de Mercure. La flotte visible dans Venus (Network → Flotte) vient uniquement de Mercure.

Le même heartbeat publie aussi un log structuré vers POST /logs (Mercure), qui est ensuite routé sur Kafka (leukos-node-logs) puis indexé dans Elasticsearch. Ce log inclut l'état mqtt/bridge, le statut synthétique (up|degraded|down) et les plugins bridge actifs dans meta.

Règle de statut effective côté heartbeat :

  • down si le service systemd leukos-agent n'est pas active.
  • up si le service est active et qu'au moins une liaison est up (mqtt ou bridge).
  • degraded sinon.

Le timer leukos-agent-heartbeat.timer journalise périodiquement l'état des deux liaisons dans journald (mqtt côté central + bridge côté MCU/plugins) :

sudo systemctl start leukos-agent-heartbeat.service
sudo journalctl -t leukos-agent-heartbeat -n 20 --no-pager