Pilot v2: Core implementation + battery telemetry
Major updates: - Complete Rust rewrite (pilot-v2/) with working MQTT client - Fixed MQTT event loop deadlock (background task pattern) - Battery telemetry for Linux (auto-detected via /sys/class/power_supply) - Home Assistant auto-discovery for all sensors and switches - Comprehensive documentation (AVANCEMENT.md, CLAUDE.md, roadmap) - Docker test environment with Mosquitto broker - Helper scripts for development and testing Features working: ✅ MQTT connectivity with LWT ✅ YAML configuration with validation ✅ Telemetry: CPU, memory, IP, battery (Linux) ✅ Commands: shutdown, reboot, sleep, screen (dry-run tested) ✅ HA discovery and integration ✅ Allowlist and cooldown protection Ready for testing on real hardware. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
/monenv
|
||||
README.html
|
||||
@@ -0,0 +1,95 @@
|
||||
# Aorus
|
||||
|
||||
## Description
|
||||
|
||||
installe un service qui creer des entites pour home assistant:
|
||||
- etat de la batterie
|
||||
- button pour eteindre le pc
|
||||
- reboot
|
||||
- eteindre l'ecran
|
||||
- cpu
|
||||
- memory
|
||||
- adresse ip
|
||||
- cpu frequency
|
||||
|
||||
## Prérequis
|
||||
|
||||
installation de python3 et de python-env:
|
||||
```
|
||||
sudo apt install python3-venv
|
||||
```
|
||||
|
||||
```
|
||||
python3 -m venv monenv
|
||||
```
|
||||
activation de l'environnement:
|
||||
|
||||
```
|
||||
source monenv/bin/activate
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
git clone http://10.0.1.200/pilot/aorus.git
|
||||
```
|
||||
|
||||
```
|
||||
cd aorus
|
||||
```
|
||||
Création de l'environnement:
|
||||
|
||||
```
|
||||
python3 -m venv monenv
|
||||
```
|
||||
Activation de l'environnement:
|
||||
```
|
||||
source monenv/bin/activate
|
||||
```
|
||||
Installation des paquets additionnels:
|
||||
```
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
## Test du programme
|
||||
|
||||
```
|
||||
python3 main.py
|
||||
```
|
||||
Sortie de l'environnement:
|
||||
```
|
||||
deactivate
|
||||
```
|
||||
|
||||
## Installation en temps que service
|
||||
|
||||
authorisation de shutdown: ajouter a la fin du fichier
|
||||
|
||||
```
|
||||
sudo visudo
|
||||
```
|
||||
```
|
||||
gilles ALL=(ALL) NOPASSWD: /sbin/shutdown
|
||||
gilles ALL=(ALL) NOPASSWD: /sbin/reboot
|
||||
gilles ALL=(ALL) NOPASSWD: /usr/bin/tee /sys/devices/system/cpu/cpu0/cpufreq/scaling_setspeed
|
||||
```
|
||||
Installation du service en manuel:
|
||||
|
||||
```
|
||||
sudo cp mqtt_pilot.service /etc/systemd/system/
|
||||
```
|
||||
```
|
||||
sudo systemctl daemon-reload
|
||||
```
|
||||
```
|
||||
sudo systemctl enable mqtt_pilot.service
|
||||
```
|
||||
```
|
||||
sudo systemctl start mqtt_pilot.service
|
||||
```
|
||||
```
|
||||
sudo systemctl status mqtt_pilot.service
|
||||
```
|
||||
lancer l'installation du service:
|
||||
```
|
||||
sudo ./install.sh
|
||||
```
|
||||
@@ -0,0 +1,171 @@
|
||||
# PROMPT CODEX — Redev V2 : Agent MQTT de pilotage PC + Analyse V1
|
||||
|
||||
## 0) Rôle et objectif
|
||||
Tu es **Codex**, expert senior **Python / Windows+Linux / MQTT / packaging / sécurité**.
|
||||
Tu dois **analyser un projet existant** (application de pilotage d’un ordinateur via messages MQTT), en comprendre l’architecture et produire une **documentation d’état des lieux**.
|
||||
|
||||
Ensuite, tu dois préparer une **méthode de re-développement / re-déploiement** sous forme de **consignes de prompts** (itératives), afin que l’utilisateur puisse compléter les besoins supplémentaires avant de lancer l’implémentation.
|
||||
|
||||
Contraintes :
|
||||
- Ne modifie rien tant que l’analyse n’est pas terminée.
|
||||
- Sois factuel : si une info n’est pas trouvée dans le projet, indique “Non trouvé”.
|
||||
- Structure claire, utilisable comme base de refonte.
|
||||
|
||||
Livrables attendus :
|
||||
1) `analyse_version_1.md` (créé à la racine du repo)
|
||||
2) `prompt_method_redev.md` (créé à la racine du repo)
|
||||
|
||||
---
|
||||
|
||||
## 1) Étape A — Scan & compréhension du projet
|
||||
### 1.1 Cartographie
|
||||
- Liste l’arborescence (équivalent `tree -a -I "node_modules|.git|__pycache__|dist|build"`).
|
||||
- Identifie :
|
||||
- langage(s), frameworks, dépendances
|
||||
- points d’entrée (main, service, CLI, daemon)
|
||||
- modules clés (mqtt, commandes système, sécurité, config, logs, UI éventuelle)
|
||||
|
||||
### 1.2 Exécution et cycle de vie
|
||||
Déduis et documente :
|
||||
- comment l’app se lance (commande, service systemd, tâche planifiée Windows, docker ?)
|
||||
- comment elle s’arrête proprement
|
||||
- gestion de l’état (retained, availability, LWT, reconnexion, QoS)
|
||||
- compatibilité OS (Windows/Linux), permissions nécessaires
|
||||
|
||||
### 1.3 MQTT : topics & payloads
|
||||
Reconstitue précisément (si présent) :
|
||||
- broker, auth (user/pass, TLS), client_id
|
||||
- topics (commandes / état / availability / logs / télémétrie)
|
||||
- structure des messages (JSON ? champs, valeurs possibles)
|
||||
- mapping “topic → action exécutée”
|
||||
- sécurité : validation d’input, allowlist, anti-injection
|
||||
|
||||
### 1.4 Configuration
|
||||
- Où sont les configs ? (`.env`, yaml, json, ini, registry, args CLI)
|
||||
- paramètres supportés, valeurs par défaut, variables d’environnement
|
||||
- secrets : comment sont-ils stockés ?
|
||||
|
||||
### 1.5 Dépendances & packaging
|
||||
- `requirements.txt`, `pyproject.toml`, `package.json`, etc.
|
||||
- version Python/Node/Go
|
||||
- build : exe (pyinstaller), wheel, docker image, service
|
||||
- fichiers docs existants (README, CHANGELOG, etc.)
|
||||
|
||||
### 1.6 Journalisation & observabilité
|
||||
- logs (format, rotation)
|
||||
- niveaux de logs
|
||||
- métriques éventuelles
|
||||
- gestion erreurs / retries / backoff
|
||||
|
||||
---
|
||||
|
||||
## 2) Étape B — Générer `analyse_version_1.md`
|
||||
Crée un fichier `analyse_version_1.md` structuré comme suit (remplis avec ce que tu trouves) :
|
||||
|
||||
## 2.1 Résumé exécutif
|
||||
- Objectif de l’app
|
||||
- OS supportés
|
||||
- Dépendances majeures
|
||||
- Mode d’exécution (service/cli/etc.)
|
||||
|
||||
## 2.2 Architecture
|
||||
- Diagramme textuel (ASCII) des composants
|
||||
- Points d’entrée, modules, flux de données
|
||||
|
||||
## 2.3 MQTT
|
||||
### 2.3.1 Connexion
|
||||
- Broker, auth, TLS, client_id, keepalive, LWT
|
||||
|
||||
### 2.3.2 Topics
|
||||
- Tableau : Topic | Direction (in/out) | QoS | Retain | Payload | Action/usage
|
||||
|
||||
### 2.3.3 Commandes supportées
|
||||
- Liste exhaustive + exemples de payloads si trouvés
|
||||
|
||||
## 2.4 Commandes système exécutées
|
||||
- actions (shutdown/reboot/sleep/commands) + méthode utilisée
|
||||
- risques sécurité + mitigations actuelles
|
||||
|
||||
## 2.5 Configuration
|
||||
- sources (env/files/args)
|
||||
- paramètres et defaults (tableau)
|
||||
|
||||
## 2.6 Stockage / état
|
||||
- fichiers, DB, mémoire, cache
|
||||
- persistence (oui/non)
|
||||
|
||||
## 2.7 Déploiement actuel
|
||||
- comment installer
|
||||
- comment lancer
|
||||
- comment update
|
||||
- comment rollback
|
||||
|
||||
## 2.8 Points faibles / dettes techniques
|
||||
- liste priorisée (P0/P1/P2)
|
||||
- éléments manquants / non trouvés
|
||||
|
||||
## 2.9 Suggestions de refonte (sans coder)
|
||||
- “quick wins”
|
||||
- architecture cible probable (optionnelle)
|
||||
- axes sécurité
|
||||
|
||||
---
|
||||
|
||||
## 3) Étape C — Générer `prompt_method_redev.md` (méthode de redev)
|
||||
Crée un fichier `prompt_method_redev.md` qui explique **comment on va re-développer** en itérations, sous forme de “prompts à donner à Codex”, avec checkpoints.
|
||||
|
||||
Format attendu :
|
||||
|
||||
# Méthode de re-développement / re-déploiement (V2)
|
||||
## 1) Pré-requis
|
||||
- outils, versions, conventions repo, structure attendue
|
||||
|
||||
## 2) Liste des informations que l’utilisateur doit compléter
|
||||
Crée une section “À compléter par l’utilisateur” avec des champs vides, par exemple :
|
||||
- OS cibles : [ ]
|
||||
- Broker MQTT : [ ]
|
||||
- Auth/TLS : [ ]
|
||||
- Liste commandes à supporter : [ ]
|
||||
- Politique de sécurité (allowlist, signature, ACL broker) : [ ]
|
||||
- Packaging désiré (exe windows, systemd linux, docker) : [ ]
|
||||
- Intégration Home Assistant autodiscovery : [oui/non] + détails : [ ]
|
||||
- Availability / birth / will : [ ]
|
||||
- Fréquence télémétrie : [ ]
|
||||
- etc.
|
||||
|
||||
## 3) Plan itératif (prompts)
|
||||
Donne une séquence de prompts “copier-coller” :
|
||||
- Prompt 1 : “Génère l’architecture cible + conventions”
|
||||
- Prompt 2 : “Implémente le noyau MQTT + config + logs”
|
||||
- Prompt 3 : “Implémente le moteur de commandes avec allowlist”
|
||||
- Prompt 4 : “Ajoute availability/LWT + heartbeat”
|
||||
- Prompt 5 : “Ajoute autodiscovery Home Assistant (si demandé)”
|
||||
- Prompt 6 : “Packaging Windows (service/tâche)”
|
||||
- Prompt 7 : “Packaging Linux (systemd)”
|
||||
- Prompt 8 : “Tests + harness MQTT + doc + exemples”
|
||||
|
||||
Chaque prompt doit :
|
||||
- préciser les fichiers à créer/modifier
|
||||
- imposer une checklist de validation (tests, lint, doc)
|
||||
- définir une sortie attendue (ex: `docs/deploy_windows.md`, `docker-compose.yml`, etc.)
|
||||
|
||||
## 4) Procédure de re-déploiement
|
||||
- stratégie : in-place vs nouveau dossier
|
||||
- migration config
|
||||
- compatibilité topics (versioning)
|
||||
- rollback
|
||||
|
||||
---
|
||||
|
||||
## 4) Règles de sortie
|
||||
- Crée uniquement les 2 fichiers demandés.
|
||||
- Si tu dois exécuter des commandes pour comprendre (ex: grep), fais-le localement dans ton environnement.
|
||||
- Termine en affichant :
|
||||
- un résumé de 10 lignes
|
||||
- le chemin des fichiers créés
|
||||
- 5 questions “À compléter par l’utilisateur” (copiées depuis la section correspondante)
|
||||
|
||||
---
|
||||
|
||||
## 5) Action immédiate
|
||||
Commence maintenant l’analyse du projet présent dans le dossier courant.
|
||||
@@ -0,0 +1,120 @@
|
||||
## 2.1 Résumé exécutif
|
||||
- Objectif de l’app : exposer via MQTT (Home Assistant autodiscovery) des entites de telemetrie PC + commandes (shutdown, reboot, ecran, CPU freq).
|
||||
- OS supportes : Linux (systemd, sudo, /sys, busctl GNOME). Windows : Non trouve.
|
||||
- Dependances majeures : Python, paho-mqtt, psutil, pynvml.
|
||||
- Mode d’execution : script Python en CLI, ou service systemd via `mqtt_pilot.service`.
|
||||
|
||||
## 2.2 Architecture
|
||||
Diagramme textuel (ASCII) :
|
||||
|
||||
[systemd service] -> [main_prog.py/main.py] -> [paho-mqtt client] -> [MQTT broker]
|
||||
|-> [psutil/pynvml] (telemetrie)
|
||||
|-> [subprocess/busctl/sudo] (actions)
|
||||
|
||||
Points d’entree, modules, flux de donnees :
|
||||
- Points d’entree : `main.py`, `main_prog.py`, `main-lenovo-bureau.py` (variantes), service systemd via `mqtt_pilot.service` -> `main_prog.py`.
|
||||
- MQTT : publication d’entites Home Assistant (discovery + availability) + publication d’etats capteurs.
|
||||
- Actions systeme : shutdown, reboot, extinction/allumage ecran, changement de frequence CPU.
|
||||
|
||||
## 2.3 MQTT
|
||||
### 2.3.1 Connexion
|
||||
- Broker : 10.0.0.3:1883
|
||||
- Auth : username/password vides (Non trouve pour TLS)
|
||||
- TLS : Non trouve
|
||||
- client_id : non defini (paho-mqtt genere un ID)
|
||||
- keepalive : 60
|
||||
- LWT : Non trouve (availability publie manuellement)
|
||||
|
||||
### 2.3.2 Topics
|
||||
Le projet contient plusieurs variantes avec topics differents (noms d’hote ou `device_name`).
|
||||
|
||||
Tableau (main_prog.py - device_name = "yoga14") :
|
||||
Topic | Direction | QoS | Retain | Payload | Action/usage
|
||||
- `homeassistant/switch/yoga14/shutdown_yoga14/config` | out | defaut | true | JSON discovery | autodiscovery switch shutdown
|
||||
- `homeassistant/switch/yoga14/reboot_yoga14/config` | out | defaut | true | JSON discovery | autodiscovery switch reboot
|
||||
- `homeassistant/switch/yoga14/screen_yoga14/config` | out | defaut | true | JSON discovery | autodiscovery switch ecran
|
||||
- `homeassistant/sensor/yoga14/battery_yoga14/config` | out | defaut | true | JSON discovery | autodiscovery batterie
|
||||
- `homeassistant/binary_sensor/yoga14/charging_status_yoga14/config` | out | defaut | true | JSON discovery | autodiscovery charge
|
||||
- `homeassistant/sensor/yoga14/cpu_temperature_yoga14/config` | out | defaut | true | JSON discovery | autodiscovery temp CPU
|
||||
- `homeassistant/sensor/yoga14/cpu_usage_yoga14/config` | out | defaut | true | JSON discovery | autodiscovery usage CPU
|
||||
- `homeassistant/sensor/yoga14/memory_usage_yoga14/config` | out | defaut | true | JSON discovery | autodiscovery usage RAM
|
||||
- `homeassistant/sensor/yoga14/cpu_frequency_yoga14/config` | out | defaut | true | JSON discovery | autodiscovery freq CPU
|
||||
- `homeassistant/sensor/yoga14/ip_address_yoga14/config` | out | defaut | true | JSON discovery | autodiscovery IP
|
||||
- `homeassistant/number/yoga14/cpu_frequency_slider_yoga14/config` | out | defaut | true | JSON discovery | autodiscovery slider freq
|
||||
- `pilot/yoga14/shutdown/set` | in | defaut | - | "ON"/"OFF" | OFF -> shutdown
|
||||
- `pilot/yoga14/reboot/set` | in | defaut | - | "ON"/"OFF" | OFF -> reboot
|
||||
- `pilot/yoga14/screen/set` | in | defaut | - | "ON"/"OFF" | OFF/ON -> busctl display
|
||||
- `pilot/yoga14/cpu_frequency_slider/set` | in | defaut | - | "<float>" GHz | set freq CPU
|
||||
- `pilot/yoga14/*/state` | out | defaut | true | valeurs numeriques/strings | etats capteurs/switch
|
||||
- `pilot/yoga14/*/available` | out | defaut | true | online/offline | availability
|
||||
|
||||
Tableau (main.py / main-lenovo-bureau.py - hostname dynamique) :
|
||||
- `homeassistant/switch/<hostname>/shutdown_<hostname>/config` | out | defaut | true | JSON discovery | autodiscovery shutdown
|
||||
- `homeassistant/sensor/<hostname>/cpu_temp_<hostname>/config` | out | defaut | true | JSON discovery | autodiscovery CPU temp
|
||||
- `homeassistant/sensor/<hostname>/memory_used_<hostname>/config` | out | defaut | true | JSON discovery | autodiscovery RAM used
|
||||
- `homeassistant/sensor/<hostname>/cpu_usage_<hostname>/config` | out | defaut | true | JSON discovery | autodiscovery CPU usage
|
||||
- `homeassistant/sensor/<hostname>/gpu_temp_<hostname>/config` | out | defaut | true | JSON discovery | autodiscovery GPU temp (main.py)
|
||||
- `homeassistant/sensor/<hostname>/gpu_memory_usage_<hostname>/config` | out | defaut | true | JSON discovery | autodiscovery GPU mem (main.py)
|
||||
- `pilot/<hostname>/shutdown/available` | in | defaut | - | "ON"/"OFF" | OFF -> shutdown
|
||||
- `pilot/<hostname>/shutdown` | out | defaut | true | "ON"/"OFF" | etat switch
|
||||
- `pilot/<hostname>/<sensor>` | out | defaut | true | valeurs numeriques | capteurs
|
||||
- `pilot/<hostname>/<sensor>/available` | out | defaut | true | online/offline | availability
|
||||
|
||||
### 2.3.3 Commandes supportees
|
||||
- Shutdown : payload "OFF" sur `pilot/<device>/shutdown/set` (main_prog.py) ou `pilot/<hostname>/shutdown/available` (main.py / main-lenovo-bureau.py).
|
||||
- Reboot : payload "OFF" sur `pilot/<device>/reboot/set` (main_prog.py).
|
||||
- Ecran on/off : payload "ON"/"OFF" sur `pilot/<device>/screen/set` (main_prog.py).
|
||||
- Slider frequence CPU : payload float en GHz sur `pilot/<device>/cpu_frequency_slider/set`.
|
||||
- Telemetrie : batterie, charge, CPU temp/usage/freq, RAM, IP, GPU temp, GPU mem (selon variante).
|
||||
|
||||
## 2.4 Commandes systeme executees
|
||||
- Shutdown : `sudo shutdown -h now`
|
||||
- Reboot : `sudo reboot`
|
||||
- Ecran : `busctl --user set-property org.gnome.Mutter.DisplayConfig ... PowerSaveMode i 1/0`
|
||||
- CPU freq : ecriture dans `/sys/devices/system/cpu/cpu0/cpufreq/scaling_setspeed`
|
||||
|
||||
Risques securite + mitigations actuelles :
|
||||
- Risques : execution de commandes privilegiees via MQTT sans validation, topics previsibles, pas d’auth/TLS.
|
||||
- Mitigations : aucune au niveau code; README recommande `sudoers` NOPASSWD.
|
||||
|
||||
## 2.5 Configuration
|
||||
Sources : hardcode dans scripts Python.
|
||||
|
||||
Parametres et defaults (tableau) :
|
||||
- mqtt_broker_ip_address : 10.0.0.3
|
||||
- mqtt_port : 1883
|
||||
- mqtt_username/mqtt_password : ""
|
||||
- discovery_prefix : homeassistant
|
||||
- device_name / hostname : "yoga14" ou hostname systeme
|
||||
- update_interval : multiples (5/10/20/60) selon entite
|
||||
|
||||
Secrets : Non trouve (pas de gestion de secrets, valeurs en clair).
|
||||
|
||||
## 2.6 Stockage / etat
|
||||
- Pas de base de donnees ni fichiers d’etat.
|
||||
- Etat publie via MQTT, souvent en retained (availability + states).
|
||||
- Persistence : non (hors retained MQTT).
|
||||
|
||||
## 2.7 Deploiement actuel
|
||||
- Installer : venv python + `pip install -r requirements.txt`.
|
||||
- Lancer : `python3 main.py` ou `python3 main_prog.py`.
|
||||
- Service : `mqtt_pilot.service` + `install.sh` (systemd).
|
||||
- Update : Non trouve (pas de script d’update).
|
||||
- Rollback : Non trouve.
|
||||
|
||||
## 2.8 Points faibles / dettes techniques
|
||||
- P0 : Pas d’auth/TLS MQTT; commandes privilegiees sans validation; allowlist absente.
|
||||
- P1 : Config hardcodee (broker, topics, device_name); duplication de code entre scripts.
|
||||
- P1 : Availability publiee en boucle sans LWT; pas de QoS/retours d’erreurs robustes.
|
||||
- P2 : Pas de tests, pas de logs structurels, pas de packaging.
|
||||
- P2 : Compatibilite Windows non traitee; dependances GPU/psutil non verifiees.
|
||||
|
||||
Elements manquants / non trouves :
|
||||
- Fichier de configuration externe (.env/yaml/json).
|
||||
- TLS, ACL broker, signature de messages.
|
||||
- Definition formelle de schema de payload.
|
||||
|
||||
## 2.9 Suggestions de refonte (sans coder)
|
||||
- Quick wins : centraliser config (env/yaml), ajouter LWT + heartbeat, factoriser modules MQTT/sensors/commands.
|
||||
- Architecture cible probable : core MQTT + module commands + module sensors + config + logging; separation OS (Linux/Windows).
|
||||
- Axes securite : auth/TLS broker, allowlist commandes, validation payloads, limitation topics, journalisation des actions.
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Vérifier si le script est exécuté avec les privilèges root
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo "Veuillez exécuter ce script avec sudo ou en tant que root."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Copier le fichier de service dans le répertoire systemd
|
||||
cp mqtt_pilot.service /etc/systemd/system/
|
||||
|
||||
# Recharger systemd pour prendre en compte le nouveau service
|
||||
systemctl daemon-reload
|
||||
|
||||
# Activer le service pour qu'il démarre au démarrage
|
||||
systemctl enable mqtt_pilot.service
|
||||
|
||||
# Démarrer le service immédiatement
|
||||
systemctl stop mqtt_pilot.service
|
||||
systemctl start mqtt_pilot.service
|
||||
|
||||
echo "Le service mqtt_pilot a été installé et démarré avec succès."
|
||||
@@ -0,0 +1,289 @@
|
||||
# ajouter cette ligne en bas du fichier : sudo visudo
|
||||
# gilles ALL=(ALL) NOPASSWD: /sbin/shutdown
|
||||
|
||||
# ajouter le hostname du computer
|
||||
|
||||
import os
|
||||
import time
|
||||
import json
|
||||
import paho.mqtt.client as mqtt
|
||||
import subprocess
|
||||
import threading
|
||||
import psutil
|
||||
import socket
|
||||
|
||||
hostname = socket.gethostname()
|
||||
|
||||
|
||||
|
||||
# Fonctions pour obtenir les températures CPU
|
||||
def get_cpu_temperature():
|
||||
temps = psutil.sensors_temperatures()
|
||||
for name, entries in temps.items():
|
||||
#if name == 'k10temp':
|
||||
if name == 'coretemp':
|
||||
for entry in entries:
|
||||
#if entry.label == 'Tctl':
|
||||
if entry.label == 'Package id 0':
|
||||
return entry.current
|
||||
|
||||
def get_cpu_usage():
|
||||
cpu_usage = psutil.cpu_percent()
|
||||
return round(cpu_usage, 1) # Arrondir à 1 chiffre après la virgule
|
||||
|
||||
|
||||
|
||||
# Paramètres MQTT
|
||||
mqtt_broker_ip_address = "10.0.0.3"
|
||||
mqtt_port = 1883
|
||||
mqtt_username = ""
|
||||
mqtt_password = ""
|
||||
#update_frequency = 60 # Mise à jour toutes les 60 secondes
|
||||
discovery_prefix = "homeassistant"
|
||||
update_frequency = 5 # en secondes
|
||||
|
||||
# Fonction pour obtenir la quantité de mémoire utilisée
|
||||
def get_memory_used():
|
||||
memory_info = psutil.virtual_memory()
|
||||
memory_used_mb = memory_info.used / 1024 / 1024 # Convertir en MB
|
||||
return round(memory_used_mb) # Arrondir à 0 chiffre après la virgule
|
||||
|
||||
|
||||
|
||||
device_info = {
|
||||
"identifiers": ["Mqtt_pilot"],
|
||||
"name": f"{hostname}",
|
||||
"manufacturer": "Black",
|
||||
"model": "desktop",
|
||||
"sw_version": "1.0.0",
|
||||
"suggested_area": "salon",
|
||||
}
|
||||
|
||||
|
||||
# Configuration des entités
|
||||
shutdown_entity = {
|
||||
"name": f"shutdown_{hostname}",
|
||||
"type": "switch",
|
||||
"unique_id": f"shutdown_{hostname}_44:37:e6:6b:53:86",
|
||||
"command_topic": f"pilot/{hostname}/shutdown/available",
|
||||
"state_topic": f"pilot/{hostname}/shutdown",
|
||||
"availability_topic": f"pilot/{hostname}/shutdown/available",
|
||||
"device_class": "switch",
|
||||
"payload_on": "ON",
|
||||
"payload_off": "OFF",
|
||||
"payload_available": "online",
|
||||
"payload_not_available": "offline",
|
||||
"icon": "mdi:power",
|
||||
"device": device_info,
|
||||
}
|
||||
|
||||
|
||||
cpu_temp_entity = {
|
||||
"name": f"cpu_temp_{hostname}",
|
||||
"type": "sensor",
|
||||
"unique_id": f"cpu_temp_{hostname}_44:37:e6:6b:53:86",
|
||||
"state_topic": f"pilot/{hostname}/cpu_temp",
|
||||
"availability_topic": f"pilot/{hostname}/cpu_temp/available",
|
||||
"device_class": "temperature",
|
||||
"unit_of_measurement": "°C",
|
||||
"payload_available": "online",
|
||||
"payload_not_available": "offline",
|
||||
"icon": "mdi:thermometer",
|
||||
"device": device_info,
|
||||
}
|
||||
|
||||
|
||||
# Définition de l'entité memory_used
|
||||
memory_used_entity = {
|
||||
"name": f"memory_used_{hostname}",
|
||||
"type": "sensor",
|
||||
"unique_id": f"memory_used_{hostname}_44:37:e6:6b:53:86",
|
||||
"state_topic": f"pilot/{hostname}/memory_used",
|
||||
"availability_topic": f"pilot/{hostname}/memory_used/available",
|
||||
# "device_class": "memory",
|
||||
"unit_of_measurement": "MB",
|
||||
"payload_available": "online",
|
||||
"payload_not_available": "offline",
|
||||
"icon": "mdi:memory",
|
||||
"device": device_info,
|
||||
}
|
||||
|
||||
|
||||
cpu_usage_entity = {
|
||||
"name": f"cpu_usage_{hostname}",
|
||||
"type": "sensor",
|
||||
"unique_id": f"cpu_usage_{hostname}_44:37:e6:6b:53:86",
|
||||
"state_topic": f"pilot/{hostname}/cpu_usage",
|
||||
"availability_topic": f"pilot/{hostname}/cpu_usage/available",
|
||||
"unit_of_measurement": "%",
|
||||
"payload_available": "online",
|
||||
"payload_not_available": "offline",
|
||||
"icon": "mdi:memory",
|
||||
"device": device_info,
|
||||
}
|
||||
|
||||
def publish_discovery_messages(client):
|
||||
# Publie les messages de découverte pour les entités
|
||||
# ...
|
||||
print("publish_discovery_messages")
|
||||
client.publish(
|
||||
f"{discovery_prefix}/switch/{hostname}/{shutdown_entity['name']}/config",
|
||||
json.dumps(shutdown_entity),
|
||||
retain=True,
|
||||
)
|
||||
# Publication de la configuration du capteur cpu_temp
|
||||
client.publish(
|
||||
f"{discovery_prefix}/sensor/{hostname}/{cpu_temp_entity['name']}/config",
|
||||
json.dumps(cpu_temp_entity),
|
||||
retain=True,
|
||||
)
|
||||
|
||||
# Publication de la configuration du capteur memory_used
|
||||
client.publish(
|
||||
f"{discovery_prefix}/sensor/{hostname}/{memory_used_entity['name']}/config",
|
||||
json.dumps(memory_used_entity),
|
||||
retain=True,
|
||||
)
|
||||
# Publication de la disponibilité pour l'entité cpu_usage
|
||||
client.publish(
|
||||
f"{discovery_prefix}/sensor/{hostname}/{cpu_usage_entity['name']}/config",
|
||||
json.dumps(cpu_usage_entity),
|
||||
retain=True,
|
||||
)
|
||||
|
||||
|
||||
|
||||
# client.publish(f"{discovery_prefix}/sensor/{battery_entity['name']}/config", json.dumps(battery_entity), retain=True)
|
||||
|
||||
|
||||
def publish_availability(client):
|
||||
client.publish(
|
||||
shutdown_entity["availability_topic"],
|
||||
shutdown_entity["payload_available"],
|
||||
retain=True,
|
||||
)
|
||||
# Publication de la disponibilité pour l'entité cpu_temp
|
||||
client.publish(
|
||||
cpu_temp_entity["availability_topic"],
|
||||
cpu_temp_entity["payload_available"],
|
||||
retain=True,
|
||||
)
|
||||
|
||||
|
||||
# Publication de la disponibilité pour l'entité memory_used
|
||||
client.publish(
|
||||
memory_used_entity["availability_topic"],
|
||||
memory_used_entity["payload_available"],
|
||||
retain=True,
|
||||
)
|
||||
|
||||
client.publish(
|
||||
cpu_usage_entity["availability_topic"],
|
||||
cpu_usage_entity["payload_available"],
|
||||
retain=True,
|
||||
)
|
||||
|
||||
|
||||
|
||||
print("Published availability for all entities")
|
||||
|
||||
|
||||
|
||||
def on_connect(client, userdata, flags, rc):
|
||||
if rc == 0:
|
||||
print("Connected with result code {rc}")
|
||||
client.subscribe(shutdown_entity["command_topic"])
|
||||
publish_discovery_messages(client)
|
||||
publish_availability(client)
|
||||
# publish_sensor_data(
|
||||
# client
|
||||
# ) # Démarre la première publication des données du capteur
|
||||
|
||||
# Publier l'état "ON" pour le switch au démarrage
|
||||
client.publish(
|
||||
shutdown_entity["state_topic"], shutdown_entity["payload_on"], retain=True
|
||||
)
|
||||
print(f"Set {shutdown_entity['name']} to ON")
|
||||
|
||||
|
||||
def on_message(client, userdata, message):
|
||||
# Gestion des messages MQTT
|
||||
print("on_message")
|
||||
# Vérifier si le message est pour le switch "shutdown"
|
||||
if message.topic == shutdown_entity["command_topic"]:
|
||||
if message.payload.decode() == shutdown_entity["payload_off"]:
|
||||
print("Received 'OFF' command - shutting down the system")
|
||||
client.publish(
|
||||
shutdown_entity["state_topic"],
|
||||
shutdown_entity["payload_off"],
|
||||
retain=True,
|
||||
)
|
||||
# Exécuter la commande de shutdown
|
||||
time.sleep(1)
|
||||
client.publish(
|
||||
shutdown_entity["availability_topic"],
|
||||
shutdown_entity["payload_not_available"],
|
||||
retain=True,
|
||||
)
|
||||
|
||||
time.sleep(1)
|
||||
subprocess.run(["sudo", "shutdown", "-h", "now"])
|
||||
elif message.payload.decode() == shutdown_entity["payload_on"]:
|
||||
print("Received 'ON' command - no action for 'ON'")
|
||||
def publish_sensor_values(client):
|
||||
cpu_temp = get_cpu_temperature()
|
||||
if cpu_temp is not None:
|
||||
client.publish(cpu_temp_entity["state_topic"], round(cpu_temp, 1), retain=True)
|
||||
|
||||
memory_used = get_memory_used()
|
||||
if memory_used is not None:
|
||||
client.publish(memory_used_entity["state_topic"], memory_used, retain=True)
|
||||
|
||||
cpu_usage = get_cpu_usage()
|
||||
if cpu_usage is not None:
|
||||
client.publish(cpu_usage_entity["state_topic"], cpu_usage, retain=True)
|
||||
|
||||
|
||||
|
||||
|
||||
# Configuration et démarrage du client MQTT
|
||||
client = mqtt.Client()
|
||||
client.username_pw_set(mqtt_username, mqtt_password)
|
||||
client.on_connect = on_connect
|
||||
client.on_message = on_message
|
||||
client.connect(mqtt_broker_ip_address, mqtt_port, 60)
|
||||
client.loop_start()
|
||||
|
||||
# Maintenir le script en exécution
|
||||
try:
|
||||
while True:
|
||||
publish_availability(client) # Maintenir l'état disponible
|
||||
publish_sensor_values(client) # Publier les valeurs des capteurs
|
||||
time.sleep(update_frequency) # Attendre avant la prochaine mise à jour
|
||||
except KeyboardInterrupt:
|
||||
print("Script interrupted, closing MQTT connection")
|
||||
# Publier l'état "unavailable" pour les entités
|
||||
client.publish(
|
||||
shutdown_entity["availability_topic"],
|
||||
shutdown_entity["payload_not_available"],
|
||||
retain=True,
|
||||
)
|
||||
client.publish(
|
||||
cpu_temp_entity["availability_topic"],
|
||||
cpu_temp_entity["payload_not_available"],
|
||||
retain=True,
|
||||
)
|
||||
|
||||
client.publish(
|
||||
memory_used_entity["availability_topic"],
|
||||
memory_used_entity["payload_not_available"],
|
||||
retain=True,
|
||||
)
|
||||
|
||||
client.publish(
|
||||
cpu_usage_entity["availability_topic"],
|
||||
cpu_usage_entity["payload_not_available"],
|
||||
retain=True,
|
||||
)
|
||||
|
||||
client.disconnect()
|
||||
@@ -0,0 +1,404 @@
|
||||
# ajouter cette ligne en bas du fichier : sudo visudo
|
||||
# gilles ALL=(ALL) NOPASSWD: /sbin/shutdown
|
||||
|
||||
# ajouter le hostname du computer
|
||||
|
||||
import os
|
||||
import time
|
||||
import json
|
||||
import paho.mqtt.client as mqtt
|
||||
import subprocess
|
||||
import threading
|
||||
import psutil
|
||||
import pynvml
|
||||
import socket
|
||||
|
||||
hostname = socket.gethostname()
|
||||
|
||||
# Initialisation de pynvml
|
||||
pynvml.nvmlInit()
|
||||
|
||||
# Fonctions pour obtenir les températures CPU et GPU
|
||||
def get_cpu_temperature():
|
||||
temps = psutil.sensors_temperatures()
|
||||
for name, entries in temps.items():
|
||||
if name == 'k10temp':
|
||||
for entry in entries:
|
||||
if entry.label == 'Tctl':
|
||||
return entry.current
|
||||
|
||||
def get_gpu_temperature():
|
||||
try:
|
||||
handle = pynvml.nvmlDeviceGetHandleByIndex(0)
|
||||
return pynvml.nvmlDeviceGetTemperature(handle, pynvml.NVML_TEMPERATURE_GPU)
|
||||
except pynvml.NVMLError as error:
|
||||
print(f"Failed to get GPU temperature: {error}")
|
||||
return None
|
||||
|
||||
def get_gpu_memory_usage():
|
||||
pynvml.nvmlInit()
|
||||
handle = pynvml.nvmlDeviceGetHandleByIndex(0) # 0 pour la première carte graphique
|
||||
info = pynvml.nvmlDeviceGetMemoryInfo(handle)
|
||||
pynvml.nvmlShutdown()
|
||||
|
||||
memory_used_gb = info.used / 1024 / 1024 # Convertir en GB
|
||||
return round(memory_used_gb, 2) # Arrondir à 2 chiffres après la virgule
|
||||
|
||||
# Paramètres MQTT
|
||||
mqtt_broker_ip_address = "10.0.0.3"
|
||||
mqtt_port = 1883
|
||||
mqtt_username = ""
|
||||
mqtt_password = ""
|
||||
#update_frequency = 60 # Mise à jour toutes les 60 secondes
|
||||
discovery_prefix = "homeassistant"
|
||||
update_frequency = 5 # en secondes
|
||||
|
||||
# Fonction pour obtenir la quantité de mémoire utilisée
|
||||
def get_memory_used():
|
||||
memory_info = psutil.virtual_memory()
|
||||
memory_used_mb = memory_info.used / 1024 / 1024 # Convertir en MB
|
||||
return round(memory_used_mb) # Arrondir à 0 chiffre après la virgule
|
||||
|
||||
def get_cpu_usage():
|
||||
cpu_usage = psutil.cpu_percent()
|
||||
return round(cpu_usage, 1) # Arrondir à 1 chiffre après la virgule
|
||||
|
||||
|
||||
device_info = {
|
||||
"identifiers": ["Mqtt_pilot"],
|
||||
"name": f"{hostname}",
|
||||
"manufacturer": "Black",
|
||||
"model": "desktop",
|
||||
"sw_version": "1.0.0",
|
||||
"suggested_area": "salon",
|
||||
}
|
||||
|
||||
|
||||
# Configuration des entités
|
||||
shutdown_entity = {
|
||||
"name": f"shutdown_{hostname}",
|
||||
"type": "switch",
|
||||
"unique_id": f"shutdown_{hostname}_18:c0:4d:b5:65:74",
|
||||
"command_topic": f"pilot/{hostname}/shutdown/available",
|
||||
"state_topic": f"pilot/{hostname}/shutdown",
|
||||
"availability_topic": f"pilot/{hostname}/shutdown/available",
|
||||
"device_class": "switch",
|
||||
"payload_on": "ON",
|
||||
"payload_off": "OFF",
|
||||
"payload_available": "online",
|
||||
"payload_not_available": "offline",
|
||||
"icon": "mdi:power",
|
||||
"device": device_info,
|
||||
}
|
||||
|
||||
# battery_entity = {
|
||||
# "name": f"battery_{hostname}",
|
||||
# "type": "sensor",
|
||||
# "unique_id": f"battery_{hostname}_18:c0:4d:b5:65:74",
|
||||
# "state_topic": f"pilot/{hostname}/battery",
|
||||
# "unit_of_measurement": "%",
|
||||
# "device_class": "battery",
|
||||
# "availability_topic": f"pilot/{hostname}/battery/available",
|
||||
# "payload_available": "online",
|
||||
# "payload_not_available": "offline",
|
||||
# "icon": "mdi:battery",
|
||||
# "device": device_info,
|
||||
# }
|
||||
|
||||
cpu_temp_entity = {
|
||||
"name": f"cpu_temp_{hostname}",
|
||||
"type": "sensor",
|
||||
"unique_id": f"cpu_temp_{hostname}_18:c0:4d:b5:65:74",
|
||||
"state_topic": f"pilot/{hostname}/cpu_temp",
|
||||
"availability_topic": f"pilot/{hostname}/cpu_temp/available",
|
||||
"device_class": "temperature",
|
||||
"unit_of_measurement": "°C",
|
||||
"payload_available": "online",
|
||||
"payload_not_available": "offline",
|
||||
"icon": "mdi:thermometer",
|
||||
"device": device_info,
|
||||
}
|
||||
|
||||
gpu_temp_entity = {
|
||||
"name": f"gpu_temp_{hostname}",
|
||||
"type": "sensor",
|
||||
"unique_id": f"gpu_temp_{hostname}_18:c0:4d:b5:65:74",
|
||||
"state_topic": f"pilot/{hostname}/gpu_temp",
|
||||
"availability_topic": f"pilot/{hostname}/gpu_temp/available",
|
||||
"device_class": "temperature",
|
||||
"unit_of_measurement": "°C",
|
||||
"payload_available": "online",
|
||||
"payload_not_available": "offline",
|
||||
"icon": "mdi:thermometer",
|
||||
"device": device_info,
|
||||
}
|
||||
# Définition de l'entité memory_used
|
||||
memory_used_entity = {
|
||||
"name": f"memory_used_{hostname}",
|
||||
"type": "sensor",
|
||||
"unique_id": f"memory_used_{hostname}_18:c0:4d:b5:65:74",
|
||||
"state_topic": f"pilot/{hostname}/memory_used",
|
||||
"availability_topic": f"pilot/{hostname}/memory_used/available",
|
||||
# "device_class": "memory",
|
||||
"unit_of_measurement": "MB",
|
||||
"payload_available": "online",
|
||||
"payload_not_available": "offline",
|
||||
"icon": "mdi:memory",
|
||||
"device": device_info,
|
||||
}
|
||||
|
||||
gpu_memory_usage_entity = {
|
||||
"name": f"gpu_memory_usage_{hostname}",
|
||||
"type": "sensor",
|
||||
"unique_id": f"gpu_memory_used_{hostname}_18:c0:4d:b5:65:74",
|
||||
"state_topic": f"pilot/{hostname}/gpu_memory_usage/state",
|
||||
"availability_topic": f"pilot/{hostname}/gpu_memory_usage/available",
|
||||
"unit_of_measurement": "MB",
|
||||
# "device_class": "memory",
|
||||
"unit_of_measurement": "MB",
|
||||
"payload_available": "online",
|
||||
"payload_not_available": "offline",
|
||||
"icon": "mdi:memory",
|
||||
"device": device_info,
|
||||
}
|
||||
|
||||
cpu_usage_entity = {
|
||||
"name": f"cpu_usage_{hostname}",
|
||||
"type": "sensor",
|
||||
"unique_id": f"cpu_usage_{hostname}_18:c0:4d:b5:65:74",
|
||||
"state_topic": f"pilot/{hostname}/cpu_usage",
|
||||
"availability_topic": f"pilot/{hostname}/cpu_usage/available",
|
||||
"unit_of_measurement": "%",
|
||||
"payload_available": "online",
|
||||
"payload_not_available": "offline",
|
||||
"icon": "mdi:memory",
|
||||
"device": device_info,
|
||||
}
|
||||
|
||||
def publish_discovery_messages(client):
|
||||
# Publie les messages de découverte pour les entités
|
||||
# ...
|
||||
print("publish_discovery_messages")
|
||||
client.publish(
|
||||
f"{discovery_prefix}/switch/{hostname}/{shutdown_entity['name']}/config",
|
||||
json.dumps(shutdown_entity),
|
||||
retain=True,
|
||||
)
|
||||
# Publication de la configuration du capteur cpu_temp
|
||||
client.publish(
|
||||
f"{discovery_prefix}/sensor/{hostname}/{cpu_temp_entity['name']}/config",
|
||||
json.dumps(cpu_temp_entity),
|
||||
retain=True,
|
||||
)
|
||||
# Publication de la configuration du capteur gpu_temp
|
||||
client.publish(
|
||||
f"{discovery_prefix}/sensor/{hostname}/{gpu_temp_entity['name']}/config",
|
||||
json.dumps(gpu_temp_entity),
|
||||
retain=True,
|
||||
)
|
||||
# Publication de la configuration du capteur memory_used
|
||||
client.publish(
|
||||
f"{discovery_prefix}/sensor/{hostname}/{memory_used_entity['name']}/config",
|
||||
json.dumps(memory_used_entity),
|
||||
retain=True,
|
||||
)
|
||||
# Publication de la disponibilité pour l'entité cpu_usage
|
||||
client.publish(
|
||||
f"{discovery_prefix}/sensor/{hostname}/{cpu_usage_entity['name']}/config",
|
||||
json.dumps(cpu_usage_entity),
|
||||
retain=True,
|
||||
)
|
||||
# Publication de la configuration du capteur gpu_memory_usage
|
||||
client.publish(
|
||||
f"{discovery_prefix}/sensor/{hostname}/{gpu_memory_usage_entity['name']}/config",
|
||||
json.dumps(gpu_memory_usage_entity),
|
||||
retain=True,
|
||||
)
|
||||
|
||||
|
||||
# client.publish(f"{discovery_prefix}/sensor/{battery_entity['name']}/config", json.dumps(battery_entity), retain=True)
|
||||
|
||||
|
||||
def publish_availability(client):
|
||||
client.publish(
|
||||
shutdown_entity["availability_topic"],
|
||||
shutdown_entity["payload_available"],
|
||||
retain=True,
|
||||
)
|
||||
# Publication de la disponibilité pour l'entité cpu_temp
|
||||
client.publish(
|
||||
cpu_temp_entity["availability_topic"],
|
||||
cpu_temp_entity["payload_available"],
|
||||
retain=True,
|
||||
)
|
||||
|
||||
# Publication de la disponibilité pour l'entité gpu_temp
|
||||
client.publish(
|
||||
gpu_temp_entity["availability_topic"],
|
||||
gpu_temp_entity["payload_available"],
|
||||
retain=True,
|
||||
)
|
||||
|
||||
# Publication de la disponibilité pour l'entité memory_used
|
||||
client.publish(
|
||||
memory_used_entity["availability_topic"],
|
||||
memory_used_entity["payload_available"],
|
||||
retain=True,
|
||||
)
|
||||
|
||||
client.publish(
|
||||
cpu_usage_entity["availability_topic"],
|
||||
cpu_usage_entity["payload_available"],
|
||||
retain=True,
|
||||
)
|
||||
# Publication de la disponibilité pour l'entité gpu_memory_usage
|
||||
client.publish(
|
||||
gpu_memory_usage_entity["availability_topic"],
|
||||
"online", # ou "offline" si le capteur n'est pas disponible
|
||||
retain=True,
|
||||
)
|
||||
|
||||
# client.publish(
|
||||
# battery_entity["availability_topic"],
|
||||
# battery_entity["payload_available"],
|
||||
# retain=True,
|
||||
# )
|
||||
print("Published availability for all entities")
|
||||
|
||||
|
||||
# def publish_sensor_data(client):
|
||||
# battery_level = get_battery_level()
|
||||
# if battery_level is not None:
|
||||
# client.publish(battery_entity["state_topic"], battery_level, retain=True)
|
||||
# threading.Timer(update_frequency, publish_sensor_data, [client]).start()
|
||||
|
||||
|
||||
def on_connect(client, userdata, flags, rc):
|
||||
if rc == 0:
|
||||
print("Connected with result code {rc}")
|
||||
client.subscribe(shutdown_entity["command_topic"])
|
||||
publish_discovery_messages(client)
|
||||
publish_availability(client)
|
||||
# publish_sensor_data(
|
||||
# client
|
||||
# ) # Démarre la première publication des données du capteur
|
||||
|
||||
# Publier l'état "ON" pour le switch au démarrage
|
||||
client.publish(
|
||||
shutdown_entity["state_topic"], shutdown_entity["payload_on"], retain=True
|
||||
)
|
||||
print(f"Set {shutdown_entity['name']} to ON")
|
||||
|
||||
|
||||
def on_message(client, userdata, message):
|
||||
# Gestion des messages MQTT
|
||||
print("on_message")
|
||||
# Vérifier si le message est pour le switch "shutdown"
|
||||
if message.topic == shutdown_entity["command_topic"]:
|
||||
if message.payload.decode() == shutdown_entity["payload_off"]:
|
||||
print("Received 'OFF' command - shutting down the system")
|
||||
client.publish(
|
||||
shutdown_entity["state_topic"],
|
||||
shutdown_entity["payload_off"],
|
||||
retain=True,
|
||||
)
|
||||
# Exécuter la commande de shutdown
|
||||
time.sleep(1)
|
||||
client.publish(
|
||||
shutdown_entity["availability_topic"],
|
||||
shutdown_entity["payload_not_available"],
|
||||
retain=True,
|
||||
)
|
||||
# client.publish(
|
||||
# battery_entity["availability_topic"],
|
||||
# battery_entity["payload_not_available"],
|
||||
# retain=True,
|
||||
# )
|
||||
time.sleep(1)
|
||||
subprocess.run(["sudo", "shutdown", "-h", "now"])
|
||||
elif message.payload.decode() == shutdown_entity["payload_on"]:
|
||||
print("Received 'ON' command - no action for 'ON'")
|
||||
def publish_sensor_values(client):
|
||||
cpu_temp = get_cpu_temperature()
|
||||
if cpu_temp is not None:
|
||||
client.publish(cpu_temp_entity["state_topic"], round(cpu_temp, 1), retain=True)
|
||||
|
||||
gpu_temp = get_gpu_temperature()
|
||||
if gpu_temp is not None:
|
||||
client.publish(gpu_temp_entity["state_topic"], round(gpu_temp, 1), retain=True)
|
||||
|
||||
memory_used = get_memory_used()
|
||||
if memory_used is not None:
|
||||
client.publish(memory_used_entity["state_topic"], memory_used, retain=True)
|
||||
|
||||
cpu_usage = get_cpu_usage()
|
||||
if cpu_usage is not None:
|
||||
client.publish(cpu_usage_entity["state_topic"], cpu_usage, retain=True)
|
||||
gpu_memory_usage = get_gpu_memory_usage()
|
||||
|
||||
if gpu_memory_usage is not None:
|
||||
client.publish(gpu_memory_usage_entity["state_topic"], gpu_memory_usage, retain=True)
|
||||
# def get_battery_level():
|
||||
# try:
|
||||
# with open("/sys/class/power_supply/BAT0/capacity", "r") as file:
|
||||
# return file.read().strip()
|
||||
# except Exception as e:
|
||||
# print(f"Error reading battery level: {e}")
|
||||
# return None
|
||||
|
||||
|
||||
# Configuration et démarrage du client MQTT
|
||||
client = mqtt.Client()
|
||||
client.username_pw_set(mqtt_username, mqtt_password)
|
||||
client.on_connect = on_connect
|
||||
client.on_message = on_message
|
||||
client.connect(mqtt_broker_ip_address, mqtt_port, 60)
|
||||
client.loop_start()
|
||||
|
||||
# Maintenir le script en exécution
|
||||
try:
|
||||
while True:
|
||||
publish_availability(client) # Maintenir l'état disponible
|
||||
publish_sensor_values(client) # Publier les valeurs des capteurs
|
||||
time.sleep(update_frequency) # Attendre avant la prochaine mise à jour
|
||||
except KeyboardInterrupt:
|
||||
print("Script interrupted, closing MQTT connection")
|
||||
# Publier l'état "unavailable" pour les entités
|
||||
client.publish(
|
||||
shutdown_entity["availability_topic"],
|
||||
shutdown_entity["payload_not_available"],
|
||||
retain=True,
|
||||
)
|
||||
client.publish(
|
||||
cpu_temp_entity["availability_topic"],
|
||||
cpu_temp_entity["payload_not_available"],
|
||||
retain=True,
|
||||
)
|
||||
client.publish(
|
||||
gpu_temp_entity["availability_topic"],
|
||||
gpu_temp_entity["payload_not_available"],
|
||||
retain=True,
|
||||
)
|
||||
client.publish(
|
||||
memory_used_entity["availability_topic"],
|
||||
memory_used_entity["payload_not_available"],
|
||||
retain=True,
|
||||
)
|
||||
|
||||
client.publish(
|
||||
cpu_usage_entity["availability_topic"],
|
||||
cpu_usage_entity["payload_not_available"],
|
||||
retain=True,
|
||||
)
|
||||
client.publish(
|
||||
gpu_memory_usage_entity["availability_topic"],
|
||||
gpu_memory_usage_entity["payload_not_available"],
|
||||
retain=True,
|
||||
)
|
||||
# client.publish(
|
||||
# battery_entity["availability_topic"],
|
||||
# battery_entity["payload_not_available"],
|
||||
# retain=True,
|
||||
# )
|
||||
# Fermer la connexion MQTT proprement
|
||||
client.disconnect()
|
||||
@@ -0,0 +1,535 @@
|
||||
# ajouter cette ligne en bas du fichier : sudo visudo
|
||||
# gilles ALL=(ALL) NOPASSWD: /sbin/shutdown
|
||||
# gilles ALL=(ALL) NOPASSWD: /sbin/reboot
|
||||
# gilles ALL=(ALL) NOPASSWD: /usr/bin/tee /sys/devices/system/cpu/cpu0/cpufreq/scaling_setspeed
|
||||
|
||||
|
||||
# structure du message discovery <discovery_prefix>/<component>/[<node_id>/]<object_id>/config
|
||||
|
||||
import os
|
||||
import time
|
||||
import json
|
||||
import paho.mqtt.client as mqtt
|
||||
import subprocess
|
||||
import threading
|
||||
import psutil
|
||||
import threading
|
||||
|
||||
stop_threads = threading.Event()
|
||||
|
||||
# Paramètres MQTT
|
||||
mqtt_broker_ip_address = "10.0.0.3"
|
||||
mqtt_port = 1883
|
||||
mqtt_username = ""
|
||||
mqtt_password = ""
|
||||
default_update_frequency = 60 # Mise à jour toutes les 60 secondes
|
||||
discovery_prefix = "homeassistant"
|
||||
device_name = "yoga14"
|
||||
mac_address = "60:57:18:99:ed:05"
|
||||
node_id = device_name
|
||||
mise_a_jours_frequente = 5
|
||||
mise_a_jour_moyenne = 30
|
||||
mise_a_jour_lente = 60
|
||||
|
||||
device_info = {
|
||||
"identifiers": [device_name],
|
||||
"name": "Yoga 14",
|
||||
"manufacturer": "Lenovo",
|
||||
"model": "laptop",
|
||||
"sw_version": "1.0.0",
|
||||
"suggested_area": "salon",
|
||||
}
|
||||
|
||||
|
||||
# Configuration des entités
|
||||
shutdown_entity = {
|
||||
"name": f"shutdown_{device_name}",
|
||||
"type": "switch",
|
||||
"unique_id": f"shutdown_{device_name}_{mac_address}",
|
||||
"command_topic": f"pilot/{device_name}/shutdown/set",
|
||||
"state_topic": f"pilot/{device_name}/shutdown/state",
|
||||
"availability_topic": f"pilot/{device_name}/shutdown/available",
|
||||
"device_class": "switch",
|
||||
"payload_on": "ON",
|
||||
"payload_off": "OFF",
|
||||
"payload_available": "online",
|
||||
"payload_not_available": "offline",
|
||||
"icon": "mdi:power",
|
||||
"device": device_info,
|
||||
|
||||
|
||||
}
|
||||
reboot_entity = {
|
||||
"name": f"reboot_{device_name}",
|
||||
"type": "switch",
|
||||
"unique_id": f"reboot_{device_name}_{mac_address}",
|
||||
"command_topic": f"pilot/{device_name}/reboot/set",
|
||||
"state_topic": f"pilot/{device_name}/reboot/state",
|
||||
"availability_topic": f"pilot/{device_name}/reboot/available",
|
||||
"device_class": "switch",
|
||||
"payload_on": "ON",
|
||||
"payload_off": "OFF",
|
||||
"payload_available": "online",
|
||||
"payload_not_available": "offline",
|
||||
"icon": "mdi:restart",
|
||||
"device": device_info,
|
||||
}
|
||||
|
||||
battery_entity = {
|
||||
"name": f"battery_{device_name}",
|
||||
"type": "sensor",
|
||||
"unique_id": f"battery_{device_name}_{mac_address}",
|
||||
"state_topic": f"pilot/{device_name}/battery/state",
|
||||
"unit_of_measurement": "%",
|
||||
"device_class": "battery",
|
||||
"availability_topic": f"pilot/{device_name}/battery/available",
|
||||
"payload_available": "online",
|
||||
"payload_not_available": "offline",
|
||||
"icon": "mdi:battery",
|
||||
"device": device_info,
|
||||
"update_interval": 60
|
||||
|
||||
}
|
||||
|
||||
charging_status_entity = {
|
||||
"name": f"charging_status_{device_name}",
|
||||
"type": "binary_sensor",
|
||||
"unique_id": f"charging_status_{device_name}_{mac_address}",
|
||||
"state_topic": f"pilot/{device_name}/charging_status/state",
|
||||
"availability_topic": f"pilot/{device_name}/charging_status/available",
|
||||
"device_class": "battery_charging",
|
||||
"payload_on": "ON",
|
||||
"payload_off": "OFF",
|
||||
"payload_available": "online",
|
||||
"payload_not_available": "offline",
|
||||
"icon": "mdi:battery-charging",
|
||||
"device": device_info,
|
||||
"update_interval": 5
|
||||
}
|
||||
|
||||
screen_entity = {
|
||||
"name": f"screen_{device_name}",
|
||||
"type": "switch",
|
||||
"unique_id": f"screen_{device_name}_{mac_address}",
|
||||
"command_topic": f"pilot/{device_name}/screen/set",
|
||||
"state_topic": f"pilot/{device_name}/screen/state",
|
||||
"availability_topic": f"pilot/{device_name}/screen/available",
|
||||
"device_class": "switch",
|
||||
"payload_on": "ON",
|
||||
"payload_off": "OFF",
|
||||
"payload_available": "online",
|
||||
"payload_not_available": "offline",
|
||||
"icon": "mdi:monitor",
|
||||
"device": device_info,
|
||||
}
|
||||
|
||||
# Ajout des nouvelles entités pour le CPU et la mémoire
|
||||
cpu_temperature_entity = {
|
||||
"name": f"cpu_temperature_{device_name}",
|
||||
"type": "sensor",
|
||||
"unique_id": f"cpu_temperature_{device_name}_{mac_address}",
|
||||
"state_topic": f"pilot/{device_name}/cpu_temperature/state",
|
||||
"unit_of_measurement": "°C",
|
||||
"device_class": "temperature",
|
||||
"availability_topic": f"pilot/{device_name}/cpu_temperature/available",
|
||||
"payload_available": "online",
|
||||
"payload_not_available": "offline",
|
||||
"icon": "mdi:thermometer",
|
||||
"device": device_info,
|
||||
"update_interval": 20
|
||||
}
|
||||
|
||||
cpu_usage_entity = {
|
||||
"name": f"cpu_usage_{device_name}",
|
||||
"type": "sensor",
|
||||
"unique_id": f"cpu_usage_{device_name}_{mac_address}",
|
||||
"state_topic": f"pilot/{device_name}/cpu_usage/state",
|
||||
"unit_of_measurement": "%",
|
||||
"device_class": "power",
|
||||
"availability_topic": f"pilot/{device_name}/cpu_usage/available",
|
||||
"payload_available": "online",
|
||||
"payload_not_available": "offline",
|
||||
"icon": "mdi:chip",
|
||||
"device": device_info,
|
||||
"update_interval": 10
|
||||
}
|
||||
|
||||
memory_usage_entity = {
|
||||
"name": f"memory_usage_{device_name}",
|
||||
"type": "sensor",
|
||||
"unique_id": f"memory_usage_{device_name}_{mac_address}",
|
||||
"state_topic": f"pilot/{device_name}/memory_usage/state",
|
||||
"unit_of_measurement": "%",
|
||||
# "device_class": "memory",
|
||||
"availability_topic": f"pilot/{device_name}/memory_usage/available",
|
||||
"payload_available": "online",
|
||||
"payload_not_available": "offline",
|
||||
"icon": "mdi:memory",
|
||||
"device": device_info,
|
||||
"update_interval": 10
|
||||
}
|
||||
|
||||
cpu_frequency_entity = {
|
||||
"name": f"cpu_frequency_{device_name}",
|
||||
"type": "sensor",
|
||||
"unique_id": f"cpu_frequency_{device_name}_{mac_address}",
|
||||
"state_topic": f"pilot/{device_name}/cpu_frequency/state",
|
||||
"unit_of_measurement": "GHz",
|
||||
"device_class": "frequency",
|
||||
"availability_topic": f"pilot/{device_name}/cpu_frequency/available",
|
||||
"payload_available": "online",
|
||||
"payload_not_available": "offline",
|
||||
"icon": "mdi:speedometer",
|
||||
"device": device_info,
|
||||
"update_interval": 20
|
||||
}
|
||||
|
||||
ip_address_entity = {
|
||||
"name": f"ip_address_{device_name}",
|
||||
"type": "sensor",
|
||||
"unique_id": f"ip_address_{device_name}_{mac_address}",
|
||||
"state_topic": f"pilot/{device_name}/ip_address/state",
|
||||
"availability_topic": f"pilot/{device_name}/ip_address/available",
|
||||
#"device_class": "connectivity",
|
||||
"payload_available": "online",
|
||||
"payload_not_available": "offline",
|
||||
"icon": "mdi:ip",
|
||||
"device": device_info,
|
||||
"update_interval": 60
|
||||
}
|
||||
|
||||
cpu_frequency_slider_entity = {
|
||||
"name": f"cpu_frequency_slider_{device_name}",
|
||||
"type": "number",
|
||||
"unique_id": f"cpu_frequency_slider_{device_name}_{mac_address}",
|
||||
"command_topic": f"pilot/{device_name}/cpu_frequency_slider/set",
|
||||
"state_topic": f"pilot/{device_name}/cpu_frequency_slider/state",
|
||||
"availability_topic": f"pilot/{device_name}/cpu_frequency_slider/available",
|
||||
"payload_available": "online",
|
||||
"payload_not_available": "offline",
|
||||
"min": 0.5,
|
||||
"max": 3.0,
|
||||
"step": 0.1,
|
||||
"unit_of_measurement": "GHz",
|
||||
"icon": "mdi:speedometer",
|
||||
"device": device_info,
|
||||
}
|
||||
|
||||
def publish_discovery_messages(client):
|
||||
# Publie les messages de découverte pour les entités
|
||||
# ...
|
||||
# print("publish_discovery_messages")
|
||||
client.publish(f"{discovery_prefix}/switch/{node_id}/{shutdown_entity['name']}/config", json.dumps(shutdown_entity), retain=True)
|
||||
client.publish(f"{discovery_prefix}/switch/{node_id}/{reboot_entity['name']}/config", json.dumps(reboot_entity), retain=True)
|
||||
client.publish(f"{discovery_prefix}/sensor/{node_id}/{battery_entity['name']}/config", json.dumps(battery_entity), retain=True)
|
||||
client.publish(f"{discovery_prefix}/switch/{node_id}/{screen_entity['name']}/config", json.dumps(screen_entity), retain=True)
|
||||
client.publish(f"{discovery_prefix}/sensor/{node_id}/{cpu_temperature_entity['name']}/config", json.dumps(cpu_temperature_entity), retain=True)
|
||||
client.publish(f"{discovery_prefix}/sensor/{node_id}/{cpu_usage_entity['name']}/config", json.dumps(cpu_usage_entity), retain=True)
|
||||
client.publish(f"{discovery_prefix}/sensor/{node_id}/{memory_usage_entity['name']}/config", json.dumps(memory_usage_entity), retain=True)
|
||||
client.publish(f"{discovery_prefix}/sensor/{node_id}/{cpu_frequency_entity['name']}/config", json.dumps(cpu_frequency_entity), retain=True)
|
||||
client.publish(f"{discovery_prefix}/binary_sensor/{node_id}/{charging_status_entity['name']}/config", json.dumps(charging_status_entity), retain=True)
|
||||
client.publish(f"{discovery_prefix}/sensor/{node_id}/{ip_address_entity['name']}/config", json.dumps(ip_address_entity), retain=True)
|
||||
client.publish(f"{discovery_prefix}/number/{node_id}/{cpu_frequency_slider_entity['name']}/config", json.dumps(cpu_frequency_slider_entity), retain=True)
|
||||
|
||||
|
||||
|
||||
# print("Discovery messages published")
|
||||
|
||||
def publish_availability(client):
|
||||
client.publish(shutdown_entity["availability_topic"], shutdown_entity["payload_available"], retain=True)
|
||||
client.publish(reboot_entity["availability_topic"], reboot_entity["payload_available"], retain=True)
|
||||
client.publish(battery_entity["availability_topic"], battery_entity["payload_available"], retain=True)
|
||||
client.publish(screen_entity["availability_topic"], screen_entity["payload_available"], retain=True)
|
||||
client.publish(cpu_temperature_entity["availability_topic"], cpu_temperature_entity["payload_available"], retain=True)
|
||||
client.publish(cpu_usage_entity["availability_topic"], cpu_usage_entity["payload_available"], retain=True)
|
||||
client.publish(memory_usage_entity["availability_topic"], memory_usage_entity["payload_available"], retain=True)
|
||||
client.publish(cpu_frequency_entity["availability_topic"], cpu_frequency_entity["payload_available"], retain=True)
|
||||
client.publish(charging_status_entity["availability_topic"], charging_status_entity["payload_available"], retain=True)
|
||||
client.publish(ip_address_entity["availability_topic"], ip_address_entity["payload_available"], retain=True)
|
||||
client.publish(cpu_frequency_slider_entity["availability_topic"], cpu_frequency_slider_entity["payload_available"], retain=True)
|
||||
# print("Published availability for all entities")
|
||||
|
||||
def get_local_ip_address():
|
||||
try:
|
||||
import socket
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
s.settimeout(0)
|
||||
s.connect(('10.254.254.254', 1)) # Adresse IP arbitraire pour établir une connexion
|
||||
local_ip = s.getsockname()[0]
|
||||
s.close()
|
||||
print(f"Publishing IP address: {local_ip}") # Log l'adresse IP
|
||||
return local_ip
|
||||
except Exception as e:
|
||||
print(f"Error retrieving local IP address: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def get_cpu_temperature():
|
||||
# Utilisation de psutil ou autre pour obtenir la température du CPU
|
||||
try:
|
||||
temp = psutil.sensors_temperatures()['coretemp'][0].current # Par exemple pour les CPUs Intel
|
||||
print(f"Publishing CPU temperature: {temp}°C")
|
||||
return temp
|
||||
except Exception as e:
|
||||
print(f"Error reading CPU temperature: {e}")
|
||||
return None
|
||||
|
||||
def get_cpu_usage():
|
||||
try:
|
||||
cpu_percent = psutil.cpu_percent(interval=1)
|
||||
print(f"CPU usage: {cpu_percent}%") # Ajoute ce log pour déboguer
|
||||
return cpu_percent
|
||||
except Exception as e:
|
||||
print(f"Error reading CPU usage: {e}")
|
||||
return None
|
||||
|
||||
def get_memory_usage():
|
||||
try:
|
||||
memory_info = psutil.virtual_memory()
|
||||
print(f"Memory usage: {memory_info.percent}%") # Ajoute ce log pour déboguer
|
||||
return memory_info.percent
|
||||
except Exception as e:
|
||||
print(f"Error reading memory usage: {e}")
|
||||
return None
|
||||
|
||||
def get_cpu_frequency():
|
||||
try:
|
||||
with open("/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq", "r") as file:
|
||||
# La fréquence est en kHz, donc on la convertit en GHz
|
||||
freq_khz = int(file.read().strip())
|
||||
freq_ghz = freq_khz / 1_000_000 # Conversion de kHz à GHz
|
||||
print(f"CPU frequency: {freq_ghz:.2f} GHz") # Ajoute ce log pour déboguer
|
||||
return f"{freq_ghz:.2f}" # Formater avec 2 chiffres après la virgule
|
||||
except Exception as e:
|
||||
print(f"Error reading CPU frequency: {e}")
|
||||
return None
|
||||
|
||||
def get_battery_level():
|
||||
try:
|
||||
with open("/sys/class/power_supply/BAT0/capacity", "r") as file:
|
||||
battery_level = file.read().strip()
|
||||
print(f"Publishing battery level: {battery_level}%")
|
||||
return battery_level
|
||||
except Exception as e:
|
||||
print(f"Error reading battery level: {e}")
|
||||
return None
|
||||
|
||||
def get_charging_status():
|
||||
try:
|
||||
with open("/sys/class/power_supply/ADP1/online", "r") as file:
|
||||
status = file.read().strip()
|
||||
print(f"Publishing charging status: {status}")
|
||||
return "ON" if status == "1" else "OFF"
|
||||
except Exception as e:
|
||||
print(f"Error reading charging status: {e}")
|
||||
return None
|
||||
|
||||
def set_cpu_frequency(frequency):
|
||||
try:
|
||||
frequency_khz = int(float(frequency) * 1_000_000) # Convertir GHz en kHz
|
||||
with open("/sys/devices/system/cpu/cpu0/cpufreq/scaling_setspeed", "w") as file:
|
||||
file.write(str(frequency_khz))
|
||||
print(f"Set CPU frequency to {frequency} GHz")
|
||||
except Exception as e:
|
||||
print(f"Error setting CPU frequency: {e}")
|
||||
|
||||
|
||||
def publish_sensor_data(client):
|
||||
battery_level = get_battery_level()
|
||||
if battery_level is not None:
|
||||
client.publish(battery_entity["state_topic"], battery_level, retain=True)
|
||||
|
||||
cpu_temp = get_cpu_temperature()
|
||||
if cpu_temp is not None:
|
||||
client.publish(cpu_temperature_entity["state_topic"], cpu_temp, retain=True)
|
||||
|
||||
cpu_usage = get_cpu_usage()
|
||||
if cpu_usage is not None:
|
||||
client.publish(cpu_usage_entity["state_topic"], cpu_usage, retain=True)
|
||||
|
||||
memory_usage = get_memory_usage()
|
||||
if memory_usage is not None:
|
||||
print(f"Publishing memory usage: {memory_usage}%") # Ajoute ce log
|
||||
client.publish(memory_usage_entity["state_topic"], memory_usage, retain=True)
|
||||
|
||||
cpu_freq = get_cpu_frequency()
|
||||
if cpu_freq is not None:
|
||||
client.publish(cpu_frequency_entity["state_topic"], cpu_freq, retain=True)
|
||||
print(f"Published CPU frequency: {cpu_freq} GHz")
|
||||
|
||||
charging_status = get_charging_status()
|
||||
if charging_status is not None:
|
||||
client.publish(charging_status_entity["state_topic"], charging_status, retain=True)
|
||||
|
||||
threading.Timer(update_frequency, publish_sensor_data, [client]).start()
|
||||
|
||||
def publish_battery_level(client):
|
||||
battery_level = get_battery_level()
|
||||
if battery_level is not None:
|
||||
client.publish(battery_entity["state_topic"], battery_level, retain=True)
|
||||
threading.Timer(get_update_interval(battery_entity), publish_battery_level, [client]).start()
|
||||
|
||||
def publish_cpu_temperature(client):
|
||||
cpu_temp = get_cpu_temperature()
|
||||
if cpu_temp is not None:
|
||||
client.publish(cpu_temperature_entity["state_topic"], cpu_temp, retain=True)
|
||||
threading.Timer(get_update_interval(cpu_temperature_entity), publish_cpu_temperature, [client]).start()
|
||||
|
||||
def publish_cpu_usage(client):
|
||||
cpu_usage = get_cpu_usage()
|
||||
if cpu_usage is not None:
|
||||
client.publish(cpu_usage_entity["state_topic"], cpu_usage, retain=True)
|
||||
threading.Timer(get_update_interval(cpu_usage_entity), publish_cpu_usage, [client]).start()
|
||||
|
||||
def publish_memory_usage(client):
|
||||
memory_usage = get_memory_usage()
|
||||
if memory_usage is not None:
|
||||
client.publish(memory_usage_entity["state_topic"], memory_usage, retain=True)
|
||||
threading.Timer(get_update_interval(memory_usage_entity), publish_memory_usage, [client]).start()
|
||||
|
||||
def publish_cpu_frequency(client):
|
||||
cpu_freq = get_cpu_frequency()
|
||||
if cpu_freq is not None:
|
||||
client.publish(cpu_frequency_entity["state_topic"], cpu_freq, retain=True)
|
||||
threading.Timer(get_update_interval(cpu_frequency_entity), publish_cpu_frequency, [client]).start()
|
||||
|
||||
def publish_charging_status(client):
|
||||
charging_status = get_charging_status()
|
||||
if charging_status is not None:
|
||||
client.publish(charging_status_entity["state_topic"], charging_status, retain=True)
|
||||
threading.Timer(get_update_interval(charging_status_entity), publish_charging_status, [client]).start()
|
||||
|
||||
def publish_ip_address(client):
|
||||
local_ip = get_local_ip_address()
|
||||
if local_ip is not None:
|
||||
client.publish(ip_address_entity["state_topic"], local_ip, retain=True)
|
||||
threading.Timer(get_update_interval(ip_address_entity), publish_ip_address, [client]).start()
|
||||
|
||||
def publish_current_cpu_frequency(client):
|
||||
cpu_freq = get_cpu_frequency()
|
||||
if cpu_freq is not None:
|
||||
client.publish(cpu_frequency_slider_entity["state_topic"], cpu_freq, retain=True)
|
||||
|
||||
def get_update_interval(entity):
|
||||
return entity.get("update_interval", default_update_frequency)
|
||||
|
||||
|
||||
def on_connect(client, userdata, flags, rc):
|
||||
if rc == 0:
|
||||
print("Connected with result code {rc}")
|
||||
client.subscribe(shutdown_entity["command_topic"])
|
||||
client.subscribe(reboot_entity["command_topic"])
|
||||
client.subscribe(screen_entity["command_topic"])
|
||||
client.subscribe(cpu_frequency_slider_entity["command_topic"]) # S'abonner au slider
|
||||
publish_discovery_messages(client)
|
||||
publish_availability(client)
|
||||
|
||||
# Démarrer la publication des données avec les fréquences respectives
|
||||
publish_battery_level(client)
|
||||
publish_cpu_temperature(client)
|
||||
publish_cpu_usage(client)
|
||||
publish_memory_usage(client)
|
||||
publish_cpu_frequency(client)
|
||||
publish_charging_status(client)
|
||||
publish_ip_address(client) # Ajout de la publication de l'adresse IP
|
||||
publish_current_cpu_frequency(client) # Publier l'état initial du slider
|
||||
|
||||
|
||||
# Publier l'état "ON" pour le switch au démarrage
|
||||
client.publish(shutdown_entity["state_topic"], shutdown_entity["payload_on"], retain=True)
|
||||
client.publish(reboot_entity["state_topic"], reboot_entity["payload_on"], retain=True)
|
||||
client.publish(screen_entity["state_topic"], screen_entity["payload_on"], retain=True)
|
||||
print(f"Set {shutdown_entity['name']} to ON")
|
||||
|
||||
def deactivate_entities(client):
|
||||
"""Désactive toutes les entités en les marquant comme 'unavailable'."""
|
||||
client.publish(shutdown_entity["availability_topic"], shutdown_entity["payload_not_available"], retain=True)
|
||||
client.publish(reboot_entity["availability_topic"], reboot_entity["payload_not_available"], retain=True)
|
||||
client.publish(battery_entity["availability_topic"], battery_entity["payload_not_available"], retain=True)
|
||||
client.publish(screen_entity["availability_topic"], screen_entity["payload_not_available"], retain=True)
|
||||
client.publish(cpu_temperature_entity["availability_topic"], cpu_temperature_entity["payload_not_available"], retain=True)
|
||||
client.publish(cpu_usage_entity["availability_topic"], cpu_usage_entity["payload_not_available"], retain=True)
|
||||
client.publish(memory_usage_entity["availability_topic"], memory_usage_entity["payload_not_available"], retain=True)
|
||||
client.publish(cpu_frequency_entity["availability_topic"], cpu_frequency_entity["payload_not_available"], retain=True)
|
||||
client.publish(charging_status_entity["availability_topic"], charging_status_entity["payload_not_available"], retain=True)
|
||||
client.publish(ip_address_entity["availability_topic"], ip_address_entity["payload_not_available"], retain=True)
|
||||
client.publish(cpu_frequency_slider_entity["availability_topic"], cpu_frequency_slider_entity["payload_not_available"], retain=True)
|
||||
client.loop_stop() # Arrête la boucle MQTT proprement pour s'assurer que tous les messages sont publiés
|
||||
print("All entities deactivated.")
|
||||
|
||||
|
||||
def on_message(client, userdata, message):
|
||||
# Gestion des messages MQTT
|
||||
print("on_message")
|
||||
# Vérifier si le message est pour le switch "shutdown"
|
||||
if message.topic == shutdown_entity["command_topic"]:
|
||||
if message.payload.decode() == shutdown_entity["payload_off"]:
|
||||
print("Received 'OFF' command - shutting down the system")
|
||||
client.publish(shutdown_entity["state_topic"], shutdown_entity["payload_off"], retain=True)
|
||||
# Exécuter la commande de shutdown
|
||||
time.sleep(1)
|
||||
deactivate_entities(client) # Désactiver toutes les entités avant de fermer
|
||||
time.sleep(2)
|
||||
subprocess.run(["sudo", "shutdown", "-h", "now"])
|
||||
exit(0) # Sortir immédiatement du programme après le shutdown
|
||||
elif message.payload.decode() == shutdown_entity["payload_on"]:
|
||||
print("Received 'ON' command - no action for 'ON'")
|
||||
|
||||
elif message.topic == reboot_entity["command_topic"]:
|
||||
if message.payload.decode() == reboot_entity["payload_off"]:
|
||||
print("Received 'OFF' command - rebooting the system")
|
||||
client.publish(reboot_entity["state_topic"], reboot_entity["payload_off"], retain=True)
|
||||
time.sleep(1)
|
||||
deactivate_entities(client) # Désactiver toutes les entités avant de redémarrer
|
||||
subprocess.run(["sudo", "reboot"])
|
||||
os._exit(0) # Sortir immédiatement du programme après le reboot
|
||||
elif message.payload.decode() == reboot_entity["payload_on"]:
|
||||
print("Received 'ON' command - no action for 'ON'")
|
||||
|
||||
elif message.topic == screen_entity["command_topic"]:
|
||||
if message.payload.decode() == screen_entity["payload_off"]:
|
||||
print("Received 'OFF' command - turning off the screen")
|
||||
client.publish(screen_entity["state_topic"], screen_entity["payload_off"], retain=True)
|
||||
result = subprocess.run(["busctl", "--user", "set-property", "org.gnome.Mutter.DisplayConfig", "/org/gnome/Mutter/DisplayConfig", "org.gnome.Mutter.DisplayConfig", "PowerSaveMode", "i", "1"], capture_output=True, text=True)
|
||||
print(f"Command output: {result.stdout}")
|
||||
if result.stderr:
|
||||
print(f"Command error: {result.stderr}")
|
||||
elif message.payload.decode() == screen_entity["payload_on"]:
|
||||
print("Received 'ON' command - turning on the screen")
|
||||
client.publish(screen_entity["state_topic"], screen_entity["payload_on"], retain=True)
|
||||
result = subprocess.run(["busctl", "--user", "set-property", "org.gnome.Mutter.DisplayConfig", "/org/gnome/Mutter/DisplayConfig", "org.gnome.Mutter.DisplayConfig", "PowerSaveMode", "i", "0"], capture_output=True, text=True)
|
||||
print(f"Command output: {result.stdout}")
|
||||
if result.stderr:
|
||||
print(f"Command error: {result.stderr}")
|
||||
|
||||
elif message.topic == cpu_frequency_slider_entity["command_topic"]:
|
||||
frequency = message.payload.decode()
|
||||
print(f"Received CPU frequency slider command: {frequency} GHz")
|
||||
set_cpu_frequency(frequency)
|
||||
client.publish(cpu_frequency_entity["state_topic"], frequency, retain=True)
|
||||
client.publish(cpu_frequency_slider_entity["state_topic"], frequency, retain=True)
|
||||
|
||||
|
||||
|
||||
|
||||
# Configuration et démarrage du client MQTT
|
||||
client = mqtt.Client()
|
||||
client.username_pw_set(mqtt_username, mqtt_password)
|
||||
client.on_connect = on_connect
|
||||
client.on_message = on_message
|
||||
client.connect(mqtt_broker_ip_address, mqtt_port, 60)
|
||||
client.loop_start()
|
||||
|
||||
# Maintenir le script en exécution
|
||||
try:
|
||||
while True:
|
||||
publish_availability(client) # Maintenir l'état disponible
|
||||
time.sleep(1) # Attendre avant la prochaine mise à jour
|
||||
except KeyboardInterrupt:
|
||||
print("Script interrupted, closing MQTT connection")
|
||||
# Publier l'état "unavailable" pour les entités
|
||||
deactivate_entities(client)
|
||||
time.sleep(1)
|
||||
|
||||
# Fermer la connexion MQTT proprement
|
||||
client.disconnect()
|
||||
@@ -0,0 +1,108 @@
|
||||
Backup manifest
|
||||
Timestamp: 20251229_0205
|
||||
Source: /home/gilles/projects/pilot
|
||||
Version note: Non trouve
|
||||
|
||||
Arborescence:
|
||||
./analyse.md
|
||||
./analyse_version_1.md
|
||||
./.git/config
|
||||
./.git/description
|
||||
./.git/HEAD
|
||||
./.git/hooks/applypatch-msg.sample
|
||||
./.git/hooks/commit-msg.sample
|
||||
./.git/hooks/fsmonitor-watchman.sample
|
||||
./.git/hooks/post-update.sample
|
||||
./.git/hooks/pre-applypatch.sample
|
||||
./.git/hooks/pre-commit.sample
|
||||
./.git/hooks/pre-merge-commit.sample
|
||||
./.git/hooks/prepare-commit-msg.sample
|
||||
./.git/hooks/pre-push.sample
|
||||
./.git/hooks/pre-rebase.sample
|
||||
./.git/hooks/pre-receive.sample
|
||||
./.git/hooks/push-to-checkout.sample
|
||||
./.git/hooks/sendemail-validate.sample
|
||||
./.git/hooks/update.sample
|
||||
./.gitignore
|
||||
./.git/index
|
||||
./.git/info/exclude
|
||||
./.git/logs/HEAD
|
||||
./.git/logs/refs/heads/main
|
||||
./.git/logs/refs/remotes/origin/HEAD
|
||||
./.git/objects/1f/879b6f482e6eaf65f3bf14cd7b2e857db0c357
|
||||
./.git/objects/3f/1d4cf71e3112b318e0138b670e404b6810b2a2
|
||||
./.git/objects/5d/6ec335b58125b20ba018ef71a629038d13193e
|
||||
./.git/objects/a0/e84cc209dc6d3c6d85249832d09ff44ed09134
|
||||
./.git/objects/c5/e4f271728b45440da82d6a2ec8564eb3ce9452
|
||||
./.git/objects/c8/5502c87a58505f35cbb5b09107da471ccfd0f8
|
||||
./.git/objects/ed/811e801fed29e2189a0cf95e7253d860c5ffbe
|
||||
./.git/objects/f2/4d287e7d938f756f6939b187d117940c590330
|
||||
./.git/objects/pack/pack-bcca462ee5dd238ad61fc02f0b68fdf0f8a40e47.idx
|
||||
./.git/objects/pack/pack-bcca462ee5dd238ad61fc02f0b68fdf0f8a40e47.pack
|
||||
./.git/objects/pack/pack-bcca462ee5dd238ad61fc02f0b68fdf0f8a40e47.rev
|
||||
./.git/packed-refs
|
||||
./.git/refs/heads/main
|
||||
./.git/refs/remotes/origin/HEAD
|
||||
./install.sh
|
||||
./main-lenovo-bureau.py
|
||||
./main_prog.py
|
||||
./main.py
|
||||
./manifest.txt
|
||||
./mqtt_pilot.service
|
||||
./mqtt_unvai.py
|
||||
./prompt_codex_v_2.md
|
||||
./prompt_method_redev.md
|
||||
./README.md
|
||||
./requirements.txt
|
||||
|
||||
SHA256:
|
||||
18d09692cf81f4ffed570b0f025ce87a809106fa8039114bf225135188db2ec2 ./analyse.md
|
||||
8970cd0087f6b30ddad80514c9db8ebc2a40a199aeaaafd347c531875849074c ./analyse_version_1.md
|
||||
0e83cf83a6a8166adf565435bf037b67318639e8dc9fd379b5267bd210332f6b ./.git/config
|
||||
85ab6c163d43a17ea9cf7788308bca1466f1b0a8d1cc92e26e9bf63da4062aee ./.git/description
|
||||
28d25bf82af4c0e2b72f50959b2beb859e3e60b9630a5e8c603dad4ddb2b6e80 ./.git/HEAD
|
||||
0223497a0b8b033aa58a3a521b8629869386cf7ab0e2f101963d328aa62193f7 ./.git/hooks/applypatch-msg.sample
|
||||
1f74d5e9292979b573ebd59741d46cb93ff391acdd083d340b94370753d92437 ./.git/hooks/commit-msg.sample
|
||||
e0549964e93897b519bd8e333c037e51fff0f88ba13e086a331592bf801fa1d0 ./.git/hooks/fsmonitor-watchman.sample
|
||||
81765af2daef323061dcbc5e61fc16481cb74b3bac9ad8a174b186523586f6c5 ./.git/hooks/post-update.sample
|
||||
e15c5b469ea3e0a695bea6f2c82bcf8e62821074939ddd85b77e0007ff165475 ./.git/hooks/pre-applypatch.sample
|
||||
57185b7b9f05239d7ab52db045f5b89eb31348d7b2177eab214f5eb872e1971b ./.git/hooks/pre-commit.sample
|
||||
d3825a70337940ebbd0a5c072984e13245920cdf8898bd225c8d27a6dfc9cb53 ./.git/hooks/pre-merge-commit.sample
|
||||
e9ddcaa4189fddd25ed97fc8c789eca7b6ca16390b2392ae3276f0c8e1aa4619 ./.git/hooks/prepare-commit-msg.sample
|
||||
ecce9c7e04d3f5dd9d8ada81753dd1d549a9634b26770042b58dda00217d086a ./.git/hooks/pre-push.sample
|
||||
4febce867790052338076f4e66cc47efb14879d18097d1d61c8261859eaaa7b3 ./.git/hooks/pre-rebase.sample
|
||||
a4c3d2b9c7bb3fd8d1441c31bd4ee71a595d66b44fcf49ddb310252320169989 ./.git/hooks/pre-receive.sample
|
||||
a53d0741798b287c6dd7afa64aee473f305e65d3f49463bb9d7408ec3b12bf5f ./.git/hooks/push-to-checkout.sample
|
||||
44ebfc923dc5466bc009602f0ecf067b9c65459abfe8868ddc49b78e6ced7a92 ./.git/hooks/sendemail-validate.sample
|
||||
8d5f2fa83e103cf08b57eaa67521df9194f45cbdbcb37da52ad586097a14d106 ./.git/hooks/update.sample
|
||||
9cfc3f4a29dca37bd189d1066f3cf3ff715b2b1f38200e0e88cf766e153e3f8c ./.gitignore
|
||||
c7758fa326bbf97311e67ff1ad18d3dfe7e1c93125f4585a17a68e69a3c2c8f7 ./.git/index
|
||||
6671fe83b7a07c8932ee89164d1f2793b2318058eb8b98dc5c06ee0a5a3b0ec1 ./.git/info/exclude
|
||||
ac7327ceb621d8d30347a7d98d44d3c7e3599564cd0f0bd0278a0e214b082ccf ./.git/logs/HEAD
|
||||
ac7327ceb621d8d30347a7d98d44d3c7e3599564cd0f0bd0278a0e214b082ccf ./.git/logs/refs/heads/main
|
||||
ac7327ceb621d8d30347a7d98d44d3c7e3599564cd0f0bd0278a0e214b082ccf ./.git/logs/refs/remotes/origin/HEAD
|
||||
7b0af6e4eb041a50bc5e1998b9c47a874774eecd83b01aa66587893be847893f ./.git/objects/1f/879b6f482e6eaf65f3bf14cd7b2e857db0c357
|
||||
6e926b948d9d4545be66aed1a672934e42c6e3a27e4474a77604220866c01d7f ./.git/objects/3f/1d4cf71e3112b318e0138b670e404b6810b2a2
|
||||
1462461eca3f962987177b7d9485bac983d9be192b37431fb4ecfd8109d7fb69 ./.git/objects/5d/6ec335b58125b20ba018ef71a629038d13193e
|
||||
3ec86ba9e7ef79dfe286cda67e2c31f3c91d5844350689375920059fe82a0ecc ./.git/objects/a0/e84cc209dc6d3c6d85249832d09ff44ed09134
|
||||
883a4358c41667852e76afa874998a6a3e6d8b1fe78e01cb63bfe32584ef3a32 ./.git/objects/c5/e4f271728b45440da82d6a2ec8564eb3ce9452
|
||||
3bbe90643732be3c41517a0182ca86d5780b16ea2331131f1934528ce9ccf158 ./.git/objects/c8/5502c87a58505f35cbb5b09107da471ccfd0f8
|
||||
91692f8ca6b05f5f0bcf35dab188eb9509b9e2c9da28de834c4f9920b4cf4a6d ./.git/objects/ed/811e801fed29e2189a0cf95e7253d860c5ffbe
|
||||
37569e5ce5100aab5ad92c175691092c99d77fe8f29be95e8ad808623a76d1ce ./.git/objects/f2/4d287e7d938f756f6939b187d117940c590330
|
||||
13d6ddd5b76ebd3f7a1231ab643b6e6ff289a945f08a5418edb2ecaa0d290ddd ./.git/objects/pack/pack-bcca462ee5dd238ad61fc02f0b68fdf0f8a40e47.idx
|
||||
de3008c3214f87f6c0ba8423fbc61cb9433c7a0b9e83b5e5dba954f6fe86081f ./.git/objects/pack/pack-bcca462ee5dd238ad61fc02f0b68fdf0f8a40e47.pack
|
||||
bf3edf37a570c0e6f13e02d257d25b6d51de1969f60b7c433bc19625dd158fab ./.git/objects/pack/pack-bcca462ee5dd238ad61fc02f0b68fdf0f8a40e47.rev
|
||||
4895bec2d2419bcca9fc9aeeb500d6b1eb6f303ac5c3ce54c3b1858a7ce6e3ec ./.git/packed-refs
|
||||
27ef7b7ffec74ee504492a3a221bfe070550f5d93ae873d3d46c78a56c49fb60 ./.git/refs/heads/main
|
||||
2bb6a24aa0fc6c484100f5d51a29bbad841cd2c755f5d93faa204e5dbb4eb2b4 ./.git/refs/remotes/origin/HEAD
|
||||
dbf44a49752fbf1de6786c24480be06551b5035a74407f435bc56d09faba73ae ./install.sh
|
||||
fc3ebed3eef0437d3c022f939b88ccdd852d170bb3a39779bd66c4ff7e745d51 ./main-lenovo-bureau.py
|
||||
b17af44d1f4fcabde0cfd1fea5576abe96636a22fab3d2520035704bf00883db ./main_prog.py
|
||||
50a314415c33ef577d160b5eb94c26b777458812036c68afb239465d2f696210 ./main.py
|
||||
9379c86ff6c45c69eaca286e3be47697e8811d754ab4df0dd3acf561e021f472 ./manifest.txt
|
||||
8b5a9be81ba60973ee0be55e4496c817ff39c2f0cd00c2d3d726ae6bc2c94140 ./mqtt_pilot.service
|
||||
ba11d01fbf25a24e8f636ab2df5466a49bf7e59b422dce0f9b04861ac1ae75b3 ./mqtt_unvai.py
|
||||
2ef347d3e390eed581760006d79b05ab7416dc23199f53f26c4a304b7759e34e ./prompt_codex_v_2.md
|
||||
b2148d6708ba826a554a042de1a80c71984f5ec56bf890caa976ab88d359a045 ./prompt_method_redev.md
|
||||
e328a510addc0a73abeb6aaa02c7dcd7ea4d844d8e0f52723add9dbd4c259d4a ./README.md
|
||||
0f92f4d5d7abd57556690c76940f118fc1b024084a1a319f7d7eb87a0b011c19 ./requirements.txt
|
||||
@@ -0,0 +1,19 @@
|
||||
[Unit]
|
||||
Description=MQTT Pilot Service
|
||||
After=network.target
|
||||
# Ajouter un délai après le démarrage du réseau
|
||||
Wants=network-online.target
|
||||
After=network-online.target
|
||||
[Service]
|
||||
Type=simple
|
||||
User=gilles
|
||||
# Délai avant de démarrer le service
|
||||
ExecStartPre=/bin/sleep 30
|
||||
ExecStart=/home/gilles/pilot/monenv/bin/python3 /home/gilles/pilot/main_prog.py
|
||||
Restart=on-failure
|
||||
RestartSec=30
|
||||
ExecStopPost=/home/gilles/pilot/monenv/bin/python3 /home/gilles/pilot/mqtt_unvai.py
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,234 @@
|
||||
import paho.mqtt.client as mqtt
|
||||
import socket
|
||||
|
||||
hostname = socket.gethostname()
|
||||
|
||||
# Paramètres MQTT
|
||||
mqtt_broker_ip_address = "10.0.0.3"
|
||||
mqtt_port = 1883
|
||||
mqtt_username = ""
|
||||
mqtt_password = ""
|
||||
|
||||
discovery_prefix = "homeassistant"
|
||||
device_name = "yoga14"
|
||||
mac_address = "60:57:18:99:ed:05" # Ajout du mac_address manquant
|
||||
|
||||
device_info = {
|
||||
"identifiers": [device_name],
|
||||
"name": "Yoga 14",
|
||||
"manufacturer": "Lenovo",
|
||||
"model": "laptop",
|
||||
"sw_version": "1.0.0",
|
||||
"suggested_area": "salon",
|
||||
}
|
||||
|
||||
# Configuration des entités
|
||||
shutdown_entity = {
|
||||
"name": f"shutdown_{device_name}",
|
||||
"type": "switch",
|
||||
"unique_id": f"shutdown_{device_name}_{mac_address}",
|
||||
"command_topic": f"pilot/{device_name}/shutdown/set",
|
||||
"state_topic": f"pilot/{device_name}/shutdown/state",
|
||||
"availability_topic": f"pilot/{device_name}/shutdown/available",
|
||||
"device_class": "switch",
|
||||
"payload_on": "ON",
|
||||
"payload_off": "OFF",
|
||||
"payload_available": "online",
|
||||
"payload_not_available": "offline",
|
||||
"icon": "mdi:power",
|
||||
"device": device_info,
|
||||
|
||||
|
||||
}
|
||||
reboot_entity = {
|
||||
"name": f"reboot_{device_name}",
|
||||
"type": "switch",
|
||||
"unique_id": f"reboot_{device_name}_{mac_address}",
|
||||
"command_topic": f"pilot/{device_name}/reboot/set",
|
||||
"state_topic": f"pilot/{device_name}/reboot/state",
|
||||
"availability_topic": f"pilot/{device_name}/reboot/available",
|
||||
"device_class": "switch",
|
||||
"payload_on": "ON",
|
||||
"payload_off": "OFF",
|
||||
"payload_available": "online",
|
||||
"payload_not_available": "offline",
|
||||
"icon": "mdi:restart",
|
||||
"device": device_info,
|
||||
}
|
||||
|
||||
battery_entity = {
|
||||
"name": f"battery_{device_name}",
|
||||
"type": "sensor",
|
||||
"unique_id": f"battery_{device_name}_{mac_address}",
|
||||
"state_topic": f"pilot/{device_name}/battery/state",
|
||||
"unit_of_measurement": "%",
|
||||
"device_class": "battery",
|
||||
"availability_topic": f"pilot/{device_name}/battery/available",
|
||||
"payload_available": "online",
|
||||
"payload_not_available": "offline",
|
||||
"icon": "mdi:battery",
|
||||
"device": device_info,
|
||||
"update_interval": 60
|
||||
|
||||
}
|
||||
|
||||
charging_status_entity = {
|
||||
"name": f"charging_status_{device_name}",
|
||||
"type": "binary_sensor",
|
||||
"unique_id": f"charging_status_{device_name}_{mac_address}",
|
||||
"state_topic": f"pilot/{device_name}/charging_status/state",
|
||||
"availability_topic": f"pilot/{device_name}/charging_status/available",
|
||||
"device_class": "battery_charging",
|
||||
"payload_on": "ON",
|
||||
"payload_off": "OFF",
|
||||
"payload_available": "online",
|
||||
"payload_not_available": "offline",
|
||||
"icon": "mdi:battery-charging",
|
||||
"device": device_info,
|
||||
"update_interval": 5
|
||||
}
|
||||
|
||||
screen_entity = {
|
||||
"name": f"screen_{device_name}",
|
||||
"type": "switch",
|
||||
"unique_id": f"screen_{device_name}_{mac_address}",
|
||||
"command_topic": f"pilot/{device_name}/screen/set",
|
||||
"state_topic": f"pilot/{device_name}/screen/state",
|
||||
"availability_topic": f"pilot/{device_name}/screen/available",
|
||||
"device_class": "switch",
|
||||
"payload_on": "ON",
|
||||
"payload_off": "OFF",
|
||||
"payload_available": "online",
|
||||
"payload_not_available": "offline",
|
||||
"icon": "mdi:monitor",
|
||||
"device": device_info,
|
||||
}
|
||||
|
||||
# Ajout des nouvelles entités pour le CPU et la mémoire
|
||||
cpu_temperature_entity = {
|
||||
"name": f"cpu_temperature_{device_name}",
|
||||
"type": "sensor",
|
||||
"unique_id": f"cpu_temperature_{device_name}_{mac_address}",
|
||||
"state_topic": f"pilot/{device_name}/cpu_temperature/state",
|
||||
"unit_of_measurement": "°C",
|
||||
"device_class": "temperature",
|
||||
"availability_topic": f"pilot/{device_name}/cpu_temperature/available",
|
||||
"payload_available": "online",
|
||||
"payload_not_available": "offline",
|
||||
"icon": "mdi:thermometer",
|
||||
"device": device_info,
|
||||
"update_interval": 20
|
||||
}
|
||||
|
||||
cpu_usage_entity = {
|
||||
"name": f"cpu_usage_{device_name}",
|
||||
"type": "sensor",
|
||||
"unique_id": f"cpu_usage_{device_name}_{mac_address}",
|
||||
"state_topic": f"pilot/{device_name}/cpu_usage/state",
|
||||
"unit_of_measurement": "%",
|
||||
"device_class": "power",
|
||||
"availability_topic": f"pilot/{device_name}/cpu_usage/available",
|
||||
"payload_available": "online",
|
||||
"payload_not_available": "offline",
|
||||
"icon": "mdi:chip",
|
||||
"device": device_info,
|
||||
"update_interval": 10
|
||||
}
|
||||
|
||||
memory_usage_entity = {
|
||||
"name": f"memory_usage_{device_name}",
|
||||
"type": "sensor",
|
||||
"unique_id": f"memory_usage_{device_name}_{mac_address}",
|
||||
"state_topic": f"pilot/{device_name}/memory_usage/state",
|
||||
"unit_of_measurement": "%",
|
||||
# "device_class": "memory",
|
||||
"availability_topic": f"pilot/{device_name}/memory_usage/available",
|
||||
"payload_available": "online",
|
||||
"payload_not_available": "offline",
|
||||
"icon": "mdi:memory",
|
||||
"device": device_info,
|
||||
"update_interval": 10
|
||||
}
|
||||
|
||||
cpu_frequency_entity = {
|
||||
"name": f"cpu_frequency_{device_name}",
|
||||
"type": "sensor",
|
||||
"unique_id": f"cpu_frequency_{device_name}_{mac_address}",
|
||||
"state_topic": f"pilot/{device_name}/cpu_frequency/state",
|
||||
"unit_of_measurement": "GHz",
|
||||
"device_class": "frequency",
|
||||
"availability_topic": f"pilot/{device_name}/cpu_frequency/available",
|
||||
"payload_available": "online",
|
||||
"payload_not_available": "offline",
|
||||
"icon": "mdi:speedometer",
|
||||
"device": device_info,
|
||||
"update_interval": 20
|
||||
}
|
||||
|
||||
ip_address_entity = {
|
||||
"name": f"ip_address_{device_name}",
|
||||
"type": "sensor",
|
||||
"unique_id": f"ip_address_{device_name}_{mac_address}",
|
||||
"state_topic": f"pilot/{device_name}/ip_address/state",
|
||||
"availability_topic": f"pilot/{device_name}/ip_address/available",
|
||||
#"device_class": "connectivity",
|
||||
"payload_available": "online",
|
||||
"payload_not_available": "offline",
|
||||
"icon": "mdi:ip",
|
||||
"device": device_info,
|
||||
"update_interval": 60
|
||||
}
|
||||
|
||||
cpu_frequency_slider_entity = {
|
||||
"name": f"cpu_frequency_slider_{device_name}",
|
||||
"type": "number",
|
||||
"unique_id": f"cpu_frequency_slider_{device_name}_{mac_address}",
|
||||
"command_topic": f"pilot/{device_name}/cpu_frequency_slider/set",
|
||||
"state_topic": f"pilot/{device_name}/cpu_frequency_slider/state",
|
||||
"availability_topic": f"pilot/{device_name}/cpu_frequency_slider/available",
|
||||
"payload_available": "online",
|
||||
"payload_not_available": "offline",
|
||||
"min": 0.5,
|
||||
"max": 3.0,
|
||||
"step": 0.1,
|
||||
"unit_of_measurement": "GHz",
|
||||
"icon": "mdi:speedometer",
|
||||
"device": device_info,
|
||||
}
|
||||
|
||||
def deactivate_entities(client):
|
||||
"""Désactive toutes les entités en les marquant comme 'unavailable'."""
|
||||
entities = [
|
||||
shutdown_entity,
|
||||
reboot_entity,
|
||||
battery_entity,
|
||||
screen_entity,
|
||||
cpu_temperature_entity,
|
||||
cpu_usage_entity,
|
||||
memory_usage_entity,
|
||||
cpu_frequency_entity,
|
||||
charging_status_entity,
|
||||
ip_address_entity,
|
||||
cpu_frequency_slider_entity
|
||||
]
|
||||
|
||||
for entity in entities:
|
||||
client.publish(entity["availability_topic"], entity["payload_not_available"], retain=True)
|
||||
|
||||
client.loop_stop() # Arrête la boucle MQTT proprement pour s'assurer que tous les messages sont publiés
|
||||
print("All entities deactivated.")
|
||||
|
||||
# Fonction appelée lors de la connexion au broker MQTT
|
||||
def on_connect(client, userdata, flags, rc):
|
||||
if rc == 0:
|
||||
print(f"Connected with result code {rc}")
|
||||
deactivate_entities(client)
|
||||
client.disconnect() # Déconnecter après la publication
|
||||
|
||||
# Configuration et démarrage du client MQTT
|
||||
client = mqtt.Client()
|
||||
client.username_pw_set(mqtt_username, mqtt_password)
|
||||
client.on_connect = on_connect
|
||||
client.connect(mqtt_broker_ip_address, mqtt_port, 60)
|
||||
|
||||
client.loop_forever()
|
||||
@@ -0,0 +1,244 @@
|
||||
# Prompt Codex — PILOT v2 (Rust) : refonte + déploiement Linux/Windows
|
||||
|
||||
## 0) Rôle et livrables attendus
|
||||
|
||||
Tu es **Codex**, agent de refonte et d’industrialisation. Tu dois :
|
||||
|
||||
1. **Analyser** le projet existant (PILOT v1) et produire un état des lieux.
|
||||
2. Concevoir puis générer **PILOT v2** en **Rust**, avec **deux builds** (Linux et Windows) partageant un **contrat MQTT commun**, mais pouvant utiliser des implémentations différentes.
|
||||
3. Produire une **documentation complète** : plan, tests, TODO, changelog, notice déploiement et notice d’utilisation.
|
||||
4. Mettre en place un **processus de backup** de la v1 avant modification.
|
||||
|
||||
**Sorties obligatoires (fichiers)**
|
||||
|
||||
- `docs/analyse_v1.md`
|
||||
- `docs/architecture_v2.md`
|
||||
- `docs/planning_v2.md`
|
||||
- `docs/todo_v2.md`
|
||||
- `CHANGELOG.md`
|
||||
- `docs/deploiement.md`
|
||||
- `docs/utilisation.md`
|
||||
- `config/config.example.yaml`
|
||||
- `README.md` (mis à jour)
|
||||
- (code) `pilot-v2/` avec structure Rust + workspace si nécessaire
|
||||
|
||||
---
|
||||
|
||||
## 1) Objectifs fonctionnels de l’application
|
||||
|
||||
L’application “pilot” expose via **MQTT** (Home Assistant autodiscovery) :
|
||||
|
||||
- **Télémetrie PC** : CPU usage, température, fréquence (lecture), RAM, IP, batterie/charge, GPU (si dispo).
|
||||
- **États système** : `power_state` : `on|off|sleep|hibernate|locked|unknown` (au minimum on/sleep/off à implémenter).
|
||||
- **Commandes** : shutdown, reboot, sleep (veille) ; écran on/off.
|
||||
- **Home Assistant** : publication discovery + availability + states.
|
||||
- **Robustesse** : reconnexion MQTT, LWT, cadence de publish, cache des valeurs.
|
||||
|
||||
### Contraintes et décisions
|
||||
|
||||
- **Config** : YAML (obligatoire).
|
||||
- **Sécurité avancée (signature/HMAC)** : **plus tard** → à mettre dans TODO.
|
||||
- **Écran** : proposer **2 technos** sélectionnables par paramètre (par OS).
|
||||
- **Deux versions** : Linux et Windows (pas forcément mêmes technos internes), mais **même schéma MQTT**.
|
||||
- **Technologie choisie** : **Rust** (déploiement binaire, robustesse).
|
||||
- **Bonnes pratiques Rust** : modules clairs, `clippy`, `fmt`, logs `tracing`, erreurs `thiserror/anyhow`, configuration `serde_yaml`, code async si MQTT async.
|
||||
|
||||
---
|
||||
|
||||
## 2) Étape 1 — Backup obligatoire de la v1
|
||||
|
||||
Avant toute modification :
|
||||
|
||||
- Créer un dossier `backup_v1/<YYYYMMDD_HHMM>/` à la racine.
|
||||
- Copier **l’intégralité** du projet v1 dans ce dossier (sans exclure).
|
||||
- Générer `backup_v1/<...>/manifest.txt` :
|
||||
- hash (sha256) des fichiers
|
||||
- liste arborescence
|
||||
- note de version (si trouvée)
|
||||
|
||||
Aucun fichier v1 ne doit être détruit.
|
||||
|
||||
---
|
||||
|
||||
## 3) Étape 2 — Analyse v1 (à produire dans docs/analyse\_v1.md)
|
||||
|
||||
- Décrire : objectifs, techno, scripts, modules, service systemd, dépendances.
|
||||
- Lister topics MQTT existants, discovery, states, commands.
|
||||
- Recenser commandes système utilisées (shutdown/reboot/screen/cpu freq).
|
||||
- Identifier points faibles (sécurité, duplication, config hardcodée, absence LWT).
|
||||
- Proposer une synthèse “ce que v2 doit corriger”.
|
||||
|
||||
---
|
||||
|
||||
## 4) Spécification v2 (contrat MQTT + config YAML)
|
||||
|
||||
### 4.1 Schéma MQTT (stable)
|
||||
|
||||
Définir un schéma **unique** (à documenter dans `docs/architecture_v2.md`) :
|
||||
|
||||
- Base topic : `pilot/<device>/...`
|
||||
- `availability` : online/offline (LWT recommandé)
|
||||
- `status` : JSON (version, os, uptime, last\_error, backends)
|
||||
- `capabilities` : JSON (features réellement actives)
|
||||
- `state/<name>` : états capteurs/états système
|
||||
- `cmd/<action>/set` : commandes
|
||||
|
||||
Décider QoS / retain :
|
||||
|
||||
- discovery: retain=true, qos=1
|
||||
- states: retain configurable (défaut true)
|
||||
- commands: retain=false
|
||||
|
||||
### 4.2 Configuration YAML (obligatoire)
|
||||
|
||||
Créer `config/config.example.yaml` et implémenter lecture depuis :
|
||||
|
||||
- Linux : `/etc/pilot/config.yaml` puis fallback `./config.yaml`
|
||||
- Windows : `C:\ProgramData\Pilot\config.yaml` puis fallback `./config.yaml`
|
||||
|
||||
Le YAML doit inclure :
|
||||
|
||||
- device (name, identifiers)
|
||||
- mqtt (host, port, creds, base\_topic, discovery\_prefix, client\_id)
|
||||
- features (telemetry, commands)
|
||||
- power backend (linux\_logind\_polkit | linux\_sudoers | windows\_service)
|
||||
- screen backend (Linux: gnome\_busctl | x11\_xset ; Windows: winapi\_session | external\_tool)
|
||||
- publish cadence (heartbeat, intervals)
|
||||
- cooldown commandes
|
||||
|
||||
---
|
||||
|
||||
## 5) Architecture code Rust (séparation Linux/Windows)
|
||||
|
||||
Créer un dossier `pilot-v2/` (Rust) avec cette structure minimale :
|
||||
|
||||
```
|
||||
pilot-v2/
|
||||
Cargo.toml
|
||||
src/
|
||||
main.rs
|
||||
lib.rs
|
||||
config/
|
||||
mqtt/
|
||||
ha/
|
||||
telemetry/
|
||||
commands/
|
||||
platform/
|
||||
linux/
|
||||
windows/
|
||||
runtime/
|
||||
security/
|
||||
docs/ (optionnel si tu préfères à la racine)
|
||||
```
|
||||
|
||||
### Principes d’implémentation
|
||||
|
||||
- Définir des `trait` internes (PowerControl, ScreenControl, TelemetryProvider).
|
||||
- `platform/linux/*` implémente Linux.
|
||||
- `platform/windows/*` implémente Windows.
|
||||
- Factory sélectionne backends selon YAML et publie `capabilities`.
|
||||
- Gestion erreurs : `thiserror` + `anyhow` (ou uniquement `anyhow`).
|
||||
- Logs : `tracing` (niveau configurable).
|
||||
- Format : `cargo fmt`, qualité : `cargo clippy` sans warnings.
|
||||
|
||||
### Dépendances Rust recommandées (à valider par Codex)
|
||||
|
||||
- MQTT : `rumqttc`
|
||||
- YAML : `serde`, `serde_yaml`
|
||||
- CLI : `clap` (optionnel mais recommandé)
|
||||
- Logs : `tracing`, `tracing-subscriber`
|
||||
- Erreurs : `anyhow`, `thiserror`
|
||||
- Temps/scheduling : `tokio` (si async), sinon scheduler maison
|
||||
|
||||
---
|
||||
|
||||
## 6) Déploiement (Linux + Windows)
|
||||
|
||||
### Linux
|
||||
|
||||
- Produire un binaire `pilot`.
|
||||
- Fournir un service systemd `pilot.service` (user dédié `pilot` recommandé).
|
||||
- Support backend power via logind/polkit (recommandé) OU sudoers (fallback).
|
||||
- Documenter installation, configuration, démarrage, logs (`journalctl -u pilot`).
|
||||
|
||||
### Windows
|
||||
|
||||
- Produire un binaire `pilot.exe`.
|
||||
- Déploiement minimal : exécutable + config + tâche planifiée OU service Windows.
|
||||
- Pour écran : prévoir limitation “service vs session”, et proposer backend `winapi_session` ou `external_tool`.
|
||||
|
||||
---
|
||||
|
||||
## 7) Planning + Tests
|
||||
|
||||
### 7.1 Planning (docs/planning\_v2.md)
|
||||
|
||||
Inclure un plan par phases :
|
||||
|
||||
1. Backup + analyse v1
|
||||
2. Spéc contract MQTT + YAML
|
||||
3. Squelette Rust + MQTT connect + LWT + status/capabilities
|
||||
4. Telemetry de base (cpu/mem/net)
|
||||
5. Commandes power (shutdown/reboot/sleep)
|
||||
6. Écran (2 backends par OS)
|
||||
7. Home Assistant discovery
|
||||
8. Packaging + services (systemd / windows)
|
||||
9. Tests + release
|
||||
|
||||
### 7.2 Tests (à implémenter au minimum)
|
||||
|
||||
- Unit tests : parsing YAML + validation
|
||||
- Unit tests : parsing commandes MQTT payload + cooldown
|
||||
- “Dry-run mode” : exécute sans faire shutdown/reboot, mais log l’action
|
||||
- Test manuel documenté : checklist HA (entités visibles, commandes ok)
|
||||
|
||||
---
|
||||
|
||||
## 8) TODO + CHANGELOG
|
||||
|
||||
- `docs/todo_v2.md` : liste items P0/P1/P2
|
||||
- inclure : sécurité avancée (HMAC), TLS, ACL, signature
|
||||
- inclure : amélioration power\_state (hibernate/locked), GPU multi-vendors
|
||||
- `CHANGELOG.md` : format Keep a Changelog, version 2.0.0 initiale
|
||||
|
||||
---
|
||||
|
||||
## 9) Notices à produire
|
||||
|
||||
### docs/deploiement.md
|
||||
|
||||
- Pré-requis
|
||||
- Installation Linux (binaire, config, service)
|
||||
- Installation Windows (binaire, config, tâche/service)
|
||||
- Upgrade / rollback (revenir v1 via dossier backup)
|
||||
- Debug (logs, topics MQTT, health)
|
||||
|
||||
### docs/utilisation.md
|
||||
|
||||
- Entités HA attendues
|
||||
- Commandes (shutdown/reboot/sleep/screen)
|
||||
- Power state (interprétation)
|
||||
- Paramètres YAML courants
|
||||
- Dépannage rapide
|
||||
|
||||
---
|
||||
|
||||
## 10) Exigences de qualité
|
||||
|
||||
- Ne pas casser v1 : tout nouveau code dans `pilot-v2/`.
|
||||
- Le contrat MQTT doit être **documenté** et stable.
|
||||
- L’agent doit publier `status` + `capabilities`.
|
||||
- L’agent doit fonctionner même si certaines features sont indisponibles (désactiver et expliquer via logs/status).
|
||||
- Pas de promesses “Windows complet” si non implémenté : publier capabilities réelles.
|
||||
|
||||
---
|
||||
|
||||
## 11) Première action à exécuter maintenant
|
||||
|
||||
1. Générer le backup v1
|
||||
2. Générer `docs/analyse_v1.md`
|
||||
3. Proposer l’arborescence Rust + `config/config.example.yaml`
|
||||
4. Proposer le contrat MQTT final (dans `docs/architecture_v2.md`) Ensuite seulement : commencer l’implémentation.
|
||||
|
||||
Fin.
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
# Methode de re-developpement / re-deploiement (V2)
|
||||
## 1) Pre-requis
|
||||
- Python 3.10+ (ou version a valider), paho-mqtt, psutil, optionnel pynvml.
|
||||
- Repo structuree : `src/`, `configs/`, `docs/`, `scripts/`, `tests/`.
|
||||
- Conventions : configuration centralisee (env/yaml), logs JSON, topics versionnes.
|
||||
|
||||
## 2) Liste des informations que l’utilisateur doit completer
|
||||
Section : A completer par l’utilisateur
|
||||
- OS cibles : [ ]
|
||||
- Broker MQTT (host:port) : [ ]
|
||||
- Auth/TLS (user/pass, CA, client cert) : [ ]
|
||||
- Client_id et keepalive : [ ]
|
||||
- Prefix topics (ex: pilot/v2) : [ ]
|
||||
- Liste commandes a supporter : [ ]
|
||||
- Politique de securite (allowlist, validation, ACL broker) : [ ]
|
||||
- Packaging desire (exe Windows, systemd Linux, docker) : [ ]
|
||||
- Integration Home Assistant autodiscovery : [oui/non] + details : [ ]
|
||||
- Availability / birth / will : [ ]
|
||||
- Frequence telemetrie (par capteur) : [ ]
|
||||
- Metriques a exposer (CPU, RAM, GPU, IP, batterie, etc.) : [ ]
|
||||
- Politique de logs (format, rotation) : [ ]
|
||||
|
||||
## 3) Plan iteratif (prompts)
|
||||
Prompt 1 :
|
||||
"Genere l’architecture cible + conventions. Cree `docs/architecture.md` et `docs/topics.md`. Liste modules, flux, schemas de payload, et conventions de config."
|
||||
- Fichiers : `docs/architecture.md`, `docs/topics.md`
|
||||
- Checklist : schema topics, flux MQTT, decisions securite
|
||||
- Sortie attendue : docs completes
|
||||
|
||||
Prompt 2 :
|
||||
"Implemente le noyau MQTT + config + logs. Cree `src/app.py`, `src/config.py`, `src/logging.py`. Ajoute `configs/example.env`."
|
||||
- Fichiers : `src/app.py`, `src/config.py`, `src/logging.py`, `configs/example.env`
|
||||
- Checklist : connexion MQTT, LWT, logs JSON, chargement config
|
||||
- Sortie attendue : demarrage local + doc config
|
||||
|
||||
Prompt 3 :
|
||||
"Implemente le moteur de commandes avec allowlist. Cree `src/commands/` et `docs/commands.md`."
|
||||
- Fichiers : `src/commands/__init__.py`, `src/commands/linux.py`, `docs/commands.md`
|
||||
- Checklist : validation payload, allowlist, dry-run option
|
||||
- Sortie attendue : commandes securisees + doc
|
||||
|
||||
Prompt 4 :
|
||||
"Ajoute availability/LWT + heartbeat + etats. Etends `src/app.py` et `docs/topics.md`."
|
||||
- Fichiers : `src/app.py`, `docs/topics.md`
|
||||
- Checklist : LWT, availability topic, cadence heartbeat
|
||||
- Sortie attendue : etats en retained
|
||||
|
||||
Prompt 5 :
|
||||
"Ajoute autodiscovery Home Assistant (si demande). Cree `src/hass.py` et `docs/hass.md`."
|
||||
- Fichiers : `src/hass.py`, `docs/hass.md`
|
||||
- Checklist : discovery payloads, device_info, tests de base
|
||||
- Sortie attendue : entites visibles dans HA
|
||||
|
||||
Prompt 6 :
|
||||
"Packaging Windows (service/tache). Cree `docs/deploy_windows.md` et scripts."
|
||||
- Fichiers : `docs/deploy_windows.md`, `scripts/install_windows.ps1`
|
||||
- Checklist : lancement automatique, permissions, rollback
|
||||
- Sortie attendue : procedure Windows claire
|
||||
|
||||
Prompt 7 :
|
||||
"Packaging Linux (systemd). Cree `docs/deploy_linux.md`, `scripts/install_linux.sh`, `packaging/mqtt_pilot.service`."
|
||||
- Fichiers : `docs/deploy_linux.md`, `scripts/install_linux.sh`, `packaging/mqtt_pilot.service`
|
||||
- Checklist : user/service, restart policy, logs journald
|
||||
- Sortie attendue : service systemd fonctionnel
|
||||
|
||||
Prompt 8 :
|
||||
"Tests + harness MQTT + doc + exemples. Cree `tests/`, `scripts/mqtt_harness.py`, `docs/examples.md`."
|
||||
- Fichiers : `tests/test_topics.py`, `scripts/mqtt_harness.py`, `docs/examples.md`
|
||||
- Checklist : tests unitaires, exemples payload, validation topics
|
||||
- Sortie attendue : test suite + exemples
|
||||
|
||||
## 4) Procedure de re-deploiement
|
||||
- Strategie : nouveau dossier (V2) + migration progressive.
|
||||
- Migration config : convertir valeurs hardcodees vers env/yaml.
|
||||
- Compatibilite topics : versionner `pilot/v2` et garder mapping legacy.
|
||||
- Rollback : conserver ancien service et bascule via systemd.
|
||||
@@ -0,0 +1,3 @@
|
||||
paho-mqtt==1.5.1
|
||||
psutil==5.9.8
|
||||
pynvml==11.5.0
|
||||
Reference in New Issue
Block a user