Je suis en train de modifier MyModbus pour intégrer jeedomdaemon de Mips et me pose une question sur la meilleure méthode à adopter quand à l’utilisation des fonctions BaseDaemon.send_to_jeedom() et BaseDaemon.add_change().
J’ai également une classe dédiée aux équipements comme dans le plugin Kroomba (volontairement pas tagué via une étiquette ici). Je n’ai pas trouvé comment la classe Roomba envoyait des infos à l’adresse de callback iRobotConfig.callback_url.
Est-ce que c’est fait et je n’ai pas trouvé ?
Si ça devait être fait, quel est le meilleur moyen ? J’avais pensé à passer l’instance en paramètre et à créer des références aux fonctions send_to_jeedom ou add_change dans l’autre classe.
Un truc du genre (juste les bouts de code qui vont bien) :
class MyModbusd(BaseDaemon):
def __init__(self) -> None:
super().__init__(config=MyModbusConfig(), on_start_cb=self.on_start, on_message_cb=self.on_message, on_stop_cb=self.on_stop)
self.set_logger_log_level('MyModbus')
self._mymodbus_clients = []
async def on_start(self) -> None:
for eqConfig in self._config.json:
new_client = MyModbusClient(eqConfig, self)
new_client.read_eqConfig()
new_client.connect()
self._logger.info(f"Démarrage de la tâche pour l'équipement {eqConfig['name']}")
self._mymodbus_clients.append(new_client)
C’est comme ça qu’il faudrait faire ?
Il y aurait plus élégant ou performant ?
A+
Michel
edit: je pense d’ailleurs que add_change seule devrait être référencée et utilisée dans cette classe pour éviter les lancements concommitents de send_to_jeedom
roomba gère autrement, il publie les infos sur mqtt et le plugin les récupère via mqtt manager.
Seule la découverte remonte via un appel http.
Pour (évidement) avoir rencontré le besoin dans d’autres plugins, je peux te partager mon avis, qui n’est qu’un avis
ca va marcher, j’ai fait ca parfois … et je suis revenu en arrière des semaines/mois après parce que je trouve que c’est une erreur:
selon moi ca casse le principe d’isolation: ta class « client » est obligée de connaitre le démon et ses méthodes et de s’y adapter; ce n’est pas sa responsabilité.
d’ailleurs ca ne marche que parce que python n’est pas fortement typé: ton démon à une référence vers ton client et ton client une référence vers ton démon => référence circulaire, c’est pas dingue et les import seront bloqués de toute façon
tu finis avec un code « spaghetti » à cause de ses imbrications mutuelles
le role et responsabilité de ta class client n’est pas clair du coup: en fait elle fait tout, du coup pourquoi ne pas tout mettre dans le démon ou inversément?
pour l’instant je vois deux méthodes:
la première, un peu plus compliqué à comprendre et utilisable seulement si tu maitrises toutes la chaine et que c’est vraiment du full async/await, qui est plus « state of the art » en développement async/await je pense; ca donnerait un truc du genre:
async with MyModbusClient(config) as client:
async for message in client.messages():
payload = {
... stuff with message ...
}
self.add_change = daemon.add_change(payload)
client.messages() serait un « generator » qui lit de temps en temps ton modbus et « yield » les résultats (chaque message) ce qui va réveiller la boucle async
tu peux lire un peu la doc autour de ça, une fois qu’on a compris le principe de générateur et yield, c’est vraiment « beau » comme code je trouve
mais ce n’est pas ce que je te conseillerais ici vu que derrière tu as pymodbus
donc la deuxième: utiliser les bons vieux callback
c’est finalement ce que j’ai choisi dans le démon en fait justement pour faciliter l’intégration avec du code mixte (pas forcément orienté asyncio)
donc dans ton client tu déclares un « callback » dans le constructeur, et ton client va appeler ce callback (s’il a été fourni) chaque fois qu’il veut remonter un message.
il est maitre du contenu: à lui de décider si c’est un objet ou juste un string ou un dict, bref, ce que tu veux
ce qui est fait après dans la méthode qu’il appelle ne le regarde pas et il s’en fou.
A charge du « core du démon » (l’orchestrateur ici du coup) de fournir une méthode (de sa class) à ce paramètre lors de la création du client et dans cette méthode tu y fait ce que tu veux, par exemple appeler le « add_change » ou autre pour remonter les infos à jeedom
et donc si tu veux un exemple, tu peux regarder le code de BaseDaemon.
send_to_jeedom envoi l’info immédiatement et l’instruction suivante dans le code ne se fait pas tant que le call n’est pas terminé donc tu as la garantie que jeedom a reçu l’info lorsque tu passes à l’instruction suivante (sauf si jeedom est down ou ne répond pas mais bon… pas vraiment probable ici)
add_change prépare le message dans une « queue » en quelque sorte et ca sera envoyé « plus tard », l’instruction suivante continue immédiatement avant que le message ne soit réellement envoyé à jeedom
si possible je recommande l’utilisation de add_change qui n’est pas bloquant, le démon sera bien plus réactif/rapide (c’est le principe du dev asynchrone: ne pas bloquer sur les opérations i/o longue: appel réseau, disk etc) mais il faut garder à l’esprit qu’avec le add_change tu n’es pas sur de quand ni de l’ordre d’arrivée des messages.
donc parfois on a besoin de faire un send immédiat, exemple j’ai des plugins qui font une découverte d’équipement au démarrage, enregistre des listeners d’event avec callback et ces callback vont finalement remonter les event via un « add_change » (un classique en domotique finalement)
et bien lors de la découverte je veux faire un send_jeedom immédiat parce qu’en fait coté php ca va me créer l’équipement s’il n’existait pas encore … et cet équipement doit être créé avant que les premiers events ne remontent car oui parfois ca va très vite
il n’y a aucun soucis d’utiliser les deux, pas de risques de collision.
Le « generator » pourrait être une « Queue » aussi, non ? Une FIFO de la class Client vers le Daemon. Ca marcherait pareil… et c’est ce que j’ai fait dans la bêta actuelle. Ca se gère tout seul et c’est facile aussi.