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) : écrire0ferme 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
Nodepour agir sur relais et vannes.
Watchdog
- Ouvre
/dev/watchdoget 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),
:8080par défaut. - Activable via
api.enableddansnode.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,
:9080par 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 (Saturn↔dnsdhcp). - 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 :
downsi le service systemdleukos-agentn'est pasactive.upsi le service estactiveet qu'au moins une liaison estup(mqttoubridge).degradedsinon.
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