Communication avec MIT App Inventor pour échanger des données de température et d’humidité

Ce tutoriel montre comment mettre en place une connexion et un échange de données entre la NUCLEO-WB55 et l’application MIT App Inventor en utilisant un service environnemental construit avec le standard Bluetooth SIG. Le schéma de principe est le suivant :


MIT APP Inventor 2 use case


Matériel requis

  1. La carte NUCLEO-WB55
  2. Un smartphone Android pour y installer l’application que nous avons créée avec MIT App Inventor.

Les codes MicroPython pour le périphérique

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. main.py, le script contenant le programme principal mais surtout la classe BLEenvironment qui permet de lancer l’advertising (protocole GAP) puis de créer un périphérique exposant un service environnemental avec deux caractéristiques : température et humidité (protocole GATT) construites selon le standard Bluetooth SIG. Notons que nous simulerons les valeurs de température et d’humidité avec un générateur de nombres aléatoires, nous n’aurons donc pas besoin d’une bibliothèque en plus pour gérer des capteurs.

Comme pour tous les autres tutoriels il faudra inclure ces fichiers dans le disque USB PYBFLASH associé à l’espace de stockage de l’USB USER de la NUCLEO-WB55. Voci le contenu de main.py :

# Cet exemple montre comment programmer un périphérique BLE GATT avec le standard Bluetooth SIG
# pour envoyer des mesures de température et d'humidité à l'aide d'un service contenant deux
# caractéristiques.
# Les mesures sont simulées avec un générateur de nombres aléatoires puis mises à jour toutes 
# les cinq secondes par le périphérique, et notifiées à la même fréquence à un central éventuellement connecté.

import bluetooth # Pour la gestion du BLE
import random # Pour la génération de valeurs aléatoires
from struct import pack # Pour construire les "payloads" des caractéristiques BLE, en aggrégeant des octets
import time # Pour la gestion du temps et des temporisations
from ble_advertising import advertising_payload # Pour construire des trames d'advertising
from binascii import hexlify # Convertit une donnée binaire en sa représentation hexadécimale

# Constantes pour construire les services BLE
_IRQ_CENTRAL_CONNECT = const(1)
_IRQ_CENTRAL_DISCONNECT = const(2)
_IRQ_GATTS_INDICATE_DONE = const(20)

_FLAG_READ = const(0x0002)
_FLAG_NOTIFY = const(0x0010)
_FLAG_INDICATE = const(0x0020)

# Identifiant SIG du service de données environnementales.
# Voir org.bluetooth.service.environmental_sensing
_ENV_SENSE_UUID = bluetooth.UUID(0x181A)

# Identifiant SIG de la caractéristique de température.
# Voir org.bluetooth.characteristic.temperature
_TEMP_CHAR = (
	bluetooth.UUID(0x2A6E),
	# La caratéristique peut être lue, se notifier et "s'indiquer"
	_FLAG_READ | _FLAG_NOTIFY | _FLAG_INDICATE,
)

# Identifiant SIG de la caractéristique d'humidité.
# Voir org.bluetooth.characteristic.temperature
_HUMI_CHAR = (
	bluetooth.UUID(0x2A6F),
	# La caratéristique peut être lue, se notifier et "s'indiquer"
	_FLAG_READ | _FLAG_NOTIFY | _FLAG_INDICATE,
)

# Construction d'un service à deux caractéristiques.
_ENV_SENSE_SERVICE = (
	_ENV_SENSE_UUID,
	(_TEMP_CHAR,_HUMI_CHAR,),
)

# Icône associée à un advertiser (GAP) de données environnementales.
# Voir org.bluetooth.characteristic.gap.appearance.xml
_ADV_APPEARANCE_GENERIC_ENVSENSOR = const(5696)

# Classe pour gérer le partage de données environnementales
class BLEenvironment:

	# Initialisations
	def __init__(self, ble, name="Nucleo-WB55"):
		self._ble = ble
		self._ble.active(True)
		self._ble.irq(self._irq)
		 # Prévoit deux caractéristiques (température et humidité)
		((self._temp_handle,self._humi_handle,),) = self._ble.gatts_register_services((_ENV_SENSE_SERVICE,))
		self._connections = set()
		self._payload = advertising_payload(
			name=name, services=[_ENV_SENSE_UUID], appearance=_ADV_APPEARANCE_GENERIC_ENVSENSOR
		)
		self._advertise()
		self._handler = None
		
		# Affiche l'adresse MAC de l'objet
		dummy, byte_mac = self._ble.config('mac')
		hex_mac = hexlify(byte_mac) 
		print("Adresse MAC : %s" %hex_mac.decode("ascii"))

	# Gestion des évènements BLE
	def _irq(self, event, data):
		# Lorsqu'un central se connecte...
		if event == _IRQ_CENTRAL_CONNECT:
			conn_handle, _, _ = data
			self._connections.add(conn_handle)
			print("Connecté")
			
		# Lorsqu'un central se déconnecte...
		elif event == _IRQ_CENTRAL_DISCONNECT:
			conn_handle, _, _ = data
			self._connections.remove(conn_handle)
			# Relance l'advertising pour de futures connexions
			self._advertise()
			print("Déconnecté")
			
		# Lorsqu'un évènement "indicate" est validé, renvoie un accusé de réception
		elif event == _IRQ_GATTS_INDICATE_DONE:
			conn_handle, value_handle, status = data

	# Pour envoyer la température ...
	def set_temp(self, temp_deg_c, notify=False, indicate=False):
		
		# Ecrit la température au format float "<f" et la laisse en lecture à un éventuel central.
		self._ble.gatts_write(self._temp_handle, pack("<f", temp_deg_c))
		if notify or indicate:
			for conn_handle in self._connections:
				if notify:
					# Notifie les centraux connectés du rafraichissement de la valeur de la température
					self._ble.gatts_notify(conn_handle, self._temp_handle)
				if indicate:
					# "Indicate" les centraux connectés (comme Notify, mais requiert un accusé de réception)
					self._ble.gatts_indicate(conn_handle, self._temp_handle)

	# Pour envoyer l'humidité ...
	def set_humi(self, humi_percent, notify=False, indicate=False):
		
		# Ecrit l'humidité au format float "<f" et la laisse en lecture à un éventuel central.
		self._ble.gatts_write(self._humi_handle, pack("<f", humi_percent))
		if notify or indicate:
			for conn_handle in self._connections:
				if notify:
					# Notifie les centraux connectés du rafraichissement de la valeur de l'humidité
					self._ble.gatts_notify(conn_handle, self._humi_handle)
				if indicate:
					# "Indicate" les centraux connectés (comme Notify, mais requiert un accusé de réception)
					self._ble.gatts_indicate(conn_handle, self._humi_handle)

	# Envoie des trames d'advertising toutes les 5 secondes, précise que l'on pourra se connecter au device
	def _advertise(self, interval_us=500000):
		self._ble.gap_advertise(interval_us, adv_data=self._payload, connectable = True)


# Programme "principal"
def demo():

	print("Périphérique BLE")

	# Objet BLE
	ble = bluetooth.BLE()
	
	# Instance de la classe environnementale
	ble_device = BLEenvironment(ble)

	while True:

		temperature = random.randint(-20, 90) # Valeur aléatoire entre -20 et 90 °C
		humidite = random.randint(0, 100) # Valeur aléatoire entre 0 et 100 %

		# Envoi en BLE de la température en choisissant de notifier l'application
		ble_device.set_temp(temperature, notify=True, indicate = False) 

		# Envoi en BLE de l'humidité en choisissant de notifier l'application
		ble_device.set_humi(humidite, notify=True, indicate = False)
		
		# Temporisation de cinq secondes
		time.sleep_ms(5000)

if __name__ == "__main__":
	demo()

L’application MIT App Inventor pour le central

Nous ne reviendrons pas sur les étapes de création d’une application MIT App Inventor adaptée à cet exemple, vous pourrez directement la récupérer dans la section BLE de la page de téléchargement ; il s’agit du fichier BLE_CENTRAL_BTSIG.aia. Un tutoriel assez détaillé expliquant comment construire avec MIT App Inventor une application central BLE est disponible ici.

Mise en oeuvre

  • Commencez par déplacer les scripts ble_advertising.py et main.py dans le dossier PYBFLASH de la NUCLEO-WB55.
  • Chargez ensuite l’application BLE_CENTRAL_BTSIG.aia sur votre smartphone (voir ce tutoriel) puis lancez celle-ci.

La liste des advertisers BLE s’affiche, parmi celle-ci figure la NUCLEO-WB55 (ligne contenant “WB55-MPY”) :



On remarque que le début de la ligne correspondante est l’adresse matérielle (adresse MAC) de la carte : “02:02:27:4E:25:16”. Appuyez sur celle-ci puis sur Connexion. Vous pouvez lire la température l’humidité envoyées par la carte :