Station météo Raspberry Pi – La capture en Python et le stockage avec SQLite

La suite de la fabrication de notre station météo avec le Raspberry Pi. Après la configuration des émetteurs et l’assemblage des boitiers, il faut récupérer les mesures et les stocker quelque part.

meteo

  1. La base de données. Pour mémoriser les données reçues il va nous falloir définir une base SQLite.Dans cette base il nous faut deux tables :
    Une table pour l’ensemble des émetteurs. Nous allons y stocker l’identifiant de l’émetteur, son voltage avec la date et heure de dernière mise à jour, la position de l’émetteur (intérieur ou extérieur par exemple), la fréquence de collecte des données.
  2. Installer sqlite3 pour contrôler votre base de données
    apt-get install sqlite3
  3. Installer ntpdate pour avoir l’heure exacte
    apt-get install ntpdate

    Puis lancer manuellement :

    ntpdate pool.ntp.org

    Par crontab, ajouter la ligne pour une mise à jour toutes les heures

    0 */1 * * * ntpdate pool.ntp.org

    en utilisant la commande

    crontab -e
  4. Installer python pour le script de récupération sur le port série
    apt-get install python
  5. Vous pouvez maintenant créer votre base de données :
    mkdir /var/www
    cd /var/www
    sqlite3 meteoPi.db

    Vous envoyez maintenant les commandes suivantes pour créer les différentes tables

    DROP TABLE IF EXISTS Emetteurs;
    CREATE TABLE Emetteurs(Id TEXT, Volt REAL, VoltUpdate TEXT, Localisation TEXT, DataFreq TEXT, Type TEXT);
    DROP TABLE IF EXISTS Mesures;
    CREATE TABLE Mesures(Id TEXT, Mesure REAL, DateMesure TEXT);
  6. Dans cette base il ne faudra pas oublier de créer un record par émetteur :
    INSERT INTO Emetteurs VALUES('ZZ', 0.0, '2013-01-01 00:00:00.000', 'Jardin', '0005M', 'Temp');

    Pour un deuxième émetteur par exemple :

    INSERT INTO Emetteurs VALUES('ZY', 0.0, '2013-01-01 00:00:00.000', 'Rez de chaussée', '0005M', 'Temp');
  7. Votre base de données est maintenant prête ! Voici le script en Python qui va stocker les captures des sondes :
    #!/usr/bin/env python
    # -*- coding: latin-1 -*-
    
    import serial
    import sqlite3
    
    from time import sleep, gmtime, strftime
    
    DEVICE = '/dev/ttyAMA0'
    BAUD = 9600
    
    con = None
    try:
        conn = sqlite3.connect('/var/www/meteoPi.db')
    
        print (strftime("%a, %d %b %Y %H:%M:%S: Début réception\n", gmtime()))
    
        ser = serial.Serial(DEVICE, BAUD)
        while True:
            print("%s: Checking..." % strftime("%a, %d %b %Y %H:%M:%S", gmtime()))
            n = ser.inWaiting()
            if n != 0:
                data = ser.read(n)
                nb_msg = len(data) / 12
                for i in range (0, nb_msg):
                    msg = data[i*12:(i+1)*12]
                    device = msg[1:3]
                    date_mesure = strftime("%Y-%m-%d %H:%M:%S.000", gmtime())
                    if msg[3:7] == "TMPA":
                        temp = msg[7:]
                        # INSERT de la mesure
                        sql_command = "INSERT INTO Mesures VALUES('"+device+"', "+temp+", '"+date_mesure+"');"
                        print("%s" % (sql_command))
                        conn.execute(sql_command)
                        conn.commit()
                    if msg[3:7] == "BATT":
                        voltage = msg[7:11]
                        if voltage == "LOW":
                            voltage = 0
                        # UPDATE du composant
                        sql_command = "UPDATE Emetteurs SET Volt="+voltage+", VoltUpdate='"+date_mesure+"' WHERE Id='"+device+"';"
                        print("%s" % (sql_command))
                        conn.execute(sql_command)
                        conn.commit()
                    print "Lignes misent à jour :", conn.total_changes
            sleep(1 * 30)
    
    except sqlite3.Error, e:
    
        print "Error %s:" % e.args[0]
        sys.exit(1)
    
    finally:
    
        if conn:
            conn.close()

    Les plus observateurs auront remarqué le test sur le message « BATT ». Tous les 10 envois vous allez recevoir le niveau de la batterie par message comme la température, par exemple 2.74 (sur les 3V de la batterie fournie avec l’émetteur).
    Si vous recevez la valeur LOW c’est qu’il est temps de changer la pile !

Vous avez maintenant un outil pour stocker toutes les mesures que vous allez capter !

Dernière étape : l’affichage dans un site web (beau de préférence) !

Vous aimerez aussi...

  • Shyne

    Bonjour,

    Je travaille sur un projet similaire en ce moment, et j’ai une question.. Penses-tu qu’il serait possible de remplacer le module XRF par une carte arduino, sur laquelle est cabler un transceiver 433Mhz standard, et du coup de faire des modules de prises de temperature, simple a base d’ATMega et de capteur de temperature DS18B20 ( histoire de baisser les coût de fabrication au maximum sans pour autant reduire les performances du systeme… )

    Merci d’avance.

    PS : Ton projet est tres interessant, transformant un simple Raspberry, en coeur complet d’une maison 2.0. Continue ^^

    • Salut,
      Comme ça je pense qu’il ne devrait pas y avoir de problème mais le mieux est d’aller voir sur où tu pourras poser des questions aux experts en électronique qui font quelque chose de similaire 😉

  • Blackmam3a

    Salut,
    Ton projet est très intéressant ! Je pense m’en inspirer fortement pour faire une station météo !

    J’ai cependant trois questions (je ne connais pas python) :
    – Ton script python écoute en continu la liaison série ?
    – Si le message envoyé par tes modules de température n’est pas reçu par le RPi, il se passe quoi ?
    – Le message « BATT » est envoyé automatiquement par les modules de température ?

    Cordialement,
    Blackmam3a

    • Blackmam3a

      J’ai trouvé la réponse à ma dernière question : In cyclic sleeping mode, the battery voltage is broadcast every 10 readings. The voltage is read at the point of tranmission so is always lower than the battery « at rest ».

    • Oui il écoute sur le port série. Toutes les 30 secondes il va lire le port.
      Pour les messages non reçu je pense qu’il ne se passe rien. Pas de ACK dans le protocole LLAP à priori mais pas certain.

      • Blackmam3a

        Je vais fouiller leur protocole à ce moment là !

        Par contre question bête, mais pourquoi écouter en continu le port série (malgré la veille), il serait pas plus judicieux d’utiliser par exemple NodeJS qui émet un évènement lorsque des données son disponible sur la liaison série ?

        • Blackmam3a

          Je me réponds de nouveau ! Dans la définition du protocole, il y a :
          RETRIES – Set the amount of messages to retry to get an ACK from the hub

          aXXRETRIESRR
          Set the amount of retries that uninitiated “sends” should try before giving up, default is 5 (number can be 00-99) this is set by RR

          Apparemment il y a bien un ACK ! Cependant il faudrait regarder les sources et sur leur github elles sont pas dispo. Je vais continuer de fouiller !

        • Parce que je ne savais même pas que c’était possible ! 😀
          S’il y a moyen de se passer du script python qui tourne à coté du serveur web alors je suis preneur !

          • Blackmam3a

            J’ai utilisé NodeJS pour justement ne pas écouter la liaison série en continue avec un programme C (même en utilisant des threads) et j’avais besoin du « pseudo temps réel ».

            Les deux librairies que j’ai utilisé si ça peut t’aider :
            – SerialPort : https://npmjs.org/package/serialport
            – SQlite3 : https://npmjs.org/package/sqlite3

          • Merci pour les pistes ! Je vais étudier ça.

          • Blackmam3a

            J’ai parcouru leur site et forum pour en savoir plus sur leur module RF et protocole.

            Ils parlent notamment de chiffrement AES mais sans donner d’information. Le défaut c’est que c’est du code propriétaire et donc dépendant de Ciseco :(. Leurs modules de température sont bien mais un module, une fonctionnalité… à 18€ j’espérais qu’on pouvait faire autre chose avec, mais non !

            Le seul avantage c’est qu’ils sont moins cher qu’un XBee mais assez loin en fonctionnalités !

          • Oui je suis d accord. La simplicité à un coût financier et une perte en terme d évolutivité. C’est pour moi le gros point noir de cette solution

  • remy

    salut,
    j’aurai besoin d’aide je ne comprend pas tout ton code.est ce que je pourrais avoir des explications plus detaillé de cette parti du code :

    if n != 0:
    data = ser.read(n)
    nb_msg = len(data) / 12
    for i in range (0, nb_msg):
    msg = data[i*12:(i+1)*12]
    device = msg[1:3]
    date_mesure = strftime(« %Y-%m-%d %H:%M:%S.000 », gmtime())
    if msg[3:7] == « TMPA »:
    temp = msg[7:]
    # INSERT de la mesure
    sql_command = « INSERT INTO Mesures VALUES(‘ »+device+ »‘, « +temp+ », ‘ »+date_mesure+ »‘); »
    print(« %s » % (sql_command))
    conn.execute(sql_command)
    conn.commit()

    • Salut
      Le code décode la trame recue pour la découper en éléments pour un stockage dans la DB : les 3 premiers caractères sont le numéro de la sonde, les 4 suivants doivent etre TMPA pour le type de mesure (temperature), les caractères après le 7ème sont ceux avec la mesure
      Ensuite on fait un insert dans la db

      • remy

        Merci pour ta réponse je comprend un peu mieu ton code, mais pourrais tu m’expliquer plus précisément cette parti :
        data = ser.read(n)
        nb_msg = len(data) / 12
        for i in range (0, nb_msg):
        msg = data[i*12:(i+1)*12]

        je ne comprends toujours pas trop cette parti. Dsl je débute en python.

        • Dans l’article d’assemblage j’explique que les messages font 12 octets. Je lis la totalité dans le buffer, découpe par paquets de 12. Puis ensuite je prends les 11 derniers caractères du message, le 1er étant toujours ‘a’. Les 2 suivants c’est le PanID et les 9 autres la valeur de la mesure

          • remy

            ah ok dsl j’avais survolé la parti assemblage car je n’utilise pas ce matériel. Je te remercie pour ton aide ça va me servir.