Mes passions, le boulots, mes coups de gueule...




Linux : Importer les données d’un Tempo Disc de BlueMaestro

Categorie : Domotique, Geek, Informatique, Linux, Raspberry Pi · par 4 Jan 2018

Introduction

Pour quelques dizaines d’euros, je viens d’acheter un Tempo Disc de marque BlueMaestro sur Amazon.

C’est un petit capteur de 33mm de diamètre sur 9,5mm d’épaisseur. Il permet de mesurer et d’enregistrer des données de température, d’humidité et de point de rosée. Il est alimenté par une pile (incluse) CR2032 facilement remplaçable qui devrait offrir une durée de vie de plus d’un an en utilisation normale. La portée sans fil est annoncée à 75 mètres en terrain dégagé et à environ 20 mètres en intérieur.

Le capteur de température et d’humidité est un SI7020 de Silicon Labs étalonné en usine. Voici ses caractéristiques :

  • Plage de température : -20º Celsius à +75º Celsius (précision de 0,4°C)
  • Plage d’humidité de 0% à 100% Humidité Relative. (précision de 4 % d’humidité relative)

Les données peuvent facilement être téléchargées par un smartphone ou une tablette (iOS 7 et supérieur et Android 4.3 et supérieur) où elles peuvent être analysées, les alertes traitées et les données exportées au moyen d’un fichier CSV pour une analyse plus poussée et la tenue de registres.

Structure des données

L’appareil utilise une puce nRF52 Bluetooth Low Energy de Nordic Semiconductor et communique en Bluetooth 4.1 (Bluetooth Low Energy).

Le constructeur indiques, sur cette page, comment les données sont transmises.

Le Tempo Disc se comporte en réalité comme une balise iBeacon qui transmet les données via un « advertisement packet ». La dimension de ce dernier étant limitée, il faudra envoyer un « scan response request » pour obtenir la suite des données.

Le constructeur documente également la structure des données dans les packets reçus :

Nous nous focaliserons sur l’ « advertisement packet » qui contient les données live qui nous intéressent. Et honnêtement, je n’ai pas encore trouvé comment envoyer la requête qui permet de recevoir la deuxième partie des données. Si vous trouvez, indiquez la marche à suivre dans les commentaires.

Si l’on utilise un outil comme GATT Browser sur iOS, on peut avoir un aperçu des données que nous allons devoir récupérer :

33 01 17 64 0e 10 00 15 00 de 01 e4 00 77 01 00 00 de 02 51 00 ab 01 cb 00 c2 02 18 00 00 00 00 00

Elles sont représentées en octets avec un offset commençant par l’identificateur Bluetooth SIG de la société BlueMaestro : 0x33 0x01.

Il suffira alors de les comparer au tableau ci-dessus (advertisement packet) pour comprendre à quoi sert chaque octet ou groupe d’octets.

Prérequis

Matériel

Il est nécessaire d’utiliser le Bluetooth Low Energy pour se connecter au Tempo Disc.

Le Raspberry Pi 3 en est équipé d’origine. Si vous utilisez un RPi de première ou de seconde génération, je vous conseille d’utiliser un dongle BLE comme celui-ci, vendu sur Amazon.

Logiciels

En principe, si vous utilisez un RPi 3, avec Jessie ou Stretch, les outils hcitool que nous allons utiliser plus loin devraient déjà être installés. Mais hcidump pourrait manquer à l’appel.
Pour plus d’infos sur l’installation, je vous renvoie vers cet article : https://thepihut.com/

$ sudo apt-get update
$ sudo apt-get upgrade
$ sudo apt-get install bluetooth bluez-utils bluez-hcidump blueman bluez

Acquisition des données

Il n’existe pas, à ma connaissance, de moyen simple sur Linux pour récupérer les données dont nous avons besoin.

hcitool / hcidump

Sur cette page, le contributeur explique comment utiliser le couple hcitool et hcidump pour récupérer les données brutes renvoyées par le capteur.

Script données brutes

Nous allons écrire un petit script pour voir ce qui est retourné :


#!/bin/bash

# On désactive le BT et on le réactive juste après pour éviter les erreurs
hciconfig hci0 down
hciconfig hci0 up

# Démarrage du scan BLE
# On fait tourner lescan durant 11sec
# SIGINT (équivalent à ctrl-c)
timeout -s SIGINT 11s hcitool lescan --duplicates > /dev/null &
sleep 1

# On s'assure que le scan a démarré
if [ "$(pidof hcitool)" ]; then
# On débute le scan des packets avec hcidump
timeout -s SIGINT 10s hcidump --raw
else
echo "ERREUR: Il semblerait que hcitool lescan n'a pas démarré correctement" >&2
exit 1
fi
 

On devrait obtenir quelque chose comme ceci :

HCI sniffer - Bluetooth packet analyzer ver 5.23
device: hci0 snap_len: 1500 filter: 0xffffffff
> 04 3E 1E 02 01 00 00 5E 36 12 58 75 11 12 02 01 02 0E 09 41 
 75 72 61 42 6F 78 2D 6C 69 67 68 74 9F 
> 04 3E 1E 02 01 00 00 5E 36 12 58 75 11 12 02 01 02 0E 09 41 
 75 72 61 42 6F 78 2D 6C 69 67 68 74 A2 
> 04 3E 27 02 01 00 00 B1 85 63 8D 7C C4 1B 02 01 06 03 02 95 
 FE 13 16 95 FE 71 20 98 00 D2 B1 85 63 8D 7C C4 0D 08 10 01 
 17 A5 
> 04 3E 19 02 01 04 00 B1 85 63 8D 7C C4 0D 0C 09 46 6C 6F 77 
 65 72 20 63 61 72 65 A5 
> 04 3E 2B 02 01 00 01 C8 FC E0 F7 F1 F7 1F 02 01 06 11 FF 33 
 01 17 64 0E 10 00 15 00 E0 01 DB 00 77 01 00 09 09 46 37 46 
 31 46 37 45 30 A8 
> 04 3E 29 02 01 04 01 C8 FC E0 F7 F1 F7 1D 1C FF 33 01 00 DE 
 02 51 00 AB 01 CB 00 DE 02 51 00 AB 01 CB 00 C2 02 18 00 00 
 00 00 00 A8 
> 04 3E 1E 02 01 00 00 5E 36 12 58 75 11 12 02 01 02 0E 09 41 
 75 72 61 42 6F 78 2D 6C 69 67 68 74 9E 
> 04 3E 1E 02 01 00 00 5E 36 12 58 75 11 12 02 01 02 0E 09 41 
 75 72 61 42 6F 78 2D 6C 69 67 68 74 9F 
> 04 3E 27 02 01 00 00 B1 85 63 8D 7C C4 1B 02 01 06 03 02 95 
 FE 13 16 95 FE 71 20 98 00 D2 B1 85 63 8D 7C C4 0D 08 10 01 
 17 A5 
> 04 3E 19 02 01 04 00 B1 85 63 8D 7C C4 0D 0C 09 46 6C 6F 77 
 65 72 20 63 61 72 65 A4 
> 04 3E 1E 02 01 00 00 5E 36 12 58 75 11 12 02 01 02 0E 09 41 
 75 72 61 42 6F 78 2D 6C 69 67 68 74 9E 
> 04 3E 0F 02 01 04 00 5E 36 12 58 75 11 03 02 01 02 9F 
> 04 3E 2B 02 01 00 01 C8 FC E0 F7 F1 F7 1F 02 01 06 11 FF 33 
 01 17 64 0E 10 00 15 00 E0 01 DA 00 77 01 00 09 09 46 37 46 
 31 46 37 45 30 B9 
> 04 3E 29 02 01 04 01 C8 FC E0 F7 F1 F7 1D 1C FF 33 01 00 DE 
 02 51 00 AB 01 CB 00 DE 02 51 00 AB 01 CB 00 C2 02 18 00 00 
 00 00 00 B9

J’ai surligné en rouge les données qui nous intéressent. Vous remarquerez que la première ligne des données surlignées se termine par 0x33 et le deuxième commence par 0x01. Ce sont les octets qui identifient BlueMaestro que nous avons vu plus haut.

Script complet

Maintenant que nous avons les données, il ne nous reste plus qu’à les récupérer et les mettre en forme.

Le script ci-dessous est largement inspiré de celui d’Elliot Larson que l’on peut trouver sur GitHub à cette adresse : https://gist.github.com/elliotlarson/1e637da6613dbe3e777c


#!/bin/bash

# Lecteur de données Tempo Disc BlueMaestro
# Ce script est inspiré de celui d'Elliot Larson disponible sur GitHub
# https://gist.github.com/elliotlarson/1e637da6613dbe3e777c

# Processus:
# 1. Arrêt et démarrage de HCI0
# 2. Démarrage de hcitool lescan
# 3. Démarrage de la lecture de données RAW avec hcidump
# 4. Réassemblage des données réparties sur plusieurs lignes
# 5. Vérification de l'entête du paquet de données.
#    - Si OK => Extraction des données
#            => STOP
#    - Si KO => On retourne et on continue

# Vérifie que l'on est en Root
if [[ $EUID -ne 0 ]];
then
  echo "Ce script doit être exécuté avec les privilèges administrateur"
  exit 1
fi

halt_hcitool_lescan() {
  sudo pkill --signal SIGINT hcitool
}

trap halt_hcitool_lescan INT

process_complete_packet() {
  # Exemple de packet :
  # > 04 3E 2B 02 01 00 01 C8 FC E0 F7 F1 F7 1F 02 01 06 11 FF 33 01 17 64 0E 10 00 10 00 C1 02 1F 00 66 01 00 09 09 46 37 46 31 46 37 45 30 B9
  
  local packet=${1//[\ |>]/}

  # Si le packet ne correspond pas à celui qu'on recherche, on retourne
  # Si il correspond, on extrait les données
  if [[ ! $packet =~ ^043E2B0201.{24}11FF ]]; then

    return

  else

  # Supprime le début du packet qui ne nous intéresse pas pour garder ce qui commence par 3301
  # 330117640E10001000C1021F0066010009094637463146374530B9
  packet=${packet:38:54}

  echo "Packet complet :" $packet
  echo

  # Lorsque la température ou le point de rosée sont négatifs, les 2 octets affichent :
  # FF FF pour -0.1 °C / F6 FF pour -1.0 °C / 9C FF pour -10.0 °C
  # Soit la formule : Température = -65536 + Température
  # J'ai arbitrairement fixé la température pivot à 32768 soit 3276.8 °C !!!

  battery_lvl=$((0x${packet:6:2}))
  time_interval=$((0x${packet:8:4}))
  stored_log_count=$((0x${packet:12:4}))
  current_temp=$((0x${packet:16:4}))
  if [[ $current_temp -gt 32768 ]];
  then
      current_temp=$((-65536 + $current_temp))
  fi
  # Division par 10 avec 1 chiffre après la virgule
  current_temp=$(echo "scale=1; $current_temp/10" | bc)
  # Forcer un "0" avant le point décimal pour éviter par exemple ".1" ou "-.1" 
  current_temp=$(echo $current_temp | bc -l | sed -e 's/^-\./-0./' -e 's/^\./0./')
  current_humidity=$((0x${packet:20:4}))
  current_humidity=$(echo "scale=1; $current_humidity/10" | bc)
  current_dew_point=$((0x${packet:24:4}))
  if [[ $current_dew_point -gt 32768 ]];
  then
      current_dew_point=$((-65536 + $current_dew_point))
  fi
  # Division par 10 avec 1 chiffre après la virgule
  current_dew_point=$(echo "scale=1; $current_dew_point/10" | bc)
  # Forcer un "0" avant le point décimal pour éviter par exemple ".1" ou "-.1" 
  current_dew_point=$(echo $current_dew_point | bc -l | sed -e 's/^-\./-0./' -e 's/^\./0./')
  mode=$((0x${packet:28:2}))
  breach_count=$((0x${packet:30:2}))
  name_identifier=$((0x${packet:32:2}))
  length_of_name=$((0x${packet:34:2}))
  name_of_device=$(echo ${packet:36:16} | xxd -r -p)

  echo "Battery (%) :" $battery_lvl
  echo "Time Interval (sec) :" $time_interval
  echo "Stored Log Cont :" $stored_log_count
  echo "Current Temp (°C) :" $current_temp
  echo "Current Humidity (%) :" $current_humidity
  echo "Current Dew Point (°C) :" $current_dew_point
  echo "Mode :" $mode
  echo "Breach Count :" $breach_count
  echo "Name Identifier :" $name_identifier
  echo "Length of Name :" $length_of_name
  echo "Name of Device :" $name_of_device

  exit 0

  fi
}

read_blescan_packet_dump() {
  # Les packets sont répartis sur plusieurs lignes et ont besoin d'être reconstitués
  # Exemple :
  # > 04 3E 2B 02 01 00 01 C8 FC E0 F7 F1 F7 1F 02 01 06 11 FF 33 
  #   01 17 64 0E 10 00 10 00 C1 02 1F 00 66 01 00 09 09 46 37 46 
  #   31 46 37 45 30 B9 
  packet=""
  while read line; do
    # Les packets qui débutent par : ">"
    if [[ $line =~ ^\> ]]; then
      # Traite le paquet terminé (sauf si c'est la première fois que c'est terminé)
      if [ "$packet" ]; then
        process_complete_packet "$packet"
      fi
      # Commencer un nouveau packet
      packet=$line
    else
      # Continuer à construire le packet
      packet="$packet $line"
    fi
  done
}

# On désactive le BT et on le réactive juste après pour éviter les erreurs
hciconfig hci0 down
hciconfig hci0 up

# Démarrage du scan BLE
# Si le périphérique BLE n'est pas trouvé dans les 10 sec on arrête lescan
# SIGINT (équivalent à ctrl-c)
timeout -s SIGINT 11s hcitool lescan --duplicates > /dev/null &
sleep 1

# On s'assure que le scan a démarré
if [ "$(pidof hcitool)" ]; then
  # On débute le scan des packets avec hcidump et on stream le tout à un process
   timeout -s SIGINT 10s hcidump --raw | read_blescan_packet_dump
else
  echo "ERREUR: Il semblerait que hcitool lescan n'a pas démarré correctement" >&2
  exit 1
fi

Vous pouvez télécharger ce script en cliquant sur ce lien.

Pour le rendre exécutable :

$ chmod +x temp-disc.bash

Et pour l’exécuter :

$ sudo ./temp-disc.bash

Vous devriez obtenir ce genre de données :

Packet complet : 330117640E10001300D701F90074010009094637463146374530AC

Battery (%) : 100
Time Interval (sec) : 3600
Stored Log Cont : 19
Current Temp (°C) : 21.5
Current Humidity (%) : 50.5
Current Dew Point (°C) : 11.6
Mode : 1
Breach Count : 0
Name Identifier : 9
Length of Name : 9
Name of Device : F7F1F7E0

Laisser un commentaire