[PYTHON] Gérez les transitions d'état et communiquez avec les compteurs intelligents

introduction

Dans l'article précédent, Smart Meter Measurement Logger with Worker Thread Design Pattern, le fonctionnement à long terme est effectué en redémarrant l'objet (thread) responsable de la mesure. C'était une approche qui a rendu cela possible. Cependant, il s'agit d'un dernier recours et on suppose que le thread fonctionne de manière stable. De plus, même si le thread est en cours d'exécution, ce ne sera pas une contre-mesure s'il est bloqué par un traitement.

En particulier, lorsque vous communiquez avec un compteur intelligent via la route B, vous devez prendre plusieurs étapes, telles que la numérisation et l'authentification, et certaines étapes peuvent échouer ou expirer. Vous devez penser au traitement tel que réessayer ou réinitialiser et recommencer, mais les choses à faire changeront en fonction de la situation, donc si vous ne l'organisez pas et ne l'implémentez pas correctement, vous attendrez une éternité. Vous êtes plus susceptible de continuer ou de recevoir des données inattendues et de planter.

Cette fois, je présenterai la communication avec le compteur intelligent de la route B car j'ai géré la transition d'état et l'ai mise en œuvre.

De Code source keilog / broute.py

Ecrire un diagramme de transition d'état

Tout d'abord, il est nécessaire d'organiser correctement ce qu'il faut faire dans quelle situation, puis de le programmer d'une manière facile à comprendre. Dans la boucle de thread run () de la classe BrouteReader, le traitement est approximativement exécuté avec les transitions d'état suivantes. Le carré bleu représente l'état et, en fonction du résultat de l'exécution de la fonction appelée dans cet état, des tentatives ou des transitions d'état sont effectuées. Ce n'est pas toujours la bonne réponse, et d'autres méthodes sont possibles, mais cela fonctionne bien dans le but d'obtenir une puissance instantanée et une puissance intégrée.

Broute状態遷移図.001.jpeg

Méthode de mise en œuvre

Le code qui implémente ceci est le suivant. (En fait un peu plus compliqué)

class BrouteReader ( Worker ):
    
    # <réduction>

    def run( self ):
        while not self.stopEvent.is_set():
            if self.state == self._STATE_INIT:
                self._open()

            elif self.state == self._STATE_OPEN:
                self._setup()

            elif self.state == self._STATE_SETUP:
                self._scan()

            elif self.state == self._STATE_SCAN:
                self._join()

            elif self.state == self._STATE_JOIN:
                if time_has_come(): #Comparez en fait l'heure de la dernière mise à jour avec l'heure actuelle
                    self._sendto('Message de demande de valeur de propriété')
                dataframe = self._receive() #Recevoir le message recevoir()Expiration en 1 seconde
                if dataframe:
                    self._accept(dataframe) #Traiter le message reçu

                if long_time_has_gone():
                    logger.error('Je n'ai pas reçu de message depuis longtemps')
                    self._term()
                    self._close()
                    self.state = self._STATE_INIT
                    time.sleep(5)

        self._term() #Déconnecter le compteur intelligent
        self._close() #Ouverture des appareils WiSun
        logger.info('[STOP]')

Comme présenté dans l '[article] précédent (https://qiita.com/matsuda33/items/edbaf0eddf0bae8252f9), dans run (), la boucle est répétée sauf si stopEvent est défini.

À l'intérieur de la boucle, le code qui appartient à l'un des états est exécuté en fonction de l'état actuel représenté par self.state. Ici, seule la fonction est appelée, mais le traitement nécessaire est effectué dans la fonction et self.state est réécrit pour mettre à jour l'état. Ensuite, dans la boucle suivante, le code de l'état suivant de la transition sera exécuté. C'est le flux de base. Le traitement des nouvelles tentatives est également défini dans scan () et join (). Les cas ont tendance à être longs selon la condition, donc je pense qu'il est important de rester aussi simple et clair que possible.

Je vais expliquer un peu le déroulement de l'opération. Il semble qu'il faut environ 1 seconde au compteur intelligent pour répondre au message envoyé par sendto (). Si time_has_come () est défini dans une boucle et qu'un message demandant la valeur de propriété du compteur intelligent est envoyé, la réponse au message de demande n'arrive pas immédiatement, elle est donc traitée dans la boucle suivante ou suivante au lieu de dans cette boucle. Ça ira. Par conséquent, le message de réponse ou de notification reçu par receive ne correspond pas toujours à celui de la demande envoyée immédiatement avant, et vous ne pouvez pas dire ce qu'il contient sans regarder à l'intérieur. accept () doit gérer n'importe quel message afin qu'il puisse être traité correctement.

Gardez également à l'esprit que chaque fonction doit être définie pour expirer. Sinon, la boucle peut y être bloquée et vous ne pourrez peut-être pas continuer. Une des racines du bloc est serial.readline (). Il est nécessaire de définir un délai (environ 1 seconde) pour cette fonction et de programmer le traitement suivant sur cette hypothèse. Ensuite, définissez un délai d'expiration pour que même s'il s'agit d'un processus qui attend juste une boucle de nouvelle tentative ou "OK", il n'attendra pas indéfiniment.

Vous trouverez ci-dessous des exemples d'open () et de setup ().

    def _open( self ):
        if self.wisundev.open():
            self.state = self._STATE_OPEN
        else:
            #En cas d'erreur, arrêtez pendant 5 secondes. Les boucles infinies rapides n'occupent plus le CPU. (Idem ci-dessous)
            time.sleep(5)

    def _setup( self ):
        #réinitialiser
        if self.wisundev.reset():
            pass
        else:
            time.sleep(5)
            return False
 
        #B Définir l'ID racine et le mot de passe sur l'appareil (enregistrés dans le registre)
        if self.wisundev.setup( self.broute_id, self.broute_pwd ):
            self.state = self._STATE_SETUP
        else:
            time.sleep(5)

Parmi les fonctions, la fonction portant le même nom que le pilote de périphérique WiSun est appelée, mais lorsque l'exécution est réussie, la transition d'état est effectuée en même temps. Chaque fois qu'une erreur se produit, mettez en veille avant de quitter la fonction. Sinon, il continuera à réessayer à grande vitesse lorsque le périphérique est déconnecté et continuera à enregistrer les erreurs et à occuper le processeur. De même, si vous définissez le délai d'expiration de serial.readline () mentionné ci-dessus trop court, la boucle peut fonctionner à grande vitesse, alors soyez prudent.

Pour améliorer la stabilité

En gérant les transitions d'état et en contrôlant l'exécution, je pense que ce qu'il faut faire sera plus clair et moins susceptible de faire des erreurs de logique. Et même si un problème survient, le code sera facile à corriger et à corriger. Il est encore nécessaire d'améliorer la précision de chaque fonction pour un fonctionnement stable, mais comme il est facile d'isoler le problème, je pense que nous pouvons identifier avec précision les contre-mesures.

En termes de précision, il est nécessaire de prendre des mesures suffisantes contre les événements involontaires et le traitement lors de la réception d'un message. Si vous spécifiez l'index du tableau sans tenir compte de la possibilité d'autres événements ou messages, le risque d'erreur augmente. De plus, comme la plupart des données à traiter sont une chaîne de caractères hexadécimales HEX, nous vérifions si elles sont correctes en tant que chaîne de caractères hexadécimaux. Cependant, selon l'environnement, ce genre d'erreur ne semble pas se produire très souvent.

D'un autre côté, il est relativement courant que la communication soit perdue. À la fin du code ci-dessus run (), si le message n'est pas reçu pendant une certaine période de temps, le processus de retour à INIT est exécuté pour traiter ce problème. La session PANA a une date d'expiration, et je dois me ré-authentifier régulièrement pour actualiser la clé, mais le RL7023 utilisé ici a une fonction de ré-authentification automatique. En regardant le journal, il y avait des preuves de cela, et la ré-authentification a réussi, mais il y a eu des cas où la communication a été interrompue par la suite. Non limité à cela, il est fort possible que la communication soit interrompue pour une raison quelconque, cette mesure est donc essentielle. Je ne sais pas ce qu'il en est des autres appareils WiSun, mais si vous avez besoin de vous authentifier à nouveau, vous pouvez effectuer une connexion régulière ().

en conclusion

Cette fois, lorsque j'ai créé un programme qui gère les transitions d'état, j'ai trouvé qu'il y avait différents points tels que la nécessité d'un délai d'attente approprié et l'attention aux boucles infinies de relance à grande vitesse. Si une erreur de programme ou une erreur provoque la transmission de rafales d'ondes radio, cela causera des problèmes aux appareils environnants utilisant la même bande, et dans le pire des cas, cela pourrait enfreindre la loi sur la radio. Il y a. J'ai pensé que je devais faire attention à ne pas émettre d'ondes radio par inadvertance. Je vous serais reconnaissant de bien vouloir souligner d’autres points à noter.

Puis, de façon inattendue, j'ai eu peur de nommer l'État. Après tout, c'est le même que le nom de la fonction, mais veuillez me faire savoir s'il existe une bonne méthode de nommage.

référence

Recommended Posts

Gérez les transitions d'état et communiquez avec les compteurs intelligents
Communiquez avec FX-5204PS avec Python et PyUSB
Communiquez entre Elixir et Python avec gRPC
Implémenter un modèle avec état et comportement
Gérez de manière déclarative l'environnement avec Nix et home-manager
Django: enregistrez l'agent utilisateur et gérez-le avec l'administrateur
Gérez les packages d'exécution Python et les packages d'environnement de développement avec Poetry