Une bonne grosse pause dans un démon Python

Hello,

Je continue d’essayer de chercher et je suis enfin tomber sur 2 plugins intéressants.

Ma fonction actuelle :

	@staticmethod
	def stripped(str):
		return "".join([i for i in str if i in range(32, 127)])

#plugin-meteofull qui a modifié la fonction comme ça :

	@staticmethod
	def stripped(str):
		return "".join([i for i in str if ord(i) in range(32, 127)])

Donc en ajoutant un ord qui selon la doc « renvoie la valeur représentant l’unicode d’un caractère spécifié ».

Et #plugin-jmqtt qui n’a pas modifié cette fonction mais qui a ajouté en début de fichier :

import sys
if sys.version_info.major == 3:
	unicode = str

ça ne serait donc pas ces informations (plutôt utilisées par jmqtt) qu’il faudrait ajouter dans le fichier pour prise en compte de Python3 ?

Perso je pense que ca sert plus à rien de faire ca, on s’en fiche de pas être compatible avec python2 donc au final ca rend le code moins lisible: ce bout de code est au début et lorsqu’on lit la fonction on voit pas forcément que le code au début à un impact => compliqué à suivre.

donc au débaut j’utilisais ord(i) mais mnt je n’utilise plus la fonction stripped, je fais ceci

message = json.loads(JEEDOM_SOCKET_MESSAGE.get().decode('utf-8'))

au lieu de

message = json.loads(jeedom_utils.stripped(JEEDOM_SOCKET_MESSAGE.get()))

mais c’est vrai que ca revient pas tout à fait au même puisque je filtre pas les caractères

Perso je n’ai pas changé le jeedom.py et ça fonctionne. La fonction que j’utilise:

def read_socket(cycle):
    while True:
        try:
            global JEEDOM_SOCKET_MESSAGE
            if not JEEDOM_SOCKET_MESSAGE.empty():
                logging.debug("SOCKET-READ------ Message received in socket JEEDOM_SOCKET_MESSAGE")
                message = json.loads(JEEDOM_SOCKET_MESSAGE.get())
                logging.debug("SOCKET-READ------ Message received in socket JEEDOM_SOCKET_MESSAGE " + message['cmd'])
                if message['apikey'] != globals.apikey:
                    logging.error("SOCKET-READ------ Invalid apikey from socket : " + str(message))
                    return
                logging.debug('SOCKET-READ------ Received command from jeedom : ' + str(message['cmd']))
                if message['cmd'] == 'action':
                    logging.debug('SOCKET-READ------ Attempt an action on a device')
                    _thread.start_new_thread(action_handler, (message,))
                    logging.debug('SOCKET-READ------ Action Thread Launched')
                elif message['cmd'] == 'changelog':
                    log = logging.getLogger()
                    for hdlr in log.handlers[:]:
                        log.removeHandler(hdlr)
                    jeedom_utils.set_log_level('info')
                    logging.info('SOCKET-READ------ Passage des log du demon en mode ' + message['level'])
                    for hdlr in log.handlers[:]:
                        log.removeHandler(hdlr)
                    jeedom_utils.set_log_level(message['level'])
        except Exception as e:
            logging.error("SOCKET-READ------ Exception on socket : %s" % str(e))
            logging.debug(traceback.format_exc())
        time.sleep(cycle)

Comme @Mips si je ne me trompe mais sans le decode utf-8

OK merci @Noyax37 et @Mips je teste ça dès que possible pour avancer sur la suite. Mais forcement sans communication Jeedom → Daemon je risque pas de pouvoir aller plus loin :sweat_smile:

Bon alors, c’est bon avec message = json.loads(JEEDOM_SOCKET_MESSAGE.get().decode('utf-8')), les messages passent.

@tomdom j’ai donc essayé de mettre en place ta technique mais j’ai un soucis.

Je passe le message depuis Jeedom et il est récupéré dans read_socket() :

def read_socket():
    global JEEDOM_SOCKET_MESSAGE
    global my_event
    if not JEEDOM_SOCKET_MESSAGE.empty():        
        #message = json.loads(jeedom_utils.stripped(JEEDOM_SOCKET_MESSAGE.get()))
        message = json.loads(JEEDOM_SOCKET_MESSAGE.get().decode('utf-8'))
        if message['apikey'] != _apikey:
            logging.error("Invalid apikey from socket : " + str(message))
            return
        # Check for there is a cmd in message
        if 'cmd' not in message or not isinstance(message['cmd'], str) or message['cmd'] == '':
            logging.error('Bad cmd parameter in message dump=%s', json.dumps(message))
            return      
        try:            
            #logging.info("test cmd")
            if message['cmd'] == 'start':
                 my_event.set() # pour débloquer run()
                 logging.info("Message to start")
                 #main()
            if message['cmd'] == 'stop':
                 my_event.clear() # pour le remettre en attente
                 logging.info("Message to stop")
                 #main()        
        except Exception as e:
            logging.error('Send command to demon error : '+str(e))

La méthode main()

async def main():
    my_event=asyncio.Event()
    asyncio.get_event_loop().run_until_complete(run(my_event))

La méthode run(my_event):

async def run(my_event):    
    while True:
        my_event.wait()
        try:
            ......  

En envoyant un start via message['cmd'] depuis Jeedom j’ai une erreur :

Send command to demon error : name 'my_event' is not defined

Je présume que je n’ai pas bien compris l’affaire du my_event !?

on peut voir ce que tu envoies ?

La chaîne start au travers de la fonction que Mips a présenté dans son tuto

Avant que je rajoute le my_event.set() ça affichait bien le message donc ça passe bien ici : if message['cmd'] == 'start':

J’ai lu la doc du event et la façon dont ils utilisent main et waiter mais je n’arrive pas à voir comment adapter ça avec l’ensemble des fonctions notamment liste qui n’est pas async.

sauf si je dis une connerie (fort possible!) … ton « my_event » n’est pas déclarer au « niveau parent » pour être considéré comme global !?
la 1ere fois qu’on le voit il est dans main

J’ai pas compris.

Puis en plus faudrait que j’exécute async main(), peut-être après listen() parce que là c’est même pas lancé.

Je m’y perd :cry:

Je tenterai de reprendre demain matin. Mais quoi qu’il en soit il n’y a pas vraiment les mêmes appels que dans l’exemple de la doc sur events

Bonjour,

ai-je bien compris ta demande :

  • Tu veux lancer un démon
  • Tu veux pouvoir envoyer un/des ordres au demon via Jeedom (pendant que le demon tourne)
  • Tu veux aussi pouvoir retourner des valeurs à Jeedom depuis ton démon.

C’est ca?

Normalement aucune nécessité de modifier jeedom.py.
C’est un script type fournit par Jeedom pour les échanges de données demon<>jeedom (et jeedom<>demon).

Tien je viens de faire ca rapidement en esperant que ca t’aident à comprendre le processus d’echange de données entre Jeedom et le demon (et vice versa).

C’est juste pour te servir d’exemple, il faut que tu l’adapte a tes besoin

C’est la fonction listen qui tourne en boucle et dans read_socket ca permet de recevoir les cmd que tu envoi de jeedom

import logging
import sys
import os
import json

try:
    from jeedom.jeedom import *
except ImportError as error:
    print(error.__class__.__name__ + ": " + str(error))
    print("Error: importing module jeedom.jeedom")
    sys.exit(1)


# DECLARE CE QUE TU AS BESOIN ICI
# ...#
# ...#


def read_socket(monsocket):
    global JEEDOM_SOCKET_MESSAGE
    global ret
    
    #ICI ON RECUPERE LE MESSAGE VENANT DE JEEDOM
    if not JEEDOM_SOCKET_MESSAGE.empty():
        logging.debug("Message received in socket JEEDOM_SOCKET_MESSAGE")
        message = json.loads(JEEDOM_SOCKET_MESSAGE.get())
        if message['apikey'] != _apikey:
            logging.error("Invalid apikey from socket : " + str(message))
            return
        logging.debug(message['cmd'])
        
        #ICI ON TEST LA VALEUR RECU (cmd ou event pour toi je crois)
        try:
            if message['cmd'] == 'startRead':
                logging.debug("MESSAGE RECU DE JEEDOM: startRead ")
                parameter = True

            elif message['cmd'] == 'stopRead':
                logging.debug("MESSAGE RECU DE JEEDOM: stopRead ")
                parameter = False
            
            if parameter : 
                #FAIS ICI TON ACTION QUE TU VEUX ET RENOIE LES DONNEES A JEEDOM
                #ret = .....
                jeedom_com.send_change_immediate(ret)
                logging.debug(json.dumps(ret))

            
        except Exception as e:
            logging.error('Send command to demon error : ' + str(e))


def listen():

    jeedom_socket.open()

    global JEEDOM_SOCKET_MESSAGE
    global parameter
    parameter = False
    mysocket = "ton socket de connexion http par exemple"

    try:
        while 1:
            time.sleep(0.5)
            if mysocket != None:
                read_socket(mysocket, parameter)
    except KeyboardInterrupt:
        shutdown()

# ----------------------------------------------------------------------------


def handler(signum=None, frame=None):
    logging.debug("Signal %i caught, exiting..." % int(signum))
    shutdown()


def shutdown():
    logging.debug("Shutdown")
    logging.debug("Removing PID file " + str(_pidfile))
    try:
        os.remove(_pidfile)
    except:
        pass
    try:
        jeedom_socket.close()
    except:
        pass
    try:
        jeedom_serial.close()
    except:
        pass
    logging.debug("Exit 0")
    sys.stdout.flush()
    os._exit(0)

# ----------------------------------------------------------------------------


_log_level = "error"
_socket_port = 55079
_socket_host = 'localhost'
_internal_addr = ''
_device = 'auto'
_pidfile = '/tmp/myplugind.pid'
_apikey = ''
_callback = ''

for arg in sys.argv:
    if arg.startswith("--loglevel="):
        temp, _log_level = arg.split("=")
    elif arg.startswith("--socketport="):
        temp, _socket_port = arg.split("=")
    elif arg.startswith("--internaladdr="):
        temp, _internal_addr = arg.split("=")
    elif arg.startswith("--sockethost="):
        temp, _socket_host = arg.split("=")
    elif arg.startswith("--pidfile="):
        temp, _pidfile = arg.split("=")
    elif arg.startswith("--apikey="):
        temp, _apikey = arg.split("=")
    elif arg.startswith("--device="):
        temp, _device = arg.split("=")
    elif arg.startswith("--callback="):
        temp, _callback = arg.split("=")

#_socket_port = int(_socket_port)

jeedom_utils.set_log_level(_log_level)

logging.info('Start demond')
logging.info('Log level : '+str(_log_level))
logging.info('Socket port : ' + str(_socket_port))
logging.info('IP Jeedom : ' + str(_internal_addr))
logging.info('Socket host : '+str(_socket_host))
logging.info('PID file : '+str(_pidfile))
logging.info('Apikey : '+str(_apikey))
logging.info('Device : '+str(_device))

signal.signal(signal.SIGINT, handler)
signal.signal(signal.SIGTERM, handler)

try:
    jeedom_utils.write_pid(str(_pidfile))
    jeedom_socket = jeedom_socket(port=int(_socket_port), address=_socket_host)
    jeedom_com = jeedom_com(apikey=_apikey, url=_callback)
    listen()

except Exception as e:
    logging.error('Fatal error : '+str(e))
    shutdown()

et coté jeedom dans ta class cmd :

 public static function startRead(){
        $value = json_encode(array('apikey' => jeedom::getApiKey('myplugin'), 'cmd' => 'startRead' ));
        self::socketConnection($value);
    }

C’est sympa ça mais c’est le code du plugin template ou presque…

oui, ca c’est sur aussi :slight_smile:

Cela ne me dérange pas que ce soit pratiquement le code du plugin template puisque je cherche à partir de là et ajouter quelques minis bouts de ce qu’il manque pour arriver au résultat. Mais là je ne vois pas bien ce qui permet d’y arriver avec ces modifications.

@c.miorin, merci beaucoup pour ton code, mais à première vue je ne vois pas ce qui permet d’arriver à ce que je voudrais faire. C’est peut–être parce que tu n’as pas compris le but (ou c’est moi qui ne comprends pas le code proposé, ce qui est possible aussi :sweat_smile:).

En résumé :

  • Communication Socket → Jeedom : c’est OK depuis le début du plugin, pas de soucis
  • Communication Jeedom → Socket pendant qu’il tourne : c’est OK depuis la modification d’une ligne et l’utilisation de message = json.loads(JEEDOM_SOCKET_MESSAGE.get().decode('utf-8')) dans la fonction que Mips utilise

Maintenant ce qui m’interesse cest de pouvoir faire passer un ordre de stop ou start au socket pour qu’il mettent en pause son traitement habituel.

C’est à dire ici :

async def run(my_event):    
    while True:
        my_event.wait()
        try:
            # Là il y a le traitement avec une boucle et l'ouverture d'un websocket vers l'extérieur
            # et c'est cette partie que je souhaite interrompre puis pouvoir relancer
            # alors même que le daemon continue de vivre évidemment 

On m’a proposé l’utilisation de Event() mais pour l’heure j’arrive pas à mettre en place :slight_smile:

Pas bon non plus je peux pas le lancer dans le code principal comme le listen

try:
    jeedom_utils.write_pid(str(_pidfile))
    jeedom_com = jeedom_com(apikey = _apikey,url = _callback,cycle=_cycle)
    if not jeedom_com.test():
        logging.error('Network communication issues. Please fixe your Jeedom network configuration.')
        shutdown()
    jeedom_socket = jeedom_socket(port=_socket_port,address=_socket_host)
    async with main()
    listen()
except Exception as e:
	logging.error('Fatal error : '+str(e))
	logging.info(traceback.format_exc())
	shutdown()

image

Je suis vraiment pas au point :rofl:

déjà pour le listen avec la boucle async, regarde le code du plugin roomba : GitHub - Mips2648/jeedom-kroomba: kroomba is a Jeedom plugin for control Roomba 980
je fait exactement ca (même si je trouve pas ca parfait pcq justement ca utilise encore le socket déclaré dans jeedom qui lance un thread dont j’aimerais me passer)

Je vais regarder ce que tu as fait et je reviens.

Là j’ai essayé avec :

listen()
asyncio.run(main())

Mais ça plante au démarrage du daemon :

8186|[2023-08-26 08:00:10]ERROR : Fatal error : This event loop is already running
8187|[2023-08-26 08:00:10]INFO : Traceback (most recent call last):
8188|File "/var/www/html/plugins/blitzortung/resources/blitzortungd/blitzortungd.py", line 253, in <module>
8189|asyncio.run(main())
8190|File "/usr/lib/python3.7/asyncio/runners.py", line 43, in run
8191|return loop.run_until_complete(main)
8192|File "/usr/lib/python3.7/asyncio/base_events.py", line 584, in run_until_complete
8193|return future.result()
8194|File "/var/www/html/plugins/blitzortung/resources/blitzortungd/blitzortungd.py", line 188, in main
8195|asyncio.get_event_loop().run_until_complete(run(my_event))
8196|File "/usr/lib/python3.7/asyncio/base_events.py", line 571, in run_until_complete
8197|self.run_forever()
8198|File "/usr/lib/python3.7/asyncio/base_events.py", line 526, in run_forever
8199|raise RuntimeError('This event loop is already running')
8200|RuntimeError: This event loop is already running
8201|[2023-08-26 08:00:10]DEBUG : Shutdown

Faudrait que j’arrive à coller à l’exemple qu’ils donnent dans la doc mais pour le moment je trouve pas comment faire cette partie en plus du Listen() qui écoute ce qui arrive de Jeedom.

async def waiter(event):
    print('waiting for it ...')
    await event.wait()
    print('... got it!')

async def main():
    # Create an Event object.
    event = asyncio.Event()

    # Spawn a Task to wait until 'event' is set.
    waiter_task = asyncio.create_task(waiter(event))

    # Sleep for 1 second and set the event.
    await asyncio.sleep(1)
    event.set()

    # Wait until the waiter task is finished.
    await waiter_task

asyncio.run(main())

Bon je vais lire kroomba :slight_smile:

Doit y avoir un truc qui m’échappe dans ce que tu veux faire parceque là tu as listen en boucle et tu analyses ce qui est envoyé par jeedom, si c’est une commande de start alors tu lances ton prog principal, si c’est une commande de stop alors tu arrêtes et tu retournes à l’état d’avant et si tu n’as pas de commande bah tu fais rien… Ce n’est pas ça ce que tu veux faire?

Ca ne marchera jamais car les 2 sont bloquant: listen() est une boucle qui ne s’arrête que lors du shutdown et ta boucle main() c’est pareil certainement

pour l’event, je cherche si je peux te partager un autre exemple

Salut,

Si ça ressemble bien à ça. Mais une fois que j’ai lancé ce qui avant était un def run(): et que ça tourne en boule là dedans, comment l’arrêter (sans que le daemon s’arrette aussi) ?