L’application ST BLE Sensor et Blue-ST

Ce tutoriel aborde la communication BLE entre une NUCLEO-WB55 et un smartphone avec l’application ST BLE Sensor. Plus précisément, il explique comment :

  • Mesurer une température avec la carte d’extension X-NUCLEO IKS01A3 et l’envoyer pour affichage à ST BLE Sensor.
  • Allumer ou éteindre une LED de la NUCLEO-WB55 depuis ST BLE Sensor

L’application ST BLE Sensor transforme votre smartphone en central BLE avec l’implémentation Blue-ST de GATT développée par STMicroelectronics. Le Software Development Kit (SDK) Blue-ST propose une définition et une gestion des caractéristiques et de leurs UUID, détaillées dans ce document.

Le schéma de principe suivant résume le cas d’usage que nous allons réaliser :


STBLESensor use case


La carte NUCLEO-WB55 et l’application ST BLE Sensor, après une phase d’advertising GAP (non représentée) on établi une connexion. C’est le protocole GATT, illustré ici, qui régit les échanges. La NUCLEO-WB55 est désormais un périphérique serveur du service ST_APP_SERVICE lequel contient deux caractéristiques, qui sont partagées avec le smartphone devenu un central client :

  • La caractéristique température régulièrement modifiée par le périphérique possède l’attribut “notify” : cela signifie que le central sera informé des modifications de sa valeur par le périphérique (ces notifications sont réalisées par des interruptions qui surviennent dans l’application ST BLE Sensor). Le smartphone pourra ainsi afficher les variations de la température au cours du temps.
  • La caractéristique interrupteur possède les attributs “notify” et “write”. Elle signalera donc ses changements au central (par “notify”) et elle autorisera celui-ci à modifier sa valeur (par “write”). C’est comme cela que l’application ST BLE Sensor pourra envoyer les commandes on/off de la LED à la NUCLEO-WB55.

Matériel requis :

  1. La carte NUCLEO-WB55
  2. La carte d’extension X-NUCLEO IKS01A3
  3. Un smartphone Android ou iOS avec l’application ST BLE Sensor installée

Installation de ST BLE Sensor sur votre smartphone

Vous trouverez ST BLE Sensor sur Google Play ou IOS Store



La description complète des différents services proposés par l’application ST BLE Sensor est disponible ici : https://github.com/STMicroelectronics/STBlueMS_Android

Les codes MicroPython

Les scripts présentés ci-après sont disponibles dans la zone de téléchargement.

Pour ce tutoriel, l’implémentation BLE repose sur deux scripts :

  1. ble_advertising.py une bibliothèque de fonctions qui seront utilisées pour construire les trames d’avertising du protocole GAP, lancé pour et avant la connexion à un central.
  2. ble_sensor.py, une bibliothèque qui appelle ble_advertising.py pour démarrer le protocole GAP et qui implémente aussi le protocole GATT. C’est ce fichier qui paramétrise les services, les caractéristiques et leurs UUID conformément à Blue-ST. Grâce au fichier ble_sensor.py nous allons pouvoir créer un objet BLE ayant 1 service et 2 caractéristiques.C’est ce fichier qu’il faudra modifier pour changer le profil BLE, si besoin.

Ces fichiers sont construits avec la classe Microbluetooth documentée ici et adaptés des scripts disponibles ici. Leur structure est assez abstraite et leur analyse complète dépasserait le cadre de l’initiative éducative STM32python. Nous allons cependant regarder plus en détails ble_sensor.py via le listing commenté ci-après.

Deux autres scripts seront nécessaires pour notre application :

  1. hts221.py, la bibliothèque qui permet de lire la température sur le capteur MEMS HTS221 de la X-NUCLEO IKS01A3.
  2. main.py, le script qui contiendra le code du programme utilisateur et qui importera ble_advertising.py, ble_sensor.py et HTS221.py.

Comme pour tous les autres tutoriels il faudra inclure ces quatre fichiers dans le disque USB “PYBFLASH” associé à l’espace de stockage de l’USB USER de la NUCLEO-WB55.

Revue de ble_sensor.py

Voici le listing commenté de ble_sensor.py. Les commentaires les plus importants, qui vous seront utiles pour adapter ce script à d’autres cas d’usages en lui rajoutant des caractéristiques, sont ceux qui expliquent le calcul de _FEATURE_MASK. L’application ST BLE Sensor ne décodera correctement les messages émis par la NUCLEO-WB55 que si ceux-ci sont rédigés dans le respect du protocole Blue-ST, il vous faudra notamment être attentif au bon codage de la fonction Python “pack” qui permet de construire la payload (fr : charge utile) des caractéristiques.

# Objet du script : Implémentation du protocole GATT Blue-ST pour un périphérique
# Définition d'un service _ST_APP_SERVICE avec deux caractéristiques :
# 1 - SWITCH : pour éteindre et allumer une LED du périphérique depuis un central
# 2 - TEMPERATURE : pour envoyer une mesure de température du périphérique à un central 

import bluetooth # Pour gérer le BLE
from ble_advertising import advertising_payload # Pour gérer l'advertising GAP
from struct import pack # Pour agréger les octets envoyés par les trames BLE
import pyb # Pour gérer les LED

# Constantes définies pour le protocole Blue-ST
_IRQ_CENTRAL_CONNECT    = const(1)
_IRQ_CENTRAL_DISCONNECT = const(2) 
_IRQ_GATTS_WRITE        = const(3)

# Pour les UUID et les codes, on se réfère à la documentation du SDK Blue-ST disponible ici :
# https://www.st.com/resource/en/user_manual/dm00550659-getting-started-with-the-bluest-protocol-and-sdk-stmicroelectronics.pdf.

# 1 - Définition du service personnalisé selon le SDK Blue-ST

# Indique que l'on va communiquer avec une appli conforme au protocole Blue-ST :
_ST_APP_UUID = bluetooth.UUID('00000000-0001-11E1-AC36-0002A5D5C51B')

# UUID d'une caractéristique de température
_TEMPERATURE_UUID = (bluetooth.UUID('00040000-0001-11E1-AC36-0002A5D5C51B'), bluetooth.FLAG_NOTIFY)

# UUID d'une caractéristique d'interrupteur
_SWITCH_UUID = (bluetooth.UUID('20000000-0001-11E1-AC36-0002A5D5C51B'), bluetooth.FLAG_WRITE|bluetooth.FLAG_NOTIFY)

# Le service contiendra ces deux caractéristiques
_ST_APP_SERVICE = (_ST_APP_UUID, (_TEMPERATURE_UUID, _SWITCH_UUID))

# 2 - Construction de la trame (contenu du message) d'avertising GAP
_PROTOCOL_VERSION = const(0x01) # Version du protocole
_DEVICE_ID = const(0x80) # Carte NUCLEO générique
_DEVICE_MAC = [0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC] # Adresse matérielle MAC fictive
_FEATURE_MASK = const(0x20040000) # Services sélectionnés : température (2^18) et interrupteur (2^29)

# Explication du calcul du masque déterminant les caractéristiques du service actif (_FEATURE_MASK)
# A chaque caractéristique est associé un code binaire. On doit simplement sommer les codes de toutes les caractéristiques
# que l'on souhaite exposer avec GATT :

# Caractéristique SWITCH : code = 2^29 =      100000000000000000000000000000 (en binaire) = 20000000  (en hexadécimal)
# Caractéristique TEMPERATURE : code = 2^18 = 000000000001000000000000000000 (en binaire) = 40000     (en hexadécimal)
# _FEATURE_MASK = SWITCH + TEMPERATURE =      100000000001000000000000000000 (en binaire) = 20040000  (en hexadécimal)

# Trame d'avertising : concaténation des informations avec la fonction Micropython "pack" 
# La chaîne '>BBI6B' désigne le format des arguments, voir la documention de pack ici : https://docs.python.org/3/library/struct.html
_MANUFACTURER = pack('>BBI6B', _PROTOCOL_VERSION, _DEVICE_ID, _FEATURE_MASK, *_DEVICE_MAC)

# Initialisation des LED
led_rouge = pyb.LED(1)
led_bleue = pyb.LED(3)

class BLESensor:

	# Initialisation, démarrage de GAP et publication radio des trames d'advertising
	def __init__(self, ble, name='WB55-MPY'):
		self._ble = ble
		self._ble.active(True)
		self._ble.irq(self._irq)
		((self._temperature_handle,self._switch_handle),) = self._ble.gatts_register_services((_ST_APP_SERVICE, ))
		self._connections = set()
		self._payload = advertising_payload(name=name, manufacturer=_MANUFACTURER)
		self._advertise()
		self._handler = None

	# Gestion des évènements BLE...
	def _irq(self, event, data):
		# Si un central a envoyé une demande de connexion
		if event == _IRQ_CENTRAL_CONNECT:
			conn_handle, _, _, = data
			# Se connecte au central (et arrête automatiquement l'advertising)
			self._connections.add(conn_handle)
			print("Connecté")
			led_bleue.on() # Allume la LED bleue

		# Si le central a envoyé une demande de déconnexion
		elif event == _IRQ_CENTRAL_DISCONNECT:
			conn_handle, _, _, = data
			self._connections.remove(conn_handle)
			# Relance l'advertising pour permettre de nouvelles connexions
			self._advertise()
			print("Déconnecté")

		# Si une écriture est détectée dans la caractéristique SWITCH (interrupteur) de la LED
		elif event == _IRQ_GATTS_WRITE:
			conn_handle, value_handle, = data
			if conn_handle in self._connections and value_handle == self._switch_handle:
				
				# Lecture de la valeur de la caractéristique
				data_received = self._ble.gatts_read(self._switch_handle)
				self._ble.gatts_write(self._switch_handle, pack('<HB', 1000, data_received[0]))
				self._ble.gatts_notify(conn_handle, self._switch_handle)
				# Selon la valeur écrite, on allume ou on éteint la LED rouge
				if data_received[0] == 1:
					led_rouge.on() # Allume la LED rouge
				else:
					led_rouge.off() # Eteint la LED rouge

	# On écrit, dans la caractéristique "temperature", le timestamp (horodatage) et la valeur de la température
	def set_data_temperature(self, timestamp, temperature, notify):
		self._ble.gatts_write(self._temperature_handle, pack('<Hh', timestamp, temperature))
		if notify:
			for conn_handle in self._connections:
				# Signale au Central (le smartphone) que la caractéristique vient d'être écrite et peut être lue
				self._ble.gatts_notify(conn_handle, self._temperature_handle)

	# Démarre l'advertising avec une période de 5 secondes, précise qu'un central pourra se connecter au périphérique
	def _advertise(self, interval_us=500000):
		self._ble.gap_advertise(interval_us, adv_data=self._payload, connectable=True)
		led_bleue.off() # Eteint la LED bleue

Revue de main.py

Concentrons nous à présent sur le script principal :

# Objet du script :
# Communication en BLE d'une valeur de température lue sur une carte d'extension X-NUCLEO IKS01A3.
# Affichage sur l'application smartphone ST BLE Sensor.
# Possibilité de commander une LED de la carte NUCLEO-WB55.
# Matériel : 
#  - Une carte NUCLEO-WB55
#  - Une carte d'extension  X-NUCLEO IKS01A3

import time # Pour gérer le temps et les temporisations
import bluetooth # Pour gérer le protocole BLE 
import ble_sensor # Pour l'implémentation du protocole GATT Blue-ST
import hts221 # Pour gérer le capteur MEMS HTS221 

from machine import I2C

# On utilise l'I2C n°1 de la carte NUCLEO-W55 pour communiquer avec les capteurs
i2c = I2C(1)

# Pause d'une seconde pour laisser à l'I2C le temps de s'initialiser
time.sleep_ms(1000)

# Liste des adresses I2C des périphériques présents
print("Adresses I2C utilisées : " + str(i2c.scan()))

# Instance du HTS221
sensor = hts221.HTS221(i2c)

ble = bluetooth.BLE() # Instance de la classe BLE
ble_device = ble_sensor.BLESensor(ble) # Instance de la classe Blue-ST

while True: # Boucle sans clause de sortie

	# Horodatage
	timestamp = time.time()

	# valeur de température
	temp = sensor.temperature()

	stime = str(timestamp)
	stemp = str(round(temp,1))
	# Multiplie par 10 la température pour la coder sous forme d'entier en conservant une décimale 
	# (norme adoptée par Blue-ST).
	ble_temp = int(temp*10)

	# Affichage sur le port série de l'USB USER
	print("Horodatage : " + stime + " Température : " + stemp + " °C")

	# Envoi en BLE de l'horodatage et de la température en choisissant de notifier l'application
	ble_device.set_data_temperature(timestamp, ble_temp, notify=1) 

	# Temporisation d'une seconde
	time.sleep_ms(1000)

Connexion avec ST BLE Sensor

Une fois le script principal lancé, la NUCLEO-WB55 émet des trames BLE de type “advertising” ; ces messages permettent d’identifier l’objet Bluetooth et de signifier qu’il est prêt à être connecté.

Le nom du périphérique est : “WB55-MPY”.Nous allons vérifier avec l’application smartphone que la carte est en émission Bluetooth.

Utilisation

Lancez l’application STBLESensor sur votre smartphone :

STBLESensor 1


Appuyez ensuite sur l’icône loupe pour afficher les périphériques BLE environnants

STBLESensor 2


Dans cet exemple, le profil BLE que nous avons choisi nous permet de simuler un thermomètre et d’allumer ou d’éteindre une LED. La valeur du thermomètre est générée aléatoirement toutes les secondes.

Connectez-vous à la carte de développement en appuyant sur “WB55-MPY”:

STBLESensor 3


La LED bleue de la carte NUCLEO-WB55 doit s’allumer lorsqu’elle est connectée à l’application.

Nous pouvons observer, sur cet écran, l’évolution de la température.

Il est possible d’afficher la température en mode graphique.

Pour cela appuyez sur le bouton menu image :

STBLESensor 4


Appuyez maintenant sur image :

STBLESensor 5


Pour afficher le graphique, appuyez sur image :

STBLESensor 6


Vous pouvez utiliser le bouton image pour modifier les options du graphique, comme la taille de l’axe X ou l’activation du changement automatique de l’échelle en Y.

Nous allons maintenant étudier l’envoi d’une information depuis le smartphone vers la NUCLEO-WB55. Pour cela nous utilisons l’application pour allumer ou éteindre une la LED rouge du kit de développement.

Appuyez sur le bouton menu image :

STBLESensor 7


Choisissez maintenant l’option image :

STBLESensor 8


Vous pouvez sur cet écran piloter la LED Rouge de la NUCLEO-WB55.

Pour aller plus loin

Le tutoriel Station environnementale Blue-ST montre comment ajouter encore deux caractéristiques dans ble_sensor.py et construire une petite station de mesures environnementales connectée.