J’ai commencé à me faire la main sur la création d’un plugin. Je poste ce message dans cette section car je n’ai pas accès au salon de développeurs (et pas encore la prétention de proposer un plugin distribuable). J’ai utilisé la lib Jeedomdaemon, et je suis parvenu à écrire un plugin qui fonctionne avec son démon associé. Je me suis pas mal inspiré de l’exemple Aiodemo du même auteur. C’est très motivant car j’ai pas mal d’idées de plugins
En revanche, je rencontre un problème sur lequel je bute depuis des jours sans comprendre pourquoi. Lorsque je démarre mon démon depuis le panneau du plugin, le premier message qui apparaît dans le log du plugin, c’est
[2025-04-17 12:46:55] ERROR : Attention je pense qu’il y a un soucis avec le démon que j’ai relancé plus de 3 fois consécutivement
Visiblement le core Jeedom ne reçoit pas l’information lui indiquant que le démon est ok. Je ne comprends pas comment fonctionne ce mécanisme. Du coup si je passe le démon en Gestion automatique, le core tente continuellement de le redémarrer.
J’ai certainement oublié ou mal compris quelque chose, auriez-vous une piste ?
Donc si j’ai bien suivi, ça doit se jouer entre les méthodes suivantes de ma classe ecodevicert.class.php:
public static function deamon_info() {
$return = array();
$return['log'] = __CLASS__;
$return['launchable'] = 'ok';
$return['state'] = 'nok';
$pid_file = jeedom::getTmpFolder(__CLASS__) . '/daemon.pid';
if (file_exists($pid_file)) {
if (@posix_getsid(trim(file_get_contents($pid_file)))) {
$return['state'] = 'ok';
log::add(__CLASS__, 'debug', ">>>> deamon_info() : Le démon est OK");
} else {
log::add(__CLASS__, 'debug', ">>>> deamon_info() : Le PID est $pid_file");
shell_exec(system::getCmdSudo() . 'rm -rf ' . $pid_file . ' 2>&1 > /dev/null');
}
}
return $return;
}
public static function deamon_start() {
self::deamon_stop();
$deamon_info = self::deamon_info();
if ($deamon_info['launchable'] != 'ok') {
throw new Exception(__('Veuillez vérifier la configuration', __FILE__));
}
config::remove('discovered_data_topics', __CLASS__);
// Récupération de la liste des équipements
$equipements = [];
foreach (eqLogic::byType(__CLASS__) as $eqLogic) {
if ($eqLogic->getIsEnable()) {
$equipements[] = [
'id' => $eqLogic->getId(),
'name' => $eqLogic->getName(),
'ip' => $eqLogic->getConfiguration('ip-address'),
'port' => $eqLogic->getConfiguration('port'),
'compte' => $eqLogic->getConfiguration('compte'),
'password' => $eqLogic->getConfiguration('password')
];
log::add(__CLASS__, 'debug', "Récupération de l'équipement : " . $eqLogic->getName());
}
}
log::add(__CLASS__, 'debug', "Liste complète des équipements : " . json_encode($equipements));
$path = realpath(dirname(__FILE__) . '/../../resources');
$cmd = self::getPython3() . "{$path}/myDaemon.py";
$cmd .= ' --loglevel ' . log::convertLogLevel(log::getLogLevel(__CLASS__));
$cmd .= ' --socketport ' . config::byKey('socketport', __CLASS__, '55005');
$cmd .= ' --cycle ' . config::byKey('cycle', __CLASS__, 2);
$cmd .= ' --callback ' . network::getNetworkAccess('internal', 'proto:127.0.0.1:port:comp') . '/plugins/ecodevicert/core/php/ecodevicert.inc.php';
$cmd .= ' --apikey ' . jeedom::getApiKey(__CLASS__);
$cmd .= ' --pid ' . jeedom::getTmpFolder(__CLASS__) . '/daemon.pid';
log::add(__CLASS__, 'info', 'Lancement démon:' . self::getPython3() . "{$path}/myDaemon.py");
$result = exec($cmd . ' >> ' . log::getPathToLog(__CLASS__ . '_daemon') . ' 2>&1 &');
$i = 0;
while ($i < 10) {
$deamon_info = self::deamon_info();
if ($deamon_info['state'] == 'ok') {
log::add(__CLASS__, 'debug', ">>>> deamon_start() : Le démon est OK -- itération $i");
break;
}
sleep(1);
$i++;
}
if ($i >= 10) {
log::add(__CLASS__, 'error', __('Impossible de lancer le démon', __FILE__), 'unableStartDeamon');
return false;
}
message::removeAll(__CLASS__, 'unableStartDeamon');
// Envoyer la liste des équipements au démon via `sendToDaemon`
self::sendToDaemon(['action' => 'update_equipements', 'equipements' => $equipements]);
return true;
}
Et mon démon :
class MyDaemon(BaseDaemon):
def __init__(self) -> None:
# Standard initialisation
# super().__init__(on_start_cb=self.on_start, on_message_cb=self.on_message, on_stop_cb=self.on_stop)
super().__init__(config=MyDaemonConfig(), on_start_cb=self.on_start, on_message_cb=self.on_message, on_stop_cb=self.on_stop)
self.equipements = [] # Liste des équipements stockés
self.ecodevice_list = {} # Liste des objets Ecodevice
self.running = True
# Add here any initialisation your daemon would need
async def on_start(self):
"""
This method will be called when your daemon starts.
This is the place where you should create your tasks, login to remote system, etc
"""
# await self.send_to_jeedom({'action':f"Start"})
# if you don't have specific action to do on start, do not create this method
query_frequency=self._config.query_frequency
self._logger.debug("C'est cool, je viens de démarrer le démon !!!")
self._logger.info("***** Plugin ecodevicert démarré *****")
self._logger.debug("Fréquence des queries %i", query_frequency)
timeout = 30 # Temps max d'attente en secondes
start_time = time.time()
while not self.equipements:
if time.time() - start_time > timeout:
self._logger.error("Timeout atteint : aucune liste d'équipements reçue. Arrêt du démon.")
await self.on_stop() # Exécuter les actions de nettoyage
self.stop() # Arrêter proprement le démon
return
self._logger.debug("En attente de la réception des équipements...")
await asyncio.sleep(1)
self._logger.debug(f"Liste des équipements disponibles au démarrage du démon : {self.equipements}")
# Initialisation du dictionnaire objets Ecodevice
self.ecodevice_list = {}
for eq in self.equipements:
self._logger.debug(f"Ajout de l'équipement {eq.get('name')} avec ID {eq.get('id')} au traitement.")
await asyncio.sleep(5)
# await self._get_ecodevices_data()
asyncio.create_task(self._run_periodic_task()) # Démarrage de la boucle en tâche de fond
async def _run_periodic_task(self):
while self.running:
await self._get_ecodevices_data()
await asyncio.sleep(self._config.query_frequency) # Attente entre chaque exécution
async def on_message(self, message: dict):
"""
This function will be called once a message is received from Jeedom; check on api key is done already, just care about your logic
You must implement the different actions that your daemon can handle.
"""
# Ici, on réceptionne la liste des équipements ennvoyée par la classe php
if message.get('action') == 'update_equipements':
self.equipements = message.get('equipements', [])
self._logger.debug(f"Update de la liste des équipements: {self.equipements}")
# await self._get_ecodevices_data(self.equipements)
# L'appel à la fonction ci-dessus ne devrait pas être faite dans message, mais gérée par la boucle du on_start. on-message ne devrait que créer l'objet Ecodevice
# A valider, mais l'ID equipment pourrait être intégré à l'objet Ecodevice ?
# time.sleep(3)
# await self._think("This is my answer to Jeedom")
async def _return_device_values(self, message):
# this is a demo implementation of a single function, this function will be invoked once the corresponding call is received from Jeedom
await self.send_to_jeedom({'data':f"I think '{message}' was an interesting information"})
async def _get_ecodevices_data(self):
# On interroge l'Ecodevice à intervalle de temps régulier
for eq in self.equipements:
id = eq.get('id')
compteur = eq.get('name')
ip = eq.get('ip')
self._logger.debug(f"ID en cours de traitement : {id} pour le compteur '{compteur}'")
device = Ecodevice(ip, id)
status = await self._query_device(device)
self._logger.debug(f"IP address {device.address} power: {device.apparent_power()}")
if status:
self._logger.debug("Envoi des données à Jeedom...")
data_to_send = self._to_dictionary(device)
await self.send_to_jeedom({'data': f'{data_to_send}'})
def _to_dictionary(self, device):
data_to_send = {
"id": device.get_id(),
"a_power": device.apparent_power(),
"intensity_p1": device.get_intensity(Phase.ONE),
"intensity_p2": device.get_intensity(Phase.TWO),
"intensity_p3": device.get_intensity(Phase.THREE),
"consumption_day": device.get_consumption(Period.DAY),
"consumption_night": device.get_consumption(Period.NIGHT),
"consumption_total": device.get_consumption(Period.TOTAL),
"rate": device.rate()
}
return data_to_send
async def _query_device(self, device):
try:
device.fetch_teleinfo_data()
# self._logger.debug(f"IP address {device.address} power: {device.apparent_power()}")
return True
except ConnectionException as e:
self._logger.error(f"Connection failed for device at {device.address}: {e}")
except ParseException as e:
self._logger.error(f"Failed to parse XML data from device at {device.address}: {e}")
self._logger.error(f"Ecodevice may be in a wrong state, restarting...")
device.restart()
except Exception as e:
self._logger.error(f"Unexpected error for device at {device.address}: {e}")
return False
async def on_stop(self):
"""
This callback will be called when the daemon needs to stop`
You need to close your remote connexions and cancel background tasks if any here.
"""
# if you don't have specific action to do on stop, do not create this method
# self._search_task.cancel()
self.running = False # Arrêter la boucle
self._logger.debug("Arrêt du démon en cours...")
MyDaemon().run()
Dans mon log, le premier message qui apparaît est celui du core qui tente de redémarrer le démon :
[2025-04-17 14:24:13] DEBUG : Récupération de l'équipement : Autre compteur
[2025-04-17 14:24:13] DEBUG : Récupération de l'équipement : Linky
[2025-04-17 14:24:13] DEBUG : Liste complète des équipements : [{"id":"386","name":"Autre compteur","ip":"172.34.2.12","port":"","compte":"","password":""},{"id":"390","name":"Linky","ip":"192.168.10.31","port":"80","compte":"admin","password":"****"}]
[2025-04-17 14:24:13] INFO : Lancement démon:python3 /var/www/html/plugins/ecodevicert/resources/myDaemon.py
[2025-04-17 14:24:13] DEBUG : test from daemon
[2025-04-17 14:24:14] DEBUG : >>>> deamon_info() : Le démon est OK
[2025-04-17 14:24:14] DEBUG : >>>> deamon_start() : Le démon est OK -- itération 1
[2025-04-17 14:24:14] DEBUG : >>>> deamon_info() : Le démon est OK
[2025-04-17 14:24:19] DEBUG : >>>> deamon_info() : Le démon est OK
[2025-04-17 14:24:24] DEBUG : >>>> deamon_info() : Le démon est OK
[2025-04-17 14:24:29] DEBUG : >>>> deamon_info() : Le démon est OK
[2025-04-17 14:24:34] DEBUG : >>>> deamon_info() : Le démon est OK
[2025-04-17 14:24:39] DEBUG : >>>> deamon_info() : Le démon est OK
[2025-04-17 14:24:44] DEBUG : >>>> deamon_info() : Le démon est OK
[2025-04-17 14:24:46] DEBUG : >>>> deamon_info() : Le démon est OK
J’ai l’impression d’avoir loupé une étape dans la transmission de l’état du démon au core
Vous avez fait une classe spécifique pour votre démon, mais la liaison doit poser souci
Il y a deja tout de quasi intégrer via le plugin template, notamment jeedom_socket() pour communiquer de votre base php au python
Merci pour vos indications ! En fait, j’ai pris exemple sur le plugin aiodemode Mips, qui utilise la lib jeedomdaemon. Je souhaitais m’inspirer d’un exemple concret et simple, mais ce n’étais peut-être pas la bonne approche, j’en sais rien… C’est compliqué de raccrocher tous les wagons, mais bon, je me dis qu’une fois que j’aurais compris, c’est la porte ouverte à pleins d’idées
Je vais remettre l’intégralité du code de mon démon, mais comme dit, c’est une copie de l’exemple d’ aiodemo. Du coup là, je suis totalement perdu car je ne sais pas ce qu’il apporte de plus Peut-être que je l’utilise mal. J’arrive bien à avoir des échanges entre le démon et ma classe php, mais il manque l’événement qui indique au core que mon démon est ok, et ça, je ne comprends pas comment c’est censé fonctionner… Ceci dit, côté interface du plugin, il apparaît bien en statut Ok (vert).
Code complet du démon :
import random
import asyncio
import time
import requests
import xml.etree.ElementTree as ET
from jeedomdaemon.base_daemon import BaseDaemon
from jeedomdaemon.base_config import BaseConfig
from enum import Enum
class Period(str, Enum):
DAY = 'day'
NIGHT = 'night'
TOTAL = 'total'
class Phase(int, Enum):
ONE = 1
TWO = 2
THREE = 3
class EcodeviceException(Exception):
pass
class ConnectionException(EcodeviceException):
pass
class ParseException(EcodeviceException):
pass
class Ecodevice:
def __init__(self, address: str, id: int = 0, protocol: str = "http", port: int = 80, compte: str = None, password: str = None):
self.address = address.rstrip('/')
self.port = port
self.protocol = protocol
self.compte = compte
self.password = password
self.corrupted = False
self.data = {}
self.id = id
def set_id(self, id: int):
self.id = id
def get_id(self):
return self.id
def get_intensity(self, phase: Phase):
return self.data.get(f'T1_IINST{phase.value}', 'Unknown')
def data_corrupted(self):
return self.corrupted
def apparent_power(self):
return self.data.get('T1_PAPP', 'Unknown')
def get_consumption(self, period: Period):
if period == Period.TOTAL:
return int(self.data.get('T1_HCHP', 0)) + int(self.data.get('T1_HCHC', 0))
elif period == Period.DAY:
return int(self.data.get('T1_HCHP', 0))
elif period == Period.NIGHT:
return int(self.data.get('T1_HCHC', 0))
else:
return 'Unknown'
def rate(self):
return self.data.get('T1_PTEC', 'Unknown')
def fetch_teleinfo_data(self):
try:
url = f"{self.protocol}://{self.address}/protect/settings/teleinfo1.xml"
response = requests.get(url, timeout=10)
response.raise_for_status()
xml_content = response.text
if any(c in xml_content for c in '\x00\x08\x0B\x0C\x0E\x1F\x7F'):
self.corrupted = True
xml_content = ''.join(filter(lambda x: x not in '\x00\x08\x0B\x0C\x0E\x1F\x7F', xml_content))
root = ET.fromstring(xml_content)
self.data = {child.tag: child.text for child in root}
except requests.RequestException:
raise ConnectionException(f"Failed to fetch XML data from: {url}")
except ET.ParseError:
raise ParseException("Failed to parse XML data.")
except Exception as e:
print(f"General Error: {e}")
def restart(self):
url = f"{self.protocol}://{self.address}/protect/settings/reboot.htm"
try:
response = requests.get(url, timeout=10)
response.raise_for_status()
except requests.RequestException as e:
raise Exception(f"Failed to restart the device: {e}")
class MyDaemonConfig(BaseConfig):
"""This is where you declare your custom argument/configuration
Remember that all usual arguments are managed by the BaseConfig class already so you only have to take care of yours; e.g. user & password in this case
"""
def __init__(self):
# Standard initialisation
# super().__init__(config=MyDaemonConfig(), on_start_cb=self.on_start, on_message_cb=self.on_message, on_stop_cb=self.on_stop)
super().__init__()
self.add_argument("--query_frequency", type=int, default=45)
# Below you can init your own variables if needed
# self._search_task = None
class MyDaemon(BaseDaemon):
def __init__(self) -> None:
# Standard initialisation
# super().__init__(on_start_cb=self.on_start, on_message_cb=self.on_message, on_stop_cb=self.on_stop)
super().__init__(config=MyDaemonConfig(), on_start_cb=self.on_start, on_message_cb=self.on_message, on_stop_cb=self.on_stop)
self.equipements = [] # Liste des équipements stockés
self.ecodevice_list = {} # Liste des objets Ecodevice
self.running = True
# Add here any initialisation your daemon would need
async def on_start(self):
"""
This method will be called when your daemon starts.
This is the place where you should create your tasks, login to remote system, etc
"""
# await self.send_to_jeedom({'action':f"Start"})
# if you don't have specific action to do on start, do not create this method
query_frequency=self._config.query_frequency
self._logger.debug("C'est cool, je viens de démarrer le démon !!!")
self._logger.info("***** Plugin ecodevicert démarré *****")
self._logger.debug("Fréquence des queries %i", query_frequency)
timeout = 30 # Temps max d'attente en secondes
start_time = time.time()
while not self.equipements:
if time.time() - start_time > timeout:
self._logger.error("Timeout atteint : aucune liste d'équipements reçue. Arrêt du démon.")
await self.on_stop() # Exécuter les actions de nettoyage
self.stop() # Arrêter proprement le démon
return
self._logger.debug("En attente de la réception des équipements...")
await asyncio.sleep(1)
self._logger.debug(f"Liste des équipements disponibles au démarrage du démon : {self.equipements}")
# Initialisation du dictionnaire objets Ecodevice
self.ecodevice_list = {}
for eq in self.equipements:
self._logger.debug(f"Ajout de l'équipement {eq.get('name')} avec ID {eq.get('id')} au traitement.")
await asyncio.sleep(5)
# await self._get_ecodevices_data()
asyncio.create_task(self._run_periodic_task()) # Démarrage de la boucle en tâche de fond
async def _run_periodic_task(self):
while self.running:
await self._get_ecodevices_data()
await asyncio.sleep(self._config.query_frequency) # Attente entre chaque exécution
async def on_message(self, message: dict):
"""
This function will be called once a message is received from Jeedom; check on api key is done already, just care about your logic
You must implement the different actions that your daemon can handle.
"""
# Ici, on réceptionne la liste des équipements ennvoyée par la classe php
if message.get('action') == 'update_equipements':
self.equipements = message.get('equipements', [])
self._logger.debug(f"Update de la liste des équipements: {self.equipements}")
# await self._get_ecodevices_data(self.equipements)
# L'appel à la fonction ci-dessus ne devrait pas être faite dans message, mais gérée par la boucle du on_start. on-message ne devrait que créer l'objet Ecodevice
# A valider, mais l'ID equipment pourrait être intégré à l'objet Ecodevice ?
# time.sleep(3)
# await self._think("This is my answer to Jeedom")
async def _return_device_values(self, message):
# this is a demo implementation of a single function, this function will be invoked once the corresponding call is received from Jeedom
await self.send_to_jeedom({'data':f"I think '{message}' was an interesting information"})
async def _get_ecodevices_data(self):
# On interroge l'Ecodevice à intervalle de temps régulier
for eq in self.equipements:
id = eq.get('id')
compteur = eq.get('name')
ip = eq.get('ip')
self._logger.debug(f"ID en cours de traitement : {id} pour le compteur '{compteur}'")
device = Ecodevice(ip, id)
status = await self._query_device(device)
self._logger.debug(f"IP address {device.address} power: {device.apparent_power()}")
if status:
self._logger.debug("Envoi des données à Jeedom...")
data_to_send = self._to_dictionary(device)
await self.send_to_jeedom({'data': f'{data_to_send}'})
def _to_dictionary(self, device):
data_to_send = {
"id": device.get_id(),
"a_power": device.apparent_power(),
"intensity_p1": device.get_intensity(Phase.ONE),
"intensity_p2": device.get_intensity(Phase.TWO),
"intensity_p3": device.get_intensity(Phase.THREE),
"consumption_day": device.get_consumption(Period.DAY),
"consumption_night": device.get_consumption(Period.NIGHT),
"consumption_total": device.get_consumption(Period.TOTAL),
"rate": device.rate()
}
return data_to_send
async def _query_device(self, device):
try:
device.fetch_teleinfo_data()
# self._logger.debug(f"IP address {device.address} power: {device.apparent_power()}")
return True
except ConnectionException as e:
self._logger.error(f"Connection failed for device at {device.address}: {e}")
except ParseException as e:
self._logger.error(f"Failed to parse XML data from device at {device.address}: {e}")
self._logger.error(f"Ecodevice may be in a wrong state, restarting...")
device.restart()
except Exception as e:
self._logger.error(f"Unexpected error for device at {device.address}: {e}")
return False
async def on_stop(self):
"""
This callback will be called when the daemon needs to stop`
You need to close your remote connexions and cancel background tasks if any here.
"""
# if you don't have specific action to do on stop, do not create this method
# self._search_task.cancel()
self.running = False # Arrêter la boucle
self._logger.debug("Arrêt du démon en cours...")
MyDaemon().run()
C’est dans votre classe php la méthode daemon_info qui détermine si votre démon tourne.
La je vois qu’elle va chercher le pid du démon, j’ai pas lu tout le code du démon mais l’écrit il bien ? Et au bon endroit ? Quasi certain que Mips l’avait inclus dans le code d’origine
Ce message de log n’est pas visible dans votre extrait de log (message #3).
Dans le message #3, la suite de log semble logique. Et vous affichez la page de configuration du plugin, c’est pourquoi la fonction deamon_info() est lancée toutes les 5 secondes, c’est pour rafraichir la page.
page santé jeedom (histoire qu’on sache sur quoi vous développez)
page config plugin pour savoir: est-ce que le démon est OK ou KO?
car ce message peut apparaitre meme si le démon est OK lorsqu’on relance plusieurs fois de suite le démon lors de nos tests/devs même s’il est OK
je l’ai régulièrement.
La classe deamon_info() retourne bien un salut ok par l’intermédiaire de la variable $return, positionnée à ok. On voit d’ailleurs dans les logs ci-dessous, qu’elle passe dans le test if.
Entre temps j’ai activé la gestion automatique du démon, et on voit également que ce dernier est relancé toutes les 5 mins. Mais je crois savoir que le test est basé sur la communication avec le démon dans les X minutes configurées dans l’interface.
Je mets également une copie des pages santé et config comme demandé.
À ce moment, je ne sais vraiment plus quoi faire de plus. Peut-être essayer de refaire le plugin sans la classe jeedomdaemon() Pourtant j’ai l’impression que ça ne tient à pas grand chose.
0407|[2025-04-22 13:00:07] ERROR : Attention je pense qu'il y a un soucis avec le démon que j'ai relancé plus de 3 fois consécutivement
0408|[2025-04-22 13:00:09] DEBUG : Récupération de l'équipement : Autre compteur
0409|[2025-04-22 13:00:09] DEBUG : Récupération de l'équipement : Linky
0410|[2025-04-22 13:00:09] DEBUG : Liste complète des équipements : [{"id":"386","name":"Autre compteur","ip":"172.34.2.12","port":"","compte":"","password":""},{"id":"390","name":"Linky","ip":"192.168.10.31","port":"80","compte":"admin","password":"*xxx*"}]
0411|[2025-04-22 13:00:09] INFO : Lancement démon:python3 /var/www/html/plugins/ecodevicert/resources/myDaemon.py
0412|[2025-04-22 13:00:09] DEBUG : test from daemon
0413|[2025-04-22 13:00:10] DEBUG : >>>> deamon_info() : Le démon est OK
0414|[2025-04-22 13:00:10] DEBUG : >>>> deamon_start() : Le démon est OK -- itération 1
0415|[2025-04-22 13:00:10] DEBUG : params to send to daemon:{"action":"update_equipements","equipements":[{"id":"386","name":"Autre compteur","ip":"172.34.2.12","port":"","compte":"","password":""},{"id":"390","name":"Linky","ip":"192.168.10.31","port":"80","compte":"admin","password":"*x*"}]}
0416|[2025-04-22 13:00:25] DEBUG : new feedback:{"data":"{'id': '390', 'a_power': '710', 'intensity_p1': '2', 'intensity_p2': '1', 'intensity_p3': '0', 'consumption_day': 2649666, 'consumption_night': 2428635, 'consumption_total': 5078301, 'rate': 'HP'}"}
0417|[2025-04-22 13:00:25] DEBUG : Decoded message: Array ( [data] => {'id': '390', 'a_power': '710', 'intensity_p1': '2', 'intensity_p2': '1', 'intensity_p3': '0', 'consumption_day': 2649666, 'consumption_night': 2428635, 'consumption_total': 5078301, 'rate': 'HP'} )
0418|[2025-04-22 13:00:25] DEBUG : Message de type DATA
0419|[2025-04-22 13:00:25] DEBUG : ID de l'équipement: 390
0420|[2025-04-22 13:00:25] DEBUG : Valeur de a_power : 710
0421|[2025-04-22 13:00:25] DEBUG : Valeur de intensity_p1 : 2
0422|[2025-04-22 13:00:25] DEBUG : Valeur de intensity_p2 : 1
0423|[2025-04-22 13:00:25] DEBUG : Valeur de intensity_p3 : 0
0424|[2025-04-22 13:00:25] DEBUG : Valeur de consumption_day : 2649666
0425|[2025-04-22 13:00:25] DEBUG : Valeur de la consommation du jour précédent : 2646069
0426|[2025-04-22 13:00:25] DEBUG : Valeur de consumption_night : 2428635
0427|[2025-04-22 13:00:25] DEBUG : Valeur de la consommation nuit précédente : 2417092
0428|[2025-04-22 13:00:25] DEBUG : Valeur actualisée de consumption_total : 5078301
0429|[2025-04-22 13:00:25] DEBUG : Valeur de conso_j : 15140
0430|[2025-04-22 13:00:25] DEBUG : Valeur de rate : HP
0431|[2025-04-22 13:01:20] DEBUG : new feedback:{"data":"{'id': '390', 'a_power': '720', 'intensity_p1': '2', 'intensity_p2': '1', 'intensity_p3': '0', 'consumption_day': 2649674, 'consumption_night': 2428635, 'consumption_total': 5078309, 'rate': 'HP'}"}
0432|[2025-04-22 13:01:20] DEBUG : Decoded message: Array ( [data] => {'id': '390', 'a_power': '720', 'intensity_p1': '2', 'intensity_p2': '1', 'intensity_p3': '0', 'consumption_day': 2649674, 'consumption_night': 2428635, 'consumption_total': 5078309, 'rate': 'HP'} )
0433|[2025-04-22 13:01:20] DEBUG : Message de type DATA
0434|[2025-04-22 13:01:20] DEBUG : ID de l'équipement: 390
0435|[2025-04-22 13:01:20] DEBUG : Valeur de a_power : 720
0436|[2025-04-22 13:01:20] DEBUG : Valeur de intensity_p1 : 2
0437|[2025-04-22 13:01:20] DEBUG : Valeur de intensity_p2 : 1
0438|[2025-04-22 13:01:20] DEBUG : Valeur de intensity_p3 : 0
0439|[2025-04-22 13:01:20] DEBUG : Valeur de consumption_day : 2649674
0440|[2025-04-22 13:01:20] DEBUG : Valeur de la consommation du jour précédent : 2646069
0441|[2025-04-22 13:01:20] DEBUG : Valeur de consumption_night : 2428635
0442|[2025-04-22 13:01:20] DEBUG : Valeur de la consommation nuit précédente : 2417092
0443|[2025-04-22 13:01:20] DEBUG : Valeur actualisée de consumption_total : 5078309
0444|[2025-04-22 13:01:20] DEBUG : Valeur de conso_j : 15148
0445|[2025-04-22 13:01:20] DEBUG : Valeur de rate : HP
0446|[2025-04-22 13:02:16] DEBUG : new feedback:{"data":"{'id': '390', 'a_power': '710', 'intensity_p1': '2', 'intensity_p2': '1', 'intensity_p3': '0', 'consumption_day': 2649681, 'consumption_night': 2428635, 'consumption_total': 5078316, 'rate': 'HP'}"}
0447|[2025-04-22 13:02:16] DEBUG : Decoded message: Array ( [data] => {'id': '390', 'a_power': '710', 'intensity_p1': '2', 'intensity_p2': '1', 'intensity_p3': '0', 'consumption_day': 2649681, 'consumption_night': 2428635, 'consumption_total': 5078316, 'rate': 'HP'} )
0448|[2025-04-22 13:02:16] DEBUG : Message de type DATA
0449|[2025-04-22 13:02:16] DEBUG : ID de l'équipement: 390
0450|[2025-04-22 13:02:16] DEBUG : Valeur de a_power : 710
0451|[2025-04-22 13:02:16] DEBUG : Valeur de intensity_p1 : 2
0452|[2025-04-22 13:02:16] DEBUG : Valeur de intensity_p2 : 1
0453|[2025-04-22 13:02:16] DEBUG : Valeur de intensity_p3 : 0
0454|[2025-04-22 13:02:16] DEBUG : Valeur de consumption_day : 2649681
0455|[2025-04-22 13:02:16] DEBUG : Valeur de la consommation du jour précédent : 2646069
0456|[2025-04-22 13:02:16] DEBUG : Valeur de consumption_night : 2428635
0457|[2025-04-22 13:02:16] DEBUG : Valeur de la consommation nuit précédente : 2417092
0458|[2025-04-22 13:02:16] DEBUG : Valeur actualisée de consumption_total : 5078316
0459|[2025-04-22 13:02:16] DEBUG : Valeur de conso_j : 15155
0460|[2025-04-22 13:02:16] DEBUG : Valeur de rate : HP
0461|[2025-04-22 13:03:11] DEBUG : new feedback:{"data":"{'id': '390', 'a_power': '720', 'intensity_p1': '2', 'intensity_p2': '1', 'intensity_p3': '0', 'consumption_day': 2649688, 'consumption_night': 2428635, 'consumption_total': 5078323, 'rate': 'HP'}"}
0462|[2025-04-22 13:03:11] DEBUG : Decoded message: Array ( [data] => {'id': '390', 'a_power': '720', 'intensity_p1': '2', 'intensity_p2': '1', 'intensity_p3': '0', 'consumption_day': 2649688, 'consumption_night': 2428635, 'consumption_total': 5078323, 'rate': 'HP'} )
0463|[2025-04-22 13:03:11] DEBUG : Message de type DATA
0464|[2025-04-22 13:03:11] DEBUG : ID de l'équipement: 390
0465|[2025-04-22 13:03:11] DEBUG : Valeur de a_power : 720
0466|[2025-04-22 13:03:11] DEBUG : Valeur de intensity_p1 : 2
0467|[2025-04-22 13:03:11] DEBUG : Valeur de intensity_p2 : 1
0468|[2025-04-22 13:03:11] DEBUG : Valeur de intensity_p3 : 0
0469|[2025-04-22 13:03:11] DEBUG : Valeur de consumption_day : 2649688
0470|[2025-04-22 13:03:11] DEBUG : Valeur de la consommation du jour précédent : 2646069
0471|[2025-04-22 13:03:11] DEBUG : Valeur de consumption_night : 2428635
0472|[2025-04-22 13:03:11] DEBUG : Valeur de la consommation nuit précédente : 2417092
0473|[2025-04-22 13:03:11] DEBUG : Valeur actualisée de consumption_total : 5078323
0474|[2025-04-22 13:03:11] DEBUG : Valeur de conso_j : 15162
0475|[2025-04-22 13:03:11] DEBUG : Valeur de rate : HP
0476|[2025-04-22 13:04:06] DEBUG : new feedback:{"data":"{'id': '390', 'a_power': '720', 'intensity_p1': '2', 'intensity_p2': '1', 'intensity_p3': '0', 'consumption_day': 2649695, 'consumption_night': 2428635, 'consumption_total': 5078330, 'rate': 'HP'}"}
0477|[2025-04-22 13:04:06] DEBUG : Decoded message: Array ( [data] => {'id': '390', 'a_power': '720', 'intensity_p1': '2', 'intensity_p2': '1', 'intensity_p3': '0', 'consumption_day': 2649695, 'consumption_night': 2428635, 'consumption_total': 5078330, 'rate': 'HP'} )
0478|[2025-04-22 13:04:06] DEBUG : Message de type DATA
0479|[2025-04-22 13:04:06] DEBUG : ID de l'équipement: 390
0480|[2025-04-22 13:04:06] DEBUG : Valeur de a_power : 720
0481|[2025-04-22 13:04:06] DEBUG : Valeur de intensity_p1 : 2
0482|[2025-04-22 13:04:06] DEBUG : Valeur de intensity_p2 : 1
0483|[2025-04-22 13:04:06] DEBUG : Valeur de intensity_p3 : 0
0484|[2025-04-22 13:04:06] DEBUG : Valeur de consumption_day : 2649695
0485|[2025-04-22 13:04:06] DEBUG : Valeur de la consommation du jour précédent : 2646069
0486|[2025-04-22 13:04:06] DEBUG : Valeur de consumption_night : 2428635
0487|[2025-04-22 13:04:06] DEBUG : Valeur de la consommation nuit précédente : 2417092
0488|[2025-04-22 13:04:06] DEBUG : Valeur actualisée de consumption_total : 5078330
0489|[2025-04-22 13:04:06] DEBUG : Valeur de conso_j : 15169
0490|[2025-04-22 13:04:06] DEBUG : Valeur de rate : HP
0491|[2025-04-22 13:05:01] DEBUG : new feedback:{"data":"{'id': '390', 'a_power': '750', 'intensity_p1': '2', 'intensity_p2': '1', 'intensity_p3': '1', 'consumption_day': 2649703, 'consumption_night': 2428635, 'consumption_total': 5078338, 'rate': 'HP'}"}
0492|[2025-04-22 13:05:01] DEBUG : Decoded message: Array ( [data] => {'id': '390', 'a_power': '750', 'intensity_p1': '2', 'intensity_p2': '1', 'intensity_p3': '1', 'consumption_day': 2649703, 'consumption_night': 2428635, 'consumption_total': 5078338, 'rate': 'HP'} )
0493|[2025-04-22 13:05:01] DEBUG : Message de type DATA
0494|[2025-04-22 13:05:01] DEBUG : ID de l'équipement: 390
0495|[2025-04-22 13:05:01] DEBUG : Valeur de a_power : 750
0496|[2025-04-22 13:05:01] DEBUG : Valeur de intensity_p1 : 2
0497|[2025-04-22 13:05:01] DEBUG : Valeur de intensity_p2 : 1
0498|[2025-04-22 13:05:01] DEBUG : Valeur de intensity_p3 : 1
0499|[2025-04-22 13:05:01] DEBUG : Valeur de consumption_day : 2649703
0500|[2025-04-22 13:05:01] DEBUG : Valeur de la consommation du jour précédent : 2646069
0501|[2025-04-22 13:05:01] DEBUG : Valeur de consumption_night : 2428635
0502|[2025-04-22 13:05:01] DEBUG : Valeur de la consommation nuit précédente : 2417092
0503|[2025-04-22 13:05:01] DEBUG : Valeur actualisée de consumption_total : 5078338
0504|[2025-04-22 13:05:01] DEBUG : Valeur de conso_j : 15177
0505|[2025-04-22 13:05:01] DEBUG : Valeur de rate : HP
0506|[2025-04-22 13:05:02] DEBUG : >>>> deamon_info() : Le démon est OK
0507|[2025-04-22 13:05:04] ERROR : Attention je pense qu'il y a un soucis avec le démon que j'ai relancé plus de 3 fois consécutivement
0508|[2025-04-22 13:05:06] DEBUG : Récupération de l'équipement : Autre compteur
0509|[2025-04-22 13:05:06] DEBUG : Récupération de l'équipement : Linky
0510|[2025-04-22 13:05:06] DEBUG : Liste complète des équipements : [{"id":"386","name":"Autre compteur","ip":"172.34.2.12","port":"","compte":"","password":""},{"id":"390","name":"Linky","ip":"192.168.10.31","port":"80","compte":"admin","password":"*xx*"}]
0511|[2025-04-22 13:05:06] INFO : Lancement démon:python3 /var/www/html/plugins/ecodevicert/resources/myDaemon.py
0512|[2025-04-22 13:05:07] DEBUG : test from daemon
0513|[2025-04-22 13:05:07] DEBUG : >>>> deamon_info() : Le démon est OK
0514|[2025-04-22 13:05:07] DEBUG : >>>> deamon_start() : Le démon est OK -- itération 1
0515|[2025-04-22 13:05:07] DEBUG : params to send to daemon:{"action":"update_equipements","equipements":[{"id":"386","name":"Autre compteur","ip":"172.34.2.12","port":"","compte":"","password":""},{"id":"390","name":"Linky","ip":"192.168.10.31","port":"80","compte":"admin","password":"*xx*"}]}
0516|[2025-04-22 13:05:23] DEBUG : new feedback:{"data":"{'id': '390', 'a_power': '730', 'intensity_p1': '2', 'intensity_p2': '1', 'intensity_p3': '0', 'consumption_day': 2649706, 'consumption_night': 2428635, 'consumption_total': 5078341, 'rate': 'HP'}"}
0517|[2025-04-22 13:05:23] DEBUG : Decoded message: Array ( [data] => {'id': '390', 'a_power': '730', 'intensity_p1': '2', 'intensity_p2': '1', 'intensity_p3': '0', 'consumption_day': 2649706, 'consumption_night': 2428635, 'consumption_total': 5078341, 'rate': 'HP'} )
0518|[2025-04-22 13:05:23] DEBUG : Message de type DATA
0519|[2025-04-22 13:05:23] DEBUG : ID de l'équipement: 390
0520|[2025-04-22 13:05:23] DEBUG : Valeur de a_power : 730
0521|[2025-04-22 13:05:23] DEBUG : Valeur de intensity_p1 : 2
Petit complément d’information, aucune idée si cela a un lieu ou non avec mon souci, mais lorsque j’accède à la page de config des plugins, j’ai cette erreur qui apparaît de temps à autre…
Attention le retour de daemon_info est mis en cache par le core, la valeur est modifiée que dans certains cas (après mise à jour du plugin par exemple)
Ce que je constate, c’est que le démon est lancé correctement, que la bibliothèque jeedomdaemon est sans doute bien utilisée (pas regardé en détail, donc je ne m’avance pas), mais que votre démon s’arrête et c’est pourquoi Jeedom le relance toutes les 5 minutes.
Dans un premier temps, il faudrait s’assurer que le démon tienne dans sa boucle.
L’analyse de log pluginid_daemon va vous aider (vous ne les avez pas postés).
Le reste c’est du temps :
de la lecture de documentation (surtout asyncio qui est très complet et permet de faire plein de choses)
du debug
des tests
du debug
…
Par exemple, je vous propose de regarder du coté de asyncio.Event() ou asyncio.Lock() pour votre variable MyDaemon.running.
Sans prétendre maitriser le sujet sur le bout des doigts, le plugin MyModbus peut vous servir d’exemple. Il utilise également la bibliothèque jeedomdaemon. Le dépôt github est publique.
La déclaration compte: str = None ne va pas, None n’est pas de la classe str. Il faudrait plutôt compte: str | None = None. Idem pour password.
edit: combien y a-t-il de process correspondant au démon qui tournent ?
Oui ça arrive … on l’avait signalé à Loïc et c’est quand tu coupe une requête Ajax, avant jeedom ignorait l’erreur et maintenant il l’affiche. (Donc si tu changes de page alors qu’il y avait une requête en cours, tu vois ce message, on ne peut donc pas l’éviter apparemment)
Pour répondre à la question de Mips, le problème est que mon démon est redémarré régulièrement, mais uniquement lorsque la gestion automatique est activée. Si je la laisse désactivée, aucun problème, le démon ronronne pendant des jours.
C’est précisément cette étape que je ne comprends pas. Normalement il faudrait que je puisse laisser la gestion en automatique, sinon à chaque redémarrage de Jeedom, il faudra que je relance ce démon manuellement. Du coup il y a forcément quelque chose qui fait défaut empêchant le core de considérer le démon comme ok…
Je vais refaire une passe à tête reposée et vérifier les points évoqués par chacun.