Site : tvaira.free.fr
Bluetooth est une norme de communications permettant l’échange bidirectionnel de données à très courte distance en utilisant des ondes radio UHF sur une bande de fréquence de 2,4 GHz.
Liens :
Dans les versions commercialisées en 2015 (4.0 et 4.1), largement utilisées, essentiellement dans les appareils mobiles comme les téléphones portables, la liaison Bluetooth présente les caractéristiques suivantes :
En conséquence, il est présent sur des appareils fonctionnant souvent sur batterie, désirant échanger une faible quantité de données sur une courte distance :
Un picoréseau (piconet) est un mini-réseau qui se crée de manière instantanée et automatique quand plusieurs périphériques Bluetooth sont dans un même rayon. Un picoréseau est organisé selon une topologie en étoile : il y a un « maître » et plusieurs « esclaves ».
Un périphérique « maître » peut administrer jusqu’à :
La communication est directe entre le « maître » et un « esclave ». Les « esclaves » ne peuvent pas communiquer entre eux.
Tous les « esclaves » du picoréseau sont synchronisés sur l’horloge du « maître ». C’est le « maître » qui détermine la fréquence de saut pour tout le picoréseau.
Les périphériques « esclaves » peuvent avoir plusieurs « maîtres » : les différents piconets peuvent donc être reliés entre eux. Le réseau ainsi formé est appelé un scatternet (littéralement « réseau dispersé »).
Les éléments fondamentaux d’un produit Bluetooth sont définis dans les deux premières couches protocolaires :
Ces couches prennent en charge les tâches matérielles comme le contrôle du saut de fréquence et la synchronisation des horloges.
La couche radio (la couche la plus basse) est gérée au niveau matériel. C’est elle qui s’occupe de l’émission et de la réception des ondes radio. Elle définit les caractéristiques telles que la bande de fréquence et l’arrangement des canaux, les caractéristiques du transmetteur, de la modulation, du récepteur, etc.
Le système Bluetooth opère dans la bande de fréquences comprise entre 2 400 et 2 483,5 MHz. Un transceiver à sauts de fréquences est utilisé pour limiter les interférences et l’atténuation.
Les 79 canaux RF sont numérotés de 0 à 78 et séparés de 1 MHz en commençant par 2 402 MHz. Le codage de l’information se fait par sauts de fréquences et la période est de 625 µs, ce qui permet 1 600 sauts par seconde.
Le système Bluetooth utilise une modulation de fréquence avec une rapidité de modulation est de 1 Mbaud. La transmission duplex est basé sur un multipexage temporel.
Il existe trois classes de modules radio Bluetooth sur le marché :
Classe | Puissance | Portée |
---|---|---|
1 | 100 mW (20 dBm) | 100 mètres |
2 | 2,5 mW (4 dBm) | 10 à 20 mètres |
3 | 1 mW (0 dBm) | Quelques mètres |
La plupart des fabricants d’appareils électroniques utilisent des modules classe 2.
Cette couche matérielle permet de définir les adresses physiques des périphériques (équivalentes à l’adresse MAC d’une carte réseau). Cette adresse est nommée BD_ADDR (Bluetooth Device Address) et est codée sur 48 bits. Ces adresses sont gérées par la IEEE Registration Authority.
La bande de base peut donc gérer deux types majeurs de liens logiques :
les liens SCO (Synchronous Connection-Oriented) : pour la transmission voix temps réel (point à point et bidirectionnel).
les liens ACL (Asynchronous Connection-Less) : conçu pour l’échange de données. Le broadcast est possible.
Les données transportées sur ces liens logiques sont sous forme de paquets. Il existe divers types de paquets et peuvent être utilisés par les deux liens logiques ou seulement par un seul type de lien. Chaque paquet est composé globalement de la même manière.
On retrouve trois parties essentielles :
Cette couche gère les liens entre les périphériques « maîtres » et « esclaves » ainsi que les types de liaisons (synchrones ou asynchrones).
C’est le gestionnaire de liaisons qui implémente les mécanismes de sécurité comme :
Il utilise le protocole L2CAP pour interagir avec son homologue sur les équipements distants.
Cette couche fournit une méthode uniforme pour accéder aux couches matérielles. Son rôle de séparation permet un développement indépendant du matériel et du logiciel.
Les protocoles de transport supportés sont HCI (Host Controller Interface) USB, RS-232, UART, …
La couche L2CAP (Logical Link Control & Adaptation Protocol) fournit les services de multiplexage des protocoles de niveau supérieur et la segmentation et le réassemblage des paquets ainsi que le transport des informations de qualité de service. Les protocoles de haut niveau peuvent ainsi transmettre et recevoir des paquets jusqu’à 64 Ko. Elle autorise un contrôle de flux par canal de communication.
La couche L2CAP utilise des canaux logiques.
Il existe plusieurs services : RFCOMM, SDP (Service Discovery Protocol) et OBEX (OBject EXchange).
Le service RFCOMM (Radio frequency communication) est basé sur les spécifications RS-232, qui émule des liaisons séries. Il peut notamment servir à faire passer une communication IP par Bluetooth. RFCOMM est utilisé lorsque le débit des données n’atteint pas plus de 360 kbit/s.
Un profil correspond à une spécification fonctionnelle d’un usage particulier. Les profils peuvent également correspondre à différents types de périphériques. Les profils ont pour but d’assurer une interopérabilité entre tous les appareils Bluetooth. Ils définissent notamment la manière d’implémenter un usage défini et les protocoles spécifiques à utiliser.
Il existe une hiérarchie entre profils et donc des dépendances entre eux. Pour chaque profil, il existe plusieurs points qui sont redéfinis ou non : rôle, scénario, principes de base …
Le profil d’accès générique GAP (Generic Access Profile) est le profil de base dont tous les autres profils héritent. Il définit les procédures génériques de recherche d’appareils, de connexion et de sécurité.
Les rôles dans une communication seront les suivants :
Remarque : on est assez proche des rôles Client/Serveur.
Le profil d’accès générique GAP expose l’ensemble des caractéristiques de tous les équipements Bluetooth :
Afin d’échanger des données, les appareils doivent être appairés. L’appairage se fait en lançant la découverte à partir d’un appareil et en échangeant un code.
Remarques : Dans certains cas, le code est libre, et il suffit aux deux appareils de saisir le même code. Dans d’autres cas, le code est fixé par l’un des deux appareils (appareil dépourvu de clavier, par exemple), et l’autre doit le connaître pour s’y raccorder. Par la suite, les codes sont mémorisés, et il suffit qu’un appareil demande le raccordement et que l’autre l’accepte pour que les données puissent être échangées. Afin de limiter les risques d’intrusion, les appareils qui utilisent un code préprogrammé (souvent 0000 ou 1234) doivent être activés manuellement, et l’appairage ne peut se faire que durant une courte période.
Il existe de nombreux modules Bluetooth très similaires (www.martyncurrey.com/bluetooth-modules/) dont le HC-05 qui peut fonctionner en maître ou esclave.
Ce type de module s’interface par une liaison série TTL. Il existe généralement deux modes d’exploitation : la communication (souvent par défaut 9600/8/N/1) et la configuration par commandes AT (38400/8/N/1) suivies de \r\n
.
AT
OK
AT+ADDR?
+ADDR:2015:11:167604 ---- BD_ADDR
OK
AT+NAME?
+NAME: HC-05 ---- Nom
OK
AT+ROLE?
+ROLE:0 ---- Esclave
OK
...
Remarque : Le module HC05 possède également une broche key permettant de basculer du mode configuration AT (key au niveau haut) au mode communication (key au niveau bas).
Programmation : Mise en oeuvre d’un port série sous Qt
$ sudo systemctl status bluetooth.service
bluetooth.service - Bluetooth service
Loaded: loaded (/lib/systemd/system/bluetooth.service; enabled; vendor preset: enabled)
Active: active (running) since Fri 2018-02-16 18:01:03 CET; 21min ago
Docs: man:bluetoothd(8)
Main PID: 558 (bluetoothd)
Status: "Running"
CGroup: /system.slice/bluetooth.service
└─558 /usr/lib/bluetooth/bluetoothd
févr. 16 18:01:03 raspberrypi systemd[1]: Starting Bluetooth service...
févr. 16 18:01:03 raspberrypi bluetoothd[558]: Bluetooth daemon 5.43
févr. 16 18:01:03 raspberrypi systemd[1]: Started Bluetooth service.
févr. 16 18:01:03 raspberrypi bluetoothd[558]: Starting SDP server
févr. 16 18:01:03 raspberrypi bluetoothd[558]: Bluetooth management interface 1.14 initialized
...
$ dmesg | grep -i bluetooth
[ 10.678749] Bluetooth: Core ver 2.22
[ 10.678813] Bluetooth: HCI device and connection manager initialized
[ 10.678826] Bluetooth: HCI socket layer initialized
[ 10.678834] Bluetooth: L2CAP socket layer initialized
[ 10.678853] Bluetooth: SCO socket layer initialized
[ 10.732670] Bluetooth: HCI UART driver ver 2.3
[ 10.732682] Bluetooth: HCI UART protocol H4 registered
[ 10.732686] Bluetooth: HCI UART protocol Three-wire (H5) registered
[ 10.732820] Bluetooth: HCI UART protocol Broadcom registered
[ 11.177154] Bluetooth: BNEP (Ethernet Emulation) ver 1.3
[ 11.177166] Bluetooth: BNEP filters: protocol multicast
[ 11.177185] Bluetooth: BNEP socket layer initialized
[ 11.230865] Bluetooth: RFCOMM TTY layer initialized
[ 11.230894] Bluetooth: RFCOMM socket layer initialized
[ 11.230913] Bluetooth: RFCOMM ver 1.11
$ lsmod | grep -i -E "bluetooth|bnep|rfcomm"
rfcomm 37723 6
bnep 12051 2
bluetooth 365780 29 hci_uart,bnep,btbcm,rfcomm
rfkill 20851 6 bluetooth,cfg80211
$ modinfo bluetooth
filename: /lib/modules/4.9.59-v7+/kernel/net/bluetooth/bluetooth.ko
alias: net-pf-31
license: GPL
version: 2.22
description: Bluetooth Core ver 2.22
author: Marcel Holtmann <marcel@holtmann.org>
srcversion: CEEDA16781A58CDD168D597
depends: rfkill
intree: Y
vermagic: 4.9.59-v7+ SMP mod_unload modversions ARMv7 p2v8
parm: disable_esco:Disable eSCO connection creation (bool)
parm: disable_ertm:Disable enhanced retransmission mode (bool)
...
$ dpkg -l | grep -iE "bluez|bluetooth"
ii bluealsa 0.6 armhf Bluetooth ALSA Audio backend
ii bluez 5.43-2+rpt2+deb9u2 armhf Bluetooth tools and daemons
ii bluez-firmware 1.2-3+rpt1 all Firmware for Bluetooth devices
ii bluez-obexd 5.43-2+rpt2+deb9u2 armhf bluez obex daemon
ii bluez-test-tools 5.43-2+rpt2+deb9u2 armhf test tools of bluez
ii bluez-tools 0.2.0~20140808-5 armhf Set of tools to manage Bluetooth devices for linux
ii libbluetooth3:armhf 5.43-2+rpt2+deb9u2 armhf Library to use the BlueZ Linux Bluetooth stack
ii lxplug-bluetooth 0.4 armhf Bluetooth plugin for lxpanel
ii pi-bluetooth 0.1.6 armhf Raspberry Pi 3 bluetooth
Liste des outils du paquetage BlueZ :
$ dpkg -L bluez | grep "/usr/bin"
/usr/bin/bccmd
/usr/bin/bluemoon
/usr/bin/bluetoothctl
/usr/bin/btattach
/usr/bin/btmgmt
/usr/bin/btmon
/usr/bin/ciptool
/usr/bin/gatttool
/usr/bin/hciattach
/usr/bin/hcitool
/usr/bin/hex2hcd
/usr/bin/l2ping
/usr/bin/l2test
/usr/bin/mpris-proxy
/usr/bin/obexctl
/usr/bin/rctest
/usr/bin/rfcomm
/usr/bin/sdptool
bluetoothctl
est un outil de contrôle du Bluetooth (il remplace les anciennes commandes bluez-xxx-xxx) :
$ bluetoothctl
[NEW] Controller B8:27:EB:01:98:7E raspberrypi [default]
[bluetooth]# list
Controller B8:27:EB:01:98:7E raspberrypi [default]
[bluetooth]# show
Controller B8:27:EB:01:98:7E
Name: raspberrypi
Alias: raspberrypi
Class: 0x6c0000
Powered: yes
Discoverable: no
Pairable: yes
UUID: Headset AG (00001112-0000-1000-8000-00805f9b34fb)
UUID: Generic Attribute Profile (00001801-0000-1000-8000-00805f9b34fb)
UUID: A/V Remote Control (0000110e-0000-1000-8000-00805f9b34fb)
UUID: Generic Access Profile (00001800-0000-1000-8000-00805f9b34fb)
UUID: PnP Information (00001200-0000-1000-8000-00805f9b34fb)
UUID: A/V Remote Control Target (0000110c-0000-1000-8000-00805f9b34fb)
UUID: Audio Sink (0000110b-0000-1000-8000-00805f9b34fb)
UUID: Audio Source (0000110a-0000-1000-8000-00805f9b34fb)
UUID: Handsfree (0000111e-0000-1000-8000-00805f9b34fb)
UUID: Handsfree Audio Gateway (0000111f-0000-1000-8000-00805f9b34fb)
UUID: Headset (00001108-0000-1000-8000-00805f9b34fb)
Modalias: usb:v1D6Bp0246d052B
Discovering: no
[bluetooth]# scan on
Discovery started
[CHG] Controller B8:27:EB:01:98:7E Discovering: yes
[NEW] Device 20:15:11:16:76:04 HC-05
[bluetooth]# devices
Device 20:15:11:16:76:04 HC-05
[bluetooth]# agent on
Agent registered
[bluetooth]# pair 20:15:11:16:76:04
Attempting to pair with 20:15:11:16:76:04
[CHG] Device 20:15:11:16:76:04 Connected: yes
Request PIN code
[agent] Enter PIN code: 1234
[CHG] Device 20:15:11:16:76:04 UUIDs: 00001101-0000-1000-8000-00805f9b34fb
[CHG] Device 20:15:11:16:76:04 ServicesResolved: yes
[CHG] Device 20:15:11:16:76:04 Paired: yes
Pairing successful
[CHG] Device 20:15:11:16:76:04 ServicesResolved: no
[CHG] Device 20:15:11:16:76:04 Connected: no
[bluetooth]# paired-devices
Device 20:15:11:16:76:04 HC-05
[bluetooth]# trust 20:15:11:16:76:04
[CHG] Device 20:15:11:16:76:04 Trusted: yes
Changing 20:15:11:16:76:04 trust succeeded
[bluetooth]# quit
hciconfig
est utilisé pour configurer les périphériques Bluetooth (hciX
est le nom d’un périphérique Bluetooth installé dans le système) :
$ hciconfig
hci0: Type: Primary Bus: UART
BD Address: B8:27:EB:01:98:7E ACL MTU: 1021:8 SCO MTU: 64:1
UP RUNNING
RX bytes:822 acl:0 sco:0 events:57 errors:0
TX bytes:4231 acl:0 sco:0 commands:57 errors:0
$ hciconfig hci0 ptype
...
Packet type: DM1 DM3 DM5 DH1 DH3 DH5 HV1 HV2 HV3
$ hciconfig hci0 lm
...
Link mode: SLAVE ACCEPT
$ hciconfig hci0 class
...
Class: 0x6c0000
Service Classes: Rendering, Capturing, Audio, Telephony
Device Class: Miscellaneous,
$ hciconfig hci0 iac
...
IAC: 0x9e8b33
hcitool
est utilisé pour configurer les connexions Bluetooth et envoyer une commande spéciale aux périphériques Bluetooth :
$ hcitool dev
Devices:
hci0 B8:27:EB:01:98:7E
$ hcitool scan
Scanning ...
20:15:11:16:76:04 HC-05
$ hcitool inq
Inquiring ...
20:15:11:16:76:04 clock offset: 0x248f class: 0x001f00
$ sudo hcitool info 20:15:11:16:76:04
Requesting information ...
BD Address: 20:15:11:16:76:04
Device Name: HC-05
LMP Version: 2.1 (0x4) LMP Subversion: 0x1735
Manufacturer: Cambridge Silicon Radio (10)
Features page 0: 0xff 0xff 0x8f 0xfe 0x9b 0xff 0x59 0x83
<3-slot packets> <5-slot packets> <encryption> <slot offset>
...
l2ping
envoie une requête d’écho L2CAP à l’adresse MAC Bluetooth BD_ADDR donnée en notation hexadécimale :
$ sudo l2ping -c 1 20:15:11:16:76:04
Ping: 20:15:11:16:76:04 from B8:27:EB:01:98:7E (data size 44) ...
4 bytes from 20:15:11:16:76:04 id 0 time 9.69ms
1 sent, 1 received, 0% loss
rfcomm
est utilisé pour configurer, maintenir et inspecter la configuration RFCOMM du sous-système Bluetooth dans le noyau Linux. Si aucune commande n’est donnée, ou si l’option -a est utilisée, rfcomm imprime des informations sur les périphériques RFCOMM configurés.
$ rfcomm -a
sdptool
permet d’effectuer des requêtes SDP sur les périphériques Bluetooth.
$ sdptool browse local
$ sdptool browse 34:14:5F:D2:50:D8
bluez-simple-agent
est un programme qui permet de gérer l’appairage de périphériques Bluetooth :
$ bluez-simple-agent hci0 20:15:11:16:76:04
RequestPinCode (/org/bluez/1525/hci0/dev_20_15_11_16_76_04)
Enter PIN Code: 1234
Release
New device (/org/bluez/1525/hci0/dev_20_15_11_16_76_04)
$ bluez-simple-agent hci0 20:15:11:16:76:04 remove
hcidump
lit et affiche les données HCI brutes d’une communication Bluetooth.
$ hcidump
HCI sniffer - Bluetooth packet analyzer ver 2.2
device: hci0 snap_len: 1028 filter: 0xffffffffffffffff
> HCI Event: Command Status (0x0f) plen 4
Exit Sniff Mode (0x02|0x0004) status 0x00 ncmd 1
> HCI Event: Command Status (0x0f) plen 4
Exit Sniff Mode (0x02|0x0004) status 0x00 ncmd 1
> HCI Event: Command Status (0x0f) plen 4
Disconnect (0x01|0x0006) status 0x00 ncmd 1
> HCI Event: Disconn Complete (0x05) plen 4
status 0x00 handle 43 reason 0x16
Reason: Connection Terminated by Local Host
...
Recherche de périphériques à proximité :
$ hcitool scan
Scanning ...
34:14:5F:D2:50:D8 Galaxy Tab S2
20:15:11:16:76:04 HC-05
Appairage :
$ bluetoothctl
[NEW] Controller B8:27:EB:01:98:7E raspberrypi [default]
[bluetooth]# scan on
Discovery started
[CHG] Controller B8:27:EB:01:98:7E Discovering: yes
[NEW] Device 20:15:11:16:76:04 HC-05
[bluetooth]# agent on
Agent registered
[bluetooth]# pair 20:15:11:16:76:04
Attempting to pair with 20:15:11:16:76:04
[CHG] Device 20:15:11:16:76:04 Connected: yes
Request PIN code
[agent] Enter PIN code: 1234
[CHG] Device 20:15:11:16:76:04 UUIDs: 00001101-0000-1000-8000-00805f9b34fb
[CHG] Device 20:15:11:16:76:04 ServicesResolved: yes
[CHG] Device 20:15:11:16:76:04 Paired: yes
Pairing successful
[CHG] Device 20:15:11:16:76:04 ServicesResolved: no
[CHG] Device 20:15:11:16:76:04 Connected: no
[bluetooth]# paired-devices
Device 20:15:11:16:76:04 HC-05
[bluetooth]# trust 20:15:11:16:76:04
[CHG] Device 20:15:11:16:76:04 Trusted: yes
Changing 20:15:11:16:76:04 trust succeeded
[bluetooth]# quit
$ bluez-simple-agent hci0 20:15:11:16:76:04
RequestPinCode (/org/bluez/1525/hci0/dev_20_15_11_16_76_04)
Enter PIN Code: 1234
Release
New device (/org/bluez/1525/hci0/dev_20_15_11_16_76_04)
Connexion :
$ sudo rfcomm bind 0 20:15:11:16:76:04
$ rfcomm -a
rfcomm0: 20:15:11:16:76:04 channel 1 clean
$ ls -l /dev/rfcomm0
crw-rw---- 1 root dialout 216, 0 févr. 23 16:26 /dev/rfcomm0
Remarque : bind
lie le périphérique RFCOMM à un périphérique Bluetooth distant. La commande n’établit pas une connexion avec le périphérique distant, elle crée uniquement la liaison. La connexion sera établie juste après qu’une application essaie d’ouvrir le périphérique RFCOMM (/dev/rfcomm0
).
Communication :
Simple test d’écriture et de lecture avec la console sur le fichier spécial /dev/rfcomm0
:
$ echo "hello wordl" > /dev/rfcomm0
$ sudo cat /dev/rfcomm0
En utilisant un utilitaire type cutecom
ou putty
sur le fichier spécial /dev/rfcomm0
, on peut ensuite lire et écrire via le Bluetooth.
$ rfcomm -a
rfcomm0: 20:15:11:16:76:04 channel 1 connected [tty-attached]
Déconnexion :
$ rfcomm -a
rfcomm0: 34:14:5F:D2:50:D8 channel 1 closed
$ sudo rfcomm release 0
Il est possible de configurer une liaison automatique avec un périphérique Bluetooth qui sera accessible via le fichier spécial /dev/rfcomm0
:
$ sudo vim /etc/bluetooth/rfcomm.conf
# RFCOMM configuration file.
rfcomm0 {
# # Automatically bind the device at startup
bind yes;
# # Bluetooth address of the device
device 20:15:11:16:76:04;
# # RFCOMM channel for the connection
channel 1;
# # Description of the connection
comment "Exemple Bluetooth device";
}
$ sudo /etc/init.d/bluetooth restart
bluetooth stop/waiting
bluetooth start/running, process 4995
$ ls -l /dev/rfcomm0
crw-rw---- 1 root dialout 216, 0 févr. 23 16:53 /dev/rfcomm0
Qt et Bluetooth : état actuel et améliorations à venir
Qt 5.2 a apporté le module Bluetooth, initialement très limité : il n’était utilisable que sur Linux par BlueZ 4 et sur BlackBerry 10. Chaque version a amélioré l’état du module : avec Qt 5.3, il devient compatible avec Android ; avec Qt 5.4, il devient compatible avec BlueZ 5 sous Linux et la version basse énergie Bluetooth LE (pour Low Energy) est accessible (uniquement pour BlueZ, en préversion). Avec Qt 5.5, il devrait être compatible avec OS X (y compris la variante LE), WinRT (ARM) pour la 5.6.
L’objectif principal de ce module est de fournir une couche d’abstraction des fonctionnalités Bluetooth de base, utilisable peu importe la plateforme : chercher des périphériques Bluetooth accessibles, leur envoyer des données par OPP (object push profile), s’y connecter par un canal RFCOMM en série (SPP, serial port profile), etc. La déclinaison LE devient de plus importante, dans Qt mais aussi dans ses applications : avec l’habitronique, les divers capteurs connectés, tant pour la santé que l’environnement, les périphériques doivent limiter au maximum leur consommation ; Qt reste utile pour ces applications, grâce à ses fonctionnalités Bluetooth LE.
De nouvelles fonctionnalités sont prévues pour les prochaines versions de Qt : une compatibilité avec Windows (x86), des interfaces Qt Quick, des améliorations pour la détection et la communication des fonctionnalités disponibles, par exemple pour l’interaction avec des périphériques iBeacon (Apple).
Installation Qt5 sur une Raspberry PI 3 (Raspbian GNU/Linux 9 (stretch) Linux 4.9.59-v7+) :
$ sudo apt-get install qt5-default
$ sudo apt-get install libbluetooth-dev qtconnectivity5-dev qtconnectivity5-examples
$ qmake -v
QMake version 3.0
Using Qt version 5.7.1 in /usr/lib/arm-linux-gnueabihf
Documentations :
Pour utiliser l’API Qt Bluetooth dans votre application, il faut commencer par ajouter l’option de configuration suivante à votre fichier de projet .pro
:
QT += bluetooth
Avec l’API Qt Bluetooth, les cas d’utilisation qui nous intéressent sont :
Remarques : Pour Qt4
Il faut installer le paquetage qtmobility-dev :
$ sudo apt-get install qtmobility-dev
Pour utiliser l’API Qt Bluetooth dans votre application Qt4, il faut commencer par ajouter l’option de configuration suivante à votre fichier de projet .pro
et enlever le module bluetooth de la variable QT :
CONFIG += mobility
MOBILITY = connectivity
Pour accéder aux classes de l’API Qt Bluetooth, il faut ajouter l’espace de noms QtMobility :
L’API Qt Bluetooth permet d’obtenir des informations sur les périphériques locaux et distants. Les premières étapes de la récupération des informations sur le périphérique consistent à vérifier si Bluetooth est disponible sur l’appareil et à lire l’adresse et le nom de l’appareil local. QBluetoothLocalDevice est la classe qui fournit toutes ces informations. En outre, vous pouvez l’utiliser pour activer / désactiver Bluetooth, définir la visibilité de l’appareil et lister les connexions actuelles.
Principe :
QBluetoothLocalDevice localDevice;
QString localDeviceName;
// Check if Bluetooth is available on this device
if (localDevice.isValid())
{
// Turn Bluetooth on
localDevice.powerOn();
// Read local device name
localDeviceName = localDevice.name();
// Make it visible to others
localDevice.setHostMode(QBluetoothLocalDevice::HostDiscoverable);
// Get connected devices
QList<QBluetoothAddress> remotes;
remotes = localDevice.connectedDevices();
}
L’API propose QBluetoothDeviceInfo qui fournit des informations pour les périphériques distants. Bien qu’il est possible de créer ses objets QBluetoothDeviceInfo, le moyen le plus simple consiste à utiliser QBluetoothDeviceDiscoveryAgent pour démarrer une recherche automatique des périphériques Bluetooth visibles dans la plage connectable.
Principe :
void MaClasse::startDeviceDiscovery()
{
// Create a discovery agent and connect to its signals
QBluetoothDeviceDiscoveryAgent *discoveryAgent = new QBluetoothDeviceDiscoveryAgent(this);
connect(discoveryAgent, SIGNAL(deviceDiscovered(QBluetoothDeviceInfo)),
this, SLOT(deviceDiscovered(QBluetoothDeviceInfo)));
// Start a discovery
discoveryAgent->start();
//...
}
void MaClasse::deviceDiscovered(const QBluetoothDeviceInfo &device)
{
qDebug() << "Found new device:" << device.name() << '(' << device.address().toString() << ')';
}
On va voir :
Pour dialoguer entre périphériques Bluetooth, on utilisera un QBluetoothSocket. On utilisera le signal readyRead()
pour lire les données disponibles avec readAll()
par exemple.
Remarque : QBluetoothSocket ne supporte pas les opération de lecture/écriture synchrones. Les méthodes waitForReadyRead()
et waitForBytesWritten()
ne sont pas utilisables. les opération de lecture/écriture seraont réalisées read()
et write()
.
Principe :
void MaClasse::connecter(const QBluetoothDeviceInfo device)
{
socket = new QBluetoothSocket(QBluetoothServiceInfo::RfcommProtocol);
connect(socket, SIGNAL(connected()), this, SLOT(socketConnected()));
connect(socket, SIGNAL(disconnected()), this, SLOT(socketDisconnected()));
connect(socket, SIGNAL(readyRead()), this, SLOT(socketReadyRead()));
QBluetoothAddress adresse = QBluetoothAddress(device.address());
QBluetoothUuid uuid = QBluetoothUuid(QBluetoothUuid::SerialPort);
socket->connectToService(adresse, uuid);
socket->open(QIODevice::ReadWrite);
}
void MaClasse::deconnecter()
{
if (socket->isOpen())
{
socket->close();
}
}
bool MaClasse::estConnecte()
{
return socket->isOpen();
}
void MaClasse::socketConnected()
{
qDebug() << Q_FUNC_INFO;
QString message = QString::fromUtf8("Périphérique connecté ") + socket->peerName() + " [" + socket->peerAddress().toString() + "]";
qDebug() << message;
}
void MaClasse::socketDisconnected()
{
qDebug() << Q_FUNC_INFO;
QString message = QString::fromUtf8("Périphérique déconnecté ");
qDebug() << message;
}
void MaClasse::socketReadyRead()
{
qDebug() << Q_FUNC_INFO;
QByteArray donnees;
while (socket->bytesAvailable())
{
donnees += socket->readAll();
usleep(150000); // cf. timeout
}
qDebug() << QString::fromUtf8("Données reçues : ") << QString(donnees);
}
void MaClasse::envoyer(QString trame)
{
qDebug() << Q_FUNC_INFO;
if (socket == NULL || !socket->isOpen())
{
return;
}
trame += "\r\n";
socket->write(trame.toLatin1());
}
Pour placer le QBluetoothServer en écoute, il est possible d’utiliser une des deux méthodes suivantes :
listen(const QBluetoothAddress &address = QBluetoothAddress(), quint16 port = 0)
: on utilise ici l’adresse du périphérique Bluetooth local qur lequel on écoutera les demandes de connexion
listen(const QBluetoothUuid &uuid, const QString &serviceName = QString())
: fonction pratique pour enregistrer un service SPP avec un UUID et se mettre en écoute
L’état du périphérique Bluetooth local peut être défini avec setHostMode()
. On distinguera ces différents états :
QBluetoothLocalDevice::HostPoweredOff
: le périphérique Bluetooth est éteint. Il peut être allumé en appelant powerOn()
.
QBluetoothLocalDevice::HostConnectable
: les périphériques Bluetooth distants pourront se connecter s’ils y sont déjà associés ou s’ils connaissent l’adresse. Cela permet aussi d’allumer l’appareil s’il était éteint.
QBluetoothLocalDevice::HostDiscoverable
: les périphériques Bluetooth distants pourront détecter la présence du périphérique Bluetooth local. L’appareil sera également connectable et allumé. Ce mode reste parfois actif pendant une durée limitée (sur Android pendant 5 minutes maximum).
On peut connaître l’état en appelant hostMode()
ou utiliser le signal hostModeStateChanged()
pour détecter un changement.
Remarque : après la déconnexion d’un client, le périphérique Bluetooth local reste dans l’état QBluetoothLocalDevice::HostConnectable
.
Principe :
static const QString serviceUuid(QStringLiteral("00001101-0000-1000-8000-00805F9B34FB"));
static const QString serviceNom(QStringLiteral("raspberry"));
...
void MonServeur::demarrer()
{
if (!serveur)
{
serveur = new QBluetoothServer(QBluetoothServiceInfo::RfcommProtocol, this);
connect(serveur, SIGNAL(newConnection()), this, SLOT(nouveauClient()));
QBluetoothUuid uuid = QBluetoothUuid(serviceUuid);
serviceInfo = serveur->listen(uuid, serviceNom);
}
}
void MonServeur::arreter()
{
if (!serveur)
return;
serviceInfo.unregisterService();
if (socket)
{
if (socket->isOpen())
socket->close();
delete socket;
socket = NULL;
}
delete serveur;
serveur = NULL;
}
void MonServeur::nouveauClient()
{
// on récupère la socket
socket = serveur->nextPendingConnection();
if (!socket)
return;
connect(socket, SIGNAL(disconnected()), this, SLOT(socketDisconnected()));
connect(socket, SIGNAL(readyRead()), this, SLOT(socketReadyRead()));
connect(socket, SIGNAL(error(QBluetoothSocket::SocketError)), this, SLOT(socketError(QBluetoothSocket::SocketError)));
connect(socket, SIGNAL(stateChanged(QBluetoothSocket::SocketState)), this, SLOT(socketStateChanged(QBluetoothSocket::SocketState)));
}
...
Remarque : pour une serveur multi-clients, voir l’exemple Bluetooth Chat.
Voir aussi : Programmation socket Bluetooth en C/C++ et Qt
Codes sources Qt5 :
Version client widgets : test-mo-qt-bluetooth.zip
Version client QML : qt-bluetooth-qml.zip
Serveur widgets : qt-bluetooth-desktop.zip ou qt-bluetooth-rpi.zip
Code source Qt4 : test-mo-qt4-bluetooth.zip
Version widgets :
Version QML :
Connexion et échange :
Déconnexion :
Remarque : Pour changer le nom du périphérique Bluetooth sur Ubuntu 18.04
$ sudo vim /etc/bluetooth/main.conf
[General]
# Default adaper name
# Defaults to 'BlueZ X.YZ'
Name = un-nom
$ ls -l /var/lib/bluetooth/
drwx------ 6 root root 4096 mars 27 12:01 00:1A:7D:DA:71:13
$ sudo vim /var/lib/bluetooth/00\:1A\:7D\:DA\:71\:13/settings
[General]
Discoverable=true
Name=un-nom
$ sudo systemctl restart bluetooth.service
Une autre approche serait d’émuler directement un port série virtuel via la commande rfcomm
et d’utiliser QSerialPort
pour dialoguer en Bluetooth.
Sur une Raspberry Pi :
$ uname -a
Linux raspberrypi 4.9.59-v7+ #1047 SMP Sun Oct 29 12:19:23 GMT 2017 armv7l GNU/Linux
Il est possible d’utiliser la commande rfcomm
pour communiquer en Bluetooth en utilisant un port série virtuel avec le fichier spécial /dev/rfcomm0
.
La Raspberry Pi 3 utilise BlueZ 5. Pour accéder aux fonctions de BlueZ 4, il faut utiliser l’option --compat
du service bluetoothd :
$ sudo vim /etc/systemd/system/dbus-org.bluez.service
ExecStart=/usr/lib/bluetooth/bluetoothd --compat
$ sudo systemctl daemon-reload
$ sudo systemctl restart bluetooth
$ sudo chmod 777 /var/run/sdp
On va ajouter le service Serial Port (SP) que l’on va associer au channel 22 :
$ sudo sdptool add --channel=22 SP
$ sdptool browse local
...
Service Name: Serial Port
Service Description: COM Port
Service Provider: BlueZ
Service RecHandle: 0x1000b
Service Class ID List:
"Serial Port" (0x1101)
Protocol Descriptor List:
"L2CAP" (0x0100)
"RFCOMM" (0x0003)
Channel: 22
Language Base Attr List:
code_ISO639: 0x656e
encoding: 0x6a
base_offset: 0x100
Profile Descriptor List:
"Serial Port" (0x1101)
Version: 0x0100
Les commandes disponibles pour l’utilitaire rfcomm
sont :
connect
: connecte le périphérique RFCOMM au périphérique Bluetooth distant sur le canal spécifié.
listen
: écoute sur un canal RFCOMM spécifié pour les connexions entrantes. Si cmd est donné, il sera exécuté dès qu’un client se connecte. Lorsque le processus fils se termine ou que le client se déconnecte, la commande se termine. Les occurrences de {} dans cmd seront remplacées par le nom du périphérique utilisé par la connexion. Cette commande peut être terminée avec la séquence de touches CTRL-C.
watch
est identique à listen sauf que lorsque le processus fils se termine ou que le client se déconnecte, la commande redémarre en écoutant avec les mêmes paramètres.
bind
: lie le périphérique RFCOMM à un périphérique Bluetooth distant. La commande n’établit pas de connexion avec le périphérique distant, elle crée uniquement la liaison. La connexion sera établie juste après qu’une application essaie d’ouvrir le périphérique RFCOMM.
On va utiliser la commande watch sur le cahannel 22 :
$ sudo rfcomm watch 0 22 cat {}
Remarque : {}
dans cat
sera remplacé par le nom du périphérique utilisé par la connexion.
Pour tester, il suffit d’utiliser un client capable de se connecter sur notre périphérique. Il en existe de nombreuses applications Android (comme Bluetooth Terminal) pour faire cela.
Il faudra tout d’abord appairer le périphérique Bluetooth de la Raspberry Pi 3 :
$ bluetoothctl
[NEW] Controller B8:27:EB:01:98:7E raspberrypi [default]
[bluetooth]# scan on
Discovery started
[bluetooth]# devices
Device 20:15:11:16:76:04 HC-05
Device 34:14:5F:D2:50:D8 Galaxy Tab S2
[bluetooth]# pair 34:14:5F:D2:50:D8
Attempting to pair with 34:14:5F:D2:50:D8
[CHG] Device 34:14:5F:D2:50:D8 Connected: yes
[CHG] Device 34:14:5F:D2:50:D8 Modalias: bluetooth:v0075p0100d0200
[CHG] Device 34:14:5F:D2:50:D8 UUIDs: 00001101-0000-1000-8000-00805f9b34fb
[CHG] Device 34:14:5F:D2:50:D8 UUIDs: 00001105-0000-1000-8000-00805f9b34fb
[CHG] Device 34:14:5F:D2:50:D8 UUIDs: 0000110a-0000-1000-8000-00805f9b34fb
[CHG] Device 34:14:5F:D2:50:D8 UUIDs: 0000110c-0000-1000-8000-00805f9b34fb
[CHG] Device 34:14:5F:D2:50:D8 UUIDs: 00001112-0000-1000-8000-00805f9b34fb
[CHG] Device 34:14:5F:D2:50:D8 UUIDs: 00001115-0000-1000-8000-00805f9b34fb
[CHG] Device 34:14:5F:D2:50:D8 UUIDs: 0000111f-0000-1000-8000-00805f9b34fb
[CHG] Device 34:14:5F:D2:50:D8 UUIDs: 00001200-0000-1000-8000-00805f9b34fb
[CHG] Device 34:14:5F:D2:50:D8 UUIDs: 00001800-0000-1000-8000-00805f9b34fb
[CHG] Device 34:14:5F:D2:50:D8 UUIDs: 00001801-0000-1000-8000-00805f9b34fb
[CHG] Device 34:14:5F:D2:50:D8 UUIDs: 00001802-0000-1000-8000-00805f9b34fb
[CHG] Device 34:14:5F:D2:50:D8 UUIDs: 00001805-0000-1000-8000-00805f9b34fb
[CHG] Device 34:14:5F:D2:50:D8 UUIDs: 00001811-0000-1000-8000-00805f9b34fb
[CHG] Device 34:14:5F:D2:50:D8 UUIDs: 00001902-0000-1000-8000-00805f9b34fb
[CHG] Device 34:14:5F:D2:50:D8 ServicesResolved: yes
[CHG] Device 34:14:5F:D2:50:D8 Paired: yes
Pairing successful
[CHG] Device 34:14:5F:D2:50:D8 ServicesResolved: no
[CHG] Device 34:14:5F:D2:50:D8 Connected: no
[bluetooth]# trust 34:14:5F:D2:50:D8
[CHG] Device 34:14:5F:D2:50:D8 Trusted: yes
Changing 34:14:5F:D2:50:D8 trust succeeded
[bluetooth]# quit
Pour finir, on peut réaliser ce script :
sudo chmod 777 /var/run/sdp
sudo sdptool add --channel=22 SP > /dev/null
sudo rfcomm watch /dev/rfcomm0 22 1>/dev/null 2>&1 &
Ou encore mieux, on va créer un service rfcomm :
$ sudo vim /etc/systemd/system/rfcomm.service
[Unit]
Description=RFCOMM service
After=bluetooth.service
Requires=bluetooth.service
[Service]
ExecStartPre=/bin/chmod 777 /var/run/sdp
ExecStart=/usr/bin/rfcomm watch 0 22
Restart=on-failure
RestartSec=5s
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
Puis on modifie le service bluetooth pour y intégrer la commande sdptool :
$ sudo vim /etc/systemd/system/dbus-org.bluez.service
[Unit]
Description=Bluetooth service
Documentation=man:bluetoothd(8)
ConditionPathIsDirectory=/sys/class/bluetooth
[Service]
Type=dbus
BusName=org.bluez
ExecStart=/usr/lib/bluetooth/bluetoothd --compat --noplugin=sap
ExecStartPost=/usr/bin/sdptool add --channel=22 SP
NotifyAccess=main
#WatchdogSec=10
#Restart=on-failure
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
LimitNPROC=1
ProtectHome=true
ProtectSystem=full
[Install]
WantedBy=bluetooth.target
Alias=dbus-org.bluez.service
Pour activer le service rfcomm au démarrage, il faut faire :
$ sudo systemctl enabled rfcomm.service
Et un petit programme Qt5 en utilisant le module serialport
et la classe QSerialPort :
QT += serialport
TEMPLATE = app
TARGET = qt-serial
DEPENDPATH += .
INCLUDEPATH += .
OBJECTS_DIR = ./tmp
MOC_DIR = ./tmp
DESTDIR = ./bin
# Input
SOURCES += main.cpp
#include <QSerialPort>
#include <QDebug>
#include <unistd.h>
#define PORT "/dev/rfcomm0"
int main()
{
QSerialPort *port;
// instanciation du port
port = new QSerialPort(QLatin1String(PORT));
// configuration du port
enum QSerialPort::BaudRate baudRate;
baudRate = QSerialPort::Baud9600;
port->setBaudRate(baudRate);
port->setDataBits(QSerialPort::Data8);
port->setParity(QSerialPort::NoParity);
port->setStopBits(QSerialPort::OneStop);
port->setFlowControl(QSerialPort::NoFlowControl);
// ouverture du port
port->open(QIODevice::ReadWrite);
qDebug("<debug> etat ouverture port : %d", port->isOpen());
if(!port->isOpen())
{
delete port;
return 1;
}
// envoyer des données
QString trame = "hello world";
port->write(trame.toLatin1());
usleep(500000);
// receptionner des données
// attente
port->waitForReadyRead(-1);
QByteArray donnees;
while (port->bytesAvailable())
{
donnees += port->readAll();
usleep(100000); // cf. timeout
}
QString trameRecue = QString(donnees.data());
qDebug() << QString::fromUtf8("données reçues :") << trameRecue;
// fermeture du port
port->close();
qDebug("<debug> etat ouverture port : %d", port->isOpen());
delete port;
return 0;
}
Avec un client Android :
On obtient côté Raspberry Pi :
$ bin/qt-serial
<debug> etat ouverture port : 1
"données reçues :" "ok\r\n"
<debug> etat ouverture port : 0
Avec un module HC-05, on pourra simplement faire :
$ sudo rfcomm bind 0 20:15:11:16:76:04
Voir aussi : Mise en oeuvre d’un port série sous Qt
Voir : Activité n°4 : Bluetooth
BlueZ est un logiciel qui met en œuvre la technologie sans fil Bluetooth sur le système d’exploitation Linux. Il est intégré au noyau Linux.