Hello à tous,
Je suis en train de développer mon tout premier démon et je vais avoir besoin d’un coup de main.
Tout d’abord un grand merci à @nebz et @Mips pour la mise à disposition de la gestion des dépendances et la lib « jeedomdaemon »
![]()
Ca m’a grandement simplifié les choses.
L’objectif de ce démon est de récupérer des messages MQTT envoyés depuis un broker externe !
J’ai donc installer un pyenv avec la librairie paho-mqqt qui me servira de client MQTT.
Jusque là tout va bien.
Je viens également de finir le code de mon démon :
import ssl
import json
import asyncio
import paho.mqtt.client as mqtt
from jeedomdaemon.base_daemon import BaseDaemon
from jeedomdaemon.base_config import BaseConfig
class DaemonConfig(BaseConfig):
def __init__(self):
super().__init__()
self.add_argument("--host", help="MQTT host", type=str)
self.add_argument("--port", help="MQTT port", type=int)
self.add_argument("--username", help="MQTT username", type=str)
self.add_argument("--password", help="MQTT password (ID token)", type=str)
@property
def mqtt_host(self): return str(self._args.host)
@property
def mqtt_port(self): return int(self._args.port)
@property
def mqtt_username(self): return str(self._args.username)
@property
def mqtt_password(self): return str(self._args.password)
class MyBMWDaemon(BaseDaemon):
def __init__(self) -> None:
self._config = DaemonConfig()
super().__init__(self._config, self.on_start, self.on_message, self.on_stop)
self._mqtt_client = None
self._subscriptions = set()
self._connected = False
self._loop = None
async def on_start(self):
""" Daemon initialization and MQTT connection """
self._logger.info("Starting myBMW daemon...")
self._loop = asyncio.get_running_loop()
await self.__connect_mqtt()
async def on_message(self, message: dict):
""" Message received from Jeedom """
try:
action = message['action']
vin = message['vin']
topic = f"{self._config.mqtt_username}/{vin}"
if action == 'subscribe':
if topic not in self._subscriptions:
self._subscriptions.add(topic)
if self._connected:
self._mqtt_client.subscribe(topic)
self._logger.info(f"Subscribed to topic {topic}")
else:
self._logger.warning(f"Will subscribe to topic {topic} after reconnect")
elif action == 'unsubscribe':
if topic in self._subscriptions:
self._subscriptions.remove(topic)
if self._connected:
self._mqtt_client.unsubscribe(topic)
self._logger.info(f"Unsubscribed from topic {topic}")
except Exception as e:
self._logger.error(f"Error handling message from Jeedom: {e}")
async def on_stop(self):
""" MQTT disconnection """
self._logger.info("Stopping myBMW daemon...")
if self._mqtt_client:
self._mqtt_client.loop_stop()
self._mqtt_client.disconnect()
self._subscriptions.clear()
async def __connect_mqtt(self):
""" Secure connection to BMW MQTT broker """
self._logger.info("Connecting to BMW MQTT Broker...")
def on_connect(client, userdata, flags, rc):
if rc == 0:
self._connected = True
self._logger.info(f"Connected successfully to BMW MQTT broker {self._config.mqtt_host}:{self._config.mqtt_port}")
for topic in self._subscriptions:
client.subscribe(topic)
self._logger.info(f"Re-subscribed to {topic}")
else:
self._logger.error(f"MQTT connection failed with code {rc}")
def on_message(client, userdata, msg):
try:
payload = json.loads(msg.payload.decode("utf-8"))
asyncio.run_coroutine_threadsafe(
self.send_to_jeedom({"topic": msg.topic, "data": payload}),
self._loop
)
self._logger.debug(f"Message received on {msg.topic} : {payload}")
except Exception as e:
self._logger.error(f"Error processing MQTT message: {e}")
def on_disconnect(client, userdata, rc):
self._logger.warning(f"Disconnected from MQTT broker (code {rc})")
self._connected = False
# MQTT client initialization
self._mqtt_client = mqtt.Client()
self._mqtt_client.username_pw_set(self._config.mqtt_username, self._config.mqtt_password)
self._mqtt_client.on_connect = on_connect
self._mqtt_client.on_message = on_message
self._mqtt_client.on_disconnect = on_disconnect
self._mqtt_client.reconnect_delay_set(min_delay=5, max_delay=60)
# BMW TLS configuration
self._mqtt_client.tls_set(cert_reqs=ssl.CERT_REQUIRED, tls_version=ssl.PROTOCOL_TLSv1_2)
self._mqtt_client.tls_insecure_set(False)
try:
self._mqtt_client.connect(self._config.mqtt_host, self._config.mqtt_port, keepalive=60)
self._mqtt_client.loop_start()
except Exception as e:
self._logger.error(f"Failed to connect to MQTT broker: {e}")
MyBMWDaemon().run()
Ainsi que la fonction php qui le lance :
public static function deamon_start()
{
self::deamon_stop();
$deamon_info = self::deamon_info();
if ($deamon_info['launchable'] != 'ok') {
throw new Exception('Please check the configuration');
}
$username = config::byKey('username', __CLASS__);
$password = self::getIdToken();
$host = config::byKey('host', __CLASS__) ?: 'customer.streaming-cardata.bmwgroup.com';
$port = (int) (config::byKey('port', __CLASS__) ?: 9000);
$path = realpath(__DIR__ . '/../../resources');
$cmd = self::PYTHON_PATH . " {$path}/myBMW.py";
$cmd .= ' --loglevel ' . log::convertLogLevel(log::getLogLevel(__CLASS__));
$cmd .= ' --host ' . $host;
$cmd .= ' --port ' . $port;
$cmd .= ' --username ' . escapeshellarg(trim($username));
$cmd .= ' --password ' . escapeshellarg(trim($password));
$cmd .= ' --callback ' . network::getNetworkAccess('internal', 'proto:127.0.0.1:port:comp') . '/plugins/myBMW/core/php/jeemyBMW.php';
$cmd .= ' --apikey ' . jeedom::getApiKey(__CLASS__);
$cmd .= ' --pid ' . jeedom::getTmpFolder(__CLASS__) . '/daemon.pid';
log::add(__CLASS__, 'debug', 'Lancement démon');
log::add('myBMW', 'debug', 'CMD= ' . $cmd);
$result = exec($cmd . ' >> ' . log::getPathToLog(__CLASS__ . '_daemon') . ' 2>&1 &');
$i = 0;
while ($i < 10) {
$deamon_info = self::deamon_info();
if ($deamon_info['state'] == 'ok') {
break;
}
sleep(1);
$i++;
}
if ($i >= 10) {
log::add(__CLASS__, 'error', 'Unable to start daemon', 'unableStartDeamon');
return false;
}
message::removeAll(__CLASS__, 'unableStartDeamon');
return true;
}
Mais quand je lance le démon, j’ai l’erreur suivante :
0024|[2025-10-28 19:52:52] INFO : Starting daemon (lib version 1.2.9) with log level: debug
0025|[2025-10-28 19:52:52] DEBUG : Writing PID 8626 to /tmp/jeedom/myBMW/daemon.pid
0026|[2025-10-28 19:52:52] ERROR : Fatal error: (<class 'ValueError'>) in /var/www/html/plugins/myBMW/resources/venv/lib/python3.11/site-packages/jeedomdaemon/base_daemon.py on line 90
0027|[2025-10-28 19:52:52] INFO : Shutdown
0028|[2025-10-28 19:52:52] DEBUG : Removing PID file /tmp/jeedom/myBMW/daemon.pid
0029|[2025-10-28 19:52:52] DEBUG : Exit 0
J’ai beau vérifier les arguments (si cela vient de là), je ne vois pas d’erreur ![]()
Côté base_daemon.py : l’erreur se produit sur la ligne
asyncio.run(self.__run())
Avis aux experts python pour me donner une piste ![]()
Merci d’avance
Xav