Gérer les pannes de courants

Gérer les pannes de courants

Ici en Suisse, il existe un risque de pénurie d'énergie pour l'hiver prochain, et il n'est pas exclu que l'on soit confronté à des pannes d'électricité agendées ou non. C'est l'occasion de fabriquer un petit gadget pour nous aider à gérer les conséquences d’éventuelles pannes de courant.





Chez Yoctopuce, beaucoup de tâches sont automatisées à l'aide d'ordinateurs éparpillés un peu partout, et après une panne de courant il faut les rallumer un par un sans en oublier. Le projet de cette semaine est donc de construire un petit boîtier qui indiquera l'état de chaque machine, et si en plus le boîtier permettait de lancer un shutdown propre sur toutes des machines, cela permettrait d'éteindre facilement nos machines avant les coupures de courant annoncées.

Principe

La partie matérielle est juste triviale dans la mesure où on a juste besoin d'un panneau de LEDs et d'un gros bouton, il nous faut donc

Schéma de principe
Schéma de principe



Le principe consiste à faire tourner sur chaque machine un petit script qui prend le contrôle de la boîte et allume en vert la LED qui lui correspond. Si pour une raison ou une autre le script s'arrête, la même LED passe automatiquement en rouge. Si le script détecte que le commutateur de la boîte a été enclenché, il passe la LED en orange et lance un shutdown de la machine. Une fois que la machine est éteinte la LED s’éteint aussi.


La partie électronique  La façade nue
La boîte en question
La boîte en question


Programmation

La partie un peu compliquée de ce projet est la programmation du script, il y a différents aspects qui doivent être gérés correctement. On a décidé de faire une première version de ce script en Python parce que c'est un langage qui est facilement disponible tant sous Windows que Linux et Mac OS.

Gestion des connexions

Le YoctoHub-Ethernet ne peut gérer qu'un nombre limité de connexions parallèles. Le script doit donc se connecter sur la boîte, faire ce qu'il a à faire le plus vite possible avant de fermer la connexion, puis attendre un temps plus ou moins aléatoire avant de recommencer. Attendre un temps aléatoire permet d'éviter que toutes les machines n'essayent de se connecter à la boîte en même temps.
La structure du code ressemble donc à ceci:

  while True:
    if (YAPI.TestHub(CONTROLER_ADDR, 1000, errmsg) != YAPI.SUCCESS):
        print("cannot reach " + CONTROLER_ADDR)
        YAPI.FreeAPI()
    else:
      try:
        if (YAPI.RegisterHub(CONTROLER_ADDR, errmsg) == YAPI.SUCCESS):

          ##  payload

          YAPI.FreeAPI()
      except Exception as e:
        print("Error " + str(e))
    t = random.randint(SLEEP_AVG - 10, SLEEP_AVG + 10)
    print("sleeping for " + str(t) + "sec")
    time.sleep(t)



Configuration du système

Il faut stocker quelque part quelle machine correspond à quelle LED. On pourrait fournir ce paramètre à chaque instance du script grâce à la ligne commande, mais il nous a paru plus judicieux de centraliser cette information. Il se trouve que le YoctoHub-Ethernet contient un petit système de fichiers dans lequel on peut sauver un fichier de configuration. On a choisi un format archi-simple où chaque ligne correspond à une machine sous la forme LED index=Addresse IP, par exemple:

1=192.168.1.43 2=192.168.1.72 3=192.168.1.88


Comme on aimerait éviter de relancer les scripts après chaque changement de config tout en ne parsant le fichier de config que quand c'est nécessaire, pour cela on utilise le fait que le YoctoHub-Ethernet peut calculer le CRC de chaque fichier pour détecter les changements.

        myIPAddr = socket.gethostbyname(socket.gethostname())
        files = YFiles.FirstFiles()
        if files is None:
          sys.exit("No Files on " + CONTROLER_ADDR)
        fileslist = files.get_list(CONFIG_FILE)
        if len(fileslist) > 0:
          if (fileslist[0].get_name() == CONFIG_FILE and fileslist[0].get_crc() != lastCRC ):
            ledIndex = -1
            lastCRC = fileslist[0].get_crc()
            config = files.download(CONFIG_FILE).decode('utf-8')
            lines = config.split()
            for line in lines:
              tokens = line.split("=")
              if len(tokens) == 2:
                if tokens[1] == myIPAddr:
                  ledIndex = int(tokens[0])
            if (ledIndex < 0):
              print("No Ledindex for " + myIPAddr + " in config.txt on " + CONTROLER_ADDR)
            else:
              print("LED index=" + str(ledIndex))
        else:
          print("No config.txt file on " + CONTROLER_ADDR)



Notez que sur les machines pourvues de plusieurs adaptateurs réseau, nous ne savez pas trop quelle adresse IP votre script va détecter en premier. Ce n'est pas un gros problème dans la mesure où rien ne vous empêche de définir plusieurs lignes pour la même LED dans le fichier de configuration. Du moment que toutes ces adresses correspondent à la même machine, ça marchera.


Gestion des LEDs

Le but principal du script est de faire passer en vert la LED qui lui correspond sur la façade de la boîte, mais on veut aussi que cette LED passe automatiquement au rouge si le script s’arrête, typiquement parce que la machine s'est éteinte de manière impromptue. Similairement, lorsque que le script détecte une demande de shutdown, le LED passe à l'orange puis s’éteint automatiquement quand le script s'est arrêté parce que la machine s'est effectivement éteinte.

Pour faire en sorte qu'une led change automatiquement de couleur lorsque que le script qui la contrôle s'est arrêté, on ré-utilise l'astuce des animations du Yocto-Color. Elle utilise le fait que les animations du Yocto-Color tournent en boucle, à moins qu'elles ne contiennent une instruction de saut qui permet d'enchaîner sur une autre animation. Elle consiste à pré-programmer deux animations:

  • La première maintient la première couleur, par exemple vert, pendant un temps prédéterminé, puis saute à la deuxième animation
  • La seconde animation maintient la deuxième couleur, par exemple rouge, en boucle

Tant que le script tourne, la LED est périodiquement réaffectée à la première animation avant qu'elle n'ait le temps de sauter à la deuxième animation. Si le script s'arrête, la LED finira par enchaîner sur la deuxième animation et changera de couleur toute seule.

Il est possible de définir les animations une bonne fois pour toutes et les sauver sur le système de ficher du Yocto-Color. Mais on a utilisé une approche un peu différente: le script vérifie au démarrage si les animations sont déjà définie dans la mémoire du Yocto-Color et les crée au besoin. Ainsi, si on souhaite changer les animations, il suffit de changer le script et de redémarrer le module puis le script pour que les animations soient mises à jour.

En pendant qu'on y est, l’initialisation définit, si ce n'est pas déjà fait, la couleur de démarrage de la LED à rouge. Ainsi, lorsque le courant reviendra après une panne, toutes les LEDS utilisées seront rouges par défaut.

    cluster = YColorLedCluster.FirstColorLedCluster()
      if cluster is None:
        sys.exit("No ColorLedCluster on " + CONTROLER_ADDR)

      if FirstTime:
        signatures = cluster.get_blinkSeqSignatures(0, 5)

        if (signatures[GREENSEQ] == 0):
          cluster.resetBlinkSeq(GREENSEQ)
          cluster.addRgbMoveToBlinkSeq(GREENSEQ, GREEN, 0)
          cluster.addRgbMoveToBlinkSeq(GREENSEQ, GREEN, DELAY)
          cluster.addJumpToBlinkSeq(GREENSEQ, REDSEQ)
          cluster.startBlinkSeq(GREENSEQ)


        if (signatures[REDSEQ] == 0):
          cluster.resetBlinkSeq(REDSEQ)
          cluster.addRgbMoveToBlinkSeq(REDSEQ, RED, 0)
          cluster.addRgbMoveToBlinkSeq(REDSEQ, RED, 10000)
          cluster.startBlinkSeq(REDSEQ)

        ## define other sequences ...



      if cluster.get_rgbColorArrayAtPowerOn(ledIndex, 1)[0] != RED:
        cluster.set_rgbColorAtPowerOn(ledIndex, 1, RED)
        cluster.saveLedsConfigAtPowerOn()



Gestion du shutdown

La gestion du shutdown consiste d'abord à vérifier que le commutateur n'est pas déjà enclenché au démarrage du script pour éviter qu'une machine ne se ré-éteigne à peine allumée parce qu'on a oublié de remettre le commutateur en place. Une fois la commande de shutdown envoyée au système, le script continue à tourner en maintenant la LED à l'orange, on compte sur le processus de shutdown pour le tuer. Une fois le script mort, la LED s'éteindra grâce au mécanisme d'animations. Notez que le script aura probablement besoin de privilèges adéquats pour pouvoir lancer la commande de shutdown.

  if (mustShutDown):
    if (ShutDownAlreadySetAtStartUp):
      print("Switch was left in shutdown position")
      cluster.linkLedToBlinkSeq(ledIndex, 1, ORANGEBLINKSEQ, 0)
    else:            
      cluster.linkLedToBlinkSeq(ledIndex, 1, ORANGESEQ, 0)
      if ShutdownAlreadySent:
        print("Shutdown in progress...")
      else:
        print("Initialing shutdown...")
        platformName = platform.system();
        #platformName = "DISABLE SHUTDOWN"
        if platformName == "Windows":
          os.system('shutdown -s')
        elif platformName == "Linux" :
          os.system('systemctl poweroff')
        elif platformName == "Darwin":  #Mac
          os.system("shutdown -h now")  # might not work if not root
        else:
          print("Unknown platform : " + platformName)
        ShutdownAlreadySent = True



Conclusion

On a un peu fait le tour des points importants à propos de cette petite application. Si vous voulez en savoir plus, vous pouvez toujours télécharger le code source complet ici.

On est prêt
On est prêt


A l'occasion, on pourra réécrire le script en C++ pour en faire un service Windows, ce qui sera probablement plus élégant que d'avoir une fenêtre DOS ouverte en permanence sur les machines Windows gérées par le système.

Notez que si la boîte permet d'éteindre les machines à distance, elle ne permet pas de les rallumer. On aura peut-être l'occasion d'en reparler :-)

Commenter aucun commentaire Retour au blog












Yoctopuce, get your stuff connected.