Réseaux et Télécommunications
Fermer ×

Le bluetooth

Le bluetooth standard

Présentation générale

Le bluetooth est un système de communication sans fil. La norme 1.0 a été définie en 1999. Les normes et spécifications ont été définies par le BSIG (Bluetooth Special Interest group) dont les membres étaient (certains n'existent plus) :

De nombreux appareils intègrent le système bluetooth

Les protocoles et couches

Une interface bluetooth utilise une adresse souvent nommée bdaddr composée de 6 octets et présenté sous la forme de 6 valeurs hexadécimales séparées par le symbole :.

  • Applications ce sont les programmes qui utilisent les services bluetooth.
  • Interfaces pour les autres protocoles de communication
    • OBEX (Object Exchange) intègre les protocoles de transfert de données et de fichiers,
    • WAP (Wireless Application Protocol) protocole d'accès à internet, il n'est plus très utilisé.
  • Les services
    • TCS (Telephony Control Protocol Specification) gère l'envoi des appels téléphoniques vers bluetooth comme le renvoi des appels d'un téléphone portable vers un ordinateur,
    • RFCOMM permet d'émuler une liaison série de type UART, cette couche utilise un canal L2CAP,
    • SDP (Service Discovery Protocol) est une base de données des services disponibles.
  • L2CAP (Logical Link Control and Adaptation Protocol) fournit un canal de transmission aux couches supérieures, gères les paquets de données, assure le multiplexage des données pour le lien ACL. Il gère la qualité de service en garantissant la bande passante.
  • HCI (Host Controller Interface) est l'interface entre les couches inférieures (module bluetooth) et la couche supérieure. Il offre des fonctionnalités logicielles aux couches supérieures. il peut utiliser une liaison série (UART) ou USB.
  • Gestionnaire liaisons gère l'authentification, le chiffrement, le changement maître/esclave, l'établissement des connexions SCO/ACL.
  • Contrôleur de liaisons est responsable de la gestion et de l’identification des périphériques, il établit et maintient la connexion.
  • Bande de base qui gère le codage et le transfert des données
    • SCO (Synchronous Connection Oriented) pour la transmission de l'audio avec un débit de 64kbits/s
    • ACL (Asycnhronous Connection Oriented) pour la transmission des données, on a un lien ACL par pico-réseau
  • Radio qui assure la transformation (modulation) des données binaires pour le transmettre par onde radio.

Classes de puissances d'émission

La réglementation de la puissance d’émission permet une portée maximale de 100m. Dans cet intervalle de distance, il existe 3 classes bluetooth :

ClassePuissanceDistance
1100mW20dBm100m
22.5mW4dBm20m
31mW0dBm10m
dBm : dB milliwatt

Les profiles

Un profile est une implémentation d'un protocole pour fournir un service particulier, on trouve par exemple :

Interconnexion

Pico-réseau

Un seul maître établit la communication avec les esclaves.

C'est le maître qui autorise l'esclave à transmettre ses données.

Un maître peut gérer plusieurs esclaves accessibles par radio. On a ainsi une organisation en pico-réseau qui contient un seul maître. Dans un pico-réseau, le maître synchronise tous les esclaves afin d'éviter les perturbations et il n'y a pas de connexion entre esclaves. Un maître ne peut pas être le maître de plusieurs pico-réseaux.

Un appareil qui cherche (inquiry) l'ensemble des appareils accessibles (portée radio), émet des paquets de données à intervalles réguliers. Les appareils qui reçoivent ces données répondent en transmettant les informations d'identification comme leur adresse bdaddr, ces informations permettent à l'émetteur d'établir une connexion.

La connexion entre appareils est une suite d'échanges de données.

Réseaux chaînés

Il est possible d'interconnecter plusieurs pico-réseaux avec un seul maître par pico-réseau, pour cela il y a plusieurs possibilités.

Le maître d'un pico-réseaux est l'esclave d'un autre pico-réseau.

Sur la figure, le maître du premier pico-réseau (bleu) est l'esclave du deuxième pico-réseau (rouge).

Chaque maître synchronise son pico-réseau de manière indépendante. Les pico-réseaux se partagent la même zone radio, ce qui crée des perturbations et fait baisser le débit.

Les pico-réseaux se partagent le même esclave.

Le problème de perturbation existe toujours dans cette configuration mais peut être atténué par la distance entre les maîtres qui est plus importante.

Utilisation sous linux

Sous linux les librairies et outils sont regroupés dans les paquetages bluetooth et bluez, avec en plus, des paquetages graphiques inclus dans les différents gestionnaire de fenêtres.

Ces paquetages fournissent des commandes dont :

Exemple de programmation

Librairies et options de compilation

La programmation en C utilise la librairie bluez qui permettent de créer des applications qui utilisent les couches l2cap et rfcomm. Il ne faut pas oublier d'inclure les paquetages dev et ajouter la librairie bluetooth à l'édition de liens (-lbluetooth).

scan en C

Le scan utilise les fonctions C hci qui sont :

  1. fdhci=hci_open_dev(hcidev) qui ouvre le socket hci correspondant à l'interface de numéro hcidev, cette fonction retourne l'identifiant de l'interface fdhci.
  2. hci_inquiry(hcidev,longueur,nbrep,octets,adr_infos,options) cherche les appareils bluetooth accessibles avec hcidev qui est le numéro de l'interface bluetooth, longueur qui représente le temps de scan en multiple de 1.28s, nbrep qui correspond aux maximum de réponses, octets qui est un tableau d'octets (non documenté), adr_infos qui est l'adresse d'un pointeur vers une structure de type inquiry_info et options qui vaut en général 0 (très peu documenté). Cette fonction retourne le nombre de réponses obtenues.
  3. hci_read_remote_name(fdhci,bdaddr,taille,nom,timeout) qui effectue une demande de nom pour une adresse bdaddr fournie. fdhci est l'identifiant retourné par hci_open_dev, bdaddr (structure qui contient un tableau de 6 octets) adresse qui est un champ de la structure adr_infos, taille qui est la longueur maximale du nom, nom qui contient le nom, timeout qui est le temps maximum accordé à la requête.
  4. close(fdhci) ferme l'interface hci.
Voir le contenu du code du programme de scan en C

#include <stdio.h>
#include <stdlib.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>
#include <unistd.h>

int main(int argc, char **argv) {
	int hci_dev, fdhci , nbrep;
	inquiry_info *info = NULL;
	bdaddr_t bdaddr;
	hci_dev = 0; 
	char nom[128] = "";
	
    fdhci = hci_open_dev(hci_dev);
    if (fdhci < 0) {
        perror("opening socket");
        return EXIT_FAILURE;
    }	
	nbrep = hci_inquiry (hci_dev, 8, 255, NULL, &info, 0);
	for (int i=0 ; i<nbrep ; i+=1 ) {
		baswap (&bdaddr, &(info+i)->bdaddr);
		int nomexiste = hci_read_remote_name(fdhci,&(info+i)->bdaddr,sizeof(nom),nom,0);
		if (nomexiste < 0) {
			strcpy(nom,"inconnu");
		}		
		printf ("\t%s %s\n", batostr(&bdaddr),nom) ;
	}
	free (info);
	close(fdhci);
	return EXIT_SUCCESS;
}
					

inquiry_info est un pointeur vers une structure qui contient, entre autres, l'adresse de l'appareil détecté.

hci_dev vaut 0 car l'ordinateur ne possède qu'une interface bluetooth.

longueur vaut 8 pour obtenir un temps de 8*1.28=10.24s et nbrep=255 ce qui correspond au temps précédent.

En cas d'erreur avec hci_open_dev, on affiche le code d'erreur et termine le programme.

hci_inquiry effectue le scan et retourne un tableau dynamique de structures inquiry_info qu'il ne faudra pas oublier de libérer à la fin du programme. Cette fonction retourne le nombre de réponses obtenues.

baswap modifie ou non l'ordre des octets en fonction du type de machine. Cela permet d'obtenir les octets dans le bon ordre quelque soit la machine.

batostr convertit l'adresse qui est un tableau d'octets en une chaîne de caractères

scan en python

La programmation en python utilise également la librairie bluez.

Il faut importer la librairie bluetooth, les fonctions pour scanner les appareils accessibles sont :

  1. bluetooth.discover_devices qui retourne une liste d'adresses des appareils accessibles
  2. bluetooth.lookup_name(bdaddr) qui retourne le nom correspondant à l'adresse bdaddr
Voir le contenu du code du programme de scan en python

import bluetooth

def main(args):
	appareils = bluetooth.discover_devices()
	for bdaddr in appareils :
		nom = bluetooth.lookup_name(bdaddr)
		print(f"{bdaddr} : {nom}")
	return 0

if __name__ == '__main__':
	import sys
	sys.exit(main(sys.argv))
					

appareils est une liste d'adresses sous la forme de chaînes de caractères.

Le bluetooth faible énergie (BLE)

Présentation générale

Le BLE (Bluetooth Low Energy) est une version allégée du bluetooth afin de minimiser la consommation d'énergie, cette version est surtout implémentée sur les systèmes embarqués et objets connectés.

Le BLE est apparu en 2010 avec la version 4.0 de bluetooth.

Comme le bluetooth standard un composant BLE est identifié par une adresse bdaddr exprimée sur 6 octets

Les protocoles et couches

  • Applications ce sont les programmes qui utilisent les services bluetooth.
  • Profiles
    • GAP (Generic Access profile) qui permet d'identifier le circuit ainsi que les services disponibles
    • GATT (Generic Attribute Profile) gère l'échange de données qui se fait sous la forme client-serveur. Ce profile est composé de services qui eux même sont composés de caractéristiques, une caractéristique est un échange de données entre le client et le serveur (lecture/écriture), une caractéristique est définie par sa valeur (suite d'octets) et son descripteur (information sur le format et rôle de la valeur)
  • Sécurité et services
    • SMP (Security Manager Protocol) gère la sécurité des échanges de données.
    • ATT (Attribute Protocol) structure de données des services et caractéristiques qui incluent la valeur, le descripteur et un UUID (Universally unique identifier) sur 128 bits ainsi que les permissions (lecture,écriture).
  • L2CAP (Logical Link Control and Adaptation Protocol) fournit un canal de transmission aux couches supérieures, gères les paquets de données, assure le multiplexage des données pour le lien ACL.
  • HCI (Host Controller Interface) est l'interface entre les couches inférieures (module bluetooth) et la couche supérieure. Il offre des fonctionnalités logicielles aux couches supérieures.
  • Couche liaison de données est responsable du chiffrement, de la gestion et de l’identification des périphériques, il établit et maintient la connexion.
  • Couche physique qui assure la transformation (modulation) des données binaires pour le transmettre par onde radio.

Les identifiants utilisés dans les services et caractéristiques sont normalisés et associés à des fonctionnalités précises. Les protocoles sont désignés par des UUID 16 bits. Les services et caractéristiques le sont pas des UUID 128 bits. l'ensemble de ces valeurs est parfaitement décrit dans les spécifications bluetooth. Il est également possible d'obtenir un UUID 128 bits en ligne.

Interconnexion

Le système est composé d'un système central et de périphériques

  • centrale est le maître du réseau BLE, il établit et gère les connexions. Il écoute les paquets de publication des périphériques afin de se connecter
  • périphérique est le composant qui peut être un objet connecté. Il émet les paquets de publication à intervalles réguliers afin de pouvoir établir une connexion avec le système central.

Exemples d'applications client-serveur

Le serveur est installé sur un système embarqué connecté et le client peut être une application smartphone comme par exemple : BLE scanner disponible sur Android et sur Apple (non testé) ou encore la commande bluetootctl sous linux:

  1. scan on pour démarrer la découverte des périphériques bluetooth
  2. scan off pour arrêter la découverte des périphériques bluetooth
  3. connect bdaddr pour se connecter au périphérique bluetooth concerné
  4. gat.list-attributes bdaddr pour obtenir la liste des services et caractéristiques
  5. gat.select-attribute UUID pour accéder à une caractéristique d'identifiant UUID
  6. gat.read pour obtenir la valeur de la caractéristique d'identifiant UUID
  7. gat.write valeur pour envoyer une nouvelle valeur à la caractéristique d'identifiant UUID
  8. disconnect pour se déconnecter
  9. quit pour quitter bluetoothctl

Arduino et MKR1010

Il est vivement conseillé de se reporter à la programmation de la carte MKR1010.

On utilise la librairie Arduino BLE.

La réalisation d'un serveur GATT fait appel aux méthodes de l'objet BLE ainsi que les classes BLEService , BLEByteCharacteristic et BLEDescriptor.

Lors de l'initialisation :

  1. BLE.begin() initialise l'interface bluetooth
  2. BLE.setLocalName(chaine) définit le nom de l'application
  3. BLE.setDeviceName(chaine) définit le nom de la carte
  4. BLE.setAppearance définit le code d'identification de l' application
  5. BLE.setAdvertisedService(objetBLEService) définit le service utilisé
  6. BLE.addService(objetBLEService) ajoute le service à l'interface après avoir configuré l'objet BLEService :
    1. ObjetBLEByteCharacteristic.addDescriptor(objetBLEDescriptor) ajoute un objet descriptor à la caractéristique
    2. objetBLEService.addCharacteristic(BLEByteCharacteristic) ajoute l'objet caractéristique au service
    3. ObjetBLEByteCharacteristic.writeValue(valeur) initialise, si cela est nécessaire, le contenu de la caractéristique
  7. BLE.advertise() démarre la publication du service

Dans la boucle de surveillance des connexions clientes :

Voir le contenu du code du serveur GATT

#include <ArduinoBLE.h>
BLEService ledService("f29b181c-9a04-4f7f-8dc7-df499f6602ad"); 
BLEByteCharacteristic switchCharacteristic("6ffa8a48-32ce-4a87-92f5-07977fd3ebbe", BLERead | BLEWrite);
BLEDescriptor ledCharacteristic("6a610785-5620-4219-a209-77006a3f082f", "commande led type texte : 0 ou 1");

void setup() {
  Serial.begin(9600);
  while (!Serial);
  pinMode(LED_BUILTIN, OUTPUT);
  if (!BLE.begin()) {
    Serial.println("Initialisation BLE ERREUR");
    while (1);
  }
  Serial.println("Initialisation BLE OK");
  String adresse = BLE.address();
  Serial.print("adresse ");
  Serial.println(adresse);
  BLE.setLocalName("LED");
  BLE.setDeviceName("MKR1010");
  BLE.setAppearance(0x0785);
  BLE.setAdvertisedService(ledService);
  switchCharacteristic.addDescriptor(ledCharacteristic);
  ledService.addCharacteristic(switchCharacteristic);
  BLE.addService(ledService);
  Serial.println("initialise service 0");
  switchCharacteristic.writeValue(0);
  BLE.advertise();
  Serial.println("Prêt");
}

void loop() {
  BLEDevice central = BLE.central();
  if (central) {
    Serial.print("Client connecté : ");
    Serial.println(central.address());
    while (central.connected()) {
      if (switchCharacteristic.written()) {
        if (switchCharacteristic.value()=='1') {   
          Serial.println("LED on");
          digitalWrite(LED_BUILTIN, HIGH);         
        } 
        else {                             
          Serial.println(F("LED off"));
          digitalWrite(LED_BUILTIN, LOW);          
        }
      }
    }
    Serial.print(F("Client déconnecté "));
    Serial.println(central.address());
  }
}
					

Exemple de commandes après avoir lancé la commande bluetoothctl

[bluetooth]# scan on
Discovery started
[NEW] Device XX:XX:XX:XX:XX:XX
...
[bluetooth]# scan off
Discovery stopped
[bluetooth]# gat.list-attributes XX:XX:XX:XX:XX:XX
Characteristic (Handle XXXXXX)
	..............................................
	6ffa8a48-32ce-4a87-92f5-07977fd3ebbe
	Vendor specific
[bluetooth]# gat.select-attribute 6ffa8a48-32ce-4a87-92f5-07977fd3ebbe
[MKR1010:/xxxxxxxxxxxxxxx]# gat.read
Attempting to read ................................................................
[CHG] Attribute ................................................................... Value:
  30                                               0               
  30
[MKR1010:/xxxxxxxxxxxxxxx]# gat.write 0x31
Attempting to write ................................................................
[MKR1010:/xxxxxxxxxxxxxxx]# gat.write 0x30
Attempting to write ................................................................
[MKR1010:/xxxxxxxxxxxxxxx]#disconnect
Attempting to disconnect from A4:CF:12:8A:A6:4A
[CHG] Device XX:XX:XX:XX:XX:XX ServicesResolved: no
Successful disconnected
[CHG] Device XX:XX:XX:XX:XX:XX Connected: no
[bluetooth]#quit
					

Dans la liste des attributs, on cherche l'UUID qui correspond à celui qui est défini dans le programme arduino soit :

6ffa8a48-32ce-4a87-92f5-07977fd3ebbe.
Puis on sélectionne cet UID avant d'utiliser les commandes read et write. La commande gat.write 0x31 allume la led de la carte arduino, et gat.write 0x30 l'éteint.

C et raspberry picow

Il est vivement conseillé de se reporter à la programmation de la carte picow.

On trouve difficilement de la documentation sur la programmation en C de bluetooth sur raspberry picow. Les codes qui suivent sont inspirés des exemples picow/bt de l'ensemble des exemples.

  1. cyw43_arch_init() initialise la puce wifi/bluetooth CYW43439
  2. l2cap_init() initialise la couche l2cap
  3. sm_init() initialise la couche de sécurité
  4. att_server_init(profile_data, att_read_callback, att_write_callback); initialise le serveur GATT, profile_data est une structure de données définie dans un fichier .h créé lors de la compilation à partir d'un fichier .gatt, ce dernier est un fichier csv qui contient la liste des services et caractéristiques, att_read_callback est une fonction callback qui est appelée lors d'une demande de lecture du client, att_write_callback est une fonction callback qui est appelée lors d'une demande d'écriture du client.
  5. att_server_register_packet_handler(packet_handler) ajoute la fonction callback (packet_handler) de gestion des évènements
  6. hci_power_control(HCI_POWER_ON) active la puce bluetooth.

Il n'y a pas besoin de surveiller les demandes des clients bluetooth dans la boucle principale, tout est assuré par les fonctions callback.

Dans la fonction callback packet_handler, il faut publier les services avec les fonctions

  1. gap_advertisements_set_data(taille,adv_datas) avec adv_datas qui est un tableau d'octets dont le contenu respecte un format particulier :
    taille_champ,identifiant_champ,donnees,taille_champ,identifiant_champ,donnees,...
    avec taille_champ : octet qui contient le nombre d'octets des données + 1, identitfiant_champ : octet qui contient le type des données qui suivent et enfin donnees qui est une suite d'octets qui contiennent les données correspondantes à l'identifiant.
  2. gap_advertisements_enable(1) activation de la publication des services
Voir le contenu du code du serveur GATT en C

Déclaration des fonctions serveur


#ifndef __SERVER_H
#define __SERVER_H

extern uint8_t const profile_data[];

void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
uint16_t att_read_callback(hci_con_handle_t connection_handle, uint16_t att_handle, uint16_t offset, uint8_t * buffer, uint16_t buffer_size);
int att_write_callback(hci_con_handle_t connection_handle, uint16_t att_handle, uint16_t transaction_mode, uint16_t offset, uint8_t *buffer, uint16_t buffer_size);

#endif
					

La structure de données adv_data contient les données de publication sous la forme d'un tableau d'octets.

Toutes ces fonctions définies dans le fichier h sont des callback appelées sur des évènements de connexion, déconnexion, lecture d'une caractéristique, écriture d'une caractéristique, ou d'autres évènements.

La fonction packet_handler traite l'initialisation en effectuant la publication des services, et affichant un message de déconnexion.

La fonction read_callback effectue la lecture de l'état de la led et la transmet au client.

La fonction write_callback lit la valeur reçue et affecte cette valeur à la led.

Les fonctions print_buffer et commandeLED sont des fonctions privées uniquement utilisées par les fonctions callback

Définitions des services et caractéristiques picow_ble_led.gatt

PRIMARY_SERVICE, GAP_SERVICE
CHARACTERISTIC, GAP_DEVICE_NAME, READ, "picow_temp"

PRIMARY_SERVICE, GATT_SERVICE
CHARACTERISTIC, GATT_DATABASE_HASH, READ,

PRIMARY_SERVICE, f29b181c-9a04-4f7f-8dc7-df499f6602ad
CHARACTERISTIC, 6ffa8a48-32ce-4a87-92f5-07977fd3ebbe, READ | WRITE | DYNAMIC,
					

C'est un fichier au format CSV qui contient la description des services et caractéristiques. On a choisi les mêmes UID que ceux utilisés par le programme Arduino précédent.

Ce fichier gatt permettra de créer le fichier de définitions du même nom picow_ble_led.h


#include <stdio.h>
#include "pico/cyw43_arch.h"
#include "btstack.h"
#include "picow_ble_led.h"
#include "server.h"

#define APP_AD_FLAGS 0x06
static uint8_t adv_data[] = {
    0x02, BLUETOOTH_DATA_TYPE_FLAGS, APP_AD_FLAGS,
    0x08, BLUETOOTH_DATA_TYPE_COMPLETE_LOCAL_NAME, 'P', 'i', 'c', 'o', 'L' , 'E' , 'D' ,
    0x03, BLUETOOTH_DATA_TYPE_COMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS, 0x1a, 0x18,
};
static const uint8_t adv_data_len = sizeof(adv_data);
static uint8_t etatLed = 0;


void print_buffer(uint8_t *octets,uint16_t taille) {
	for(int i=0;i<taille;i+=1) {
		printf("%02x ",octets[i]);
	}
	printf("\n");
}

void commandeLED(uint8_t caractere) {
	if (caractere == '1') {
		etatLed = 1 ;
		cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, etatLed);
	}
	else {
		etatLed = 0 ;
		cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, etatLed);
	}
}

void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
    bd_addr_t local_addr;
    if (packet_type != HCI_EVENT_PACKET) return;
    uint8_t event_type = hci_event_packet_get_type(packet);
    switch(event_type){
        case BTSTACK_EVENT_STATE:
            if (btstack_event_state_get_state(packet) != HCI_STATE_WORKING) return;
            gap_local_bd_addr(local_addr);
            printf("BTstack up and running on %s.\n", bd_addr_to_str(local_addr));
            gap_advertisements_set_data(adv_data_len, (uint8_t*) adv_data);
            gap_advertisements_enable(1);
            break;
        case HCI_EVENT_DISCONNECTION_COMPLETE:
			printf("HCI_EVENT_DISCONNECTION_COMPLETE\n");
            break;
        default:
            break;
    }
}

uint16_t att_read_callback(hci_con_handle_t connection_handle, uint16_t att_handle, uint16_t offset, uint8_t * buffer, uint16_t buffer_size) {
	printf("att_read_callback\n");
    if (att_handle == ATT_CHARACTERISTIC_6ffa8a48_32ce_4a87_92f5_07977fd3ebbe_01_VALUE_HANDLE){
		uint8_t chled[1] ;
		chled[0] = etatLed + '0' ;
		printf("etat led %c\n",chled[0]);
		return att_read_callback_handle_blob(chled, 1, offset, buffer, buffer_size);
    }
    return 0;
}

int att_write_callback(hci_con_handle_t connection_handle, uint16_t att_handle, uint16_t transaction_mode, uint16_t offset, uint8_t *buffer, uint16_t buffer_size) {
    printf("att_write_callback\n");
    if (att_handle == ATT_CHARACTERISTIC_6ffa8a48_32ce_4a87_92f5_07977fd3ebbe_01_VALUE_HANDLE){
		print_buffer(buffer,buffer_size);
		commandeLED(buffer[0]);
	}
    return 0;
}
					
Voir le contenu du code du programme de test

Contenu du fichier CMakeLists.txt


cmake_minimum_required(VERSION 3.13)
include(pico_sdk_import.cmake)
project(picow_ble_led C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
set(PICO_BOARD pico_w)
pico_sdk_init()
add_executable(picow_ble_led
    picow_ble_led.c server.c
    )
    
pico_enable_stdio_usb(picow_ble_led 1)
pico_enable_stdio_uart(picow_ble_led 1)
pico_add_extra_outputs(picow_ble_led)

target_link_libraries(picow_ble_led
    pico_stdlib
    pico_btstack_ble
    pico_btstack_cyw43
    pico_cyw43_arch_none
    )
target_include_directories(picow_ble_led PRIVATE
    ${CMAKE_CURRENT_LIST_DIR} 
    )
pico_btstack_make_gatt_header(picow_ble_led PRIVATE "${CMAKE_CURRENT_LIST_DIR}/picow_ble_led.gatt")
pico_add_extra_outputs(picow_ble_led)
					

Contenu du programme de test


#include "btstack.h"
#include "pico/cyw43_arch.h"
#include "pico/btstack_cyw43.h"
#include "pico/stdlib.h"
#include "server.h"

static btstack_packet_callback_registration_t hci_event_callback_registration;

int main() {
    stdio_init_all();
    while (!stdio_usb_connected());
    if (cyw43_arch_init()) {
        printf("initialisation cyw43_arch erreur\n");
        return -1;
    }
    l2cap_init();
    sm_init();
    att_server_init(profile_data, att_read_callback, att_write_callback);    
    hci_event_callback_registration.callback = &packet_handler;
    hci_add_event_handler(&hci_event_callback_registration);
    att_server_register_packet_handler(packet_handler);
    hci_power_control(HCI_POWER_ON); 
    while(true) {      
        sleep_ms(1000);
    }
    return 0;
}
					

Pour tester ce serveur on utilise les mêmes clients que ceux utilisés pour la version arduino.

Python et raspberry picow

On utilise la librairie bluetooth qui inclut la classe BLE qui permet de gérer la communication bluetooth et de créer, par exemple, un serveur GATT.

Lors de l'initialisation :

  1. objetble = bluetooth.BLE() : créattion de l'objet BLE
  2. objetble.active(True) active le circuit bluetooth
  3. (addr_type,addr)=objetble.config('mac') retourne l'adresse bluetooth de la carte ainsi que le type d'adresse.
  4. objetble.irq(traiteirq) installe la fonction callback traiteirq qui sera appelée lors la connexion, déconnexion, l'envoi de données par le client (cas du serveur).
  5. ( ( handle_led ,) , ) = objetble.gatts_register_services(SERVICES) qui enregistre les services et caractéristiques avec SERVICES qui est une liste composée de :
    • SERVICE_UID (obtenu avec bluetooth.UUID suivi d'une liste de caractéristiques composée de l'UID ainsi que des modes d'accès (bluetooth.FLAG_READ et/ou bluetooth.FLAG_WRITE )
    • La valeur de retour handle_led est l'identifiant qui sera utilisé par les méthodes de lecture et d'écriture.
  6. ble.gap_advertise(intervalle,adv_data=advertise_datas,connectable=True) qui publie les services au rythme de intervalle en microsecondes, adv_data est un tableau d'octets qui contient l'identifiant de l'application, connectable autorise ou non au client de se connecter.

Dans la boucle de surveillance qui est synchronisée aux évènements dépendant du client (callbak irq)

Voir le contenu du code du serveur GATT en python

from micropython import const
import struct
from machine import Pin
import bluetooth
import time

_IRQ_CENTRAL_CONNECT = const(1)
_IRQ_CENTRAL_DISCONNECT = const(2)
_IRQ_GATTS_WRITE = const(3)
_ADV_TYPE_NAME = const(0x09)
BLE_SERVICE_UUID = bluetooth.UUID('f29b181c-9a04-4f7f-8dc7-df499f6602ad')
BLE_LED_UUID = ( bluetooth.UUID('6ffa8a48-32ce-4a87-92f5-07977fd3ebbe') , bluetooth.FLAG_WRITE | bluetooth.FLAG_READ)
SERVICES = ( ( BLE_SERVICE_UUID, ( BLE_LED_UUID ,) , ) , )

def clevaleurtooctets(cle,valeur):
    paire=bytearray()
    paire = struct.pack("BB", len(valeur) + 1, cle) + valeur
    return paire

def printaddr(addr):
    taille=len(addr)
    for i in range(taille-1):
        octet=addr[i]
        print(f"{octet:02x}",end=':')
    octet=addr[taille-1]
    print(f"{octet:02x}")    

estconnecte=False
conn_handle=0
ecriture=False

def traiteirq(event, data):
    global estconnecte
    global ecriture
    global conn_handle
    if event == _IRQ_CENTRAL_CONNECT:
        conn_handle, addr_type, addr = data
        print(f"connecté  (handle={conn_handle})", end=": ")
        printaddr(addr)
        estconnecte=True
    elif event == _IRQ_CENTRAL_DISCONNECT:
        conn_handle, addr_type, addr = data
        print("déconnecté")
        estconnecte=False
    elif event == _IRQ_GATTS_WRITE:
        conn_handle, attr_handle = data
        print(f"écriture client (handle={attr_handle})")
        ecriture=True

advertise_datas=bytearray()

led = Pin("LED", Pin.OUT)
led.off()
ble = bluetooth.BLE()
led.off()
ble.active(True)
(addr_type,addr)=ble.config('mac')
printaddr(addr)
ble.irq(traiteirq)
( ( handle_led ,) , ) = ble.gatts_register_services(SERVICES)
advertise_datas = clevaleurtooctets(_ADV_TYPE_NAME,"picoled")
ble.gap_advertise(1000,adv_data=advertise_datas,connectable=True)
ble.gatts_write(handle_led,"0")
while True:
    if estconnecte :
        if ecriture :
            valeurs=ble.gatts_read(handle_led)
            ecriture=False
            if (len(valeurs)>0):
                commande=valeurs[0]
                if (commande == ord('1')):
                    led.on()
                else:
                    led.off()
    time.sleep(0.5)
					

bluetooth.UUID(chaine) crée l'objet UUID qui sera intégré aux services et caractéristiques.

Une caractéristique est une liste composée d'un UID retourné par bluetooth.UUID(chaine), suivi d'indicateur de lecture et/ou écriture.

Chaque service est une liste composée d'un UID de service et de listes de caractéristiques.

L'ensemble des services enregistrés est également une liste de services

Le type adv_data est un tableau d'octets qui peut être créé avec la méthode pack d'un objet struct.

La méthode read retourne une liste de valeurs, dans notre cas il s'agit d'un seul octet qui correspond au code ascii du caractère 0 ou 1