TI Sensortag : Récupérer les données en BLE (partie 2)

11 aout 2014 rdorigny 0 commentaires

Je vous avais présenté le TI Sensortag CC2541 par l'article précédent. C'est un petit bijou de technologie qui regroupe pas moins de six capteurs et dispose d'un émetteur/récepteur Bluetooth 4.0 Low Energy (BLE).

Dans cette article, nous allons voir comment automatiser une connexion BLE pour récupérer les données des capteurs.



1)Projet de réalisation

Mon objectif est de réaliser une station météo d'intérieur chargé de mesurer la température, la pression et l'humidité de la maison. Pour cela, je compte mettre en place l'architecture ci-dessous:

L'idée principale est de dédier un raspberry pi comme centrale domotique. Ce mini ordinateur fonctionne sous Linux et consomme très peu d'énergie (ce qui est intéressant pour ne pas exploser ma facture d'électricité).

Donc:
  • Première étape : Le raspberry pi sera chargé de récupérer les données,
  • Seconde étape: Le raspberry pi transmet les données à un serveur apache-mysql (mon hébergeur) pour stocker les mesures des capteurs dans une base de données,
  • Troisième étape : On accède aux données par une application web ou App Android.
  • Voyons comment pour réaliser la première étape.

    2)Récupération des données

    2.1)Genèse de la solution

    Au niveau de la connexion sur le Sensortag, j'ai choisis d'utiliser une connexion classique en Bluetooth et non en IBeacon. Sachez néanmoins que le Sensortag est capable d'utiliser ce mécanisme : http://processors.wiki.ti.com/index.php/SensorTag_with_iBeacon

    La première chose que j'ai essayé de faire, c'est de trouver de la documentation sur l'API du Bluez du stack Linux. Et bien..., impossible de trouver une documentation sur ce sujet! C'est même presque scandaleux, s'afficher comme le "Official Linux Bluetooth protocol stack" et ne fournir aucune documentation pour les développeurs, que fait Linus Torvalds! Bref,en recherchant une solution, je me suis rabattu sur un script Python grâce à Michael Saunby. Petit hic, je ne connaissais pas ce langage il y a encore un mois, donc j'ai un peu bouquiné pendant les vacances... Python est un langage de script assez simple à apprendre si vous déjà quelques notions en langage de programmation, son apprentissage sera rapide.

    Sur le dernier article, je vous avais montré comment obtenir les valeurs en hexa des capteurs puis averti que Texas Instruments propose une page web de documentation (http://processors.wiki.ti.com/index.php/SensorTag_User_Guide) où l'on trouve comment sont structurées les données et surtout l’algorithme de calcul des mesures pour chaque capteur. Donc, l'idée est d'interroger par un script Python la sonde en utilisant les commande shell de Bluez.

    2.2)Le script

    #***************************************************************************************************** # # Robert DORIGNY - www.doritique.fr le 6 aout 2014 # # Ce script est charge de recuperer les donnees des sondes du TI Sensortag CC2541 via une connexion # bluetooth low energy (BLE). N oubliez pas de modifier l'adresse BLE de votre Sensortag, et d'appuyer # sur le side button pour allumer le tag. # #***************************************************************************************************** import os import sys import pexpect import time import urllib2 import urllib #Adresse BLE du sensortag (pour la connaitre tapez la commande hcitool lescan) ble_addr="BC:6A:29:AC:63:CD" tosigned = lambda n: float(n-0x10000) if n>0x7fff else float(n) tosignedbyte = lambda n: float(n-0x100) if n>0x7f else float(n) #Fonction de transformation de valeur hexa en float def floatfromhex(h): t = float.fromhex(h) if t > float.fromhex('7FFF'): t = -(float.fromhex('FFFF') - t) pass return t #Classe du sensortag class Sensortag: #Le constructeur def __init__(self,ble_addr): self.ble_addr=ble_addr self.child = pexpect.spawn('gatttool -b ' + ble_addr + ' --interactive') self.child.expect('[LE]>') print "Tentative de connection sur le sensortag..." self.child.sendline('connect') self.child.expect('[CON].*>') #Recup valeurs de calibration du barometre self.child.sendline('char-write-cmd 0x4f 02') #Active la lecture des datas de calibration self.child.expect('[LE]>') self.child.sendline('char-read-hnd 0x52') self.child.expect('descriptor: .*') rval=self.child.after.split() mycalib=[] for i in range(1,17) : mycalib.append(int(rval[i],16)) #print mycalib self.barometer = Barometer(mycalib) return #Getter de la temperature IR def get_IRtmp(self): #Active la sonde self.child.sendline('char-write-cmd 0x29 01') self.child.expect('[LE]>') self.child.sendline('char-read-hnd 0x25') self.child.expect('descriptor: .*') rval = self.child.after.split() objT = floatfromhex(rval[2] + rval[1]) ambT = floatfromhex(rval[4] + rval[3]) #Desactive la sonde #self.child.sendline('char-write-cmd 0x29 00') return(self.IRTmp(objT, ambT)) #Getter de l humidite et de la temperature def get_HUM(self): self.child.sendline('char-write-cmd 0x3C 01') self.child.expect('[LE]>') self.child.sendline('char-read-hnd 0x38') self.child.expect('descriptor: .*') rval=self.child.after.split() #print rval self.child.sendline('char-write-cmd 0x3C 00') return(self.HUMtmp(floatfromhex(rval[2]+rval[1])),self.HUMhum(floatfromhex(rval[4]+rval[3]))) def get_PRES(self): #Recup valeurs de pression et calcul self.child.sendline('char-write-cmd 0x4f 01') #Active la lecture des datas self.child.expect('[LE]>') self.child.sendline('char-write-cmd 0x4C 0x01 00') #Active la lecture des datas self.child.expect('[LE]>') self.child.sendline('char-read-hnd 0x4b') self.child.expect('descriptor: .*') rval=self.child.after.split() #print rval temp=int(rval[2] + rval[1],16) pres=int(rval[4] + rval[3],16) return self.barometer.calc(temp,pres) #Calcul de la temperature par le capteur IR def IRTmp(self,objT, ambT): m_tmpAmb = ambT/128.0 Vobj2 = objT * 0.00000015625 Tdie2 = m_tmpAmb + 273.15 S0 = 6.4E-14 # Calibration factor a1 = 1.75E-3 a2 = -1.678E-5 b0 = -2.94E-5 b1 = -5.7E-7 b2 = 4.63E-9 c2 = 13.4 Tref = 298.15 S = S0*(1+a1*(Tdie2 - Tref)+a2*pow((Tdie2 - Tref),2)) Vos = b0 + b1*(Tdie2 - Tref) + b2*pow((Tdie2 - Tref),2) fObj = (Vobj2 - Vos) + c2*pow((Vobj2 - Vos),2) temp = pow(pow(Tdie2,4) + (fObj/S),.25) temp = (temp - 273.15) #print "%.2f C" % temp #sendval(temp) return temp #Calcul de la temperature par le capteur d humidite def HUMtmp(self,temp): t = -46.85 + 175.72/65536.0 * temp #print "%.2f C" % t return t #Calcul de l humidite def HUMhum(self,hum): hum = float(int(hum) & ~0x0003); h = -6.0 + 125.0/65536.0 * hum #print "%.2f pourcents" % h return abs(h) #Classe particuliere pour le barometre class Barometer: def __init__(self, rawCalibration): self.m_barCalib = self.Calib( rawCalibration ) return def calc(self, rawT, rawP): self.m_raw_temp = tosigned(rawT) self.m_raw_pres = rawP bar_temp = self.calcBarTmp( self.m_raw_temp ) bar_pres = self.calcBarPress( self.m_raw_temp, self.m_raw_pres ) return( bar_temp, bar_pres) def calcBarTmp(self, raw_temp): c1 = self.m_barCalib.c1 c2 = self.m_barCalib.c2 val = long((c1 * raw_temp) * 100) temp = val >> 24 val = long(c2 * 100) temp += (val >> 10) return float(temp) / 100.0 def calcBarPress(self,Tr,Pr): c3 = self.m_barCalib.c3 c4 = self.m_barCalib.c4 c5 = self.m_barCalib.c5 c6 = self.m_barCalib.c6 c7 = self.m_barCalib.c7 c8 = self.m_barCalib.c8 # Sensitivity s = long(c3) val = long(c4 * Tr) s += (val >> 17) val = long(c5 * Tr * Tr) s += (val >> 34) # Offset o = long(c6) << 14 val = long(c7 * Tr) o += (val >> 3) val = long(c8 * Tr * Tr) o += (val >> 19) # Pression en Pa pres = ((s * Pr) + o) >> 14 return float(pres)/100.0 class Calib: def bld_int(self, lobyte, hibyte): return (lobyte & 0x0FF) + ((hibyte & 0x0FF) << 8) def __init__( self, pData ): self.c1 = self.bld_int(pData[0],pData[1]) self.c2 = self.bld_int(pData[2],pData[3]) self.c3 = self.bld_int(pData[4],pData[5]) self.c4 = self.bld_int(pData[6],pData[7]) self.c5 = tosigned(self.bld_int(pData[8],pData[9])) self.c6 = tosigned(self.bld_int(pData[10],pData[11])) self.c7 = tosigned(self.bld_int(pData[12],pData[13])) self.c8 = tosigned(self.bld_int(pData[14],pData[15])) #*******************************************Fonction principale************************************ def main(): #Creation de l instance et connection sur le sensortag sensortag=Sensortag(ble_addr) while True: #Recuperation de la temperature infra rouge tmpIR=sensortag.get_IRtmp() print "TmpIR : %.2f" % tmpIR #Recuperation de la temperature et l humidite TabHUM=[] TabHUM=sensortag.get_HUM() print "TmpHUM %.2f" % TabHUM[0] print "HUM %.2f" % TabHUM[1] #Recuperation de la temperature et de la pression TabPRES=sensortag.get_PRES() print "TmpPRES %.2f" % TabPRES[0] print "PRES %.2f" % TabPRES[1] #Transmission des donnees time.sleep(10) if __name__ == "__main__": main()

    Ce qui affichera:

    J'ai laissé le script faire trois mesures pour simplifier l'affichage, à noter que la première mesure du capteur de température infra-rouge renvoie toujours 2.45°C. Heureusement, la seconde est bonne..., je n'ai pas compris pourquoi ce bug. Si quelqu'un à une idée, je suis preneur.

    2.3)Explications

    Quelques explications sur le fonctionnement de ce script:
  • J'ai créé une classe Sensortag pour modéliser le tag. Le constructeur est chargé d'initier la connexion, et les fonctions dites getters sont chargées de retourner les valeurs décimales des mesures,
  • La classe Barometer est spécifique au baromètre, elle est composée d'une classe interne Calib pour réaliser la récupération des données de calibration de l'instrument de mesure,
  • Le programme principal instancie la classe Sensortag, puis ouvre une boucle infinie sur la récupération des données à intervalle régulier (10 secondes).

  • Conclusion

    Voilà pour cet article. Sur le prochain, nous allons voir comment transmettre les données et traiter la récupération/transmission des données par l'intermédiaire de Threads. Pourquoi des threads? Pour transmettre toutes les opérations risquées à un autre processus que le programme principal. L'intérêt est simple, éviter de planter notre programme principal. Il s'agira ainsi de fiabiliser et pérenniser le bon fonctionnement de notre script.









    Pseudonyme (obligatoire) :
    Adresse mail (obligatoire) :
    Site web :




    © 2024 www.doritique.fr par Robert DORIGNY